:root{
  --bg:#0d1117; --bg2:#161b22; --bg3:#21262d; --border:#30363d;
  --text:#c9d1d9; --dim:#8b949e; --bright:#e6edf3;
  --accent:#58a6ff; --accent2:#79c0ff; --accent-chat:#1f6feb;
  --green:#3fb950; --yellow:#e3b341; --orange:#f0883e; --red:#f85149;
  --userbub:#1f3a5f; --aibub:#1c2128;
  --tint-blue:#1c2d3f; --tint-green:#196c2e; --tint-yellow:#5a3600; --tint-red:#5c1a1a; --stop-hover:#3a1111;
  /* Match-icon circles (checkmark/tilde/cross) stay this vivid regardless of theme — they're small
     filled badges that need to pop on their own, unlike --green/--yellow/--red (used for TEXT, which
     the light theme deliberately darkens for contrast against a white page). */
  --icon-green:#2ecc71; --icon-yellow:#f2c94c; --icon-red:#f85149; --icon-glyph:#0d1117;
  /* Text color for buttons/tags painted with --accent as their background. Dark theme's --accent
     is a bright sky blue, so near-black text reads fine; light theme's --accent is a darker,
     more saturated blue where that same near-black text loses contrast — needs white instead. */
  --on-accent:#07101c;
}
/* Theme follows the browser/OS preference automatically — no in-app toggle. Almost every color in
   this stylesheet already runs through these custom properties, so this one block is enough to
   re-theme the whole site; the handful of former literal dark-navy/dark-status colors (tag
   backgrounds, status badges, etc.) were moved onto --tint-* variables specifically so they don't
   stay stuck looking "dark theme" when the rest of the page goes light. */
@media (prefers-color-scheme: light){
  :root{
    --bg:#ffffff; --bg2:#f6f8fa; --bg3:#eaeef2; --border:#d0d7de;
    --text:#24292f; --dim:#57606a; --bright:#1b1f24;
    --accent:#0969da; --accent2:#218bff; --accent-chat:#0969da;
    --green:#1a7f37; --yellow:#9a6700; --orange:#bc4c00; --red:#cf222e;
    --userbub:#ddf1ff; --aibub:#f0f2f5;
    --tint-blue:#ddeeff; --tint-green:#dafbe1; --tint-yellow:#fff8c5; --tint-red:#ffebe9; --stop-hover:#ffebe9;
    --icon-glyph:#ffffff; --on-accent:#ffffff;
  }
}
*{box-sizing:border-box;margin:0;padding:0;}
/* Without this, Android Chrome's automatic text-size-adjust heuristic boosts font-size inside
   narrow columns (confirmed real bug, 2026-08-04: .fcard-name in the Filter grid measured a
   correct 277px-wide box at its specified 12px font via getComputedStyle/getBoundingClientRect —
   plenty of room — yet rendered on an actual phone as almost one word per line, wasting most of
   the card's height/width). Disabling the auto-boost makes rendered size match the CSS again. */
html{-webkit-text-size-adjust:100%;text-size-adjust:100%;}
html,body{height:100%;overflow:hidden;}
body{background:var(--bg);color:var(--text);font:14px/1.5 'Segoe UI',system-ui,sans-serif;display:flex;flex-direction:column;}

/* ── HEADER ── */
header{height:52px;flex-shrink:0;background:var(--bg2);border-bottom:1px solid var(--border);
  display:flex;align-items:center;gap:14px;padding:0 150px 0 18px;position:relative;}
/* Public "/" header grows to fit the 2x logo + bigger nav (2026-08-04, per feedback) — the admin
   shell keeps the compact 52px bar, only .header:has(.public-tabs) gets the taller one. */
header:has(.public-tabs){height:88px;gap:20px;}
.logo{display:flex;align-items:center;white-space:nowrap;}
/* Both logo files are genuinely transparent PNGs (confirmed via canvas pixel sampling) — the
   official dark-wordmark version reads fine directly on a light background, and the white
   "reverse" version (see <picture> below / server.mjs's route comment) reads fine on dark, so
   neither ever needs a background card behind it. */
.brand-logo{height:32px;display:block;}
header:has(.public-tabs) .brand-logo{height:64px;}
.tabs{display:flex;gap:2px;margin-left:4px;}
.tab{padding:6px 16px;border-radius:7px;cursor:pointer;font-size:13px;font-weight:600;
  color:var(--dim);border:1px solid transparent;transition:all .15s;}
.tab:hover{color:var(--text);background:var(--bg3);}
.tab.active{color:var(--bright);background:var(--bg3);border-color:var(--border);}
/* Bigger, more page-like nav for anonymous visitors — scoped to .public-tabs (added only in
   bootPublicOnly) so the admin's own tab bar, same .tab elements reused, stays compact. */
.public-tabs .tab{padding:14px 30px;font-size:18px;border-radius:10px;}
/* MiMo Web lives in a hover flyout under the MiMo Chat tab, not its own top-level tab —
   hover "MiMo Chat" to reveal it underneath. */
/* No gap between the tab and the flyout — position:absolute children don't extend their
   positioned ancestor's box, so any margin/gap here is dead space the mouse leaves the
   hoverable area to cross, which drops :hover and hides the menu before the cursor arrives
   (confirmed real bug: a 2px margin-top here made the flyout vanish on the way down). Zero
   gap plus `padding-top` (still hoverable, part of the flyout's own box) gives the same
   visual breathing room with no dead zone. */
.tab-group{position:relative;display:flex;}
.tab-flyout{position:absolute;top:100%;left:0;display:none;flex-direction:column;
  background:var(--bg2);border:1px solid var(--border);border-radius:8px;padding:8px 4px 4px;
  min-width:140px;z-index:60;box-shadow:0 8px 20px #0007;}
.tab-group:hover .tab-flyout{display:flex;}
.tab-flyout-item{padding:7px 14px;border-radius:6px;cursor:pointer;font-size:13px;font-weight:600;
  color:var(--dim);white-space:nowrap;transition:all .15s;}
.tab-flyout-item:hover{color:var(--text);background:var(--bg3);}
.tab-flyout-item.active{color:var(--bright);background:var(--bg3);}
.hstats{margin-left:auto;font-size:12px;color:var(--dim);display:flex;gap:18px;}
.hstats b{color:var(--bright);}
/* Below this width even the short public tab list (logo+Catalog+Filter) is wide enough to reach
   past viewport-center — positionHeaderDisclaimer()'s "plain 50%" branch (see its own comment)
   only accounted for a short tab list being narrow enough to leave room either side, which stops
   being true on phone-width screens. Simplest correct fix: this disclaimer is a secondary note,
   not load-bearing UI — drop it rather than fight for space a real product comparison needs more. */
/* iOS Safari auto-zooms the whole page on focusing any text input/select/textarea whose font-
   size is under 16px — an accessibility behavior, not a bug on Apple's end, but it reads as the
   page "jumping"/breaking layout on focus (real feedback, 2026-08-05: happens on an iPhone,
   doesn't on Android — Chrome has no equivalent auto-zoom). Nearly every field on this site
   (search-input, the spec-field editor's fe-num/fe-select, login, etc.) is styled well under
   16px for a dense admin UI, and there are too many of them to safely hunt down one at a time —
   force every field's rendered size to 16px specifically on phone widths (both orientations;
   desktop/Safari-on-Mac has no auto-zoom, so it's untouched there) rather than trying to enumerate
   every input class. */
@media (max-width:700px), (max-width:900px) and (orientation:landscape) and (max-height:500px){
  input,select,textarea{font-size:16px!important;}
}
@media (max-width:700px){
  .header-disclaimer{display:none!important;}
  /* The 2x public header/logo/nav (see header:has(.public-tabs) above) doesn't fit a phone width
     doing the math (logo ~146px + two ~150px-wide buttons > 375px viewport) — scale back down,
     still a bit bigger than the pre-2026-08-04 defaults but not literally double. */
  header:has(.public-tabs){height:60px;gap:10px;}
  header:has(.public-tabs) .brand-logo{height:38px;}
  .public-tabs .tab{padding:8px 16px;font-size:14px;}
}
/* A rotated phone is routinely WIDER than 700px (e.g. 812-932px landscape) even though it's very
   much still a phone — the max-width:700px rule above never catches it, so the desktop-style
   .header-disclaimer (absolutely positioned, centered relative to the tabs) was reappearing in
   landscape and overlapping the Catalog/Filter tab buttons (real bug, confirmed 2026-08-05). Use
   the SAME max-width:900px+landscape+max-height:500px breakpoint as the rest of the phone-
   landscape treatment below, rather than duplicating a separate width guess. */
@media (max-width:900px) and (orientation:landscape) and (max-height:500px){
  .header-disclaimer{display:none!important;}
}
.mobile-disclaimer{display:none;}
/* Collapsed to one line by default (matches the desktop .disclaimer-short/.disclaimer-full split)
   — tap toggles .expanded (see the click listener near positionHeaderDisclaimer's own resize
   listener) since a phone has no hover to reveal the full text like desktop does. */
.mobile-disclaimer-short{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.mobile-disclaimer-full{display:none;margin-top:4px;}
.mobile-disclaimer.expanded .mobile-disclaimer-full{display:block;}
/* Also shown in phone landscape (see the matching breakpoint further down, which now hides the
   desktop-style .header-disclaimer there instead of letting it reappear) — same collapsed-to-one-
   line banner as portrait, just the one that actually fits a phone regardless of orientation. */
@media (max-width:700px) and (orientation:portrait), (max-width:900px) and (orientation:landscape) and (max-height:500px){
  .mobile-disclaimer{display:block;background:var(--bg2);border-bottom:1px solid var(--border);
    padding:8px 16px;font-size:11px;line-height:1.4;color:var(--dim);text-align:center;cursor:pointer;}
  .footer-disclaimer{display:none;}
}
/* Phone landscape: almost no vertical room (a rotated phone is ~375-430px tall). Used to just
   drop the whole header/disclaimer outright — but that also meant no way to ever get the header
   back, even scrolling to the very top, and the footer (still using the desktop nested-scroll
   layout at this width, since that fix below was portrait-only) stayed fixed mid-screen, covering
   about half the viewport. Now landscape gets the SAME plain-document-flow treatment as portrait
   (header/content/footer all in one normal scrolling page, footer only reached by scrolling past
   everything — see the max-width:700px block above), and the header instead auto-hides on scroll-
   down / reappears on scroll-up (see the .auto-hide-header/.hidden JS toggle) rather than being
   gone for good. max-height keeps this from also matching a landscape tablet/small laptop window,
   which has room to spare. */
@media (max-width:900px) and (orientation:landscape) and (max-height:500px){
  html{height:auto;min-height:100%;overflow-y:auto;overflow-x:hidden;} /* see the max-width:700px block's comment on why html itself (not just body) needs this */
  body{height:auto;min-height:100vh;overflow-y:auto;overflow-x:hidden;}
  #view-catalog.active,#view-filter.active,#view-preview.active,#view-stats.active{flex:1 0 auto;overflow:visible;}
  .layout{overflow:visible;}
  .detail{overflow:visible;}
  header:has(.public-tabs){display:flex;position:sticky;top:0;z-index:20;transition:transform .22s ease;}
  header:has(.public-tabs).auto-hide-header{transform:translateY(-100%);}
}
/* Desktop normal-flow for public pages (2026-08-13) — the mobile/portrait block above already
   switches the public page to plain document-flow (header/content/footer in one scroll, footer
   only reached after all content). On desktop (≥701px) the default was still the nested-scroll
   model (html,body overflow:hidden, each view/detail scrolls independently), so the footer — a
   sibling outside that box — stayed permanently visible below the scrolling detail pane. The
   :has(#site-footer:not(.hidden)) guard means this ONLY fires on the public "/" page (where the
   footer is actually in the DOM and visible), not on the admin shell (footer is .hidden there). */
@media (min-width:701px){
  html:has(body #site-footer:not(.hidden)){height:auto;min-height:100%;overflow-y:auto;overflow-x:hidden;}
  body:has(#site-footer:not(.hidden)){height:auto;min-height:100vh;overflow-y:auto;overflow-x:hidden;}
  body:has(#site-footer:not(.hidden)) #view-preview.active{flex:1 0 auto;overflow:visible;}
  body:has(#site-footer:not(.hidden)) #view-preview.active .layout{overflow:visible;}
  body:has(#site-footer:not(.hidden)) #view-preview.active .detail{overflow:visible;}
}

/* ── FOOTER (public "/" only) ── */
footer#site-footer{flex-shrink:0;background:var(--bg2);border-top:1px solid var(--border);
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:14px 24px;font-size:12px;color:var(--dim);}
.footer-col{display:flex;flex-direction:column;gap:0px;flex:1;min-width:0;}
.footer-col-center{align-items:center;text-align:center;}
.footer-col-right{align-items:flex-end;text-align:right;}
.footer-disclaimer{position:relative;margin-bottom:5px;outline:none;}
.footer-disclaimer .disclaimer-full{top:auto;bottom:100%;margin-top:0;margin-bottom:8px;}
.footer-disclaimer:hover .disclaimer-full,.footer-disclaimer:focus-within .disclaimer-full{display:block;}
.footer-heading{font-weight:700;color:var(--text);font-size:11px;text-transform:uppercase;letter-spacing:.04em;margin-bottom:2px;}
.footer-col a{color:var(--accent);text-decoration:none;}
.footer-col a:hover{text-decoration:underline;}
@media (max-width:700px){
  footer#site-footer{flex-direction:column;align-items:center;text-align:center;padding:14px 16px;gap:10px;}
  .footer-col,.footer-col-center,.footer-col-right{align-items:center;text-align:center;}
  .footer-disclaimer .disclaimer-short{white-space:normal;}
  .footer-col-right{order:2;}
  .footer-col-center{order:3;}
  /* Desktop nests scroll containers (.detail/.sidebar each scroll independently inside a
     viewport-height-locked view/body) so the footer — a sibling OUTSIDE that box — always sits
     at a fixed distance below it, permanently occupying screen space rather than requiring a
     scroll to reach (real complaint: on a phone this ate roughly half the screen during a product
     comparison). Mobile instead lets the page itself scroll as one normal document — header,
     content, and footer all in the same flow — so the footer only appears once you've scrolled
     past everything above it, same as any ordinary web page footer.  */
  /* min-height (not just height:auto) on both html and body — flex:1 0 auto below only pushes the
     footer down to the true viewport bottom on short content if body actually HAS a viewport-tall
     box to grow into; height:auto alone made body only as tall as its content, so a short product
     page left the footer stranded above a big empty gap instead of touching bottom. */
  /* overflow-y MUST be set on html, not just body — per spec, whichever of the two has a non-
     visible overflow is what actually becomes the viewport's scrolling box; html still had the
     default overflow:hidden from the html,body{...} rule near the top of the file, which silently
     blocked all page scrolling no matter what body said (real regression, confirmed live: the page
     couldn't be swiped/scrolled at all after this got split from the original single html,body{}
     rule into two separate ones for the min-height change below). */
  html{height:auto;min-height:100%;overflow-y:auto;overflow-x:hidden;}
  body{height:auto;min-height:100vh;overflow-y:auto;overflow-x:hidden;}
  #view-catalog.active,#view-filter.active,#view-preview.active,#view-stats.active{flex:1 0 auto;overflow:visible;}
  .layout{overflow:visible;}
  .detail{overflow:visible;}
}
/* Absolutely positioned, horizontal `left` set by JS (positionHeaderDisclaimer) rather than a
   static 50% — the row's right side (hstats / chat-controls / nothing, each a different width)
   used to make a flex:1 filler's centered content land at a different on-screen x per page
   (confirmed real bug: visibly different position on Catalog vs FAQ vs MiMo Chat). A plain 50%-
   of-viewport center fixes that but creates a NEW problem: at narrower windows the tabs list
   (logo+Catalog+Filter+MiMo Chat+FAQ+Preview, once logged in) is wide enough to visually overlap
   viewport-center. positionHeaderDisclaimer() instead centers within the space between the tabs'
   actual right edge and the header's reserved right zone (see .user-chip — position:fixed, hence
   the header's own 150px right padding) — consistent across pages (tabs width doesn't change
   between pages once authenticated) and never overlaps either side. */
.header-disclaimer{position:absolute;top:0;bottom:0;transform:translateX(-50%);
  display:flex;justify-content:center;align-items:center;z-index:5;}
.disclaimer-short{font-size:15px;font-weight:700;color:var(--bright);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;cursor:default;}
.disclaimer-full{display:none;position:absolute;top:auto;bottom:calc(50% + 12px);left:50%;transform:translateX(-50%);margin:0;
  background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:5px 13px;
  font-size:14px;color:var(--text);line-height:1.35;width:1256px;max-width:calc(100vw - 24px);z-index:60;box-shadow:0 8px 20px rgba(0,0,0,.4);}
.header-disclaimer:hover .disclaimer-full{display:block;}
.iconbtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);width:36px;height:36px;
  border-radius:8px;cursor:pointer;font-size:16px;flex-shrink:0;}
.iconbtn:hover{background:var(--border);}
select{font:inherit;padding:5px 8px;border:1px solid var(--border);border-radius:7px;background:var(--bg3);color:var(--text);font-size:13px;}

/* ── VIEWS ── */
#view-catalog,#view-chat,#view-web,#view-faq,#view-filter,#view-preview,#view-stats,#view-inspected,#view-mfrs{display:none;flex:1;overflow:hidden;flex-direction:column;}
#view-catalog.active,#view-chat.active,#view-web.active,#view-faq.active,#view-filter.active,#view-preview.active,#view-stats.active,#view-inspected.active,#view-mfrs.active{display:flex;}

/* ══════════ CATALOG ══════════ */
.layout{display:flex;flex:1;overflow:hidden;}
.sidebar{width:300px;flex-shrink:0;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;}
.sidebar:not(.filter-sidebar){width:340px;}
/* .layout (sidebar+detail, shared by Catalog/Filter/Preview — see the "SAME shared DOM" comment
   near view-catalog/view-preview) is a fixed-width-sidebar row by design for a real window; on a
   phone a 300-340px sidebar left almost nothing for .detail (real bug: ~75px of usable width on a
   375px screen, results text reduced to a few characters per line). Stack instead: sidebar takes
   full width at its natural content height (capped so results are still reachable without an
   essay's worth of scrolling first), detail flows below it — each keeps its own existing internal
   scroll (.tree/.detail already have overflow-y:auto) rather than fighting one shared scroller.*/
@media (max-width:700px){
  .layout{flex-direction:column;}
  .sidebar,.sidebar:not(.filter-sidebar){width:100%;max-height:45vh;flex-shrink:0;border-right:none;border-bottom:1px solid var(--border);}
  .search-input::placeholder{font-size:12px;}
}
.search-wrap{padding:10px;border-bottom:1px solid var(--border);position:relative;}
.search-row{display:flex;gap:6px;position:relative;}
.search-row .search-input{flex:1;min-width:0;}
.search-input{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:7px 10px;color:var(--text);font-size:13px;outline:none;}
.search-input:focus{border-color:var(--accent);}
.search-input::placeholder{color:#484f58;}
.search-input.input-error{border-color:var(--red);}
.search-go-btn{flex-shrink:0;width:34px;background:var(--bg3);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:14px;cursor:pointer;}
.search-go-btn:hover{background:var(--accent);color:var(--on-accent);border-color:var(--accent);}
.search-mfr-select{width:100%;margin-top:8px;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:6px 8px;color:var(--text);font-size:12.5px;outline:none;}
.search-mfr-select:focus{border-color:var(--accent);}
.search-hint{font-size:11px;color:var(--red);margin-top:6px;display:none;line-height:1.4;}
.search-hint.show{display:block;}
.search-no-results{padding:20px 14px;text-align:center;color:var(--dim);font-size:13px;font-style:italic;}
/* Live "type CT, see 4 matches immediately" dropdown — Preview-only (see runPreviewSuggest);
   stays empty/hidden in Catalog, which keeps its existing Enter/click tree-filter behavior. */
.search-suggestions{position:absolute;left:0;right:0;top:100%;margin-top:5px;background:var(--bg2);
  border:1px solid var(--border);border-radius:8px;max-height:320px;overflow-y:auto;z-index:40;
  box-shadow:0 10px 24px rgba(0,0,0,.35);}
.search-suggestions.hidden{display:none;}
.sugg-item{display:flex;align-items:center;gap:8px;padding:9px 12px;cursor:pointer;border-top:1px solid var(--border);}
.sugg-item:first-child{border-top:none;}
.sugg-item:hover{background:var(--bg3);}
.sugg-id{font-family:'Consolas',monospace;font-weight:700;font-size:13px;color:var(--bright);white-space:nowrap;}
.sugg-pct{font-size:12px;flex-shrink:0;}
.sugg-name{font-size:12px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;}
.sugg-pill{margin-left:auto;font-size:9px;padding:2px 7px;flex-shrink:0;}
/* Preview layout (2026-07-22): no left product list at all — the tree only ever mattered for
   browsing, which an anonymous visitor shouldn't be able to do anyway (that's the whole point of
   search-only access); the search bar moves to a full-width bar on top instead, and the detail
   pane gets the width the sidebar used to take. Scoped to #view-preview specifically since
   .layout/.sidebar/.detail are the SAME shared DOM relocated between view-catalog/view-preview
   (see switchTab) — Catalog's own layout is completely untouched by these rules. */
#view-preview .layout{flex-direction:column;}
#view-preview .sidebar{width:100%;flex-direction:row;align-items:center;border-right:none;border-bottom:1px solid var(--border);overflow:visible;}
#view-preview .search-wrap{flex:1;border-bottom:none;display:flex;align-items:center;justify-content:center;gap:14px;padding:12px 20px;}
#view-preview .search-row{flex:0 1 640px;width:100%;}
#view-preview .search-hint{position:absolute;top:100%;left:20px;margin-top:4px;}
/* Preview drops the competitor picker entirely (2026-07-22) — searching one company at a time
   isn't the point of the compact external-facing view — and the search bar centers in the now-
   freed space (justify-content:center on the wrap + a fixed-basis, not flex:1, row so it doesn't
   just stretch edge-to-edge instead). Catalog keeps the picker untouched — this is scoped to
   #view-preview only. */
#view-preview .search-mfr-select,#view-preview .tree,#view-preview .search-no-results{display:none;}
.tree{flex:1;overflow-y:auto;padding-bottom:16px;}
.tree::-webkit-scrollbar{width:5px;}
.tree::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
.g-header{display:flex;align-items:center;gap:6px;padding:10px 12px 6px;cursor:pointer;user-select:none;
  font-size:11px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:.8px;
  position:sticky;top:0;background:var(--bg2);z-index:5;border-bottom:1px solid var(--border);}
.g-header:hover{color:var(--text);}
.g-header .arrow{font-size:9px;transition:transform .18s;}
.g-header.open .arrow{transform:rotate(90deg);}
.g-count{background:var(--bg3);border-radius:10px;padding:1px 7px;}
.mfr-header{display:flex;align-items:center;gap:6px;padding:6px 12px 6px 14px;cursor:pointer;user-select:none;
  font-size:12px;font-weight:600;color:var(--accent);transition:background .1s;}
.mfr-header:hover{background:var(--bg3);}
.mfr-header .arrow{font-size:9px;color:var(--dim);transition:transform .18s;}
.mfr-header.open .arrow{transform:rotate(90deg);}
.mfr-count{font-size:11px;color:var(--dim);font-weight:400;}
.mfr-hidden-badge{margin-left:8px;font-size:10px;text-transform:uppercase;letter-spacing:.03em;color:var(--red);border:1px solid var(--red);border-radius:4px;padding:1px 5px;}
/* "Time switches" sub-group (2026-07-22) — a small, grey, collapsible sub-heading nested one
   level under a manufacturer (or under ELKO EP's own top-level list), grouping any product whose
   `time_functions.functions` includes a Time Switch Program family entry (Daily/Weekly/Monthly/
   Yearly/Astro/Holiday program — see isTimeSwitchProduct()). Deliberately NOT a new data field —
   purely a client-side grouping derived from the existing function names, matching this project's
   standing rule that time switches share the exact same schema/relay_type as regular time relays. */
.ts-header{display:flex;align-items:center;gap:6px;padding:5px 12px 5px 26px;cursor:pointer;user-select:none;
  font-size:11px;font-weight:600;color:var(--dim);transition:background .1s;}
.ts-header:hover{background:var(--bg3);}
.ts-header .arrow{font-size:8px;color:var(--dim);transition:transform .18s;}
.ts-header.open .arrow{transform:rotate(90deg);}
.ts-count{font-size:11px;color:var(--dim);font-weight:400;}
.ts-items .t-item{padding-left:40px;}
.t-item{display:flex;align-items:center;padding:5px 12px 5px 28px;cursor:pointer;font-size:13px;color:var(--text);
  border-left:2px solid transparent;transition:background .1s,border-color .1s,color .1s;}
.t-item:hover{background:var(--bg3);}
.t-item.active{background:var(--tint-blue);border-left-color:var(--accent);color:var(--accent);}
.t-item.highlight{animation:blink 1.4s ease;}
@keyframes blink{0%{background:#1f6feb55}100%{background:var(--tint-blue)}}
.t-id{font-weight:600;font-size:12px;font-family:'Consolas',monospace;}
/* Best-match % against ELKO EP, right of the id/code in the sidebar tree (competitor rows only —
   see mkItem's pct param). Reuses .compat-score's red/yellow/green coloring, just smaller here. */
.t-pct{font-size:11px;margin-left:6px;}
.detail{flex:1;overflow-y:auto;padding:28px 36px 48px;background:var(--bg);}
.detail::-webkit-scrollbar{width:5px;}
.detail::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
.empty-state{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;color:var(--dim);}
.empty-relay-icon{width:175px;height:175px;object-fit:contain;}
.empty-state h3{font-size:18px;color:var(--text);}
.empty-state p{font-size:13px;max-width:320px;text-align:center;line-height:1.6;}
.d-head{display:flex;align-items:flex-start;gap:14px;margin-bottom:22px;flex-wrap:wrap;}
.d-head-txt{flex:1;}
.d-head-txt h2{font-size:21px;color:var(--bright);margin-bottom:4px;font-family:'Consolas',monospace;}
.d-head-txt .sub{font-size:13px;color:var(--dim);line-height:1.4;}
.d-head-txt .ean-line{font-family:'Consolas',monospace;font-size:12px;}
.codes-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.codes-empty{font-style:italic;color:#484f58;font-family:'Segoe UI',system-ui,sans-serif;}
.codes-edit-btn{background:none;border:1px solid var(--border);color:var(--dim);border-radius:5px;padding:1px 8px;font-size:11px;cursor:pointer;font-family:'Segoe UI',system-ui,sans-serif;transition:all .12s;}
.codes-edit-btn:hover{color:var(--accent);border-color:var(--accent);}
.codes-editor{display:flex;align-items:flex-end;gap:6px;}
/* Textarea, not a single-line input (2026-07-22) — a product can have a dozen+ long codes, and a
   single-line input only shows a horizontal-scrolled sliver of that at once; worse, clicking
   anywhere outside it to switch windows and copy/paste a code resets that scroll back to the
   start (native browser behavior for unfocused single-line inputs), losing your place every
   time. A wrapping multi-line box shows far more at once and doesn't have that reset-on-blur
   quirk at all. */
.codes-input{background:var(--bg3);border:1px solid var(--border);border-radius:5px;padding:5px 8px;color:var(--text);font-size:12px;font-family:'Consolas',monospace;width:460px;min-height:52px;max-height:200px;resize:vertical;outline:none;}
.codes-input:focus{border-color:var(--accent);}
.codes-save-btn,.codes-cancel-btn{background:var(--bg3);border:1px solid var(--border);border-radius:5px;padding:2px 10px;font-size:11px;color:var(--text);cursor:pointer;font-family:'Segoe UI',system-ui,sans-serif;}
.codes-save-btn{background:var(--accent);color:var(--on-accent);border-color:var(--accent);font-weight:600;}
.codes-save-btn:hover{opacity:.85;}
.codes-cancel-btn:hover{color:var(--red);border-color:var(--red);}
.pill{padding:4px 12px;border-radius:14px;font-size:12px;font-weight:700;border:1px solid;}
.pill-elko{background:#fff9cc;color:#575756;border-color:#fad900;}
.pill-comp{background:#da363322;color:var(--red);border-color:#da363344;}
.card{background:var(--bg2);border:1px solid var(--border);border-radius:10px;margin-bottom:14px;overflow:hidden;}
.card-title{padding:9px 16px;background:var(--bg3);font-size:11px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:.8px;}
.analogs-title-row{display:flex;align-items:center;justify-content:space-between;gap:10px;}
.analogs-toggle-btn{display:inline-flex;align-items:center;gap:6px;background:none;border:1px solid var(--border);color:var(--dim);border-radius:5px;padding:3px 10px;font-size:11px;font-weight:600;text-transform:none;letter-spacing:0;cursor:pointer;}
.analogs-toggle-btn:hover{color:var(--accent);border-color:var(--accent);}
.analogs-toggle-icon{font-size:19px;line-height:1;}
.analogs-list{overflow:hidden;transition:max-height .22s ease;}
.analogs-list.collapsed{max-height:0;}
.content-toggle-btn{display:inline-flex;align-items:center;gap:6px;background:none;border:1px solid var(--border);color:var(--dim);border-radius:5px;padding:4px 12px;font-size:14px;font-weight:600;cursor:pointer;margin-top:4px;}
.content-toggle-btn:hover{color:var(--accent);border-color:var(--accent);}
/* One button hides/shows EVERYTHING below the head row (Specifications + Functions) at once —
   open by default, the button just lets you collapse down to name+compat% if you want the extra
   room. While collapsed there's a lot of empty space, so the product subtitle (name/manufacturer)
   gets promoted from dim gray to bright white and a touch bigger to draw the eye — reverts to the
   normal look the instant the content is expanded again. (.cmp-collapsible-cell itself — display:
   none when collapsed — is declared with the rest of .compare-grid-unified's CSS.) */
.compare-body.content-collapsed .compare-col-sub{color:var(--bright);font-size:12px;font-weight:600;}
.card-body{padding:16px;}
.specs{display:grid;grid-template-columns:150px 1fr;gap:8px 14px;overflow:hidden;}
.sk{color:var(--dim);font-size:13px;line-height:1.4;align-self:stretch;padding-top:0;position:relative;}
.specs>.sk:not(:nth-last-child(2))::after{content:"";position:absolute;left:0;bottom:-4px;width:100vw;border-bottom:1px solid var(--border);opacity:.55;pointer-events:none;}
.sv{color:var(--bright);font-size:13px;line-height:1.4;}
.specs>.spec-better{color:var(--green);}
.specs>.spec-compatible{color:var(--green);}
.specs>.spec-partial{color:var(--yellow);}
.specs>.spec-incompatible{color:var(--red);}
.spec-copy-btn{background:none;border:none;cursor:pointer;font-size:11px;padding:0 0 0 6px;
  opacity:.45;vertical-align:middle;line-height:1;}
.spec-copy-btn:hover{opacity:1;}
.sv small{color:var(--dim)!important;font-size:12px;font-style:italic;}
/* Specification notes (<small>) must stay neutral gray even when the parent .sv/.mobile-spec-value
   carries a comparison state color (spec-better/compatible/partial/incompatible). Without
   !important, the state selector's higher specificity overrides .sv small's color, making notes
   appear green/yellow/red along with the main value — they're supplementary text, not part of the
   comparison result. Covers Output contact notes, Connection wire/notes, Control input notes,
   time_range notes, and voltage/range notes across desktop (.sv) and mobile (.mobile-spec-value). */
.mobile-spec-value small{color:var(--dim)!important;font-size:12px;font-style:italic;}
.tags{display:flex;flex-wrap:wrap;gap:6px;}
.tag{background:rgba(210,153,34,.14);color:var(--yellow);border:1px solid rgba(210,153,34,.38);padding:3px 10px;border-radius:4px;font-size:12px;font-family:'Consolas',monospace;}
.tag-unconfirmed{background:rgba(110,118,129,.16);color:var(--dim);border-color:rgba(110,118,129,.38);}
.tag-function-match{background:rgba(46,160,67,.14);color:var(--green);border-color:rgba(46,160,67,.38);}
.compare-grid-unified .cmp-funcLeft .tag,.compare-grid-unified .cmp-funcRight .tag{order:var(--mobile-fn-order,0);}
.tag-clickable{cursor:pointer;transition:background .12s,border-color .12s,color .12s;}
.tag-clickable:hover{background:var(--accent);color:var(--on-accent);border-color:var(--accent);}
.tag-note{color:var(--dim);font-size:12px;margin-top:8px;font-style:italic;line-height:1.5;}
.map-card{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:14px;margin-bottom:10px;transition:border-color .15s;}
.map-card:hover{border-color:#58a6ff33;}
.map-top{display:flex;align-items:center;gap:10px;margin-bottom:8px;}
.map-compare-btn{margin-left:auto;flex-shrink:0;font-size:10.5px;font-weight:700;padding:2px 8px;border-radius:4px;
  background:var(--accent);color:var(--on-accent);border:none;cursor:pointer;white-space:nowrap;}
.map-compare-btn:hover{background:var(--accent2);}
.rank-dot{width:26px;height:26px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;}
.r1{background:var(--green);color:#0d1117;}.r2{background:var(--yellow);color:#0d1117;}.r3{background:var(--orange);color:#0d1117;}
.alink{font-size:14px;font-weight:700;color:var(--accent);cursor:pointer;font-family:'Consolas',monospace;text-decoration:underline dotted #58a6ff55;transition:color .12s;}
.alink:hover{color:var(--accent2);}
.datasheet-link{font-size:13px;color:var(--accent);text-decoration:underline;transition:color .12s;}
.datasheet-link:hover{color:var(--accent2);}
.datasheet-list{display:flex;flex-direction:column;gap:2px;margin-top:6px;}
.datasheet-list a.datasheet-link{width:fit-content;}
.match-icon{display:inline-flex;vertical-align:middle;}
.match-icon-double{gap:2px;}

/* ── FILTER TAB ── */
.filter-sidebar{overflow-y:auto;}
.filter-sidebar::-webkit-scrollbar{width:5px;}
.filter-sidebar::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
.filter-group{padding:14px 14px 16px;border-bottom:1px solid var(--border);}
.filter-group-title{font-size:11px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:.6px;margin-bottom:10px;}
.filter-cat-title{font-size:12px;font-weight:700;color:var(--text);margin:10px 0 3px;}
.filter-cat-title:first-child{margin-top:0;}
.filter-check-row{display:flex;align-items:center;gap:7px;padding:3px 0;font-size:12.5px;color:var(--text);cursor:pointer;flex:1;min-width:0;}
.filter-check-row input{accent-color:var(--accent);cursor:pointer;flex-shrink:0;}
.filter-check-row span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.filter-fn-row{display:flex;align-items:center;gap:6px;}
.fn-diagram-btn{flex-shrink:0;font-size:10.5px;font-weight:700;padding:2px 8px;border-radius:4px;
  background:var(--accent);color:var(--on-accent);border:none;cursor:pointer;white-space:nowrap;}
.fn-diagram-btn:hover{background:var(--accent2);}
.fn-diagram-all-btn{display:inline-block;width:auto;margin-top:10px;font-size:10.5px;font-weight:700;padding:2px 8px;border-radius:4px;
  background:var(--accent);color:var(--on-accent);border:none;cursor:pointer;text-align:center;white-space:nowrap;}
.fn-diagram-all-btn:hover{background:var(--accent2);}
.diagram-modal{position:fixed;inset:0;background:rgba(0,0,0,.65);z-index:100;display:flex;align-items:center;justify-content:center;padding:24px;}
.diagram-modal-inner{background:var(--bg2);border:1px solid var(--border);border-radius:10px;width:min(980px,94vw);height:min(820px,90vh);overflow:hidden;display:flex;flex-direction:column;}
.diagram-modal-head{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:12px;padding:10px 14px;border-bottom:1px solid var(--border);flex-shrink:0;}
.diagram-modal-title{font-size:13px;font-weight:700;color:var(--bright);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.diagram-zoom-ctrl{display:flex;align-items:center;gap:8px;justify-self:center;}
.diagram-zoom-btn{width:26px;height:26px;border-radius:6px;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:16px;font-weight:700;cursor:pointer;line-height:1;transition:all .12s;}
.diagram-zoom-btn:hover{background:var(--accent);color:var(--on-accent);border-color:var(--accent);}
.diagram-zoom-pct{font-size:12px;color:var(--dim);font-family:'Consolas',monospace;min-width:42px;text-align:center;}
.diagram-modal-close{justify-self:end;background:none;border:none;color:var(--dim);font-size:16px;cursor:pointer;line-height:1;padding:2px 4px;}
.diagram-modal-close:hover{color:var(--red);}
.diagram-viewport{flex:1;min-height:0;overflow:auto;background:#fff;cursor:grab;}
.diagram-viewport.dragging{cursor:grabbing;}
.diagram-viewport img{display:block;}
.diagram-no-map{padding:24px;color:var(--dim);font-size:13px;font-style:italic;}
.diagram-pager{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;padding:10px 14px;border-top:1px solid var(--border);flex-shrink:0;}
.diagram-page-btn{width:30px;height:26px;border-radius:6px;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:12px;cursor:pointer;transition:all .12s;}
.diagram-page-btn:hover{border-color:var(--accent);color:var(--bright);}
.diagram-page-btn.active{background:var(--accent);color:var(--on-accent);border-color:var(--accent);font-weight:700;}
.mimo-manual-modal{position:fixed;inset:0;background:rgba(0,0,0,.65);z-index:100;display:flex;align-items:center;justify-content:center;padding:24px;}
.mimo-manual-inner{background:var(--bg2);border:1px solid var(--border);border-radius:10px;width:min(720px,94vw);height:min(820px,90vh);overflow:hidden;display:flex;flex-direction:column;}
.mimo-manual-head{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--border);flex-shrink:0;}
.mimo-manual-lang{display:flex;gap:4px;justify-self:start;}
.mimo-lang-btn{background:var(--bg3);border:1px solid var(--border);border-radius:5px;padding:4px 11px;font-size:12px;font-weight:700;color:var(--dim);cursor:pointer;}
.mimo-lang-btn:hover{color:var(--text);}
.mimo-lang-btn.active{background:var(--accent);color:var(--on-accent);border-color:var(--accent);}
.mimo-manual-views{display:flex;gap:4px;justify-self:center;}
.mimo-view-btn{background:var(--bg3);border:1px solid var(--border);border-radius:5px;padding:5px 18px;font-size:13px;font-weight:700;color:var(--dim);cursor:pointer;}
.mimo-view-btn:hover{color:var(--text);}
.mimo-view-btn.active{background:var(--accent);color:var(--on-accent);border-color:var(--accent);}
.mimo-manual-close{background:none;border:none;color:var(--dim);font-size:16px;cursor:pointer;line-height:1;padding:2px 4px;justify-self:end;}
.mimo-manual-close:hover{color:var(--red);}
.mimo-manual-body{flex:1;overflow-y:auto;padding:22px 28px 32px;font-size:13.5px;line-height:1.65;color:var(--text);}
.mimo-prompts-hint{color:var(--dim);font-size:12.5px;margin:0 0 16px;}
.mimo-prompt-card{background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:12px 14px;margin-bottom:10px;cursor:pointer;transition:border-color .12s;}
.mimo-prompt-card:hover{border-color:var(--accent);}
.mimo-prompt-title{font-weight:700;color:var(--bright);font-size:13.5px;margin-bottom:6px;}
.mimo-prompt-text{font-size:12.5px;color:var(--text);line-height:1.55;}
.mimo-prompt-text b{color:var(--accent2);}
.mimo-manual-body h3{font-size:19px;color:var(--bright);margin:0 0 10px;}
.mimo-manual-body h4{font-size:13.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--accent2);margin:22px 0 8px;border-top:1px solid var(--border);padding-top:16px;}
.mimo-manual-body h4:first-of-type{border-top:none;padding-top:0;margin-top:14px;}
.mimo-manual-body p{margin:0 0 10px;}
.mimo-manual-body ul{margin:0 0 10px;padding-left:20px;}
.mimo-manual-body li{margin-bottom:7px;}
.mimo-manual-body code{background:var(--bg3);border:1px solid var(--border);border-radius:4px;padding:1px 5px;font-family:'Consolas',monospace;font-size:12.5px;color:var(--accent2);}
.mimo-manual-body b{color:var(--bright);}
.mimo-video-intro{display:flex;align-items:center;gap:14px;margin-bottom:18px;}
.mimo-video-thumb{position:relative;flex-shrink:0;width:180px;border-radius:8px;overflow:hidden;border:1px solid var(--border);background:#000;}
.mimo-video-thumb video{display:block;width:100%;height:auto;}
.mimo-video-caption{font-size:13px;color:var(--dim);line-height:1.5;}
.mimo-video-caption a{color:var(--accent);text-decoration:underline;}
.mimo-video-caption a:hover{color:var(--accent2);}
.filter-range-vals{display:flex;justify-content:space-between;font-size:12px;color:var(--accent2);font-family:'Consolas',monospace;margin-bottom:2px;}
.dual-range{position:relative;height:26px;margin:6px 0 10px;}
.dual-range .track{position:absolute;top:11px;left:0;right:0;height:4px;background:var(--border);border-radius:2px;}
.dual-range .fill{position:absolute;top:11px;height:4px;background:var(--accent);border-radius:2px;}
.dual-range input[type=range]{position:absolute;top:0;left:0;width:100%;height:26px;margin:0;background:transparent;pointer-events:none;-webkit-appearance:none;appearance:none;}
.dual-range input[type=range]::-webkit-slider-thumb{pointer-events:auto;-webkit-appearance:none;width:14px;height:14px;border-radius:50%;background:var(--bright);border:2px solid var(--accent);cursor:pointer;margin-top:0;}
.dual-range input[type=range]::-moz-range-thumb{pointer-events:auto;width:14px;height:14px;border-radius:50%;background:var(--bright);border:2px solid var(--accent);cursor:pointer;}
.dual-range input[type=range]::-webkit-slider-runnable-track{background:transparent;}
.filter-apply-btn{width:100%;margin-top:8px;padding:6px 10px;font-size:12px;font-weight:600;background:var(--bg3);
  border:1px solid var(--border);border-radius:6px;color:var(--text);cursor:pointer;transition:all .12s;}
.filter-apply-btn:hover{background:var(--accent);border-color:var(--accent);color:#0d1117;}
.filter-apply-btn.dirty{background:var(--accent);border-color:var(--accent);color:#0d1117;}
.filter-reset-btn{width:100%;padding:9px;font-size:12.5px;font-weight:600;background:transparent;
  border:1px solid var(--border);border-radius:6px;color:var(--dim);cursor:pointer;margin:12px 0;transition:all .12s;}
.filter-reset-btn:hover{color:var(--red);border-color:var(--red);}
.filter-results-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;}
.filter-results-count{font-size:13px;color:var(--dim);}
.filter-results-count b{color:var(--bright);}
.fcard-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px;}
.fcard{background:var(--bg2);border:1px solid var(--border);border-radius:10px;overflow:hidden;cursor:pointer;transition:border-color .15s,transform .12s;}
.fcard:hover{border-color:var(--accent);transform:translateY(-2px);}
.fcard-img{width:100%;height:130px;background:#fff;display:flex;align-items:center;justify-content:center;overflow:hidden;}
.fcard-img img{width:100%;height:100%;object-fit:contain;padding:8px;}
.fcard-img .relay-placeholder{width:96px;height:96px;object-fit:contain;padding:0;opacity:.55;}
.fcard-body{padding:10px 12px 12px;}
.fcard-id{font-family:'Consolas',monospace;font-weight:700;font-size:13.5px;color:var(--accent2);}
.fcard-name{font-size:12px;color:var(--dim);margin-top:2px;line-height:1.4;height:2.8em;overflow:hidden;}
.fcard-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px;}
.fcard-tag{font-size:10.5px;background:var(--bg3);color:var(--dim);border-radius:4px;padding:2px 6px;}

/* ── FAQ ── */
.faq-add-row{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:pointer;color:var(--dim);
  font-size:13px;border-top:1px solid var(--border);transition:color .12s,background .12s;}
.faq-add-row:hover{color:var(--accent);background:var(--bg3);}
.faq-add-icon{font-size:16px;font-weight:700;color:var(--accent);width:18px;text-align:center;}
.faq-add-form{display:flex;gap:8px;padding:10px 12px;border-top:1px solid var(--border);align-items:center;}
.faq-add-form .search-input{flex:1;}

/* ── STATISTIC (admin, view-counts on competitor products) ── */
.stats-wrap{flex:1;overflow:auto;padding:20px 24px;}
/* Table itself stays full natural width and scrolls horizontally within .stats-wrap's own
   overflow:auto (already there for desktop) rather than squeezing columns unreadably — same
   pattern the Filter product grid already relies on for the Statistic/Manufacturers admin tabs. */
@media (max-width:700px){.stats-wrap{padding:14px;}.stats-table{min-width:520px;}}
.stats-head-row{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:14px;}
.stats-title{font-size:16px;font-weight:600;margin-bottom:0;}
.stats-sub{font-size:12px;font-weight:400;color:var(--dim);margin-left:4px;}
.stats-reset-row{display:flex;align-items:center;gap:10px;flex-shrink:0;}
.stats-last-reset{font-size:12px;color:var(--dim);white-space:nowrap;}
.stats-reset-btn{padding:6px 14px;border-radius:7px;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:12px;font-weight:600;cursor:pointer;transition:all .15s;}
.stats-reset-btn:hover{border-color:var(--red);color:var(--red);}
.stats-reset-btn:disabled{opacity:.6;cursor:default;}
.stats-table{width:100%;border-collapse:collapse;font-size:13px;}
.stats-table th{text-align:left;padding:8px 10px;color:var(--dim);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.03em;border-bottom:1px solid var(--border);}
/* The numeric column's own header needs the SAME alignment as .stats-count below it (real bug:
   header defaulted to the table's usual text-align:left while the count cells were text-align:
   right, so "Products"/"Views" sat at the left of their column while the actual numbers sat at
   the right — never lined up under one another). */
.stats-table th.stats-th-num{text-align:center;}
.stats-table td{padding:8px 10px;border-bottom:1px solid var(--border);}
.stats-code{font-family:'Consolas',monospace;}
.stats-count{text-align:center;font-weight:600;color:var(--accent);}
.stats-last-viewed{color:var(--dim);font-size:12px;white-space:nowrap;}
.stats-row{cursor:pointer;}
.stats-row:hover{background:var(--bg3);}
.mfr-vis-toggle{display:flex;align-items:center;cursor:pointer;}
.mfr-vis-check{cursor:pointer;accent-color:var(--accent);width:15px;height:15px;}
.visibility-section{margin-top:18px;}
.visibility-section+.visibility-section{margin-top:28px;}
.visibility-section-title{font-size:14px;font-weight:600;color:var(--bright);margin:0 0 8px;}
.visibility-section-title small{font-size:11px;font-weight:400;color:var(--dim);margin-left:5px;}
.visibility-table{max-width:920px;}
.visibility-table td:first-child{display:flex;align-items:center;gap:8px;}
.visibility-table .vdot{cursor:default;margin-left:0;}
.visibility-table .vdot:hover{transform:none;}
.visibility-group-row th{padding-top:18px!important;padding-bottom:7px!important;background:var(--bg2);color:var(--bright)!important;font-size:12px!important;text-transform:none!important;letter-spacing:0!important;border-top:1px solid var(--border);}
.visibility-group-row:first-child th{border-top:0;}
.visibility-group-row small{font-size:11px;font-weight:400;color:var(--dim);margin-left:5px;}

/* ── INSPECTED (admin, green verification coverage) ── */
.inspected-list{display:grid;gap:12px;margin-top:18px;max-width:900px;}
.inspected-row{background:var(--bg2);border:1px solid var(--border);border-radius:9px;padding:14px 16px;}
.inspected-row-head{display:grid;grid-template-columns:14px minmax(0,1fr) auto 58px;align-items:center;gap:16px;margin:0 0 10px;width:100%;background:none;border:0;color:inherit;text-align:left;font:inherit;cursor:pointer;}
.inspected-arrow{font-size:10px;color:var(--dim);transition:transform .15s;}
.inspected-row.open .inspected-arrow{transform:rotate(90deg);}
.inspected-name{font-size:14px;font-weight:600;color:var(--bright);}
.inspected-count{font-size:13px;color:var(--dim);white-space:nowrap;}
.inspected-count b{color:var(--green);}
.inspected-percent{font-size:15px;font-weight:700;color:var(--green);text-align:right;}
.inspected-progress{height:8px;border-radius:999px;background:var(--bg3);overflow:hidden;}
.inspected-progress span{display:block;height:100%;border-radius:inherit;background:var(--green);transition:width .2s ease;}
.inspected-makers{margin:14px -16px -14px;padding:12px 16px 14px;border-top:1px solid var(--border);background:var(--bg);border-radius:0 0 8px 8px;}
.inspected-makers-title{font-size:12px;font-weight:600;color:var(--text);margin-bottom:9px;}
.inspected-makers-title span{font-weight:400;color:var(--dim);}
.inspected-maker-list{display:flex;flex-wrap:wrap;gap:7px;}
.inspected-maker{display:inline-flex;align-items:center;gap:7px;padding:4px 9px;border:1px solid var(--border);border-radius:999px;background:var(--bg2);font-size:12px;color:var(--text);}
.inspected-maker i{width:7px;height:7px;border-radius:50%;background:#3fb950;flex-shrink:0;}
.inspected-makers-empty{font-size:12px;color:var(--dim);font-style:italic;}
@media (max-width:700px){
  .inspected-row-head{grid-template-columns:14px minmax(0,1fr) auto;gap:8px 10px;}
  .inspected-name{grid-column:2;}
  .inspected-count{grid-column:2;grid-row:2;}
  .inspected-percent{grid-column:3;grid-row:1 / span 2;text-align:right;}
}
.faq-item{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:9px 12px;
  cursor:pointer;font-size:13px;color:var(--text);border-left:2px solid transparent;
  transition:background .1s,border-color .1s,color .1s;}
.faq-item:hover{background:var(--bg3);}
.faq-item.active{background:var(--tint-blue);border-left-color:var(--accent);color:var(--accent);}
.faq-item-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.faq-del{flex-shrink:0;font-size:13px;opacity:.5;cursor:pointer;transition:opacity .12s;}
.faq-del:hover{opacity:1;}
.faq-composer textarea{width:100%;min-height:160px;background:var(--bg);border:1px solid var(--border);
  border-radius:6px;padding:10px;color:var(--text);font:13px/1.5 inherit;resize:vertical;outline:none;}
.faq-composer textarea:focus{border-color:var(--accent);}
.sbadge{margin-left:auto;padding:3px 10px;border-radius:12px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;}
.s-perfect{background:var(--tint-green);color:var(--green);}.s-acceptable{background:var(--tint-yellow);color:var(--yellow);}.s-no_match{background:var(--tint-red);color:var(--red);}
.map-notes{font-size:12px;color:var(--dim);line-height:1.6;}
.backref{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;margin-bottom:6px;cursor:pointer;transition:border-color .15s;}
.backref:hover{border-color:#58a6ff33;}
.br-mfr{color:var(--dim);font-size:12px;}.br-id{color:var(--accent);font-weight:700;font-size:13px;font-family:'Consolas',monospace;}
.br-rank{margin-left:auto;font-size:12px;color:var(--dim);}
.no-refs{color:var(--dim);font-size:13px;font-style:italic;}
.dq-high{color:var(--green);}.dq-medium{color:var(--yellow);}.dq-low{color:var(--red);}
.hidden{display:none!important;}
.vdot{width:8px;height:8px;border-radius:50%;flex-shrink:0;cursor:pointer;margin-left:6px;display:inline-block;transition:transform .12s;vertical-align:middle}
.tree.preview-active .vdot{display:none}
.vdot:hover{transform:scale(1.4)}
.v-gray{background:#484f58}.v-green{background:#3fb950}.v-yellow{background:#e3b341}
.vpopup{position:fixed;z-index:999;background:var(--bg2);border:1px solid var(--border);border-radius:8px;padding:8px 12px;box-shadow:0 8px 24px #0009;display:flex;align-items:center;gap:10px}
.vpopup-lbl{font-size:11px;color:var(--dim)}
/* Past-conversations picker — same fixed/floating/outside-click-dismiss pattern as .vpopup, just
   wider and a scrollable vertical list instead of a row of dots (potentially many saved
   conversations, shared across everyone using the shared admin login from different browsers). */
.chist-popup{position:fixed;z-index:999;background:var(--bg2);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px #0009;width:320px;max-height:360px;overflow-y:auto;padding:6px}
.chist-empty{padding:12px;font-size:12px;color:var(--dim);text-align:center}
.chist-item{position:relative;padding:8px 26px 8px 10px;border-radius:6px;cursor:pointer;display:flex;flex-direction:column;gap:2px}
.chist-item:hover{background:var(--bg3,#ffffff14)}
.chist-item.active{background:var(--bg3,#ffffff14);border:1px solid var(--border)}
.chist-item-title{font-size:12.5px;color:var(--bright);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.chist-del{position:absolute;top:6px;right:6px;width:18px;height:18px;border:none;border-radius:5px;background:transparent;color:var(--dim);cursor:pointer;font-size:12px;line-height:1;padding:0;}
.chist-del:hover{background:var(--red);color:#fff;}
.chist-item-meta{font-size:10.5px;color:var(--dim)}
.vopt{width:18px;height:18px;border-radius:50%;cursor:pointer;border:2px solid transparent;transition:border-color .12s,transform .1s;flex-shrink:0}
.vopt:hover{transform:scale(1.2);border-color:#ffffff44}
.vrow{display:flex;align-items:center;gap:8px;margin-top:8px;flex-wrap:wrap}
.vdot-lg{width:13px;height:13px;border-radius:50%;cursor:pointer;flex-shrink:0;transition:transform .12s}
.vdot-lg:hover{transform:scale(1.2)}
.vlabel{font-size:12px;color:var(--dim);font-style:italic}
.vnote-wrap{display:flex;align-items:flex-end;gap:6px}
.vnote-lbl{font-size:12px;color:var(--dim)}
.vnote-input{background:var(--bg3);border:1px solid var(--border);border-radius:4px;padding:3px 8px;color:var(--text);font-size:12px;font-family:inherit;width:300px;height:74px;min-height:32px;max-height:220px;resize:vertical;outline:none;transition:border-color .15s}
.vnote-input:focus{border-color:var(--accent)}
.vnote-input::placeholder{color:#484f58}
/* Visible failure indicator for any save that didn't actually persist — every write used to be
   fire-and-forget (optimistic UI update immediately, network call's result never checked), so an
   expired session or a network hiccup failed completely silently: the field kept looking "saved"
   while nothing reached the server. Confirmed real incident (2026-07-29): an admin's yellow-note
   edits silently failed for roughly an hour after their session expired mid-use — discovered only
   on the next page refresh, by which point everything typed in that window was gone (never
   persisted, so reloading pulled the old data from before the session died). This is the fix:
   surface the failure the moment it happens, right next to the field, clickable to retry. */
.save-warn{color:var(--red);font-size:11.5px;cursor:pointer;margin-top:3px;}
.save-warn:hover{text-decoration:underline;}
/* Quick-insert list for the yellow note field — the same handful of correction types show up
   over and over (see YELLOW_NOTE_PROMPTS), so focusing the field offers them instead of retyping
   the same instruction shape every time. Deliberately laid out IN-FLOW (part of the normal
   document flow, pushing the Specifications card down while open) rather than a floating
   position:fixed panel — three separate real bugs came out of the floating version (it covered
   the parameters card when it dropped below the field; the "room to the right" check measured
   the wrong element and could never find room on some layouts; opening the field/function
   sub-picker on mousedown made the browser retarget the following 'click' event to <body>,
   which then triggered the outside-click-closer and closed everything the instant it opened).
   In-flow sidesteps all three at once: nothing to position, nothing to overlap, no floating
   z-index stacking for a click to misfire against. Also fixes compare mode, where each column
   is too narrow for a floating side-panel to ever find real room either way. */
.vnote-prompts{display:none;flex-direction:column;gap:2px;margin-top:8px;padding:8px;
  background:var(--bg2);border:1px solid var(--border);border-radius:8px;max-width:480px;}
.vnote-prompts.open{display:flex;}
.vnote-prompt-item{padding:6px 10px;border-radius:6px;cursor:pointer;font-size:12.5px;
  color:var(--text);line-height:1.4;white-space:normal;}
.vnote-prompt-item:hover{background:var(--bg3);color:var(--accent);}
.vnote-prompt-sub{display:flex;flex-wrap:wrap;gap:5px;padding:2px 4px 6px 18px;}
.vnote-prompt-sub .vnote-prompt-item{background:var(--bg);border:1px solid var(--border);padding:4px 9px;}
.br-section{font-size:11px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:.6px;margin:8px 0 4px;padding-top:4px}
.specs-desc-row{display:flex;gap:16px;align-items:flex-start;}
.compare-select{font:inherit;padding:5px 8px;border:1px solid var(--border);border-radius:7px;background:var(--bg3);color:var(--text);font-size:12px;max-width:240px;}
.compare-clear{background:none;border:none;color:var(--dim);cursor:pointer;font-size:15px;padding:0 2px;line-height:1;transition:color .12s;}
.compare-clear:hover{color:var(--red);}
.compat-smiley{font-size:20px;filter:grayscale(1);opacity:.65;}
.compat-smiley.compat-red{filter:none;opacity:1;}
.compat-smiley.compat-yellow{filter:none;opacity:1;}
.compat-smiley.compat-green{filter:none;opacity:1;}
.compat-score{font-size:16px;font-weight:700;color:var(--dim);font-family:'Consolas',monospace;}
.compat-score.compat-red{color:var(--red);}
.compat-score.compat-yellow{color:var(--yellow);}
.compat-score.compat-green{color:var(--green);}
/* Compare mode replaces the whole normal header (id/name/manufacturer/verification) with just
   the grid's own column heads below — that info would otherwise be pure duplication. The overall
   compat smiley/score is docked to the top-right corner of ELKO EP's OWN column (2026-07-21 — it
   used to float in a separate shared middle gutter column, ambiguous as to which side it belonged
   to; a human reviewing the page pointed out it reads much more clearly sitting on ELKO EP's side,
   smiley+percent side by side with the See more/less toggle below them); the comparison picker +
   remove button sit next to the ELKO EP side's own name (compare-head-select-wrap) instead of a
   separate top bar. */
/* Photo + compat block now share ONE positioned wrapper (.compare-head-topright, 2026-07-21)
   instead of each being independently absolute — inset 16px from top/right so neither ever
   touches the card's own border (a real complaint once the head row became a bordered .card:
   right:0/top:0 sat flush against the new border). Being siblings in a plain flex row also means
   photo's offset no longer needs to hardcode compat's width — that was an approximation
   (right:118px) that only worked for one particular percent-text width. */
.compare-head-topright{position:absolute;top:16px;right:16px;display:flex;align-items:flex-start;gap:12px;}
/* Button stretches to match the smiley+percent row's own width above it (2026-07-21, per
   feedback) — align-items:stretch on the column plus an explicit width:100% on the button. The
   row itself (smiley+percent, no explicit width) is what actually sets the column's fit-content
   width since percentages are ignored during that intrinsic-sizing pass; the button's width:100%
   then resolves against that already-settled width, so the two always end up the same size with
   no JS measuring needed. justify-content (not text-align) centers the button's own label — the
   base .content-toggle-btn class is display:inline-flex, and text-align has no effect on a flex
   container's children. */
.compare-head-compat{display:flex;flex-direction:column;align-items:stretch;gap:4px;}
.compare-head-compat-row{display:flex;align-items:center;justify-content:center;gap:6px;}
.compare-head-compat .compat-smiley{font-size:32px;}
.compare-head-compat .compat-score{font-size:20px;}
.compare-head-compat .content-toggle-btn{width:100%;box-sizing:border-box;justify-content:center;}
.compare-head-select-wrap{display:inline-flex;align-items:center;gap:6px;}
/* ONE grid (not 4 separate ones) — grid-template-areas places head/specs/functions/description
   purely by name, independent of DOM/source order. Same markup renders as a 2-column side-by-side
   comparison on desktop (each row auto-sized to its tallest cell — if one product has 10
   functions and the other has 1, the shorter card still grows to match, default CSS Grid row-
   track + stretch behaviour) and as fully product-grouped blocks on mobile: LEFT product's
   head+specs+functions+description, then the RIGHT product's same four (2026-08-05, per
   feedback — the old 4-separate-grids version always interleaved by row-type, "both heads, then
   both specs cards, then...", with nothing on screen indicating which card belonged to which
   product). Collapse ("See more"/"See less") now toggles .cmp-collapsible-cell directly (plain
   display:none) instead of animating a shared wrapper's max-height — that single wrapper
   (spanning both products' specs+functions+description at once) is exactly what made grouping by
   product impossible before: it could never be split to sit between one product's head and the
   other's. Losing the smooth height transition is the tradeoff; the toggle still shows/hides both
   sides in sync (same class, same descendant selector, .content-collapsed on compare-body). */
.compare-grid-unified{
  /* column-gap only, no row-gap — CSS Grid's gap is a property of the TRACK layout, applied
     between every pair of adjacent rows regardless of whether the cells in them have visible
     content. A collapsed cmp-collapsible-cell (display:none) correctly takes its own margin with
     it when hidden, but a structural row-gap does NOT go away just because that row's cell is
     empty — real bug, confirmed live: the "See more"/collapsed state showed a stack of empty
     16px gaps (one per hidden row) between the head cards and the next visible section, with
     nothing in them. Vertical spacing between cards now comes from margin-bottom on the cards
     themselves (see .compare-grid-unified .card below) instead, which DOES disappear correctly
     when its card is display:none. */
  display:grid;grid-template-columns:minmax(0,1fr) 112px minmax(0,1fr);column-gap:8px;align-items:stretch;margin-bottom:14px;
  grid-template-areas:
    "headLeft headMiddle headRight"
    "specsLeft specsMiddle specsRight"
    "funcLeft funcMiddle funcRight"
    "descLeft . descRight";
}
/* display:flex (not the default block) so the .card inside actually FILLS this wrapper's full
   stretched height, rather than just sitting at its own short natural height inside a taller-but-
   invisible box — real bug, confirmed via screenshot: Specifications/Functions cards visibly
   different heights left/right despite align-items:stretch on the grid. Before this refactor,
   .card was the direct grid item and stretch applied to it directly; now .cmp-* is the item one
   level up (needed so grid-area can place it), so the stretch has to be relayed down to .card via
   flex (.compare-grid-unified .card{flex:1} does the actual filling, see below). */
.cmp-headLeft{grid-area:headLeft;display:flex}
.cmp-headMiddle{grid-area:headMiddle;display:flex}
.cmp-headRight{grid-area:headRight;display:flex}
.cmp-specsLeft{grid-area:specsLeft;display:flex}
.cmp-specsMiddle{grid-area:specsMiddle;display:flex}
.cmp-specsRight{grid-area:specsRight;display:flex}
.cmp-funcLeft{grid-area:funcLeft;display:flex}
.cmp-funcMiddle{grid-area:funcMiddle;display:flex}
.cmp-funcRight{grid-area:funcRight;display:flex}
.cmp-descLeft{grid-area:descLeft;display:flex}
.cmp-descRight{grid-area:descRight;display:flex}
.compare-body.content-collapsed .cmp-collapsible-cell{display:none;}
/* Below this width each grid column gets too narrow for the 150px product photo + id/name text
   sitting next to it in the head row (see .compare-head-photo) — content doesn't get clipped by
   a CSS grid track, it visually overflows into the neighboring column instead, so on a phone this
   read as the two products' info overlapping each other. One column, fully grouped per product
   (LEFT product's 4 blocks, then RIGHT product's 4), each with the full viewport width to breathe. */
@media (max-width:700px), (max-width:900px) and (orientation:landscape) and (max-height:500px){
  #view-preview .detail{padding-top:0;}
  .compare-grid-unified{
    grid-template-columns:minmax(0,1fr) minmax(0,1fr);
    grid-template-areas:
      "mobileSummary mobileSummary"
      "headLeft headRight"
      "mobileSpecs mobileSpecs"
      "mobileLegend mobileLegend"
      "funcLeft funcRight"
      "descRight descRight"
      "descLeft descLeft";
  }
  .cmp-mobileSummary{grid-area:mobileSummary;}
  .cmp-mobileSpecs{grid-area:mobileSpecs;}
  .cmp-mobileLegend{grid-area:mobileLegend;}
  .cmp-specsLeft,.cmp-specsRight{display:none!important;}
  .compare-desktop-only{display:none!important;}
  .compare-mobile-only{display:block!important;}
}
.compare-mobile-only{display:none;}
.compare-middle-head{align-items:center;justify-content:center;padding:12px 8px;}
.compare-middle-head .compare-head-compat-row{flex-direction:column;gap:2px;}
.compare-middle-head .compat-score{order:0;font-size:38px;line-height:1;}
.compare-middle-head .compat-score-wrap{order:-1;display:flex;flex-direction:column;align-items:center;gap:3px;}
.compare-middle-head .compat-score-owner{font-size:20px;line-height:1;font-weight:700;letter-spacing:.7px;text-transform:uppercase;color:var(--dim);transform:scaleX(1.1467);transform-origin:center;}
.compare-middle-head .compat-score-label{font-size:30px;line-height:1;font-weight:700;letter-spacing:.5px;text-transform:uppercase;color:var(--dim);transform:scaleX(.865);}
.compare-middle-head .compat-smiley{font-size:52px;line-height:1.08;}
.compare-middle-head .content-toggle-btn{font-size:12px;padding:3px 8px;}
.compare-middle-specs .card-body{padding:16px 8px;}
.compare-middle-specs .card-title{display:flex;align-items:center;justify-content:center;padding-left:0;padding-right:0;text-align:center;}
.compare-spec-icons{display:grid;grid-auto-rows:18.2px;gap:12.8px;align-items:center;justify-items:center;}
.compare-spec-icon-row{min-height:18.2px;display:flex;align-items:center;justify-content:center;}
.compare-spec-icon-row .match-icon{margin:0;}
.compare-function-legend{display:flex;flex-direction:column;}
.compare-function-legend .card-body{display:flex;flex:1;flex-direction:column;align-items:stretch;justify-content:space-evenly;gap:10px;padding:10px 8px;}
.compare-function-legend .card-title{text-align:center;}
.compare-legend-row{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:center;gap:4px;color:var(--text);font-size:13px;line-height:17px;}
.compare-legend-row .match-icon{margin:0;justify-self:center;}
.compare-spec-icons .match-icon svg{width:28px;height:28px;}
.compare-function-legend .match-icon svg{width:26px;height:26px;}
.compare-legend-star{display:inline-flex;width:26px;height:26px;align-items:center;justify-content:center;justify-self:center;box-sizing:border-box;border:1px solid rgba(110,118,129,.38);border-radius:6px;background:rgba(110,118,129,.16);color:var(--dim);font:700 20px/1 'Consolas',monospace;}
.compare-legend-row-unconfirmed{grid-template-columns:32px minmax(0,1fr);gap:4px;}
.compare-legend-row-unconfirmed>span:last-child{font-size:13px;line-height:17px;letter-spacing:-.7px;transform:scaleX(.86);transform-origin:left center;width:116.3%;}
.mobile-function-owner{display:none;}
@media (max-width:700px), (max-width:900px) and (orientation:landscape) and (max-height:500px){
  .compare-grid-unified{column-gap:8px;margin-bottom:0;}
  .cmp-mobileSummary{margin-left:-36px;margin-right:-36px;}
  .mobile-match-summary{border-radius:0;border-left:0;border-right:0;}
  .compare-grid-unified .card{margin-bottom:12px;}
  .compare-grid-head-preview{padding-top:0!important;}
  .cmp-headLeft .compare-col-head,.cmp-headRight .compare-col-head{padding:10px;align-items:flex-start;gap:6px;}
  .cmp-headLeft .compare-head-topright,.cmp-headRight .compare-head-topright{justify-content:center;margin-bottom:6px;}
  .cmp-headLeft .compare-head-photo,.cmp-headRight .compare-head-photo{width:100%;height:110px;padding:4px;}
  .cmp-headLeft .compare-col-id,.cmp-headRight .compare-col-id,.cmp-headLeft .alink,.cmp-headRight .alink{flex:0 0 100%;width:100%;font-size:12px;line-height:1.3;overflow-wrap:anywhere;}
  .cmp-headLeft .compare-col-pill,.cmp-headRight .compare-col-pill{font-size:8px;padding:2px 5px;}
  .cmp-headLeft .compare-head-select-wrap,.cmp-headRight .compare-head-select-wrap{width:100%;min-width:0;}
  .cmp-headLeft .compare-select,.cmp-headRight .compare-select{width:100%;min-width:0;max-width:none;padding:4px 5px;}
  .cmp-headLeft .compare-col-sub,.cmp-headRight .compare-col-sub{margin-top:3px;font-size:10px;line-height:1.35;overflow-wrap:anywhere;}
  .cmp-headLeft .codes-row,.cmp-headRight .codes-row{font-size:10px;line-height:1.35;overflow-wrap:anywhere;}
  .cmp-headLeft .datasheet-list,.cmp-headRight .datasheet-list{font-size:10px;}
  .compare-functions-title{display:flex;align-items:center;justify-content:space-between;gap:8px;}
  .mobile-function-owner{display:inline-flex;font-size:10px;line-height:1;padding:3px 8px;letter-spacing:0;text-transform:none;}

  /* Mobile notes toggle: "Result" title becomes a 3-column grid so the title stays centered
     while the toggle button sits right-aligned in the third column. */
  .mobile-spec-title{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;}
  .mobile-spec-title>span{grid-column:2;justify-self:center;}
  .mobile-notes-toggle{grid-column:3;justify-self:end;margin-top:0;font-size:9px;padding:3px 7px;border-radius:999px;}
  /* Default: notes hidden. .notes-visible class added by JS toggle shows them again. */
  .mobile-spec-comparison:not(.notes-visible) .mobile-spec-value small:not(.spec-contact-type){display:none;}

  .mobile-match-summary .card-body{display:grid;grid-template-columns:auto 1fr auto auto;align-items:center;row-gap:9px;column-gap:0;padding:12px 14px;}
  .mobile-match-owner{font-size:28px;line-height:1;font-weight:800;letter-spacing:.5px;color:var(--dim);}
  .mobile-match-label{font-size:28px;line-height:1;font-weight:800;text-transform:uppercase;color:var(--dim);justify-self:end;margin-right:10px;}
  .mobile-match-summary .compat-score{font-size:28px;line-height:1;margin-right:10px;}
  .mobile-match-summary .compat-smiley{font-size:34px;line-height:1;}

  .mobile-spec-comparison .card-body{padding:0 14px 4px;}
  .mobile-spec-comparison>.card-title{text-align:center;}
  .mobile-spec-columns{display:grid;grid-template-columns:minmax(0,1fr) 82px minmax(0,1fr);gap:8px;align-items:center;padding:8px 0 6px;color:var(--dim);font-size:10px;font-weight:700;text-align:center;text-transform:uppercase;letter-spacing:.45px;border-bottom:1px solid var(--border);}
  .mobile-spec-columns .mobile-spec-product-pill{display:inline-flex;justify-self:center;align-items:center;width:fit-content;padding:3px 8px;font-size:9px;line-height:1;letter-spacing:0;text-transform:none;}
  .mobile-spec-parameters-label{justify-self:center;font-size:11px;line-height:1;font-weight:700;letter-spacing:.8px;white-space:nowrap;}
  .mobile-spec-row{padding:7px 0;border-bottom:1px solid var(--border);}
  .mobile-spec-row:last-child{border-bottom:none;}
  .mobile-spec-label{margin-bottom:3px;color:var(--dim);font-size:11px;font-weight:700;text-align:center;}
  .mobile-spec-values{display:grid;grid-template-columns:minmax(0,1fr) 82px minmax(0,1fr);gap:8px;align-items:center;}
  .mobile-spec-value{min-width:0;color:var(--bright);font-size:12px;line-height:1.35;text-align:center;overflow-wrap:anywhere;}
  .mobile-spec-value.spec-better,.mobile-spec-value.spec-compatible{color:var(--green);}
  .mobile-spec-value.spec-partial{color:var(--yellow);}
  .mobile-spec-value.spec-incompatible{color:var(--red);}
  .mobile-spec-icon{display:flex;align-items:center;justify-content:center;min-height:26px;}
  .mobile-spec-icon .match-icon{margin:0;}
  .mobile-spec-icon .match-icon svg{width:26px;height:26px;}

  /* RESULT + LEGEND read as one compound card on a phone. */
  .cmp-mobileSpecs .mobile-spec-comparison{margin-bottom:0!important;border-bottom:0!important;border-radius:10px 10px 0 0;}
  .cmp-mobileLegend .compare-function-legend{border-radius:0 0 10px 10px;}
  .cmp-mobileLegend .compare-function-legend .card-title{border-top:0;}
  .cmp-mobileLegend .compare-function-legend .card-body{display:grid;grid-template-columns:max-content max-content;justify-content:space-between;gap:6px 26px;width:100%;max-width:250px;margin:0 auto;padding:9px 0 11px;}
  .cmp-mobileLegend .compare-legend-row{grid-template-columns:28px max-content;gap:6px;}
  .cmp-mobileLegend .compare-legend-row-unconfirmed{grid-column:1/-1;justify-self:center;}
  .cmp-mobileLegend .compare-legend-row-unconfirmed>span:last-child{transform:none;width:auto;letter-spacing:0;}
  .cmp-mobileLegend .compare-function-legend .match-icon svg,.cmp-mobileLegend .compare-legend-star{width:24px;height:24px;}

  .cmp-funcLeft .card-body,.cmp-funcRight .card-body{padding:10px 7px;}
  .cmp-funcLeft .tags,.cmp-funcRight .tags{display:grid;grid-template-columns:minmax(0,1fr);gap:5px;}
  .cmp-funcLeft .tag,.cmp-funcRight .tag{display:block;min-width:0;order:var(--mobile-fn-order,0);padding:4px 5px;font-size:10px;line-height:1.3;text-align:center;white-space:normal;overflow-wrap:anywhere;}
  .cmp-funcLeft .compare-functions-title,.cmp-funcRight .compare-functions-title{display:flex;align-items:center;justify-content:space-between;gap:4px;min-height:30px;padding:8px 7px;font-size:9px;letter-spacing:.35px;}
  .cmp-funcLeft .compare-functions-title>span:first-child,.cmp-funcRight .compare-functions-title>span:first-child{white-space:nowrap;}
  .cmp-funcLeft .mobile-function-owner,.cmp-funcRight .mobile-function-owner{padding:3px 5px;font-size:8px;}
}
@media (min-width:701px){
  .compare-grid-unified .specs{row-gap:12.8px;}
  .compare-grid-unified .specs>.sk:not(:nth-last-child(2))::after{bottom:-6.4px;}
  .compare-specs-scored .match-icon{display:none;}
  /* Uniform 16px gap between FUNCTIONS → ELKO EP ANALOGS (same as SPECIFICATIONS → FUNCTIONS).
     The grid's own margin-bottom:14px stacked on top of .card's margin-bottom:16px, producing
     30px total. Zeroing just the grid-level margin keeps the card margin as the single source. */
  .compare-grid-unified{margin-bottom:0;}
  /* Each function column wraps to its own natural height instead of stretching to the tallest
     sibling — a 10-function card stays tall, a 1-function card and the LEGEND stay compact. */
  .cmp-funcMiddle{align-self:start;}
  /* Compact desktop LEGEND: pack rows to the top with minimal gap instead of space-evenly
     spreading them across the card's full stretched height. */
  .compare-function-legend .card-body{justify-content:flex-start;gap:4px;padding:8px;}
  /* Left/right FUNCTIONS cards: stretch to match the taller of the two via the grid's
     align-items:stretch (both wrappers are default stretch now, not start — see the
     .cmp-funcMiddle-only align-self:start above). The inner .card has flex:1 so it fills
     the wrapper. LEGEND stays compact at its own content height. */
  .cmp-funcLeft .card,.cmp-funcRight .card{min-height:206.5px;}
  /* Desktop header already shows the disclaimer via .header-disclaimer (positioned by JS) —
     no need to repeat it in the footer. Mobile/portrait keeps its own .footer-disclaimer
     untouched (the mobile media query hides it there via .footer-disclaimer{display:none}). */
  body:has(#site-footer:not(.hidden)) .footer-col-center .footer-disclaimer{display:none;}
}
/* No divider line anymore (2026-07-21 — the segmented per-row line was removed per feedback);
   the head row is now its own bordered .card (see compare-col-head-elko/-comp below), the same
   visual language as the Specifications/Functions cards beneath it, so separation between the
   two products reads from the card borders/gap, not a center line. */
.compare-grid-head-preview{padding-top:8px;}
/* Head cell is a full .card + .card-body box now (photo/percent/smiley/name/id all inside it),
   deliberately with NO .card-title — unlike Specifications/Functions this one isn't meant to
   read as a labelled field, just a bordered container matching their look. position:relative was
   scoped to -elko only, on the assumption the photo/compat block (which needs a positioned
   ancestor for its position:absolute) only ever lands on the ELKO-classed card — true for a
   normal competitor-vs-ELKO compare, but an ELKO-vs-ELKO sibling compare assigns -elko/-comp by
   LEFT/RIGHT SLOT, not by which one is actually ELKO (both are) — the target ELKO's own head can
   end up classed compare-col-head-comp, with no positioned ancestor at all, so the compat block
   escaped to the nearest ancestor that DOES have one and rendered pinned to the corner of the
   whole page instead of the card (confirmed real bug via screenshot). Both variants need this. */
.compare-col-head-elko,.compare-col-head-comp{position:relative;}
/* Reserve room on the right of the flex-wrapping head text so it never flows under the photo
   and/or the compat smiley — both dock to that same top-right corner via .compare-head-topright
   (stacked, see above, when both are present at once). Figures include the 16px inset.
   min-height for the has-photo cases is 16 (top inset) + 150 (photo) + 16 (bottom breathing
   room) = 182 — measured 2026-07-22: the previous 168 (top inset + photo only, no matching
   bottom margin) left just ~1px of clearance under the photo on Preview's short, mostly-empty
   head (verified via getBoundingClientRect) — visually touching the card's own bottom edge. */
/* Both -elko and -comp, not just -elko (2026-08-05) — an ELKO-vs-ELKO sibling compare now shows a
   real photo on BOTH sides (per feedback), and -elko/-comp is assigned by left/right SLOT, not by
   which side is actually ELKO (see the position:relative comment above) — the slot classed -comp
   needs this same reserved space whenever IT has a photo too, or that photo would overlap the
   text instead of having room reserved for it. */
.compare-col-head-elko.has-photo,.compare-col-head-comp.has-photo{padding-right:180px;min-height:182px;}
.compare-col-head-elko.has-compat,.compare-col-head-comp.has-compat{padding-right:130px;min-height:74px;}
.compare-col-head-elko.has-photo.has-compat,.compare-col-head-comp.has-photo.has-compat{padding-right:294px;min-height:182px;}
/* Below this width the reserved padding-right (up to 294px above) left almost no room for the
   id/name text next to the absolutely-positioned photo+compat block, forcing it onto one letter-
   wide lines while the photo sat on top of it (real bug, confirmed live via getBoundingClientRect:
   .compare-col-sub computed width 0px despite the CARD itself measuring 311px — the override
   below used to sit BEFORE these three rules in the file, and since it's the exact same selectors/
   specificity, a plain rule appearing LATER in source order always wins a specificity tie
   regardless of which one is wrapped in a media query — so this "fix" was silently dead code).
   Drop back into normal flow instead: photo+badge render first (same source order, see
   renderCompareCol's topRightHtml) so they land above the text as a centered block. Must stay
   BELOW the three rules above for exactly the reason just explained — don't move it back up.
   Also fires in phone LANDSCAPE now (2026-08-05, confirmed via screenshot: a rotated phone is
   routinely wider than 700px, e.g. ~900px, so this portrait-only breakpoint never caught it there
   and the same letter-wide wrap + photo overlap happened in landscape too). */
@media (max-width:700px), (max-width:900px) and (orientation:landscape) and (max-height:500px){
  /* .compare-col-head itself is display:flex;flex-wrap:wrap — without an explicit flex-basis,
     this wrapper was just another flex item competing for space with the id/pill text next to it,
     so it got flex-shrunk toward 0 width while its 150px image (not itself flexible) kept painting
     at full size, spilling out to the left (real bug, confirmed via getBoundingClientRect: wrapper
     width 0, photo x -75). flex:0 0 100% forces it onto its own full-width line every time, which
     is what "photo on top" actually requires. */
  .compare-head-topright{position:static;flex:0 0 100%;justify-content:space-between;margin-bottom:10px;} /* was centered, then flex-start (bunched photo+percent together on the left) — per feedback, spread them to opposite edges: photo left, compat % right */
  /* 16px (not 0) — .card-body's normal padding on every other side is 16px; zeroing just the
     right side (needed on desktop to reserve room for the absolutely-positioned photo/compat
     block, irrelevant here since it's position:static in normal flow on mobile) left the photo
     flush against the card's true left inset but the compat%/button flush against the card's
     true RIGHT edge with none at all — confirmed live via getBoundingClientRect: computed
     padding-left 16px, padding-right 0px, visibly asymmetric (real bug/feedback, 2026-08-05). */
  .compare-col-head-elko.has-photo,.compare-col-head-comp.has-photo,.compare-col-head-elko.has-compat,.compare-col-head-comp.has-compat,.compare-col-head-elko.has-photo.has-compat,.compare-col-head-comp.has-photo.has-compat{padding-right:16px;min-height:0;}
}
.compare-head-photo{width:150px;height:150px;object-fit:contain;background:#fff;border:1px solid var(--border);border-radius:8px;padding:6px;flex-shrink:0;}
/* "See Datasheet" always drops to its own line below the name while comparing (both the
   competitor's archive-based list and ELKO EP's single link) — outside compare mode it stays
   inline next to the name, unchanged. */
.cmp-headLeft .datasheet-list,.cmp-headRight .datasheet-list{width:100%;margin-top:2px;}
.cmp-headLeft a.datasheet-link,.cmp-headRight a.datasheet-link{display:block;width:fit-content;margin-top:2px;}
.compare-grid-unified .card{margin-bottom:16px;flex:1;min-width:0;}
/* The trailing description row (last in the mobile stack, and visually last on desktop too) has
   nothing below it to space away from — margin-bottom on its own two cards would just add unused
   height at the very bottom of the whole comparison. */
.cmp-descLeft .card,.cmp-descRight .card{margin-bottom:0;}
/* align-content:flex-start (2026-07-21) — without it, a flex-wrap container's default
   (normal/stretch) spreads its wrapped lines apart to fill any extra cross-axis space, which
   is exactly what a now-equal-height head card has plenty of when one side (e.g. a competitor
   with no verif/note/datasheet rows) has much less content than the other: the id/pill row and
   the name/manufacturer sub-line drifted apart toward opposite ends of the card instead of
   sitting together right under the title. flex-start packs every wrapped line at the top instead. */
.compare-col-head{display:flex;align-items:center;align-content:flex-start;flex-wrap:wrap;gap:8px;}
.compare-col-id{font-family:'Consolas',monospace;font-weight:700;font-size:15px;color:var(--bright);}
.compare-col-head .alink{font-family:'Consolas',monospace;font-weight:700;font-size:15px;}
.compare-col-pill{font-size:10px;padding:2px 8px;}
.compare-col-sub{width:100%;font-size:12px;color:var(--dim);}
.cmp-headLeft .compare-col-sub,.cmp-headRight .compare-col-sub{margin-top:8px;}
.opt-perfect{color:var(--green);font-weight:700;}
.specs-desc-row .card{flex:1;min-width:0;}
.desc-textarea{width:100%;min-height:150px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;font-family:inherit;font-size:13px;resize:vertical;margin-top:8px;outline:none;transition:border-color .15s;}
.desc-textarea:focus{border-color:var(--accent);}
.desc-textarea.compact{min-height:100px;}
.desc-readonly{font-size:13px;color:var(--text);line-height:1.4;max-height:48px;overflow-y:auto;white-space:pre-wrap;}
.desc-save-btn{background:var(--accent);color:#000;border:none;border-radius:4px;padding:4px 16px;cursor:pointer;font-size:12px;font-weight:600;margin-top:6px;}
.desc-save-btn:hover{opacity:.85;}
.spec-edit-btn{background:none;border:none;cursor:pointer;font-size:11px;padding:0 0 0 4px;opacity:.55;transition:opacity .1s;}
.spec-edit-btn:hover{opacity:1;}
.fe-box{margin-top:4px;padding:10px;background:var(--bg3);border:1px solid var(--border);border-radius:7px;max-width:520px;}
.fe-select,.fe-text,.fe-num,.fe-textarea{font:inherit;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:5px;padding:5px 7px;font-size:12.5px;outline:none;}
.fe-select:focus,.fe-text:focus,.fe-num:focus,.fe-textarea:focus{border-color:var(--accent);}
.fe-num{width:80px;}
.fe-num-narrow{width:56px;}
.fe-text{width:160px;}
.fe-text-wide{width:100%;box-sizing:border-box;}
.fe-textarea{width:100%;box-sizing:border-box;min-height:44px;resize:vertical;font-family:inherit;}
.fe-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
.fe-list-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:6px;}
.fe-list-row-wrap{padding-bottom:6px;border-bottom:1px dashed var(--border);}
.fe-unit-lbl{color:var(--dim);font-size:12px;}
.fe-sep-row{display:flex;gap:4px;margin:2px 0 6px 2px;}
.fe-sep-lbl{color:var(--dim);font-size:11px;margin:2px 0 6px 2px;}
.fe-sep-btn{width:22px;height:20px;border:1px solid var(--border);border-radius:4px;background:var(--bg);color:var(--dim);cursor:pointer;font-size:12px;line-height:1;padding:0;}
.fe-sep-btn.active{background:var(--accent);color:#000;border-color:var(--accent);}
.fe-row-remove{background:none;border:none;color:var(--dim);cursor:pointer;font-size:12px;padding:2px 4px;}
.fe-row-remove:hover{color:var(--text);}
.fe-add-btn{background:var(--bg);border:1px solid var(--border);border-radius:5px;color:var(--accent);cursor:pointer;font-size:12px;padding:4px 10px;margin-top:2px;}
.fe-add-btn:hover{border-color:var(--accent);}
.fe-actions{margin-top:10px;display:flex;gap:8px;}
.fe-save-btn{background:var(--accent);color:#000;border:none;border-radius:4px;padding:4px 16px;cursor:pointer;font-size:12px;font-weight:600;}
.fe-save-btn:hover{opacity:.85;}
.fe-cancel-btn{background:none;border:1px solid var(--border);color:var(--dim);border-radius:4px;padding:4px 14px;cursor:pointer;font-size:12px;}
.fe-cancel-btn:hover{color:var(--text);}
.fe-error{color:#e3574a;font-size:12px;margin-top:6px;min-height:14px;}
.fe-fn-tags{display:flex;flex-wrap:wrap;gap:6px;}
.fe-fn-tag{background:var(--bg);border:1px solid var(--border);border-radius:12px;padding:3px 6px 3px 10px;font-size:12px;display:inline-flex;align-items:center;gap:4px;}
.fe-fn-tag button{background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;padding:0 4px;}
.fe-fn-tag button:hover{color:var(--text);}
.fe-empty-hint{color:var(--dim);font-size:12px;}
.fe-fn-results{position:absolute;top:100%;left:0;right:0;z-index:20;background:var(--bg2);border:1px solid var(--border);border-radius:6px;margin-top:2px;max-height:220px;overflow-y:auto;box-shadow:0 4px 14px rgba(0,0,0,.3);}
.fe-fn-result{padding:6px 10px;font-size:12.5px;cursor:pointer;}
.fe-fn-result:hover{background:var(--bg3);}
.fe-fn-result-custom{color:var(--accent);border-top:1px solid var(--border);}
.fe-fn-tag-star{border-color:#e3b341;}
.fe-fn-map-btn{background:none;border:none;cursor:pointer;font-size:11px;padding:0 2px 0 0;line-height:1;}
.fe-map-box{margin-top:8px;padding:8px;background:var(--bg);border:1px solid #e3b341;border-radius:6px;}
.fe-map-hdr{font-size:12px;color:var(--text);margin-bottom:6px;}
.specs-desc-row .fphoto-card{flex:0 0 220px;min-width:0;}
.fphoto-body{display:flex;align-items:center;justify-content:center;min-height:140px;background:#fff;border-radius:6px;overflow:hidden;}
.fphoto-body img{max-width:100%;max-height:220px;object-fit:contain;}
.fphoto-body .relay-placeholder{width:116px;height:116px;object-fit:contain;opacity:.55;}

@media (max-width:700px){
  .empty-relay-icon{width:90px;height:90px;}
  .fcard-img .relay-placeholder{width:53px;height:53px;}
  .fphoto-body .relay-placeholder{width:66px;height:66px;}
}

/* ══════════ CHAT ══════════ */
#view-chat,#view-web{flex-direction:column;}
#thread,#thread-web{flex:1;overflow:auto;padding:20px;display:flex;flex-direction:column;gap:14px;}
.bubble{max-width:80%;padding:11px 14px;border-radius:12px;white-space:pre-wrap;word-break:break-word;font-size:14px;}
.bubble .who{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--dim);margin-bottom:5px;}
.bubble.user{align-self:flex-end;background:var(--userbub);border:1px solid #2a4a73;}
.bubble.ai{align-self:flex-start;background:var(--aibub);border:1px solid var(--border);}
.bubble.err{border-color:var(--red);}
.attach-chip{display:inline-flex;align-items:center;gap:6px;margin-top:8px;padding:4px 9px;border-radius:6px;background:var(--bg);border:1px solid var(--border);font-size:12px;color:var(--accent2);}
.upserts{margin-top:8px;font-size:12px;color:var(--green);}
.chat-empty{color:var(--dim);text-align:center;margin:auto;font-size:14px;max-width:440px;}
.chat-empty b{color:var(--text);display:block;margin-bottom:8px;font-size:16px;}
.mimo-manual-btn{background:var(--bg3);border:1px solid var(--border);border-radius:6px;padding:5px 12px;font-size:12.5px;color:var(--text);cursor:pointer;white-space:nowrap;}
.mimo-manual-btn:hover{border-color:var(--accent);color:var(--accent);}
.working{align-self:flex-start;color:var(--dim);font-size:13px;font-style:italic;display:flex;align-items:center;gap:8px;}
.dot{width:7px;height:7px;border-radius:50%;background:var(--accent2);animation:pulse 1s infinite;}
@keyframes pulse{0%,100%{opacity:.3}50%{opacity:1}}
#livelog,#livelog-web{font:11px/1.4 ui-monospace,monospace;color:var(--dim);white-space:pre-wrap;max-height:100px;overflow:auto;padding:4px 20px;display:none;}
.composer{border-top:1px solid var(--border);padding:12px 18px;background:var(--bg2);}
.pending-list{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px;}
.pending-list:empty{display:none;}
.pending{display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:7px;background:var(--bg3);border:1px solid var(--border);font-size:13px;width:fit-content;}
.pending .x{cursor:pointer;color:var(--dim);font-weight:700;padding:0 4px;}
.pending .x:hover{color:var(--red);}
.composer-row{display:flex;gap:10px;align-items:flex-end;}
.composer textarea{flex:1;min-height:44px;max-height:160px;font:inherit;padding:10px 12px;border:1px solid var(--border);border-radius:9px;background:var(--bg);color:var(--text);resize:none;}
.composer textarea:focus{outline:none;border-color:var(--accent-chat);}
.sendbtn{height:44px;padding:0 20px;flex-shrink:0;background:var(--accent-chat);color:#fff;border:none;border-radius:9px;cursor:pointer;font:inherit;}
.sendbtn:hover{background:var(--accent2);} .sendbtn:disabled{opacity:.5;cursor:default;}
.stopbtn{height:44px;padding:0 14px;flex-shrink:0;background:var(--bg3);color:var(--red);border:1px solid var(--red);border-radius:9px;cursor:pointer;font:inherit;display:none;align-items:center;justify-content:center;white-space:nowrap;line-height:1;}
.stopbtn:hover{background:var(--stop-hover);}

/* ── LOGIN ── */
/* Anonymous visitors land straight on the public Catalog page — no full-screen block. This is
   now a small floating panel anchored under the person icon (#user-chip), opened on click, same
   pattern as .vpopup (position:fixed, JS-computed top/right, dismiss on outside click). */
#login-overlay{position:fixed;z-index:999;display:none;}
.login-box{background:var(--bg2);border:1px solid var(--border);border-radius:14px;padding:28px 32px;width:320px;display:flex;flex-direction:column;gap:16px;box-shadow:0 8px 24px #0009;}
.login-box h2{color:var(--bright);font-size:18px;font-weight:700;text-align:center;margin-bottom:4px;}
.login-box p{color:var(--dim);font-size:13px;text-align:center;margin-top:-10px;}
.login-field{display:flex;flex-direction:column;gap:6px;}
.login-field label{font-size:12px;color:var(--dim);text-transform:uppercase;letter-spacing:.05em;}
.login-field input{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:10px 12px;color:var(--text);font:inherit;outline:none;transition:border-color .15s;}
.login-field input:focus{border-color:var(--accent);}
.login-err{color:var(--red);font-size:13px;text-align:center;min-height:18px;}
.login-btn{background:var(--accent-chat);color:#fff;border:none;border-radius:9px;padding:12px;cursor:pointer;font:600 15px inherit;transition:background .15s;}
.login-btn:hover{background:var(--accent2);}
.user-chip{position:fixed;top:14px;right:18px;z-index:50;font-size:14px;color:var(--dim);cursor:pointer;padding:3px 8px;border-radius:5px;background:var(--bg3);border:1px solid var(--border);}
.user-chip:hover{color:var(--red);}

/* ── GROUP INDICATOR DOTS (2026-08-10) — manual colored dots on manufacturer/category headers ── */
.gi-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;cursor:pointer;
  margin-left:auto;vertical-align:middle;transition:transform .12s;}
.gi-dot:hover{transform:scale(1.4);}
.gi-gray{background:#484f58;}
.gi-green{background:#3fb950;}
.gi-yellow{background:#e3b341;}
.gi-popup .gi-vopt-reset{background:transparent!important;border:1px solid var(--dim);font-size:10px;
  width:18px;height:18px;display:flex;align-items:center;justify-content:center;}
