:root {
  --yellow: #FFD83D;
  --red: #FF4037;
  --blue: #3267FF;

  --paper: #FFFFFF;
  --paper-dim: #FFFFFF;
  --ink: #121212;
  --ink-soft: #4A4A4A;
  --line: var(--ink);

  --golden: #FFCB05;
  --silver: #9FB0BF;
  --bronze: #C1440E;
  --copper: #9C7A26;
  --modern: var(--blue);

  --tag: var(--red);

  --display: 'Anton', sans-serif;
  --body: 'Inter', system-ui, sans-serif;
  --mono: 'Inter', system-ui, sans-serif;

  --shadow-sm: 4px 4px 0 var(--ink);
  --shadow-md: 7px 7px 0 var(--ink);
  --shadow-lg: 10px 10px 0 var(--ink);

  --hairline: rgba(18, 18, 18, 0.12);
}

:root[data-theme="dark"] {
  --paper: #161616;
  --paper-dim: #242424;
  --ink: #F3EFE4;
  --ink-soft: #B4AFA2;
  --hairline: rgba(243, 239, 228, 0.16);
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

/* The session token is available synchronously, while /api/me is not. Hide
   the signed-out marketing pager during that short verification window so a
   returning user never sees a login-style first paint before the feed. */
html[data-findcomix-session="1"] #snap-intro { display: none !important; }
html[data-findcomix-session="1"] body:has(#snap-intro:not([hidden])) { overflow-y: auto !important; }

/* clip, not hidden -- overflow-x:hidden on html/body makes Chrome/Safari
   drop position:sticky for every descendant (a well-known engine quirk,
   not spec behavior), which silently broke the homepage's hide-on-scroll
   header once the edge-to-edge photo feed started actually bleeding past
   these edges. clip still stops the horizontal drift this was added to
   prevent, without disabling sticky. */
html { overflow-x: clip; }
body {
  margin: 0;
  background-color: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  /* Nothing on the page is meant to scroll sideways -- .era-tabs keeps its
     own explicit overflow-x:auto for the filter-pill row, this just stops
     the page itself (e.g. the listing grid on a narrow phone) from ever
     drifting right. clip rather than hidden -- see html's own rule above. */
  overflow-x: clip;
}
/* While the locked intro pager is showing, nothing else on the page should
   be scrollable underneath it. */
body:has(#snap-intro:not([hidden])) { overflow: hidden; }

.wrap {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 22px;
}

a { color: inherit; }

/* ---------- Buttons / pills ---------- */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--body);
  font-weight: 800;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  padding: 10px 16px;
  border: 3px solid var(--ink);
  border-radius: 999px;
  background: var(--paper-dim);
  color: var(--ink);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: transform 0.1s ease, box-shadow 0.1s ease;
}
.pill:hover { transform: translate(-2px, -2px); box-shadow: 6px 6px 0 var(--ink); }
.pill:active { transform: translate(0, 0); box-shadow: 2px 2px 0 var(--ink); }
.pill.dark { background: var(--ink); color: var(--paper); }
.pill:disabled {
  opacity: 0.4;
  cursor: default;
  box-shadow: var(--shadow-sm);
  transform: none;
}

/* ---------- Header ---------- */
/* Always visible, even while the intro pager (below) is fixed over the
   viewport or hidden entirely for signed-in users -- sticky + a z-index
   above .snap-intro keeps it on top instead of getting covered. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 60;
  background: var(--paper);
  padding: 8px 0;
  transition: transform 0.25s ease;
}
/* Mobile-only hide-on-scroll, same technique as index.html's own
   #home-sticky-chrome (see auth.js's initHeaderScrollHide and app.js's
   initStickyChromeScroll). Desktop never gets this class -- signed-in
   desktop hides the header entirely in favor of the sidebar (below), and
   signed-out desktop keeps it fixed in place. */
@media (max-width: 720px) {
  .site-header.header-hidden { transform: translateY(-100%); }
}
/* Was --paper-dim -- in dark mode that's a lighter elevated-surface tone
   (they're identical in light mode), so the header stood out as a
   mismatched lighter band instead of blending with the page. Uses --paper
   directly so it matches in both themes. */

/* Hides just the Messages icon out of auth.js's shared renderAccountArea()
   markup for detail pages that reserve more room for their own title. */
.account-area.no-messages-icon a[href="messages.html"] { display: none; }
/* Plain "<" back chevron for pages that dropped the wordmark/logo in favor
   of just a back button + the icon cluster (e.g. scan-status.html), same
   visual treatment as listing.html's/thread.html's own back chevrons. */
.site-header-back {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  border-radius: 50%;
  font-size: 24px;
  line-height: 1;
  color: var(--ink);
  text-decoration: none;
}
.site-header-back:hover { background: var(--paper-dim); }

/* ---------- Homepage sticky chrome (header + era tabs) ---------- */
/* index.html only -- wraps .site-header together with the era-filter tabs
   so both hide/reveal as ONE unit on scroll (see app.js's scroll listener),
   instead of just the header. #home-sticky-chrome itself is what's
   sticky/transformed; .site-header's own position:sticky is turned off
   here (position:static) so it doesn't fight its new parent for the same
   job -- every other page still gets .site-header's own sticky behavior
   untouched, since this override only applies inside this one wrapper,
   which only exists on index.html. */
#home-sticky-chrome {
  position: sticky;
  top: 0;
  z-index: 60;
  background: var(--paper);
  transition: transform 0.25s ease;
}
#home-sticky-chrome.chrome-hidden { transform: translateY(-100%); }
#home-sticky-chrome .site-header { position: static; }
/* While the homepage's signed-out intro pager is showing, the header has
   no color of its own -- each panel's own background shows through it
   (see .snap-intro / .snap-intro-track), and the wordmark swaps color per
   panel via body[data-intro-page] below. Everywhere else the header stays
   solid yellow. */
body:has(#snap-intro:not([hidden])) .site-header {
  background: transparent;
}
/* Same idea, one level up -- #home-sticky-chrome's own opaque background
   (needed once signed in, so era-tabs don't show grid cards scrolling
   underneath them) would otherwise paint a solid white
   strip over the top of the intro pager's own per-panel color, the exact
   bleed-through .site-header's own transparency above is already
   preserving. */
body:has(#snap-intro:not([hidden])) #home-sticky-chrome {
  background: transparent;
}
/* The intro panels are capped at phone width and centered (see
   .snap-intro-track > section) -- on any viewport wider than that, the
   header's own row needs the same cap so the logo/wordmark/button line up
   over the actual colored panel instead of the neutral gutter beside it.
   Desktop-width viewports override this back off below -- there, the ask
   was the opposite: push the logo/wordmark to the true left edge and the
   sign-in button to the true right edge of the screen, not bunch them
   together over the narrow phone-width panel. */
body:has(#snap-intro:not([hidden])) .header-row {
  max-width: 480px;
}
@media (min-width: 721px) {
  body:has(#snap-intro:not([hidden])) .header-row {
    max-width: none;
  }
  /* 25% larger across the board -- 48px, 20px, and 13px/8px 16px are this
     row's mobile sizes; desktop has the room to read as a proper site
     header instead of matching a phone-width tap target. */
  .site-icon { height: 60px; }
  .site-wordmark { font-size: 25px; }
  .signin-link { font-size: 16px; padding: 10px 20px; }

  /* Homepage-only, 30% larger again on top of the site-wide desktop sizes
     above -- the marketing intro pager has the room (and the ask) for a
     bigger header than the rest of the app's desktop pages. Scoped with
     the same body:has(#snap-intro...) selector .header-row's own cap uses
     above, not a bare override, so browse/account/etc. desktop pages keep
     the site-wide 60/25/16 sizes. */
  body:has(#snap-intro:not([hidden])) .site-icon { height: 78px; }
  body:has(#snap-intro:not([hidden])) .site-wordmark { font-size: 32.5px; }
  body:has(#snap-intro:not([hidden])) .signin-link { font-size: 21px; padding: 13px 26px; }
}

.header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  min-height: 36px;
}

.wordmark-link {
  display: flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
}

.site-icon {
  display: block;
  height: 48px; /* was 36px, +20%, then +~12% */
  width: auto;
}

.site-wordmark {
  font-family: var(--body);
  font-weight: 800;
  font-size: 20px;
  color: var(--ink);
  transition: color 0.3s ease;
}
/* "comix" as a yellow accent against the header's now-neutral background --
   was hardcoded white back when the header itself was solid yellow. */
.site-wordmark-light { color: var(--yellow); transition: color 0.3s ease; }

/* Pages 2 & 3 of the intro pager sit on a light tan background -- white
   "find" disappears there, so it goes dark. Page 4 is black, so it goes
   light. Page 1 (yellow) keeps the original split. "comix" (.site-wordmark
   -light) is deliberately NOT included in any of these -- it stays
   var(--yellow) on every page (same variable the sign-in button's own
   yellow uses), matching the site's actual wordmark treatment everywhere
   else. It used to be swept into these overrides too, which is what
   erased its yellow accent on pages 2-4 -- flattened to whatever plain
   dark/light color "find" needed on that page instead of keeping its own
   accent color. */
body[data-intro-page="1"] .site-wordmark,
body[data-intro-page="2"] .site-wordmark {
  color: #121212;
}
body[data-intro-page="3"] .site-wordmark {
  color: #ffffff;
}
/* Page 1's own background is literally var(--yellow) (see .snap-marketing)
   -- "comix" staying yellow there is invisible against its own background.
   White instead, matching how it used to be hardcoded white everywhere,
   back when the header itself was solid yellow (see the comment above
   .site-wordmark-light). "find" is forced black here too, explicitly --
   .site-header lives OUTSIDE .snap-marketing's own subtree in the DOM (see
   index.html: #home-sticky-chrome and .snap-intro are siblings), so
   .snap-marketing's local --ink redeclaration never actually reaches it.
   Without this, "find" was silently falling back to whatever --ink
   resolves to higher up the tree -- the light cream dark-mode value when
   the visitor's OS/browser prefers dark, making it unreadable against the
   yellow background exactly like "comix" was. */
body[data-intro-page="0"] .site-wordmark {
  color: #121212;
}
body[data-intro-page="0"] .site-wordmark-light {
  color: #ffffff;
}

.account-area { display: flex; align-items: center; gap: 10px; }

/* Hardcoded light-mode values, not themed tokens -- the header is yellow
   regardless of theme, so this button should always read as the light
   design against it too, same as the homepage's page-1 pitch screen. */
.signin-link {
  font-family: var(--body);
  font-weight: 800;
  font-size: 13px;
  text-transform: uppercase;
  background: #FFFFFF;
  color: #121212;
  border: 2px solid #121212;
  padding: 8px 16px;
  border-radius: 999px;
  text-decoration: none;
  transition: background-color 0.3s ease;
}
.signin-link:hover { background: var(--yellow); }
/* Pages 2-4's own backgrounds are white/white/black -- a white pill reads
   fine there too, but pulling the header's yellow forward instead ties it
   back to the brand color once the page itself isn't yellow anymore. */
body[data-intro-page="1"] .signin-link,
body[data-intro-page="2"] .signin-link,
body[data-intro-page="3"] .signin-link {
  background: var(--yellow);
}
body[data-intro-page="1"] .signin-link:hover,
body[data-intro-page="2"] .signin-link:hover,
body[data-intro-page="3"] .signin-link:hover {
  background: #fff;
}

/* ---------- Header icons (Search, Messages) ---------- */
/* Hardcoded ink color, not a themed token -- same reasoning as
   .signin-link above: the header stays yellow regardless of site theme,
   so its icons need to stay legible against that fixed yellow rather than
   flipping to a light color in dark mode. */
.header-icon-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: none;
  background: none;
  color: #121212;
  flex-shrink: 0;
  cursor: pointer;
  transition: background 0.12s ease;
}
.header-icon-btn:hover { background: rgba(18, 18, 18, 0.08); }
.header-icon-btn svg { width: 21px; height: 21px; }
/* Hardcoded #121212 above was invisible against the header's dark-mode
   background -- same translucent grey the bottom tab bar's icons already
   use (that bar is always dark regardless of theme, so it's the same
   dark-background problem this is solving). */
:root[data-theme="dark"] .header-icon-btn { color: rgba(255, 255, 255, 0.6); }
:root[data-theme="dark"] .header-icon-btn:hover { background: rgba(255, 255, 255, 0.08); }
/* Menu (hamburger) icon specifically -- no circular hover/tap highlight
   (plain icon, not a pill button like Search/Messages), slightly bigger and
   bolder lines so it reads clearly at a glance. */
#menu-btn { -webkit-tap-highlight-color: transparent; }
#menu-btn svg { width: 23px; height: 23px; stroke-width: 2.6; }
.header-avatar-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex: 0 0 40px;
  overflow: hidden;
  padding: 0;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  background: var(--yellow);
  color: #171717;
  cursor: pointer;
}
.header-avatar-btn img { width: 100%; height: 100%; object-fit: cover; }
.header-avatar-btn .avatar-initial {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font: 900 17px/1 var(--body);
}
.header-icon-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: 999px;
  background: var(--red);
  color: #fff;
  font-family: var(--body);
  font-weight: 800;
  font-size: 9.5px;
  line-height: 15px;
  text-align: center;
  border: 1.5px solid var(--yellow);
}

/* ---------- Account drawer ---------- */
/* Dark grey rather than the usual translucent-black scrim -- reads more
   clearly as "the page behind is dimmed" while the drawer is open. */
.drawer-overlay {
  position: fixed;
  inset: 0;
  background: rgba(34, 35, 37, 0.8);
  z-index: 300;
  opacity: 0;
  transition: opacity 0.2s ease;
}
.drawer-overlay.open { opacity: 1; }

/* A hairline edge in light mode; dropped entirely in dark mode, where the
   same line read as a distracting light-on-dark outline instead of a
   subtle divider. */
.account-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 300px;
  max-width: 85vw;
  background: var(--paper);
  border-left: 1px solid #D8D8D8;
  z-index: 301;
  transform: translateX(100%);
  transition: transform 0.22s ease;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overflow-x: hidden;
}
.account-drawer.open { transform: translateX(0); }
:root[data-theme="dark"] .account-drawer { border-left: 1px solid var(--hairline); }

.drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 18px 16px;
  border-bottom: 1px solid #D8D8D8;
}
:root[data-theme="dark"] .drawer-header { border-bottom: none; }
/* The identity row is informational, matching the native account sheet;
   profile navigation lives under Account instead of competing with it. */
.drawer-user {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  text-decoration: none;
  color: inherit;
  border-radius: 10px;
  margin: -6px;
  padding: 6px;
}
.drawer-avatar {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 2px solid var(--ink);
  overflow: hidden;
  flex-shrink: 0;
  display: block;
}
.drawer-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.drawer-avatar .avatar-initial {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-family: var(--display);
  font-size: 15px;
  background: var(--yellow);
}
.drawer-username {
  font-family: var(--body);
  font-weight: 800;
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.drawer-profile-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.drawer-view-profile {
  font-family: var(--body);
  font-weight: 600;
  font-size: 11.5px;
  color: var(--ink-soft);
}
.drawer-close {
  background: none;
  border: none;
  font-size: 16px;
  cursor: pointer;
  color: var(--ink);
  padding: 6px;
  flex-shrink: 0;
}

.drawer-nav { padding: 10px 0; }
.drawer-item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 18px;
  font-family: var(--body);
  font-weight: 700;
  font-size: 15px;
  color: var(--ink);
  text-decoration: none;
}
.drawer-item:hover { background: var(--paper-dim); }
/* .drawer-view-toggle is a <button>, not an <a> like its siblings -- reset
   the browser's default button chrome so it matches them exactly. */
button.drawer-item {
  width: 100%;
  border: none;
  background: none;
  text-align: left;
  cursor: pointer;
}
.drawer-icon { display: flex; align-items: center; justify-content: center; width: 22px; flex-shrink: 0; color: var(--ink); }
.drawer-chevron { margin-left: auto; color: var(--ink-soft); }
.drawer-count { color: var(--ink-soft); font-weight: 600; }

.drawer-section-label {
  font-family: var(--body);
  font-weight: 800;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--ink-soft);
  padding: 16px 18px 6px;
}

.drawer-theme-row {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 18px;
  font-family: var(--body);
  font-weight: 700;
  font-size: 15px;
  color: var(--ink);
}
.drawer-theme-label { margin-right: auto; }

.drawer-display-preferences {
  margin-top: auto;
  padding: 0 18px 14px;
  border-top: 1px solid var(--hairline);
}
.drawer-display-preferences .drawer-section-label { padding-left: 0; padding-right: 0; }
.drawer-preference-row { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.drawer-preference-name {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--ink);
  font-size: 14px;
  font-weight: 800;
}
.drawer-segmented-control {
  display: flex;
  align-items: center;
  width: 100%;
  padding: 3px;
  border: 1px solid var(--hairline);
  border-radius: 17px;
  background: var(--paper-dim);
}
.drawer-segmented-control button {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1;
  min-height: 30px;
  padding: 0 10px;
  border: 0;
  border-radius: 14px;
  background: transparent;
  color: var(--ink-soft);
  font: 900 11px/1 var(--body);
  cursor: pointer;
}
.drawer-segmented-control button.is-active { background: var(--yellow); color: #171717; }
.drawer-view-control svg { width: 16px; height: 16px; }

.theme-toggle-switch {
  position: relative;
  display: inline-block;
  width: 42px;
  height: 24px;
  flex-shrink: 0;
}
.theme-toggle-switch input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}
.theme-toggle-slider {
  position: absolute;
  inset: 0;
  background: var(--paper-dim);
  border: 1.5px solid var(--ink);
  border-radius: 999px;
  transition: background 0.15s ease;
}
.theme-toggle-slider::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 17px;
  height: 17px;
  background: var(--ink);
  border-radius: 50%;
  transition: transform 0.18s ease;
}
.theme-toggle-switch input:checked + .theme-toggle-slider { background: var(--ink); }
.theme-toggle-switch input:checked + .theme-toggle-slider::before {
  transform: translateX(18px);
  background: var(--paper);
}

.drawer-signout {
  margin: 0 18px 20px;
  font-family: var(--body);
  font-weight: 800;
  font-size: 13px;
  text-transform: uppercase;
  background: var(--paper-dim);
  border: 2px solid var(--ink);
  color: var(--ink);
  padding: 12px;
  border-radius: 999px;
  cursor: pointer;
}
.drawer-signout:hover { background: var(--red); color: #fff; }
.drawer-footer { margin-top: auto; }
.drawer-footer .drawer-signout {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: calc(100% - 36px);
}
.drawer-footer .drawer-signout .drawer-icon { color: inherit; }
.drawer-legal {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-height: 48px;
  padding-bottom: env(safe-area-inset-bottom);
  color: var(--ink-soft);
  font: 700 13px/1 var(--body);
}
.drawer-legal a { color: inherit; text-decoration: none; }
.drawer-legal a:hover { color: var(--ink); text-decoration: underline; }

/* ---------- Bottom tab bar ---------- */
/* Mobile app-style themed surface. Desktop uses the left navigation rail. */
.bottom-tab-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 200;
  display: none;
  min-height: 70px;
  background: var(--paper-dim);
  border-top: 1px solid var(--hairline);
  padding: 5px 6px calc(5px + env(safe-area-inset-bottom));
  transition: transform 0.25s ease;
  /* Forces its own GPU compositing layer -- without this, iOS Safari
     (worse in home-screen/PWA standalone mode) can detach a plain
     position:fixed element from the viewport during momentum scrolling,
     visually stranding it wherever the page happened to be scrolled to. */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  will-change: transform;
  backface-visibility: hidden;
}
:root[data-theme="dark"] .bottom-tab-bar { background: #1c1c1c; }
/* Not driven by scroll direction anymore (that read as flaky across
   browsers) -- toggled deliberately by full-screen lightboxes/viewers
   (comic.js, listing.js, seller.js, group.js, messages.js) that need the
   bar out of the way while they're open. */
.bottom-tab-bar.tab-bar-hidden { transform: translateY(100%); }

.bottom-tab-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  flex: 1 1 0;
  padding: 2px 4px;
  text-decoration: none;
  color: var(--ink-soft);
}
.bottom-tab-scan {
  border: 0;
  background: transparent;
  font: inherit;
  cursor: pointer;
}
.bottom-tab-scan .bottom-tab-icon {
  width: 38px;
  height: 38px;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--yellow);
  color: #121212;
}
.bottom-tab-item.active { color: var(--yellow); }
.bottom-tab-label {
  font-family: var(--body);
  font-weight: 700;
  font-size: 10px;
}
.bottom-tab-icon { display: flex; position: relative; }
.bottom-tab-icon svg { width: 23px; height: 23px; }
.bottom-tab-scan .bottom-tab-icon svg { width: 25px; height: 25px; }
.bottom-tab-icon-badged { overflow: visible; }
.bottom-tab-badge {
  position: absolute;
  top: -5px;
  right: -8px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: 999px;
  background: var(--red);
  color: #fff;
  font-family: var(--body);
  font-weight: 800;
  font-size: 9.5px;
  line-height: 15px;
  text-align: center;
  border: 1.5px solid var(--paper-dim);
}
@media (max-width: 720px) {
  .bottom-tab-bar { display: flex; }
  body:has(#bottom-tab-bar) { padding-bottom: calc(70px + env(safe-area-inset-bottom)); }
  body:has(#bottom-tab-bar) > .site-footer { display: none; }
  .add-comics-fab { display: none; }
  /* Rooms pages (see the .room-topbar-row note below, ~line 673): on mobile
     the bottom tab bar is the signed-in nav, so #account-area's icon
     cluster (search/messages/menu) riding next to the room's own
     title/search/kebab bar just reads as two headers mashed together.
     Hide it here the same way desktop hides it once the sidebar mounts. */
  body:has(#bottom-tab-bar) .room-topbar-row #account-area { display: none; }
}

.mobile-page-eyebrow,
.mobile-page-intro { display: none; }

@media (max-width: 720px) {
  .mobile-page-heading { min-width: 0; }
  .mobile-page-eyebrow {
    display: block;
    margin: 0 0 5px;
    color: var(--yellow);
    font: 900 11px/1.25 var(--body);
    letter-spacing: 1.2px;
    text-transform: uppercase;
  }
  :root:not([data-theme="dark"]) .mobile-page-eyebrow { color: #806000; }
  .mobile-page-intro {
    display: block;
    max-width: 540px;
    margin: 9px 0 0;
    color: var(--ink-soft);
    font: 500 14px/1.45 var(--body);
  }
}

@media (max-width: 720px) {
  /* Shared signed-in shell, sized to the native app's 58px top bar. The
     marketing/login header is intentionally untouched. */
  body:has(#bottom-tab-bar) .site-header {
    padding: env(safe-area-inset-top) 0 0;
    border-bottom: 1px solid var(--hairline);
    background: var(--paper);
  }
  body:has(#bottom-tab-bar) .header-row {
    min-height: 58px;
    padding: 0 18px;
    gap: 0;
  }
  body:has(#bottom-tab-bar) .wordmark-link {
    flex: 1 1 auto;
    min-width: 0;
    gap: 8px;
  }
  body:has(#bottom-tab-bar) .site-wordmark {
    overflow: hidden;
    font-size: 23px;
    letter-spacing: -1px;
    white-space: nowrap;
  }
  body:has(#bottom-tab-bar) .account-area {
    flex: 0 0 auto;
    gap: 0;
  }
  body:has(#bottom-tab-bar) .header-icon-btn {
    width: 44px;
    height: 44px;
    color: var(--ink);
  }
  body:has(#bottom-tab-bar) .header-icon-btn svg { width: 25px; height: 25px; }
  body:has(#bottom-tab-bar) .header-icon-badge {
    top: 1px;
    right: 1px;
    border-color: var(--paper);
  }
  body:has(#bottom-tab-bar) #home-sticky-chrome .home-top-nav-action { display: none; }

  /* The native account sheet occupies 86% of the phone and uses full-width
     divider rows, so mirror that density here. */
  .drawer-overlay { background: rgba(0, 0, 0, 0.42); }
  .account-drawer {
    width: 86vw;
    max-width: 86vw;
    padding-bottom: env(safe-area-inset-bottom);
  }
  .drawer-header {
    min-height: calc(88px + env(safe-area-inset-top));
    padding: calc(14px + env(safe-area-inset-top)) 18px 14px;
  }
  .drawer-avatar { width: 54px; height: 54px; border: 0; }
  .drawer-avatar .avatar-initial { font: 900 22px/1 var(--body); }
  .drawer-username { font-size: 20px; line-height: 25px; }
  .drawer-view-profile { margin-top: 2px; font-size: 13px; }
  .drawer-nav { padding: 0 18px; }
  .drawer-item {
    min-height: 55px;
    padding: 0;
    border-top: 1px solid var(--hairline);
    font-size: 16px;
  }
  .drawer-section-label { padding: 18px 0 8px; }
  .drawer-display-preferences {
    margin: auto 18px 0;
    padding: 0 0 14px;
  }
  .drawer-theme-row {
    min-height: 55px;
    margin: auto 18px 0;
    padding: 0;
    border-top: 1px solid var(--hairline);
    font-size: 16px;
  }
  .drawer-signout { margin: 12px 18px 18px; }
  .drawer-footer .drawer-signout {
    justify-content: flex-start;
    width: 100%;
    min-height: 55px;
    margin: 0;
    padding: 0 18px;
    border: 0;
    border-top: 1px solid var(--hairline);
    border-radius: 0;
    background: none;
    color: var(--red);
    font-size: 16px;
    text-transform: none;
  }
}

@media (max-width: 359px) {
  body:has(#bottom-tab-bar) .site-wordmark { display: none; }
}

/* ---------- Left sidebar (desktop) ---------- */
/* The bottom tab bar's mirror for signed-in users above the mobile
   breakpoint, where the bottom bar never shows at all and there'd
   otherwise be no persistent nav. Fixed for the full viewport height
   (not just below the header) so it doesn't need to know the sticky
   header's height -- body gets nudged over instead of every page's markup
   being rewritten around a flex layout. */
.left-sidebar { display: none; }

@media (min-width: 721px) {
  .left-sidebar {
    display: flex;
    flex-direction: column;
    gap: 2px;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 216px;
    z-index: 70;
    background: var(--paper);
    border-right: 1px solid var(--hairline);
    padding: 22px 10px;
    box-sizing: border-box;
    overflow-y: auto;
  }
  body:has(#left-sidebar) { margin-left: 216px; }
  /* The sidebar now carries the logo (see .sidebar-logo below) and every
     destination the header's hamburger drawer offers -- Membership,
     Pending Sales/Sold, Admin, Display, Dark Mode, Sign Out (see
     ensureLeftSidebarMounted in auth.js) -- so at this same breakpoint the
     header's own wordmark and hamburger button would just be a redundant
     second copy of both. Mobile is untouched: under 721px neither rule
     applies and the header keeps its usual logo + hamburger. */
  body:has(#left-sidebar) .wordmark-link { display: none; }
  body:has(#left-sidebar) #menu-btn { display: none; }
  body:has(#left-sidebar) .account-area { margin-left: auto; }
  /* Signed-in desktop gets the sidebar's own logo/navigation, so the fixed
     header would just duplicate it. The homepage keeps only its tab bar. */
  body:has(#left-sidebar) .site-header { display: none; }
  /* Rooms pages (groups.html/group.html/thread.html) have no .site-header --
     #account-area rides along next to .room-topbar instead (see
     rooms-shared.css) purely so auth.js still mounts the sidebar/tab-bar.
     Hide it there too once the sidebar takes over, matching every other
     page. Scoped to .room-topbar-row specifically, not #account-area
     generally, since index.html relocates its own #account-area into the
     era-tabs row at this breakpoint and must keep it visible there. */
  body:has(#left-sidebar) .room-topbar-row #account-area { display: none; }
  /* listing.html's own topbar (see listing.css) -- same reasoning as the
     Rooms pages above: the back chevron/title is the comic's own identity
     and stays, only the account-chrome icons hide once the sidebar exists. */
  body:has(#left-sidebar) .listing-topbar-row #account-area { display: none; }
}

/* ---------- Floating "Add Comics" shortcut ---------- */
/* Fixed, not sticky-to-scroll-position -- stays put regardless of where the
   feed is scrolled to, unlike the bottom tab bar which hides on scroll-down
   (see auth.js's ensureAddFabMounted). Sits above the mobile tab bar via
   the media query below; on desktop (no tab bar) it just floats at the
   corner. */
.add-comics-fab {
  position: fixed;
  right: 20px;
  bottom: 24px;
  z-index: 150;
  min-width: 56px;
  height: 56px;
  border-radius: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  background: var(--yellow);
  /* Hardcoded, not var(--ink) -- --ink flips to a light cream in dark mode
     (see :root[data-theme="dark"]), which would fade the border/icon into
     the yellow fill instead of keeping the same black outline both themes. */
  color: #121212;
  border: 3px solid #121212;
  box-shadow: var(--shadow);
  text-decoration: none;
  cursor: pointer;
  padding: 0 16px;
  font: inherit;
  white-space: nowrap;
  transition: transform 0.15s ease, bottom 0.25s ease;
}
.add-comics-fab:hover { transform: translateY(-2px); }
.add-comics-fab svg { width: 26px; height: 26px; flex-shrink: 0; }
.add-comics-fab-label { font-size: 14px; font-weight: 800; }
@media (max-width: 720px) {
  .add-comics-fab { bottom: calc(88px + env(safe-area-inset-bottom)); }
}

/* ---------- Scan mode choice modal ---------- */
/* Shown before the file picker opens on every scan entry point (the FAB,
   account.js's "Choose Photo"/"Scan" buttons) -- see auth.js's
   showScanModeDialog. Self-contained here (not messages.css's near-
   identical compose-modal) since this needs to render on every page. */
.scan-mode-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 280;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(18, 18, 18, 0.55);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
}
.scan-mode-modal-backdrop.is-open { opacity: 1; pointer-events: auto; }

.scan-mode-modal {
  width: 100%;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  box-sizing: border-box;
  background: var(--paper-dim);
  border: 1px solid var(--hairline);
  border-radius: 20px;
  padding: 18px;
  box-shadow: 0 20px 50px -12px rgba(18, 18, 18, 0.5);
  transform: scale(0.96) translateY(6px);
  transition: transform 0.18s ease;
}
.scan-mode-modal-backdrop.is-open .scan-mode-modal { transform: scale(1) translateY(0); }

.scan-mode-option {
  display: flex;
  flex-direction: column;
  gap: 4px;
  text-align: left;
  border: 1.5px solid var(--hairline);
  border-radius: 14px;
  background: var(--paper);
  padding: 14px 16px;
  cursor: pointer;
  font: inherit;
  color: var(--ink);
}
.scan-mode-option:hover { border-color: var(--ink); }
.scan-mode-option-title {
  font-family: var(--body);
  font-weight: 800;
  font-size: 15px;
  color: var(--ink);
}
.scan-mode-option-desc {
  font-family: var(--body);
  font-weight: 500;
  font-size: 13px;
  color: var(--ink-soft);
}
.scan-mode-option-note {
  display: block;
  margin-top: 5px;
  color: var(--ink);
  font-size: 12px;
  line-height: 1.35;
  font-weight: 800;
}
.scan-mode-cancel {
  align-self: center;
  margin-top: 4px;
  border: none;
  background: none;
  font-family: var(--body);
  font-weight: 700;
  font-size: 13px;
  color: var(--ink-soft);
  cursor: pointer;
  padding: 6px;
}
.scan-mode-cancel:hover { color: var(--ink); }

/* ---------- Free-scan paywall ---------- */
/* Shared by the floating scanner and account.html's scan controls. This is
   intentionally a real dialog rather than alert() so the membership path is
   useful on a phone, where the browser's native prompt hides the product
   context and offers no route to checkout. */
.scan-limit-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(18, 18, 18, 0.68);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
.scan-limit-modal-backdrop.is-open { opacity: 1; pointer-events: auto; }
.scan-limit-modal {
  position: relative;
  width: min(430px, 100%);
  box-sizing: border-box;
  padding: 34px 26px 24px;
  overflow: hidden;
  border: 1px solid var(--hairline);
  border-radius: 24px;
  background: var(--paper-dim);
  color: var(--ink);
  text-align: center;
  box-shadow: 0 24px 70px -20px rgba(18, 18, 18, 0.7);
  transform: scale(0.96) translateY(8px);
  transition: transform 0.2s ease;
}
.scan-limit-modal::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 7px;
  background: linear-gradient(90deg, var(--red), var(--yellow), #58B94D);
}
.scan-limit-modal-backdrop.is-open .scan-limit-modal { transform: scale(1) translateY(0); }
.scan-limit-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink-soft);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
}
.scan-limit-close:hover { color: var(--ink); background: var(--paper-dim); }
.scan-limit-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 58px;
  height: 58px;
  margin: 0 auto 14px;
  border: 3px solid #121212;
  border-radius: 18px;
  background: var(--yellow);
  color: #121212;
  box-shadow: 4px 4px 0 var(--red);
  transform: rotate(-4deg);
}
.scan-limit-icon svg { width: 32px; height: 32px; }
.scan-limit-kicker {
  margin: 0 0 7px;
  color: var(--red);
  font-family: var(--body);
  font-size: 12px;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.scan-limit-title {
  margin: 0 0 10px;
  color: var(--ink);
  font-family: var(--display);
  font-size: 28px;
  letter-spacing: -0.2px;
  line-height: 1.05;
  text-transform: uppercase;
}
.scan-limit-body {
  max-width: 350px;
  margin: 0 auto 18px;
  color: var(--ink-soft);
  font-family: var(--body);
  font-size: 14px;
  line-height: 1.5;
}
.scan-limit-benefits {
  display: grid;
  gap: 8px;
  margin: 0 auto 20px;
  padding: 14px 16px;
  border: 1px solid var(--hairline);
  border-radius: 15px;
  background: var(--paper);
  color: var(--ink);
  text-align: left;
  font-family: var(--body);
  font-size: 13px;
  font-weight: 700;
}
.scan-limit-benefits span { margin-right: 7px; color: #258C4E; font-weight: 900; }
.scan-limit-membership-btn { width: 100%; }
.scan-limit-later {
  display: block;
  margin: 13px auto 0;
  padding: 4px 8px;
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  font-family: var(--body);
  font-size: 13px;
  font-weight: 700;
}
.scan-limit-later:hover { color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .scan-limit-modal-backdrop,
  .scan-limit-modal { transition: none; }
}

.likers-card-backdrop {
  position: fixed;
  inset: 0;
  z-index: 850;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(0, 0, 0, 0.56);
}
.likers-card-backdrop[hidden] { display: none; }
.likers-card {
  width: min(390px, 100%);
  max-height: min(620px, 78vh);
  overflow: hidden;
  border: 1px solid var(--hairline);
  border-radius: 20px;
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.32);
}
.likers-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 20px 14px;
  border-bottom: 1px solid var(--hairline);
}
.likers-card-head h2 { margin: 0; font: 800 19px/1.2 var(--body); }
.likers-card-close {
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 50%;
  background: var(--paper-dim);
  color: var(--ink);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}
.likers-card-list { max-height: min(540px, 66vh); overflow-y: auto; padding: 8px 0; }
.likers-card-list.is-state { padding: 28px 20px; color: var(--ink-soft); text-align: center; }
.likers-card-user {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 62px;
  padding: 8px 20px;
  color: var(--ink);
  text-decoration: none;
}
.likers-card-user:hover { background: var(--paper-dim); }
.likers-card-user img,
.likers-card-avatar-fallback {
  width: 44px;
  height: 44px;
  flex: 0 0 44px;
  border-radius: 50%;
  object-fit: cover;
}
.likers-card-avatar-fallback {
  display: grid;
  place-items: center;
  background: var(--yellow);
  color: #171717;
  font-weight: 900;
}
.likers-card-user > span:last-child { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.likers-card-user strong { overflow: hidden; text-overflow: ellipsis; }
.likers-card-user small { color: var(--ink-soft); }
.gcomment-notification-target {
  border-radius: 12px;
  background: color-mix(in srgb, var(--yellow) 18%, transparent);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--yellow) 18%, transparent);
  transition: background 0.5s ease, box-shadow 0.5s ease;
}

@media (max-width: 600px) {
  .likers-card-backdrop { place-items: end center; padding: 12px; }
  .likers-card { border-radius: 20px 20px 16px 16px; max-height: 72vh; }
}

/* Multi-select pickers do not promise click-order, so scan-one asks the
   seller to identify the front cover visually before upload. */
.scan-cover-backdrop {
  position: fixed;
  inset: 0;
  z-index: 290;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(18, 18, 18, 0.72);
}
.scan-cover-dialog {
  width: min(520px, 100%);
  max-height: min(760px, calc(100dvh - 40px));
  overflow: auto;
  box-sizing: border-box;
  padding: 20px;
  border: 1px solid var(--hairline);
  border-radius: 20px;
  background: var(--paper-dim);
  color: var(--ink);
  box-shadow: 0 20px 50px -12px rgba(18, 18, 18, 0.55);
}
.scan-cover-dialog h2 { margin: 0 0 6px; font-family: var(--body); font-size: 22px; }
.scan-cover-dialog > p { margin: 0 0 16px; color: var(--ink-soft); font-size: 14px; line-height: 1.4; }
.scan-cover-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
.scan-cover-choice {
  position: relative;
  min-width: 0;
  aspect-ratio: 2 / 3;
  overflow: hidden;
  padding: 0;
  border: 3px solid transparent;
  border-radius: 12px;
  background: var(--paper);
  cursor: pointer;
}
.scan-cover-choice.is-selected { border-color: var(--yellow); box-shadow: 0 0 0 2px #121212; }
.scan-cover-choice img { width: 100%; height: 100%; object-fit: cover; display: block; }
.scan-cover-choice-label {
  position: absolute;
  left: 6px;
  bottom: 6px;
  padding: 4px 7px;
  border-radius: 999px;
  background: rgba(18, 18, 18, 0.82);
  color: #fff;
  font-size: 10px;
  font-weight: 900;
  letter-spacing: 0.04em;
}
.scan-cover-choice.is-selected .scan-cover-choice-label { background: var(--yellow); color: #121212; }
.scan-cover-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }
.scan-cover-cancel,
.scan-cover-continue {
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font: 800 13px var(--body);
  cursor: pointer;
}
.scan-cover-continue { border-color: var(--yellow); background: var(--yellow); color: #121212; }
.scan-cover-actions button:disabled { opacity: 0.55; cursor: wait; }
.scan-cover-crop-dialog { overflow: hidden; }
.scan-cover-crop-stage {
  position: relative;
  height: min(56dvh, 560px);
  min-height: 300px;
  overflow: hidden;
  border-radius: 14px;
  background: #080808;
}
.scan-cover-crop-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  user-select: none;
  pointer-events: none;
}
.scan-cover-crop-box {
  position: absolute;
  box-sizing: border-box;
  border: 3px solid var(--yellow);
  background: rgba(255, 216, 61, 0.05);
  cursor: move;
  touch-action: none;
}
.scan-cover-crop-handle {
  position: absolute;
  width: 28px;
  height: 28px;
  border-color: var(--yellow);
}
.scan-cover-crop-handle.is-nw { top: -5px; left: -5px; border-top: 6px solid; border-left: 6px solid; cursor: nwse-resize; }
.scan-cover-crop-handle.is-ne { top: -5px; right: -5px; border-top: 6px solid; border-right: 6px solid; cursor: nesw-resize; }
.scan-cover-crop-handle.is-sw { bottom: -5px; left: -5px; border-bottom: 6px solid; border-left: 6px solid; cursor: nesw-resize; }
.scan-cover-crop-handle.is-se { right: -5px; bottom: -5px; border-right: 6px solid; border-bottom: 6px solid; cursor: nwse-resize; }
@media (max-width: 520px) {
  .scan-cover-backdrop { align-items: flex-end; padding: 0; }
  .scan-cover-dialog { max-height: 86dvh; border-radius: 22px 22px 0 0; padding-bottom: calc(20px + env(safe-area-inset-bottom)); }
  .scan-cover-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .scan-cover-actions { position: sticky; bottom: 0; padding-top: 12px; background: var(--paper-dim); }
  .scan-cover-crop-dialog { height: 92dvh; max-height: 92dvh; }
  .scan-cover-crop-stage { height: auto; min-height: 0; flex: 1; }
  .scan-cover-crop-dialog { display: flex; flex-direction: column; }
}

/* ---------- Scan-job progress widget ---------- */
/* Tracks a background photo scan (see main.py's scan_worker_loop) from
   wherever the seller happens to be browsing -- mounted once globally
   (auth.js's ensureScanJobWidgetMounted) rather than per-page, since the
   scan itself isn't tied to any one page either. Two faces sharing one
   piece of state: the full card (.scan-job-card) and a swipe-down-
   collapsed compact indicator (.scan-job-compact) -- exactly one of the
   two is ever unhidden at a time (see auth.js's showScanJobWidget). */
/* [hidden] beats the plain class rule below on specificity, so toggling
   the hidden attribute (see auth.js's showScanJobWidget/hideScanJobWidget)
   reliably wins over the unconditional display:flex/inline-flex further
   down -- without this, the browser's own [hidden]{display:none} default
   loses to author CSS of equal-or-higher specificity, a well-known gotcha. */
.scan-job-card[hidden],
.scan-job-compact[hidden] { display: none; }

.scan-job-card {
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: calc(88px + env(safe-area-inset-bottom));
  z-index: 190;
  max-width: 420px;
  margin: 0 auto;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  background: #1c1c1e;
  color: #fff;
  border-radius: 16px;
  padding: 16px 40px 16px 18px;
  box-shadow: 0 12px 28px -10px rgba(0, 0, 0, 0.55);
  /* Same GPU-compositing fix as .bottom-tab-bar -- fixed elements drift
     during iOS Safari/PWA momentum scroll without it. */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  will-change: transform;
  backface-visibility: hidden;
  cursor: pointer;
  transition: bottom 0.25s ease;
}
.scan-job-card-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  font-size: 15px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
}
.scan-job-card-close:hover { background: rgba(255, 255, 255, 0.24); }
:root[data-theme="dark"] .scan-job-card-close { background: rgba(18, 18, 18, 0.1); color: #121212; }
:root[data-theme="dark"] .scan-job-card-close:hover { background: rgba(18, 18, 18, 0.18); }
@media (min-width: 721px) {
  /* No bottom tab bar at this breakpoint, so no reason to float it as
     high -- and the sidebar already occupies the left edge. */
  .scan-job-card { left: 232px; right: 24px; bottom: 24px; max-width: 380px; margin: 0; }
}
/* Inverted relative to the page's own theme -- deliberately, so the floating
   card pops against whatever's behind it instead of blending into the page's
   own paper/ink tokens. Same pattern as account.css's .bulk-action-bar. */
:root[data-theme="dark"] .scan-job-card {
  background: #FFFFFF;
  color: #121212;
  box-shadow: 0 12px 28px -10px rgba(18, 18, 18, 0.25);
}
.scan-job-card-icon {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(255, 216, 61, 0.16);
  color: var(--yellow);
  display: flex;
  align-items: center;
  justify-content: center;
}
.scan-job-card-icon svg { width: 18px; height: 18px; }
.scan-job-card-body { flex: 1 1 auto; min-width: 0; }
.scan-job-card-title {
  font-family: var(--body);
  font-weight: 800;
  font-size: 14px;
  margin: 0 0 8px;
}
.scan-job-card.is-done .scan-job-card-title { color: var(--yellow); }
:root[data-theme="dark"] .scan-job-card.is-done .scan-job-card-title { color: #B8860B; }
.scan-job-progress-track {
  height: 5px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.16);
  overflow: hidden;
}
:root[data-theme="dark"] .scan-job-progress-track { background: rgba(18, 18, 18, 0.12); }
.scan-job-progress-fill {
  height: 100%;
  background: var(--yellow);
  border-radius: 999px;
  transition: width 0.4s ease;
}
.scan-job-card-hint {
  font-family: var(--body);
  font-weight: 500;
  font-size: 12px;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.65);
  margin: 8px 0 0;
}
:root[data-theme="dark"] .scan-job-card-hint { color: rgba(18, 18, 18, 0.65); }

/* Collapsed state -- a small pill sitting to the left of wherever the
   current page's own "Scan" button is (account.html's library header),
   or before the header's account icons everywhere else (see auth.js's
   placeScanJobCompact). */
.scan-job-compact {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 6px 8px;
  cursor: pointer;
  font-family: var(--body);
  font-weight: 700;
  font-size: 11.5px;
  color: var(--ink-soft);
}
.scan-job-compact.is-done { color: var(--yellow); }
.scan-job-compact-spinner {
  width: 13px;
  height: 13px;
  border-width: 2px;
  border-style: solid;
  border-color: var(--hairline);
  border-top-color: var(--yellow);
  border-radius: 50%;
  animation: scan-spin 0.8s linear infinite;
}
.scan-job-compact.is-done .scan-job-compact-spinner { animation: none; border-top-color: var(--yellow); }
@keyframes scan-spin {
  to { transform: rotate(360deg); }
}

.side-nav-item {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 11px 12px;
  border-radius: 10px;
  text-decoration: none;
  color: var(--ink-soft);
  font-family: var(--body);
  font-weight: 500;
  font-size: 14px;
}
.side-nav-item:hover { background: var(--paper-dim); color: var(--ink); }
.side-nav-item.active {
  background: rgba(255, 216, 61, 0.18);
  color: var(--ink);
  box-shadow: 0 0 0 1px rgba(255, 216, 61, 0.45), 0 0 14px rgba(255, 216, 61, 0.4);
}
.side-nav-label {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 400;
  font-size: 16px;
  line-height: 1.25;
  letter-spacing: normal;
}
.side-nav-icon { display: flex; align-items: center; justify-content: center; width: 22px; flex-shrink: 0; position: relative; }
.side-nav-badge {
  position: absolute;
  top: -6px;
  right: -9px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: 999px;
  background: var(--red);
  color: #fff;
  font-family: var(--body);
  font-weight: 800;
  font-size: 9.5px;
  line-height: 15px;
  text-align: center;
  border: 1.5px solid var(--paper);
}

/* Logo, moved here from .site-header (see body:has(#left-sidebar)
   .wordmark-link above) -- same icon+wordmark pairing as .wordmark-link,
   just sized for the sidebar's own top slot instead of the header row. */
.sidebar-logo {
  display: flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
  padding: 6px 0 16px;
}
.sidebar-logo-icon { display: block; height: 34px; width: auto; }
.sidebar-logo-wordmark { font-family: var(--body); font-weight: 800; font-size: 18px; color: var(--ink); }
.sidebar-logo-wordmark-light { color: var(--yellow); }

/* .drawer-user/.drawer-avatar/.drawer-username/.drawer-section-label/
   .drawer-theme-row/.drawer-signout (all defined above, for the mobile
   drawer) are reused as-is here rather than duplicated -- none of them are
   scoped to .account-drawer, so the same classes drop into the sidebar's
   own layout unchanged. Only the horizontal padding needs a nudge, since
   .side-nav-item's is narrower (12px, to fit the sidebar's own 10px outer
   padding) than the 300px-wide drawer's (18px). */
.sidebar-profile-link { margin-bottom: 6px; }
.sidebar-profile-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.sidebar-view-profile {
  font-family: var(--body);
  font-weight: 600;
  font-size: 11.5px;
  color: var(--ink-soft);
}
#left-sidebar .drawer-section-label { padding: 14px 12px 6px; }
#left-sidebar .drawer-theme-row { padding: 11px 12px; }
#left-sidebar .drawer-signout { margin: 10px 2px 0; }
.sidebar-disclosure { margin-top: 4px; }
.sidebar-disclosure > summary { list-style: none; cursor: pointer; }
.sidebar-disclosure > summary::-webkit-details-marker { display: none; }
.sidebar-disclosure-chevron {
  margin-left: auto;
  font-size: 18px;
  line-height: 1;
  transition: transform 0.16s ease;
}
.sidebar-disclosure[open] > summary .sidebar-disclosure-chevron { transform: rotate(180deg); }
.sidebar-disclosure-content {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 3px 0 2px 10px;
}
.sidebar-disclosure-content .side-nav-item { padding-left: 12px; }
.sidebar-account {
  margin-top: auto;
  padding-top: 8px;
  border-top: 1px solid var(--hairline);
}
.sidebar-account .drawer-theme-row { margin-left: 0; }
/* .drawer-view-toggle's button reset (see button.drawer-item above)
   applied to the sidebar's own <button>-based Display toggle, which
   otherwise inherits browser default button chrome instead of
   .side-nav-item's own look. */
button.side-nav-item {
  width: 100%;
  border: none;
  background: none;
  text-align: left;
  cursor: pointer;
}

/* ---------- Intro screens: marketing / scan / showcase / community ---------- */
/* Locked, app-style pager: #snap-intro is a fixed full-viewport window
   (running underneath the now-transparent sticky header, see .site-header
   above) with overflow hidden -- no native scrolling. #snap-intro-track
   holds all 4 panels stacked vertically and slides via transform:
   translateY, one full panel per wheel/touch/keydown gesture (see the
   pager in app.js). Each panel's own BACKGROUND spans the full viewport
   width (so it reads as a full-bleed color on desktop, not just a phone-
   width column) -- only its inner .wrap content is capped at phone width
   and centered (see the .snap-intro-track .wrap rule below). Each panel
   exactly fills the track's height, so it has to fit on one screen with
   no internal scroll -- deliberate, matching the phone-screenshot mockups
   this design is built from. Each panel adds ~56px of extra top padding
   (see its own rule) to clear the header's own content, since the header
   no longer reserves that space itself. */
.snap-intro {
  position: fixed;
  inset: 0;
  overflow: hidden;
  touch-action: none;
  z-index: 50;
  background: var(--paper);
}
.snap-intro[hidden] { display: none; }

.snap-intro-track {
  height: 100%;
  width: 100%;
  transition: transform 0.5s cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
}
.snap-intro-track > section {
  height: 100%;
  width: 100%;
  overflow: hidden;
}
/* Overrides .wrap's normal 1200px cap so the actual content stays
   phone-width and centered even though the panel's own background is
   full-bleed. */
.snap-intro-track .wrap {
  max-width: 480px;
  margin: 0 auto;
}

/* Dots hover as a floating overlay on top of whichever panel is showing --
   not a separate nav rail. */
.intro-dot-nav {
  position: absolute;
  right: 16px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 65;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.intro-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #121212;
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.85);
  border: none;
  padding: 0;
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;
}
.intro-dot.active { background: var(--yellow); transform: scale(1.35); }

.snap-marketing {
  /* Pins every themed token (--ink, --paper, etc.) back to their light-mode
     values for this panel and everything inside it, regardless of
     :root[data-theme="dark"] -- this is the homepage's first pitch screen
     and is meant to always read as the yellow/light design, never the dark
     theme. Custom properties resolve from the nearest ancestor that sets
     them, so redeclaring these here overrides the inherited dark-mode
     values for this whole subtree without needing to touch every
     individual rule. */
  --paper: #FFFFFF;
  --paper-dim: #FFFFFF;
  --ink: #121212;
  --ink-soft: #4A4A4A;
  --hairline: rgba(18, 18, 18, 0.12);
  /* --shadow-* embed var(--ink) *inside* their own value, declared once at
     :root -- that inner var(--ink) resolves using :root's cascade (i.e.
     the dark-mode value) the moment --shadow-* itself is computed there,
     and it's that already-resolved string that inherits down, not a live
     reference re-evaluated at each descendant. So redeclaring --ink above
     doesn't fix a shadow still using the plain --shadow-sm/-md/-lg tokens
     -- they need their own light-pinned redeclaration too. */
  --shadow-sm: 4px 4px 0 #121212;
  --shadow-md: 7px 7px 0 #121212;
  --shadow-lg: 10px 10px 0 #121212;

  position: relative;
  height: 100%;
  background: var(--yellow);
  display: flex;
  flex-direction: column;
  /* Bottom padding reserves room for the absolutely-positioned
     .snap-cta-group below (Get Started pill + swipe hint stacked). */
  padding: 72px 0 130px;
}
/* Auto top/bottom margins center the headline block within the space
   between the header and the reserved bottom padding above -- landing it
   noticeably lower than a top-anchored layout, close to center without
   being dead-center (the extra bottom padding biases it slightly high). */
.snap-marketing > .wrap {
  min-height: 0;
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.marketing-text-group {
  flex: 0 0 auto;
  width: 100%;
}
.snap-marketing .hero-headline-big { margin-bottom: 18px; }
.snap-marketing .hero-desc { margin-bottom: 0; }
.marketing-preview-row {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(14px, 4vw, 28px);
}
.snap-marketing .marketing-preview-row .hero-desc {
  flex: 1 1 0;
  max-width: 175px;
  margin: 0;
  text-align: left;
  font-size: clamp(17px, 5vw, 23px);
  line-height: 1.2;
  font-weight: 700;
}
.marketing-phone-wrap {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin-top: 14px;
}
.marketing-preview-row .marketing-phone-wrap {
  flex: 0 0 auto;
  margin-top: 0;
}
.phone-frame.marketing-phone-frame {
  width: clamp(148px, min(41.4vw, 24.4vh), 235px);
  height: auto;
  aspect-ratio: 1 / 1.99;
  box-shadow: 0 20px 40px -14px rgba(18, 18, 18, 0.5);
}
.marketing-phone-preview-hint {
  margin-top: 8px;
  color: #121212;
  font-family: var(--body);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.02em;
}
.marketing-phone-screen {
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: 15px;
  background: #0a0a0a;
}
.phone-slide[data-fullscreen-src] { cursor: zoom-in; }
.phone-slide[data-fullscreen-src]:focus-visible {
  outline: 3px solid #fff;
  outline-offset: -4px;
}
.marketing-slide-viewer {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  box-sizing: border-box;
  padding: 0;
  background: rgba(0, 0, 0, 0.96);
  cursor: zoom-out;
  touch-action: none;
  overscroll-behavior: contain;
  animation: lightbox-fade-in 0.18s ease;
}
.marketing-slide-viewer[hidden] { display: none; }
.marketing-slide-viewer img {
  display: block;
  width: 100vw;
  height: 100dvh;
  max-width: none;
  max-height: none;
  object-fit: contain;
  border-radius: 0;
  box-shadow: none;
  cursor: default;
}
.marketing-slide-viewer-close {
  position: fixed;
  top: max(16px, env(safe-area-inset-top));
  right: max(16px, env(safe-area-inset-right));
  z-index: 1;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 50%;
  background: rgba(18, 18, 18, 0.78);
  color: #fff;
  cursor: pointer;
  backdrop-filter: blur(8px);
}
.marketing-slide-viewer-close svg { width: 24px; height: 24px; }
.marketing-slide-viewer-close:hover { background: rgba(52, 52, 52, 0.9); }
body.marketing-slide-viewer-open { overflow: hidden; }
.snap-marketing > .snap-cta-group,
.snap-scan > .snap-cta-group,
.snap-showcase > .snap-cta-group {
  position: absolute;
  left: 50%;
  bottom: 14px;
  transform: translateX(-50%);
  width: 100%;
  max-width: 320px;
}

/* Groups the "Get Started" pill with the swipe hint below it so the two
   move as one absolutely-positioned block anchored to each panel's bottom
   edge, above. Each panel reserves matching bottom padding on itself (see
   e.g. .snap-scan's padding-bottom) so its own in-flow content never runs
   under this. */
.snap-cta-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.hero-headline-big {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: clamp(36px, 10.5vw, 56px);
  line-height: 0.98;
  letter-spacing: 0.5px;
  margin: 0 0 30px;
  color: var(--ink);
}
.hero-headline-white { color: #fff; }

.hero-tagline {
  font-family: var(--body);
  font-weight: 800;
  font-size: 16px;
  margin: 0 0 8px;
  color: var(--ink);
}
.hero-desc {
  font-family: var(--body);
  font-size: 13px;
  line-height: 1.5;
  font-weight: 500;
  color: var(--ink);
  max-width: 380px;
  margin: 0 auto 18px;
}
.hero-desc strong { font-weight: 800; }
.snap-marketing .wrap { text-align: center; }

.phone-frame {
  position: relative;
  aspect-ratio: 9 / 19.5;
  background: #0a0a0a;
  border-radius: 22px;
  padding: 7px;
  box-shadow: 0 16px 32px -12px rgba(18, 18, 18, 0.55);
}
.phone-notch {
  position: absolute;
  top: 7px;
  left: 50%;
  transform: translateX(-50%);
  width: 36%;
  height: 13px;
  background: #0a0a0a;
  border-radius: 0 0 9px 9px;
  z-index: 2;
}
.phone-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2px;
  width: 100%;
  height: 100%;
  border-radius: 15px;
  overflow: hidden;
  background: #000;
}
.phone-grid img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}


.get-started-btn {
  display: block;
  width: 100%;
  max-width: 320px;
  margin: 0 auto;
  text-align: center;
  font-family: var(--body);
  font-weight: 800;
  font-size: 16px;
  background: var(--ink);
  color: var(--yellow);
  padding: 14px 20px;
  border-radius: 999px;
  text-decoration: none;
  transition: opacity 0.12s ease;
}
.get-started-btn:hover { opacity: 0.85; }
.get-started-btn:active { opacity: 0.7; }
.get-started-btn-yellow { background: var(--yellow); color: var(--ink); }

.swipe-hint-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin: 0;
  font-family: var(--body);
  font-weight: 700;
  font-size: 12px;
  color: var(--ink);
  background: none;
  border: none;
  cursor: pointer;
  padding: 6px;
}
.swipe-hint-btn:hover { opacity: 0.7; }

/* ---------- Intro page 2: scan features ---------- */
.snap-scan {
  /* Same light-mode pin as .snap-marketing (see its comment) -- this is
     also a signed-out pitch screen and should never flip to the dark
     theme, just white instead of yellow this time. */
  --paper: #FFFFFF;
  --paper-dim: #FFFFFF;
  --ink: #121212;
  --ink-soft: #4A4A4A;
  --hairline: rgba(18, 18, 18, 0.12);
  --shadow-sm: 4px 4px 0 #121212;
  --shadow-md: 7px 7px 0 #121212;
  --shadow-lg: 10px 10px 0 #121212;

  position: relative;
  height: 100%;
  background: #fff;
  /* Bottom padding reserves room below .wrap for the absolutely-positioned
     .snap-cta-group (see .snap-scan > .snap-cta-group) so it never overlaps
     the feature list above it. */
  padding: 72px 0 130px;
}
/* Centers the header+phone+features stack as one block within the space
   between the sticky header above and the absolutely-positioned CTA group
   below (.snap-cta-group lives outside .wrap entirely, see below), rather
   than left at flex's top-anchored default -- that left a big dead gap
   between the feature list and the CTA on every phone taller than the
   content itself. Same technique as the desktop fix earlier this session,
   just no longer gated to desktop-only. */
.snap-scan .wrap {
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
}

/* Header/subhead on top, then the (landscape) phone, then the feature
   list underneath -- a vertical stack rather than side-by-side columns. */
.scan-phone-wrap {
  display: flex;
  justify-content: center;
  margin: 20px 0;
}
.scan-phone-wrap .phone-frame {
  box-shadow: 0 18px 36px -14px rgba(18, 18, 18, 0.5);
}
/* Rotated on its side: swap the aspect ratio and move the notch from the
   top edge to the left edge to match. */
.scan-phone-landscape {
  aspect-ratio: 19.5 / 9;
  width: clamp(220px, 62vw, 320px);
  /* .scan-phone-wrap is a flex row, and this element's default flex-shrink
     of 1 means a wide-enough viewport (desktop's width clamp can compute
     past the homepage's own capped-at-436px content column, see
     .snap-intro-track .wrap) shrinks its WIDTH down to fit that column --
     but since width and height are set independently here (no aspect-ratio
     link once both are explicit, on desktop below), only the width shrinks
     while height stays full-size, squashing it into a short, fat shape.
     flex-shrink:0 lets it overflow that column visually (harmless -- the
     actual clipping boundary is the whole panel, far wider) instead. */
  flex-shrink: 0;
  /* Clips the grid's bottom overflow below (from pushing it down to clear
     the topbar) instead of letting it spill past the frame's rounded
     corners. */
  overflow: hidden;
}
.scan-phone-landscape .phone-notch {
  top: 50%;
  left: 7px;
  transform: translateY(-50%);
  width: 13px;
  height: 34%;
  border-radius: 0 9px 9px 0;
}
.scan-phone-slideshow { border-radius: 15px; }
.scan-phone-slide,
.scan-edit-slide {
  width: 100%;
  height: 100%;
}
.scan-edit-slide {
  display: block;
  object-fit: cover;
  image-rendering: smooth;
}
/* Wraps .phone-grid at exactly the screen's own rect (100% of the frame's
   content box, same as the grid used to size itself against directly) and
   clips there -- .scan-phone-landscape's own overflow:hidden only clips at
   the OUTER frame edge (past the 7px bezel, since that's where its padding
   box sits), so the grid's deliberate below-bottom overflow (see its own
   comment) used to bleed the bottom row into the bezel instead of getting
   cropped at the screen boundary. This wrapper has no padding of its own,
   so its padding box IS its content box -- clipping happens right where
   the screen ends. */
.scan-screen-clip {
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: 15px;
}
.scan-phone-landscape .phone-grid {
  /* minmax(0, 1fr), not plain 1fr -- plain 1fr's implicit "auto" minimum
     let the images' intrinsic size push each row past its fair 1/2 share,
     overflowing the frame so only the top row was ever visible. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: repeat(2, minmax(0, 1fr));
  /* Pushed down so the topbar has clear background above the covers
     instead of overlapping them -- the grid still fills its full height,
     so this pushes the same amount of content past .scan-screen-clip's
     bottom edge, cropped off by that wrapper's own overflow:hidden above. */
  margin-top: 24px;
  /* Transparent, not the base .phone-grid's near-black -- the "screen"
     fill now comes from .scan-ui-frame::before below, which spans the
     whole inner area (including the 24px strip behind the topbar text
     that this margin creates), so the grid doesn't paint its own
     mismatched black square on top of just its own shorter box. */
  background: transparent;
}
/* Each cell becomes a flex box so the cover -- sized to its own natural
   aspect ratio via height:100%/width:auto, not stretched or letterboxed
   inside the wider cell -- can be pushed to whichever edge points toward
   the row's middle cover. The outer two end up almost touching the center
   one; the glow border goes right on the image itself, so it hugs the
   cover exactly instead of outlining the whole (wider) cell. */
.scan-phone-landscape .scan-cell {
  display: flex;
  align-items: center;
  padding: 2px;
}
.scan-phone-landscape .scan-cell:nth-child(3n+1) { justify-content: flex-end; }
.scan-phone-landscape .scan-cell:nth-child(3n+2) { justify-content: center; }
.scan-phone-landscape .scan-cell:nth-child(3n) { justify-content: flex-start; }
.scan-phone-landscape .scan-cell::after { display: none; }
.scan-phone-landscape .scan-cell img {
  height: 100%;
  width: auto;
  max-width: 100%;
  object-fit: cover;
  image-rendering: smooth;
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  border-radius: 3px;
  box-shadow: 0 0 5px 1px rgba(255, 255, 255, 0.6);
}

.scan-headline-2 {
  font-family: var(--body);
  font-weight: 900;
  font-size: clamp(22px, 6.4vw, 32px);
  line-height: 1.08;
  letter-spacing: -0.5px;
  margin: 0 0 8px;
  color: var(--ink);
}
.scan-headline-accent-2 { color: #C9A400; }
.scan-desc-2 {
  font-family: var(--body);
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink);
  margin: 0 auto 16px;
  max-width: 340px;
}
.scan-desc-2 strong { font-weight: 800; }
.scan-desc-accent { color: #C9A400; }

.hero-divider {
  border: none;
  border-top: 1px solid var(--hairline);
  max-width: 360px;
  margin: 0 auto 14px;
}

.feature-list {
  display: flex;
  flex-direction: column;
  gap: 13px;
  max-width: 360px;
  margin: 28px auto 0;
  text-align: left;
}
.feature-row { display: flex; align-items: flex-start; gap: 12px; }
.feature-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  background: #FFF3D6;
  border-radius: 10px;
  color: #121212;
}
.feature-icon svg { width: 20px; height: 20px; }
.feature-text h3 {
  font-family: var(--body);
  font-weight: 800;
  font-size: 15px;
  margin: 0 0 2px;
  color: var(--ink);
}
.feature-text p {
  font-family: var(--body);
  font-size: 12.5px;
  line-height: 1.35;
  margin: 0;
  color: var(--ink-soft);
}

/* .snap-scan's headline+phone+4-row feature list has no max-height and
   this panel never scrolls (see app.js's initIntroPager) -- on a real
   phone with a shorter EFFECTIVE viewport (Chrome/Safari's own address
   bar and gesture-nav bar eat into the actual content height well below
   the device's full screen height -- confirmed on an S22 Ultra and an
   iPhone 17 Pro, and reproduced locally in an iframe sized to that
   shorter effective height, not the device's full resolution), the
   bottom rows silently overflow the panel and sit behind the absolutely-
   positioned CTA group instead of getting cut off cleanly. Tightening
   margins/gaps only when height is actually tight -- not on every mobile
   width -- keeps the more spacious default on taller viewports where
   there's no problem to fix. */
@media (max-height: 800px) {
  .snap-marketing .hero-headline-big { margin-bottom: 12px; }
  .snap-marketing .hero-desc { font-size: 12px; line-height: 1.35; }
  .marketing-phone-wrap { margin-top: 8px; }
  .phone-frame.marketing-phone-frame { width: clamp(131px, min(35.4vw, 20.4vh), 191px); }
  .snap-scan { padding: 48px 0 110px; }
  .scan-phone-wrap { margin: 8px 0; }
  .hero-divider { margin: 0 auto 6px; }
  .feature-list { gap: 6px; margin: 6px auto 0; }
  .feature-icon { width: 30px; height: 30px; }
  .feature-icon svg { width: 16px; height: 16px; }
}
/* A second, tighter tier for genuinely short effective viewports (a lot of
   phones with prominent browser chrome land under ~650px of actual content
   height, not just under 800) -- shrinks the scan phone mockup itself too,
   since by this point it's one of the largest single space consumers. */
@media (max-height: 650px) {
  /* padding-top stays at the 800px tier's 48px, not reduced further --
     .snap-intro is position:fixed starting at the true viewport top (see
     its own rule), with the sticky header rendering on top of it at a
     higher z-index, so padding-top's real job is clearing the header's
     own rendered height, not "how much top margin looks nice" -- confirmed
     by testing 32px here directly: it visibly collided with the header. */
  .snap-marketing .hero-headline-big { font-size: 30px; }
  .snap-marketing .hero-desc { font-size: 10.5px; }
  .phone-frame.marketing-phone-frame { width: 114px; }
  .snap-scan { padding-bottom: 100px; }
  .scan-phone-landscape { width: clamp(160px, 48vw, 240px); }
  .scan-desc-2 { margin: 0 auto 8px; }
  .feature-list { gap: 4px; margin: 4px auto 0; }
  .feature-text h3 { font-size: 14px; }
  .feature-text p { font-size: 12px; }
}

.scan-cell { position: relative; }
.scan-cell::after {
  content: "";
  position: absolute;
  inset: 3px;
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  border-radius: 3px;
  box-shadow: 0 0 5px 1px rgba(255, 255, 255, 0.6);
  pointer-events: none;
}
/* isolation:isolate forces this into its own stacking context so the
   ::before's negative z-index below is scoped locally (painting behind
   .phone-grid's non-positioned content but above this element's own
   background) instead of being compared against unrelated ancestors. */
.scan-ui-frame { position: relative; isolation: isolate; }
/* The actual "screen" fill, painted behind everything else -- unlike
   coloring .phone-grid alone, this reaches all the way up behind the
   "Scanning Comics..." topbar too, instead of stopping at the grid's own
   margin-top offset. inset:7px, not 0 -- inset:0 on an absolutely
   positioned box aligns to the OUTER edge of the ancestor's padding (same
   rect as its border box, since .phone-frame has no border), covering the
   7px bezel too and erasing it; matching .phone-frame's own 7px padding
   here instead lands this exactly on the content box .phone-grid fills,
   leaving the bezel ring showing the frame's own near-black background
   around it. */
.scan-ui-frame::before {
  content: "";
  position: absolute;
  inset: 7px;
  border-radius: 15px;
  background: #2E2E33;
  /* Negative, not 0 -- .phone-grid is a plain non-positioned block, and
     those paint before (i.e. visually under) a positioned z-index:0/auto
     sibling in CSS's stacking order regardless of DOM order. A z-index of
     0 here would have this overlay covering the cover images instead of
     sitting behind them. Negative puts it in the layer just above the
     frame's own background but below all non-positioned content. */
  z-index: -1;
}
.scan-ui-topbar {
  position: absolute;
  top: 10px;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  color: #fff;
  font-family: var(--body);
  font-weight: 700;
  font-size: 10px;
  z-index: 3;
  pointer-events: none;
}
/* Glowing horizontal line that sweeps down the photo grid on a loop, to
   read as an active scan rather than a static screenshot. Rests around
   3/4 of the way down. */
.scan-ui-line {
  position: absolute;
  left: 6%;
  right: 6%;
  top: 75%;
  height: 2px;
  background: #fff;
  border-radius: 2px;
  box-shadow: 0 0 6px 2px rgba(255, 216, 61, 0.85), 0 0 14px 5px rgba(255, 255, 255, 0.35);
  z-index: 3;
  pointer-events: none;
  animation: scan-line-sweep 2.8s ease-in-out infinite;
}
@keyframes scan-line-sweep {
  0%, 100% { top: 14%; opacity: 0.45; }
  50% { top: 88%; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .scan-ui-line { animation: none; top: 75%; }
}
.scan-ui-bottombar {
  position: absolute;
  bottom: 10px;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 3;
  pointer-events: none;
}
.scan-ui-shutter {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid rgba(255, 255, 255, 0.5);
}

/* ---------- Intro page 3: marketplace showcase ---------- */
.snap-showcase {
  --paper: #FFFFFF;
  --paper-dim: #FFFFFF;
  --ink: #121212;
  --ink-soft: #666666;
  --hairline: rgba(18, 18, 18, 0.12);
  --shadow-sm: 4px 4px 0 #121212;
  --shadow-md: 7px 7px 0 #121212;
  --shadow-lg: 10px 10px 0 #121212;

  position: relative;
  height: 100%;
  background: #fff;
  /* Bottom padding reserves room for the absolutely-positioned
     .snap-cta-group below (Get Started pill + swipe hint stacked). */
  padding: 72px 0 130px;
  display: flex;
  flex-direction: column;
}
.snap-showcase .wrap {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.showcase-headline {
  font-family: var(--body);
  font-weight: 900;
  font-size: clamp(21px, 5vw, 30px);
  line-height: 1.08;
  margin: 0 0 5px;
  color: var(--ink);
}
.showcase-headline-accent { color: #C9A400; }
.showcase-desc {
  font-family: var(--body);
  font-size: 12.5px;
  color: var(--ink-soft);
  margin: 0 0 14px;
}

/* The whole marketplace preview -- search, filters, listings, tab bar --
   lives inside a phone mockup now. It's static example content, not the
   live grid: this is "here's a look at what you get when you log in,"
   not a functional feed. */
.showcase-phone-wrap {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
/* Base size for both pitch-screen phone mockups (page 3 & page 4).
   Page 3 gets a small width increase below so its three marketplace
   screenshots have a little more visual weight. Driven by WIDTH, not height: width is the flex row's
   main axis, so it's always resolved as a definite value up front and
   aspect-ratio derives the height reliably from it. (Deriving width FROM a
   percentage height was the earlier "short and fat" bug -- that direction
   doesn't reliably combine with aspect-ratio inside a flex row in every
   browser.) */
.showcase-phone-frame,
.community-phone-frame {
  width: clamp(150px, 42vw, 210px);
  height: auto;
  /* 9/17.4 (~0.517), not the realistic-iPhone 9/19.5 (~0.462) the other
     phone mockups use -- this frame's content is always one of the real
     app screenshots (~0.51-0.52 themselves), not a hand-built mockup UI,
     so matching its ratio to THEM keeps object-fit: cover's crop down to
     ~1%, imperceptible, instead of a noticeable slice off the sides or
     (tried briefly) empty letterbox bars from object-fit: contain. */
  aspect-ratio: 9 / 17.4;
}
.showcase-phone-frame {
  width: clamp(162px, 45.4vw, 227px);
  box-shadow: 0 20px 40px -14px rgba(18, 18, 18, 0.5);
}
/* Real app screenshots standing in for the pitch phones' content --
   object-fit: cover, but the frame itself is now sized to (nearly) match
   these screenshots' own aspect ratio (see .showcase-phone-frame's own
   comment), so cover only trims an imperceptible ~1% instead of a visible
   slice of real content. contain was tried instead at one point, to avoid
   cropping entirely -- but the letterbox bars it leaves (this container's
   white background showing through above/below on every screenshot,
   jarringly so on the dark-mode ones) looked worse than a barely-there
   crop. image-rendering hints the browser to use its best (not fastest)
   downscale filter -- the source files are still higher-resolution than
   their tiny display size here, so a cheap filter shows up softly
   aliased at those dimensions otherwise. "high-quality" is not a real
   CSS keyword (valid values: auto | smooth | crisp-edges | pixelated) --
   browsers silently ignore an invalid value and fall back to the default,
   so this hint was never actually reaching the renderer. "smooth" is the
   spec-defined value for this ("scaled with an algorithm that maximizes
   the appearance of the image"), and is what was actually intended here. */
.phone-screenshot {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  image-rendering: smooth;
}
/* Auto-advancing slideshow (showcase-slideshow) -- each slide shows for a
   couple seconds, then the next slides in from the right while the current
   one exits left, continuously looping. Positioning is driven entirely by
   inline transforms set in app.js's initShowcaseSlideshow (it needs to
   silently reset a just-passed slide back to the right, off-screen, before
   its next lap -- a plain CSS class toggle can't do that); the transform
   below is just the pre-JS default so nothing overlaps if JS is slow to
   run. That JS also starts the whole loop only once a visitor actually
   reaches this panel. */
.phone-slideshow {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.phone-slideshow .phone-slide {
  position: absolute;
  inset: 0;
  transform: translateX(100%);
  transition: transform 0.6s ease;
}
.phone-slideshow .phone-slide.is-active { transform: translateX(0); }
.phone-slideshow .phone-slide.is-prev { transform: translateX(-100%); }
@media (prefers-reduced-motion: reduce) {
  .phone-slideshow .phone-slide { transition: none; }
}
.showcase-phone-screen {
  height: 100%;
  width: 100%;
  border-radius: 15px;
  overflow: hidden;
  background: #fff;
  display: flex;
  flex-direction: column;
  text-align: left;
}
.showcase-phone-content {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 8px 10px 0;
}

/* Small recreation of the real site header (logo + wordmark, hamburger
   menu) shown inside the phone mockups on pages 3 & 4 -- these are static
   previews of what a signed-in user actually sees, not live app chrome. */
.phone-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--yellow);
  padding: 20px 9px 6px;
}
.phone-header-brand { display: flex; align-items: center; gap: 3px; }
.phone-header-icon { height: 12px; width: auto; display: block; }
.phone-header-wordmark { font-family: var(--body); font-weight: 800; font-size: 8px; color: #121212; }
.phone-header-wordmark-light { color: #fff; }
.phone-header-menu { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.phone-header-menu-bar { display: block; width: 12px; height: 1.4px; border-radius: 1px; background: #121212; }
.phone-header-menu-bar:nth-child(2) { width: 9px; }

.showcase-search-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--ink);
  color: #fff;
  border-radius: 999px;
  padding: 7px 11px;
  margin-bottom: 7px;
}
.showcase-search-bar svg { flex-shrink: 0; opacity: 0.8; }
.showcase-search-placeholder {
  flex: 1 1 auto;
  font-family: var(--body);
  font-size: 9px;
  color: rgba(255, 255, 255, 0.65);
}

.showcase-filter-pills {
  display: flex;
  gap: 5px;
  margin-bottom: 10px;
}
.showcase-pill {
  display: inline-flex;
  align-items: center;
  font-family: var(--body);
  font-weight: 700;
  font-size: 8px;
  background: var(--ink);
  color: var(--yellow);
  padding: 5px 8px;
  border-radius: 999px;
  white-space: nowrap;
}

.showcase-listings { display: flex; flex-direction: column; }
.showcase-listing-row {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 0;
  border-bottom: 1px solid var(--hairline);
}
.showcase-listing-thumb {
  width: 26px;
  height: 26px;
  border-radius: 5px;
  object-fit: cover;
  flex-shrink: 0;
  background: #eee;
}
.showcase-listing-info { flex: 1 1 auto; min-width: 0; }
.showcase-listing-info h4 {
  font-family: var(--body);
  font-weight: 800;
  font-size: 9px;
  margin: 0 0 1px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.showcase-listing-meta { font-family: var(--body); font-size: 7.5px; color: var(--ink-soft); }
.showcase-listing-right { flex-shrink: 0; }
.showcase-listing-price { font-family: var(--body); font-weight: 800; font-size: 9px; color: var(--ink); }

.showcase-phone-tabbar {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-around;
  padding: 6px 4px 10px;
  border-top: 1px solid var(--hairline);
}
.showcase-ptab { display: flex; color: #9a9a9a; }
.showcase-ptab.active-ptab { color: var(--ink); }
.showcase-ptab-scan {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--yellow);
  margin-top: -10px;
}

/* ---------- Intro page 4: community ---------- */
.snap-community {
  --paper: #0A0A0A;
  --paper-dim: #0A0A0A;
  --ink: #FFFFFF;
  --ink-soft: #B8B8B8;
  --hairline: rgba(255, 255, 255, 0.14);
  --shadow-sm: 4px 4px 0 #000000;
  --shadow-md: 7px 7px 0 #000000;
  --shadow-lg: 10px 10px 0 #000000;

  height: 100%;
  background: #0A0A0A;
  padding: 72px 0 14px;
  display: flex;
  flex-direction: column;
}
.snap-community .wrap {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.community-headline {
  font-family: var(--body);
  font-weight: 900;
  font-size: clamp(18px, 4.2vw, 25px);
  line-height: 1.12;
  text-align: center;
  margin: 0 0 6px;
  color: #fff;
}
.community-headline-accent { color: var(--yellow); }
.community-desc {
  font-family: var(--body);
  font-size: 11px;
  line-height: 1.4;
  color: #B8B8B8;
  text-align: center;
  max-width: 440px;
  margin: 0 auto 12px;
}

/* Community-cards preview lives inside a phone mockup, same treatment as
   page 3 -- this is a look at the feature, not a live/functional feed.
   Declared here (mobile-base, before the desktop media query below) rather
   than after it -- a later unconditional rule with the same specificity
   always wins over an earlier @media one regardless of viewport, so a
   duplicate declared after the media query silently ate the desktop
   flex:0 0 auto override every time. Matches .showcase-phone-wrap's own
   placement for the same reason. */
.community-phone-wrap {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin-bottom: 10px;
}

/* ---------- Intro pager text, scaled up for desktop ---------- */
/* Every headline/description above was sized with clamp()'s vw component
   tuned for a phone-width viewport -- past a certain width the vw term
   outgrows the clamp's own max and gets capped there, so on a real desktop
   monitor (.wrap tops out at 1200px) all of this stays stuck near its
   mobile size while surrounded by much more open space, reading as tiny.
   Re-declaring fresh, taller clamps here (rather than adjusting the
   mobile-tuned ones directly) keeps each in its own comfortable range for
   its own viewport instead of one clamp doing double duty for both. */
@media (min-width: 721px) {
  /* Capped at 74px, not a bigger round number -- .snap-intro-track .wrap
     overrides the normal .wrap to a fixed 480px max-width for every intro
     panel regardless of viewport (keeps them phone-width even on desktop),
     so this headline's real constraint is that fixed 436px text column,
     not the screen. 76px was the largest size (checked empirically via
     screenshot) that still keeps "FROM LONGBOX" on one line before the
     browser's own wrapping (not the deliberate <br> after it) drops
     "LONGBOX" to its own line; 74px keeps a couple px of safety margin
     against font-rendering differences across browsers/OS. */
  .hero-headline-big { font-size: clamp(58px, 6vw, 74px); }
  .hero-tagline { font-size: 22px; }
  .hero-desc { font-size: 18px; max-width: 560px; }

  /* Scaled up less than the other panels' headlines -- this panel also
     carries a 4-row feature list none of the others have, so the same
     aggressive bump that works for a short headline+phone+desc panel was
     making the combined stack tall enough to overflow the panel in both
     directions once centered (see .snap-scan .wrap's justify-content:
     center below). */
  /* Capped at 40px, not the round 42 the clamp used to hit -- same fixed
     436px text column as .hero-headline-big (see its own comment), and 42px
     is exactly where "Cataloging" wraps to its own line; 40px keeps a
     couple px of safety margin against font-rendering differences. */
  .scan-headline-2 { font-size: clamp(32px, 3.4vw, 40px); }
  /* Matched to the feature list's own p / .hero-desc's 18px, same as the
     h3 lead-ins above -- was noticeably smaller than everything around it. */
  .scan-desc-2 { font-size: 18px; max-width: 460px; margin-bottom: 0; }
  /* The scan phone now matches showcase/community's exact size (rotated),
     which is a lot bigger than this panel's old landscape mockup -- the
     4-row feature list below it has to compact hard to still fit above
     the CTA without overlapping it. Wider max-width (not just smaller
     text) matters here too: at the old 360px cap the longer descriptions
     (e.g. "Forward receipts...") wrapped to 3 lines; a bit more width
     drops most of them to 2, which saves more vertical space than any
     further font-size cut would. */
  .hero-divider { margin: 0 auto 0; }
  /* An explicit width, not max-width -- widening past the panel's own
     436px content column only helps if it can actually force the box
     wider than its (436px) parent; max-width alone is just a ceiling that
     can't exceed a narrower parent's own size. Same reasoning as
     .scan-phone-landscape's flex-shrink:0 above: letting the longest
     description (row 2) wrap to 2 lines instead of 3 saves far more
     vertical room than any further margin/gap trim could, and nothing
     clips it (the real boundary is the whole panel, much wider). */
  .feature-list { gap: 20px; width: 600px; max-width: none; margin-top: 0; transform: translateX(-32px); }
  .feature-row { gap: 8px; }
  .feature-icon { width: 26px; height: 26px; }
  .feature-icon svg { width: 16px; height: 16px; }
  /* h3 matched down to p's own size -- same 18px as .hero-desc (page 1's
     "Catalog your collection..." paragraph) -- so the lead-in reads as
     regular body text like everything else around it, not a bigger
     sub-heading. Weight (800 vs p's 400) is what still sets it apart. */
  .feature-text h3 { font-size: 18px; margin-bottom: 0; }
  .feature-text p { font-size: 18px; line-height: 1.22; }

  .showcase-headline { font-size: clamp(32px, 3.4vw, 42px); }
  .showcase-desc { font-size: 17px; }

  .community-headline { font-size: clamp(28px, 2.8vw, 36px); }
  .community-desc { font-size: 16px; max-width: 520px; }

  .get-started-btn { font-size: 19px; max-width: 380px; padding: 16px 24px; }
  .swipe-hint-btn { font-size: 15px; }

  .phone-frame.marketing-phone-frame {
    width: clamp(173px, min(23.1vw, 23.1vh), 244px);
    aspect-ratio: 1206 / 2470;
  }

  /* Phone mockups, bigger than their mobile size where there's room for it
     -- same reasoning as the text above: each one's mobile-tuned clamp
     maxes out well before desktop widths, so it rendered stuck at its
     smallest-viewport-havoc-avoiding cap regardless of how much open space
     surrounded it. The middle term is min(26vw, 30vh), not just 26vw --
     these frames are sized by WIDTH with height following via aspect-ratio
     (see the shared rule's own comment on why), which has no awareness of
     how much vertical room .showcase-phone-wrap/.community-phone-wrap
     actually got -- pure vw growth let a wide-but-short desktop window
     size a frame tall enough to overlap the text above it. Folding in a
     vh-based term makes it self-limit on short viewports; the floor also
     comes back down to the original mobile minimum (150px) rather than a
     forced "always at least 50% bigger" value, since on a genuinely short
     viewport there just isn't room for both a bigger phone and the text
     below it -- fitting without overlap wins over hitting a fixed scale
     factor. */
  .showcase-phone-frame,
  .community-phone-frame { width: clamp(150px, min(26vw, 30vh), 315px); }
  .phone-frame.showcase-phone-frame { width: clamp(162px, min(28.1vw, 32.4vh), 340px); }
  /* This mockup is turned 90deg counter-clockwise, so its HEIGHT reads as
     "the phone's width" visually -- matched to showcase/community's own
     width clamp above (verbatim), so it's exactly as prominent as theirs.
     Its WIDTH (visually "the phone's height") is NOT scaled by their own
     9/17.4 frame ratio, though -- that ratio is deliberately squatter than
     a real phone (chosen so their real app-screenshot content isn't
     cropped, see that rule's own comment), and reusing it here made this
     mockup, which has no such screenshot to protect, look visibly too fat
     and short to read as "a phone on its side". 19.5/9 is what the OTHER
     realistic phone mockups on this page actually use (see e.g. line
     ~1028's aspect-ratio); scaling by that instead keeps the elongated
     phone silhouette while still matching pages 3/4's size on the one
     dimension that was actually asked to match. Both dimensions set
     explicitly (not just one dimension + aspect-ratio) so this doesn't
     depend on how a browser resolves aspect-ratio for the non-main-axis
     dimension inside .scan-phone-wrap's flex row. */
  .scan-phone-landscape {
    height: clamp(150px, min(26vw, 30vh), 315px);
    width: clamp(325px, min(56.33vw, 65vh), 683px);
  }

  /* .snap-scan .wrap / .snap-showcase .wrap's justify-content:center is
     no longer declared here -- it's on their base rules now, so it also
     applies on mobile (see those rules' own comments). The vh-capped
     phone size below is what actually keeps the combined stack short
     enough to fit without overlapping either the header above or
     whatever anchors the panel's bottom (the Get Started CTA on pages 2 &
     3, the footer bar on page 4). */
  .scan-phone-wrap,
  .showcase-phone-wrap,
  .community-phone-wrap {
    flex: 0 0 auto;
    margin-top: 20px;
  }
  /* Tighter than the shared 20px above -- the scan phone is now exactly
     as big as showcase/community's (see .scan-phone-landscape), which
     leaves this panel's 4-row feature list far less room to work with
     than their single short line of description. */
  .scan-phone-wrap { margin-top: 0; }
  /* Page 4 has a 4th sibling (.community-footer-bar) the other panels
     don't, so centering .wrap as a whole (like scan/showcase above) would
     pull the footer up into the centered block instead of leaving it
     pinned near the bottom. Two auto margins instead -- one on the first
     item of the header+phone group, one on the last -- center just THAT
     group within the leftover space above the footer, while the footer's
     own height is left out of the centering math entirely and lands near
     the true bottom of the panel. */
  .snap-community .wrap { justify-content: flex-start; }
  .community-text-group { margin-top: auto; }
  .community-phone-wrap { margin-bottom: auto; }
  /* padding-top stays >= the sticky header's own rendered height (~64px)
     -- same reasoning as the mobile max-height tiers' own comment: .snap-
     intro is position:fixed starting at the true viewport top, with the
     header rendering on top of it at a higher z-index, on desktop same as
     mobile. Learned this the hard way once already; not repeating it. */
  .snap-scan { padding: 64px 0 180px; }
  .scan-features-group {
    margin-top: 20px;
  }
}

/* A second desktop tier, width-independent like the mobile max-height
   tiers above -- checked at common laptop window heights (1280x720,
   1366x768), not just the 900px-tall window every other desktop check in
   this file was verified against, and pages 2, 3, and 4 all overlapped
   their header and/or CTA there: the phone mockups' own 30vh cap shrinks
   them some, but not enough, and everything else in the panel (header
   clearance, CTA group, page 2's text+feature list) is sized in fixed px
   that doesn't shrink with viewport height at all. Reclaims room from two
   places: the CTA group itself (shared by every panel, so shrinking it
   helps all of them at once) and a lower vh cap on the phones; page 2 also
   gets its text/feature list compacted further on top of that. */
@media (min-width: 721px) and (max-height: 820px) {
  .get-started-btn { padding: 12px 20px; font-size: 16px; }
  .swipe-hint-btn { font-size: 13px; }
  .snap-cta-group { gap: 8px; }

  .phone-frame.marketing-phone-frame { width: clamp(135px, min(18vw, 18vh), 186px); }
  .showcase-phone-frame,
  .community-phone-frame { width: clamp(130px, min(22vw, 20vh), 260px); }
  .phone-frame.showcase-phone-frame { width: clamp(140px, min(23.8vw, 21.6vh), 281px); }
  .scan-phone-landscape {
    height: clamp(130px, min(22vw, 20vh), 260px);
    width: clamp(282px, min(47.67vw, 43.33vh), 563px);
  }
  .showcase-phone-wrap,
  .community-phone-wrap,
  .scan-phone-wrap { margin-top: 0; }
  .showcase-desc,
  .community-desc { margin-bottom: 6px; }

  .scan-headline-2 { font-size: clamp(24px, 2.6vw, 30px); }
  .scan-desc-2 { margin-bottom: 0; }
  .hero-divider { display: none; }
  .feature-list { gap: 0; margin-top: 2px; }
  .feature-icon { width: 20px; height: 20px; }
  .feature-icon svg { width: 12px; height: 12px; }
  .feature-text h3 { font-size: 13.5px; margin-bottom: 0; }
  .feature-text p { font-size: 11px; line-height: 1.2; }
}

.community-phone-frame {
  /* Its base sizing and aspect ratio are shared with the showcase phone;
     only the showcase phone receives the small page-specific width bump. */
  background: #1c1c1e;
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 20px 40px -14px rgba(0, 0, 0, 0.6);
}
.community-phone-screen {
  /* .snap-community redefines --ink to white for its own dark theme, but
     this screen is a white "island" (reusing .showcase-phone-tabbar's
     shared, --ink-based styles) -- without re-pinning it here, the tab
     bar's black scan-badge circle resolves to white-on-white and vanishes. */
  --ink: #121212;
  height: 100%;
  width: 100%;
  border-radius: 15px;
  overflow: hidden;
  background: #fff;
  display: flex;
  flex-direction: column;
}
.community-phone-content {
  flex: 1 1 auto;
  min-height: 0;
  padding: 12px 11px 0;
}

.community-phone-label {
  font-family: var(--body);
  font-weight: 800;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: #9a9a9a;
  margin: 0 0 7px;
}

.community-cards { display: flex; flex-direction: column; gap: 7px; }
.community-card {
  display: flex;
  align-items: center;
  gap: 9px;
  border: 1px solid rgba(18, 18, 18, 0.12);
  border-radius: 12px;
  padding: 7px 10px;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.community-card-thumb {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  object-fit: cover;
  border: 2px solid #ddd;
}
.community-card-body { flex: 1 1 auto; min-width: 0; }
.community-card h3 {
  font-family: var(--body);
  font-weight: 800;
  font-size: 9.5px;
  margin: 0;
  color: #121212;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.community-card-activity {
  display: flex;
  align-items: center;
  gap: 4px;
  font-family: var(--body);
  font-weight: 600;
  font-size: 9px;
  color: #9a9a9a;
  margin-top: 2px;
}
.community-card-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--blue);
  flex-shrink: 0;
}
.community-card-chevron { flex-shrink: 0; color: #b8b8b8; }

/* A little "flair" -- each category gets its own accent, reusing the
   site's existing era-color tokens where they already fit. */
.community-card--silver .community-card-thumb { border-color: #9FB0BF; }
.community-card--golden .community-card-thumb { border-color: var(--golden); }
.community-card--bronze .community-card-thumb { border-color: var(--bronze); }
.community-card--copper .community-card-thumb { border-color: var(--copper); }
.community-card--red .community-card-thumb { border-color: var(--red); }

.community-footer-bar {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 14px;
  text-align: center;
  padding-top: 16px;
  border-top: 1px solid rgba(255, 255, 255, 0.15);
}
.community-footer-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.community-footer-links {
  display: flex;
  gap: 16px;
}
.community-footer-links a {
  font-family: var(--body);
  font-weight: 600;
  font-size: 12px;
  color: #fff;
  opacity: 0.75;
  text-decoration: none;
}
.community-footer-links a:hover { opacity: 1; text-decoration: underline; }
.community-footer-social { display: flex; gap: 10px; }
.community-footer-social-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1.2px solid rgba(255, 255, 255, 0.35);
  color: #fff;
}
.community-footer-social-icon svg { width: 13px; height: 13px; }
.community-footer-copyright {
  margin: 0;
  font-family: var(--body);
  font-size: 10px;
  color: rgba(255, 255, 255, 0.5);
}

.search-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 26px;
  max-width: 480px;
  padding: 13px 18px;
  border-radius: 999px;
  border: 1.5px solid var(--ink-soft);
  background: var(--paper-dim);
}
.search-bar:focus-within { border-color: var(--ink); }
.search-icon { color: var(--ink-soft); flex-shrink: 0; }
.search-bar input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  padding: 0;
  background: transparent;
  color: var(--ink);
  font: 600 15px var(--body);
}

.hero-art { min-height: 420px; position: relative; }
.hero-art .burst {
  position: absolute;
  inset: 20px;
  background-color: var(--yellow);
  background-image: radial-gradient(circle, rgba(0,0,0,0.18) 1px, transparent 1.1px);
  background-size: 9px 9px;
  clip-path: polygon(50% 0,60% 31%,82% 8%,73% 38%,100% 36%,78% 53%,99% 68%,70% 66%,78% 100%,56% 72%,42% 100%,42% 70%,11% 93%,29% 61%,0 57%,29% 47%,7% 26%,39% 36%);
  border: 5px solid var(--ink);
}
.hero-issue {
  position: absolute;
  width: 220px;
  height: 320px;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) rotate(6deg);
  background: var(--blue);
  border: 6px solid var(--ink);
  box-shadow: 12px 12px 0 rgba(0,0,0,0.85);
  overflow: hidden;
}
.hero-issue img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hero-issue .placeholder-label {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-family: var(--display);
  text-align: center;
  font-size: 22px;
  line-height: 1;
  padding: 12px;
  text-transform: uppercase;
}
.hero-badge {
  position: absolute;
  right: 0px;
  top: 10px;
  width: 165px;
  height: auto;
  transform: rotate(10deg);
}

.cta-btn {
  display: inline-block;
  font-family: var(--body);
  font-weight: 900;
  text-transform: uppercase;
  font-size: 15px;
  letter-spacing: 0.3px;
  background: var(--red);
  color: #fff;
  border: 3px solid var(--ink);
  padding: 15px 28px;
  border-radius: 999px;
  cursor: pointer;
  text-decoration: none;
  box-shadow: var(--shadow-md);
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.cta-btn:hover { transform: translate(-3px, -3px); box-shadow: 10px 10px 0 var(--ink); }
.cta-btn:active { transform: translate(0, 0); box-shadow: var(--shadow-sm); }

/* ---------- Home feed utility chrome ---------- */
/* .era-tabs is the outer wrap: a utility row followed by an always-visible,
   horizontally scrollable row of era pills. Both scroll away together with
   the sticky chrome (see #home-sticky-chrome). */
.era-tabs {
  display: flex;
  flex-direction: column;
  padding: 18px 22px 0;
  margin-bottom: 26px;
}

/* The hairline divider sits below the feed utility controls and the era
   filter -- not at the very bottom of the whole nav block. It's
   a full-width background stripe (not a border) so it stays unbroken across
   the gaps between tabs; each tab's own border-bottom sits flush against
   that same bottom edge, so the active tab's yellow segment reads as a
   thicker stretch of the one continuous line rather than a second line. */
.home-tabs-row {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  margin-bottom: 6px;
}
.home-page-title {
  color: var(--ink);
  font-family: var(--body);
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.1px;
}
.home-top-nav-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}
.home-top-nav-action {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  color: var(--ink-soft);
  text-decoration: none;
  transition: background-color 0.12s ease, color 0.12s ease;
}
.home-top-nav-action:hover { background: var(--paper-dim); color: var(--ink); }
.home-top-nav-action svg { width: 19px; height: 19px; }
.market-layout-toggle {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-left: 4px;
  padding: 2px;
  border: 1px solid var(--hairline);
  border-radius: 10px;
  background: var(--paper-dim);
}
.market-layout-toggle-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background-color 0.12s ease, color 0.12s ease;
}
.market-layout-toggle-button:hover { color: var(--ink); }
.market-layout-toggle-button.is-active {
  background: var(--yellow);
  color: #171717;
}
.market-layout-toggle-button svg { width: 17px; height: 17px; }
.home-top-nav-badge {
  position: absolute;
  top: 0;
  right: -2px;
  min-width: 14px;
  height: 14px;
  padding: 0 2px;
  border: 1.5px solid var(--paper);
  border-radius: 999px;
  background: var(--red);
  color: #fff;
  font-size: 8px;
  font-weight: 800;
  line-height: 14px;
  text-align: center;
}
@media (max-width: 720px) {
  .home-top-nav-actions { display: flex; }
  body:has(#bottom-tab-bar) .era-tabs { padding-top: 22px; }
  body:has(#bottom-tab-bar) .home-tabs-row { align-items: flex-start; margin-bottom: 10px; }
  body:has(#bottom-tab-bar) .home-page-title {
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: 34px;
    line-height: 39px;
    font-weight: 800;
    letter-spacing: -1.1px;
  }
  body:has(#bottom-tab-bar) .home-page-title::before {
    content: attr(data-eyebrow);
    color: var(--yellow);
    font-size: 11px;
    line-height: 14px;
    font-weight: 800;
    letter-spacing: 1.2px;
  }
  body:has(#bottom-tab-bar) .market-layout-toggle {
    margin-top: 4px;
    padding: 3px;
    border-radius: 18px;
  }
  body:has(#bottom-tab-bar) .market-layout-toggle-button {
    width: 34px;
    height: 30px;
    border-radius: 15px;
  }
}
/* Social-style capsule pills (filled yellow when active) -- NOT the heavy
   block-shadow .pill button used elsewhere on the site. Multi-select:
   several can be active at once, "All" is active whenever none are. */
.era-tabs-scroll {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 8px;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 2px 0 8px;
  border-bottom: 1px solid var(--hairline);
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.era-tabs-scroll::-webkit-scrollbar { display: none; }

.era-pill {
  font-family: var(--body);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.2px;
  color: var(--ink);
  background: var(--paper-dim);
  border: 1.5px solid var(--hairline);
  border-radius: 999px;
  padding: 5px 11px;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.era-pill:hover { border-color: var(--ink-soft); }
.era-pill.active {
  background: var(--yellow);
  border-color: var(--yellow);
  color: #121212;
  font-weight: 800;
}
.era-pill-more-chevron { width: 12px; height: 12px; }

/* Community post cards (Community tab only -- For You uses its own
   .foryou-card, see below) reuse rooms-shared.css's .gpost-feed as-is inside
   the homepage feed, except its own bottom hairline + margin (built for a
   bordered list in a room) is redundant next to .grid's own 22px gap --
   swap it for a full border so it reads as a distinct card. */
#grid .gpost-feed {
  margin-bottom: 0;
  border-bottom: none;
  border: 1px solid var(--hairline);
}

/* ---------- For You cards ----------
   The one tab that mixes sale listings and community posts, so each card
   leads with a colored badge naming which kind it is (see
   ~/Downloads/foryou.png). Deliberately its own component rather than
   reusing .listing-post/.grid-card/.gpost-feed -- none of those have a
   badge row, and Market/Community's own tabs don't need one since every
   card there is already a known type. */
.foryou-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px;
  border: 1px solid var(--hairline);
  border-radius: 14px;
  background: var(--paper);
}
.foryou-card-blend {
  gap: 10px;
  border-color: transparent;
  border-bottom-color: var(--hairline);
}
.foryou-context-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-width: 0;
  padding: 1px 2px;
  font-family: "DM Sans", var(--body);
  font-size: 12px;
  color: var(--ink-soft);
}
.foryou-context {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.foryou-context-kind {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 4px 7px;
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.3px;
  text-transform: uppercase;
}
.foryou-card-sale .foryou-context-kind { background: var(--red); }
.foryou-card-community .foryou-context-kind { background: var(--blue); }
.foryou-context-source,
.foryou-seller-context {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.foryou-seller-context .online-dot { width: 7px; height: 7px; }
.foryou-seller-context .verified-badge svg { width: 14px; height: 14px; }
.foryou-group-icon { font-size: 15px; line-height: 1; flex: 0 0 auto; }
.foryou-context-controls { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; }
.foryou-context-controls .post-owner-menu-btn { width: 25px; height: 25px; }
.foryou-context-controls .post-owner-menu-icon svg { width: 15px; height: 15px; }
.foryou-context-time { flex-shrink: 0; font-size: 11.5px; }
.foryou-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.foryou-badge {
  display: inline-flex;
  align-items: center;
  font-family: var(--body);
  font-weight: 800;
  font-size: 11px;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  padding: 5px 10px;
  border-radius: 7px;
}
.foryou-badge-sale { background: var(--yellow); color: #121212; }
.foryou-badge-community { background: var(--blue); color: #fff; }
.foryou-source {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--body);
  font-weight: 600;
  font-size: 12px;
  color: var(--ink-soft);
  text-align: right;
  min-width: 0;
  max-width: 60%;
}
.foryou-source svg { width: 14px; height: 14px; flex-shrink: 0; }
.foryou-source span {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.foryou-mini-thumb-wrap {
  flex: 0 0 150px;
  width: 150px;
  height: 150px;
  position: relative;
  overflow: hidden;
  border-radius: 10px;
  border: 1.5px solid var(--hairline);
}
.foryou-mini-thumb { width: 100%; height: 100%; object-fit: cover; display: block; }
.foryou-mini-thumb-wrap.is-processing .foryou-mini-thumb { filter: blur(9px); transform: scale(1.05); }

.foryou-card-main {
  display: flex;
  text-decoration: none;
  color: inherit;
}
.foryou-card-main-community { flex-direction: row; gap: 12px; align-items: flex-start; }

.foryou-thumb {
  position: relative;
  flex: 0 0 190px;
  width: 190px;
  height: 190px;
  border-radius: 10px;
  overflow: hidden;
  background: var(--paper-dim);
  border: 1.5px solid var(--hairline);
}
.foryou-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Sale cards show the uncropped cutout ("feed" photo variant, transparent
   background) over the same feedbg.png texture the homepage feed uses
   behind cutout covers (see #grid .listing-post .listing-photo-card::before
   in this file), instead of a hard object-fit:cover crop. No title/price/
   description text anymore -- this is the whole card-main, so it fills the
   full card width rather than sitting in the fixed square thumb box. */
.foryou-thumb-sale {
  flex: 1 1 auto;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
}
.foryou-thumb-sale::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("feedbg.png") center / cover no-repeat;
  opacity: 0.35;
}
.foryou-thumb-sale img {
  position: relative;
  object-fit: contain;
  background: transparent;
}
.foryou-card-blend .foryou-card-main-sale { flex-direction: column; gap: 9px; }
.foryou-listing-caption { display: flex; flex-direction: column; gap: 3px; padding: 0 3px 1px; }
.foryou-card-blend .foryou-title-sale { font-size: 18px; }
.foryou-card-blend .foryou-footer {
  border-top: 0;
  padding-top: 0;
  padding-bottom: 0;
}
.foryou-community-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.foryou-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: 5px;
}
.foryou-title {
  margin: 0;
  line-height: 1.25;
}
.foryou-title-sale {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 19px;
  letter-spacing: -0.2px;
}
.foryou-title-sale .issue { font-family: var(--body); font-weight: 600; color: var(--ink-soft); }
.foryou-title-community {
  font-family: var(--body);
  font-weight: 800;
  font-size: 17px;
}
.foryou-subline {
  font-family: var(--body);
  font-weight: 600;
  font-size: 12.5px;
  color: var(--ink-soft);
}
.foryou-price-tag {
  display: inline-block;
  align-self: flex-start;
  margin-top: 2px;
  background: var(--red);
  color: #fff;
  font-family: var(--body);
  font-weight: 800;
  font-size: 13px;
  padding: 5px 10px;
  border-radius: 7px;
}
.foryou-price-tag-pending {
  background: var(--ink-soft);
  text-transform: uppercase;
  font-size: 10.5px;
  letter-spacing: 0.2px;
}

.foryou-community-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.foryou-time {
  font-family: var(--body);
  font-size: 12.5px;
  color: var(--ink-soft);
}
.foryou-username {
  font-family: var(--body);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink);
}
.foryou-body-preview {
  font-family: var(--body);
  font-size: 13.5px;
  color: var(--ink-soft);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.foryou-footer { border-top: 1px solid var(--hairline); padding-top: 10px; }
.foryou-actions { display: flex; align-items: center; gap: 4px; }
.foryou-action-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  padding: 6px 8px;
  border-radius: 999px;
  font-family: var(--body);
  font-weight: 700;
  font-size: 12.5px;
  color: var(--ink-soft);
  cursor: pointer;
}
.foryou-action-btn:hover { background: var(--paper-dim); color: var(--ink); }
.foryou-action-btn.is-active { color: var(--red); }
.foryou-action-btn.wishlist-active { color: var(--blue); }
.foryou-action-btn.foryou-action-static { cursor: default; }
.foryou-action-btn.foryou-action-end { margin-left: auto; }
.foryou-action-icon { display: flex; }
.foryou-action-icon svg { width: 17px; height: 17px; display: block; }

/* Home feed content intentionally uses a quieter UI face than the comic-book
   display type: the art is the hero here, while titles, seller details, and
   actions should be easy to scan like a modern social feed. */
#grid .listing-post,
#grid .foryou-card { font-family: "DM Sans", var(--body); }
#grid .listing-post .post-name,
#grid .listing-post .post-description,
#grid .listing-post .listing-meta,
#grid .listing-post .post-actions,
#grid .foryou-card .foryou-title-sale,
#grid .foryou-card .foryou-subline,
#grid .foryou-card .foryou-actions { font-family: "DM Sans", var(--body); }
#grid .listing-post .post-stats h1,
#grid .foryou-card .foryou-title-sale {
  font-family: var(--body);
  font-weight: 600;
  text-transform: none;
  letter-spacing: -0.45px;
}

/* ---------- Grid ----------
   A single-column feed of full social posts (see "Social post" below for
   .listing-post itself) -- one book per post, scrolling like a feed. */
.collection-glance {
  margin: 0 0 22px;
  padding: 0 2px 18px;
  border-bottom: 1px solid var(--hairline);
  font-family: var(--body);
}
.collection-glance-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 11px;
}
.collection-glance-heading h2 {
  margin: 0;
  color: var(--ink);
  font-size: 13px;
  line-height: 1.2;
  font-weight: 850;
  letter-spacing: 0.2px;
}
.collection-glance-heading a {
  flex: 0 0 auto;
  color: var(--ink-soft);
  font-size: 10.5px;
  line-height: 1.2;
  font-weight: 750;
  text-decoration: none;
}
.collection-glance-heading a:hover,
.collection-glance-heading a:focus-visible { color: var(--ink); }
.collection-glance-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.collection-glance-stat {
  position: relative;
  display: flex;
  min-width: 0;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  color: inherit;
  text-decoration: none;
}
.collection-glance-stat + .collection-glance-stat::before {
  content: "";
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 0;
  width: 1px;
  background: var(--hairline);
}
.collection-glance-stat strong {
  overflow: hidden;
  max-width: 100%;
  color: var(--ink);
  font-size: 21px;
  line-height: 1.05;
  font-weight: 850;
  letter-spacing: -0.55px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.collection-glance-stat span {
  color: var(--ink-soft);
  font-size: 8.5px;
  line-height: 1.2;
  font-weight: 800;
  letter-spacing: 0.55px;
  text-transform: uppercase;
}
.collection-glance-empty {
  margin-top: 12px;
  padding-top: 11px;
  border-top: 1px solid var(--hairline);
  color: inherit;
  text-decoration: none;
  display: block;
  color: var(--ink-soft);
  font-size: 11px;
  line-height: 1.35;
  font-weight: 700;
}
.collection-glance-empty:hover,
.collection-glance-empty:focus-visible { color: var(--ink); }
.grid {
  display: flex;
  flex-direction: column;
  gap: 22px;
  padding-bottom: 50px;
}
.home-feed-layout,
.home-feed-column { min-width: 0; }
.home-desktop-rail { display: none; }
/* No hover background here on purpose -- a full-width post filled with a
   solid color on hover reads as a box appearing around it, exactly what
   we're trying to avoid. Interactive sub-elements (photo, buttons) still
   have their own hover states. */

/* ---------- Grid view (Display toggle, see auth.js's setHomeView) ----------
   Compact 2-up cards instead of the one-post-per-row feed above -- #grid
   itself switches from a flex column to an actual CSS grid while this
   class is on. auto-fill/minmax means it's exactly 2 columns at typical
   phone widths (the "2 column view" this was asked for) and grows on
   wider screens, same responsive rule seller.css's own For Sale grid uses. */
#grid.grid-mode {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 20px 16px;
}
.grid-card { display: flex; flex-direction: column; }

@media (min-width: 721px) {
  /* Desktop: cap at a fixed 4 columns instead of auto-fill's "as many
     140px-minimum columns as fit" (which packs 6-8 skinny columns across a
     wide grid) -- 1fr still stretches each card to fill the row. */
  #grid.grid-mode {
    grid-template-columns: repeat(4, 1fr);
  }
}

@media (min-width: 721px) {
  /* The feed's 576px column is inside the main wrap's 22px side padding.
     Give the nav that same outer padding so its separator spans the full
     cover width rather than ending 22px short on each side. */
  body:has(#left-sidebar) .era-tabs {
    width: 620px;
    max-width: 100%;
    margin-left: max(0px, calc((100% - 1200px) / 2));
    margin-right: auto;
  }

  /* Grid cards use the full browse width, so carry the filter divider to
     the last card's right edge. Feed mode keeps the narrower alignment. */
  body:has(#left-sidebar) .era-tabs.grid-layout-active {
    width: 1200px;
    margin-left: auto;
    margin-right: auto;
  }

  /* Feed-mode posts are designed at mobile width -- letting them stretch
     across the full 1200px .wrap on a wide desktop screen looked broken.
     Pin feed mode to a fixed, mobile-widget-ish width (same convention as
     the site's other narrow single-column layouts) and leave the rest of
     the row open on the right for future content (suggestions, friend
     activity, etc.) instead of centering it. Capping #grid itself (rather
     than #browse-main, which stays the normal centered 1200px .wrap) means
     the feed column starts flush at .wrap's own left padding edge -- the
     exact same spot grid-mode's cards already start at -- instead of
     getting its own separate offset. Grid mode is untouched -- its cards
     already reflow nicely across the full width. */
  body:has(#left-sidebar) #grid:not(.grid-mode) {
    max-width: 576px;
  }
}

/* Desktop feeds use the otherwise-empty right side for quiet, contextual
   navigation. These rails deliberately disappear before they would squeeze
   the primary 576px post column; phone/tablet controls remain unchanged. */
@media (min-width: 1180px) {
  .home-feed-layout.is-foryou,
  .home-feed-layout.is-market-feed {
    display: grid;
    grid-template-columns: minmax(0, 576px) minmax(280px, 340px);
    align-items: start;
    gap: 48px;
  }
  .home-feed-layout.is-market-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    align-items: start;
    gap: 32px;
  }
  .home-feed-layout.is-market-grid #grid.grid-mode {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .home-desktop-rail {
    --home-rail-ink: #5f5d58;
    --home-rail-soft: #77746f;
    position: sticky;
    top: 84px;
    display: block;
    min-width: 0;
    max-height: calc(100vh - 104px);
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--hairline) transparent;
  }
  :root[data-theme="dark"] .home-desktop-rail {
    --home-rail-ink: #b1aca2;
    --home-rail-soft: #8f8b83;
  }
  body:has(#market-filter-rail:not([hidden])) .era-tabs-scroll { display: none; }
  body:has(#market-filter-rail:not([hidden])) .home-tabs-row {
    padding-bottom: 10px;
    border-bottom: 1px solid var(--hairline);
  }
  .home-rail-section { color: var(--home-rail-ink); }
  .home-rail-heading {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    padding-bottom: 13px;
    border-bottom: 1px solid var(--hairline);
  }
  .home-rail-heading h2 {
    margin: 0;
    color: var(--home-rail-ink);
    font-family: var(--body);
    font-size: 18px;
    line-height: 1.25;
    font-weight: 850;
    letter-spacing: -0.3px;
  }
  .home-rail-heading > a,
  .market-filter-reset {
    flex: 0 0 auto;
    color: var(--home-rail-soft);
    font-family: var(--body);
    font-size: 12px;
    line-height: 1;
    font-weight: 750;
    text-decoration: none;
  }
  .home-rail-heading > a:hover,
  .home-rail-heading > a:focus-visible,
  .market-filter-reset:hover,
  .market-filter-reset:focus-visible { color: var(--ink); }
  .home-rail-status {
    margin: 0;
    padding: 20px 0;
    color: var(--home-rail-soft);
    font-family: var(--body);
    font-size: 13px;
    line-height: 1.45;
  }
  .home-room-row {
    display: grid;
    grid-template-columns: 38px minmax(0, 1fr);
    gap: 11px;
    padding: 15px 0;
    border-bottom: 1px solid var(--hairline);
    color: inherit;
    text-decoration: none;
  }
  .home-room-row:hover .home-room-latest,
  .home-room-row:focus-visible .home-room-latest { color: var(--ink); }
  .home-room-row:focus-visible { outline: 2px solid var(--yellow); outline-offset: 3px; }
  .home-room-icon {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border: 1px solid var(--hairline);
    border-radius: 50%;
    background: var(--paper-dim);
    font-size: 19px;
  }
  .home-room-copy { display: block; min-width: 0; }
  .home-room-name-line {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    margin-bottom: 5px;
  }
  .home-room-name-line strong {
    overflow: hidden;
    color: var(--home-rail-ink);
    font-family: var(--body);
    font-size: 12.5px;
    line-height: 1.25;
    font-weight: 800;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .home-room-following,
  .home-room-unread {
    flex: 0 0 auto;
    padding: 2px 6px;
    border-radius: 999px;
    font-family: var(--body);
    font-size: 8.5px;
    line-height: 1.2;
    font-weight: 850;
    letter-spacing: 0.2px;
  }
  .home-room-following { border: 1px solid var(--hairline); color: var(--home-rail-soft); }
  .home-room-unread { background: var(--yellow); color: #121212; }
  .home-room-latest {
    display: -webkit-box;
    overflow: hidden;
    margin-bottom: 6px;
    color: var(--home-rail-ink);
    font-family: var(--body);
    font-size: 13.5px;
    line-height: 1.35;
    font-weight: 750;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    transition: color 0.12s ease;
  }
  .home-room-meta {
    display: block;
    color: var(--home-rail-soft);
    font-family: var(--body);
    font-size: 10.5px;
    line-height: 1.3;
    font-weight: 650;
  }

  .market-filter-heading { align-items: flex-start; }
  .market-filter-heading p {
    margin: 4px 0 0;
    color: var(--home-rail-soft);
    font-family: var(--body);
    font-size: 11px;
    line-height: 1.3;
    font-weight: 650;
  }
  .market-filter-reset {
    padding: 4px 0;
    border: 0;
    background: transparent;
    cursor: pointer;
  }
  .market-filter-reset:disabled { opacity: 0.38; cursor: default; }
  #market-filter-form { padding-top: 17px; }
  .market-filter-label,
  .market-filter-eras legend {
    display: block;
    margin: 0 0 7px;
    color: var(--home-rail-ink);
    font-family: var(--body);
    font-size: 10px;
    line-height: 1.2;
    font-weight: 850;
    letter-spacing: 0.65px;
    text-transform: uppercase;
  }
  .market-filter-search,
  .market-filter-select-wrap {
    position: relative;
    display: flex;
    align-items: center;
    margin-bottom: 18px;
    border-bottom: 1px solid var(--hairline);
  }
  .market-filter-search > svg {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    color: var(--home-rail-soft);
  }
  .market-filter-search input,
  .market-filter-select-wrap select {
    width: 100%;
    min-width: 0;
    border: 0;
    outline: 0;
    background: transparent;
    color: var(--home-rail-ink);
    font-family: var(--body);
    font-size: 13px;
    font-weight: 650;
  }
  .market-filter-search input { height: 38px; padding: 0 8px; }
  .market-filter-search input::placeholder { color: var(--home-rail-soft); opacity: 0.8; }
  .market-filter-select-wrap select {
    height: 38px;
    padding: 0 28px 0 0;
    appearance: none;
    cursor: pointer;
  }
  .market-filter-select-wrap > svg {
    position: absolute;
    right: 2px;
    width: 15px;
    height: 15px;
    color: var(--home-rail-soft);
    pointer-events: none;
  }
  .market-filter-eras { min-width: 0; margin: 0; padding: 0; border: 0; }
  #market-filter-era-options {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 7px;
  }
  .market-filter-era-option {
    overflow: hidden;
    min-width: 0;
    padding: 7px 9px;
    border: 1px solid var(--hairline);
    border-radius: 999px;
    background: transparent;
    color: var(--home-rail-soft);
    font-family: var(--body);
    font-size: 10.5px;
    line-height: 1.2;
    font-weight: 700;
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
  }
  .market-filter-era-option:hover,
  .market-filter-era-option:focus-visible { border-color: var(--home-rail-ink); color: var(--home-rail-ink); }
  .market-filter-era-option.is-selected {
    border-color: var(--yellow);
    background: var(--yellow);
    color: #121212;
    font-weight: 850;
  }
}

/* Card content (photo/title/price) duplicated from seller.css's own .card
   per this codebase's page-scoped CSS convention -- index.html doesn't
   load seller.css. */
.grid-card .card {
  display: block;
  text-decoration: none;
  color: inherit;
}
.grid-card .card-img-wrap {
  position: relative;
  aspect-ratio: 2 / 3;
  border-radius: 10px;
  overflow: hidden;
  background: var(--paper-dim);
  border: 1.5px solid var(--hairline);
  margin-bottom: 8px;
  transition: transform 0.12s ease;
}
.grid-card .card:hover .card-img-wrap { transform: translateY(-2px); }
.grid-card .card-img-wrap img { width: 100%; height: 100%; object-fit: cover; display: block; }
.grid-card .card-price-tag {
  position: absolute;
  right: 7px;
  bottom: 7px;
  background: var(--red);
  color: #fff;
  font-family: var(--body);
  font-weight: 800;
  font-size: 12px;
  padding: 3px 8px;
  border-radius: 6px;
}
.grid-card .card-price-tag-pending {
  background: var(--ink-soft);
  text-transform: uppercase;
  font-size: 10.5px;
  letter-spacing: 0.2px;
}
.grid-card .card h3 {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 14px;
  letter-spacing: -0.2px;
  line-height: 1.2;
  margin: 0 0 2px;
}
.grid-card .card .issue {
  font-family: var(--body);
  font-weight: 600;
  font-size: 12px;
  color: var(--ink-soft);
}
.grid-card .card .card-meta {
  font-family: var(--body);
  font-weight: 600;
  font-size: 12px;
  color: var(--ink-soft);
}
/* Sized down from .post-key-badge's normal feed-photo size -- these cards
   are much smaller. */
.grid-card .post-key-badge { top: 7px; right: 7px; font-size: 9.5px; padding: 3px 7px; }

/* Seller line above the photo -- the one thing a profile page's own For
   Sale grid doesn't need (every card there already belongs to one known
   seller) but this homepage grid does, since it mixes sellers. */
.grid-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
  margin-bottom: 6px;
}
/* .post-owner-menu-dropdown's own "right: 0" then lands flush with the
   card's right edge, same as the feed headline -- fine to spill slightly
   past the card into the grid gutter on these narrow columns. */
.grid-card-header .post-owner-menu-btn { width: 24px; height: 24px; }
.grid-card-header .post-owner-menu-icon svg { width: 15px; height: 15px; }

.grid-card-seller {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 0;
  text-decoration: none;
  color: var(--ink);
  min-width: 0;
}
.grid-card-avatar {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--paper-dim);
}
.grid-card-avatar-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-soft);
}
.grid-card-avatar-fallback svg { width: 12px; height: 12px; }
.grid-card-seller-name {
  font-family: var(--body);
  font-weight: 700;
  font-size: 12.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.grid-card-seller:hover .grid-card-seller-name { text-decoration: underline; }
.grid-card-seller-locked {
  display: flex;
  align-items: center;
  gap: 5px;
  font-family: var(--body);
  font-weight: 600;
  font-size: 12px;
  color: var(--ink-soft);
  margin-bottom: 0;
}
.grid-card-seller-locked svg { width: 12px; height: 12px; }

.empty-state {
  font-family: var(--body);
  font-weight: 600;
  color: var(--ink-soft);
  padding: 40px 0;
  text-align: center;
}

@keyframes lightbox-fade-in { from { opacity: 0; } to { opacity: 1; } }
.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.94);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px;
  z-index: 200;
  cursor: zoom-out;
  animation: lightbox-fade-in 0.18s ease;
}
.lightbox img {
  max-width: 100%;
  max-height: 100%;
  border: none;
  border-radius: 14px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  cursor: zoom-in;
}
.lightbox.lightbox-native-resolution {
  display: block;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}
.lightbox.lightbox-native-resolution img {
  display: block;
  max-width: none;
  max-height: none;
  margin: 0 auto;
  cursor: zoom-out;
}
.lightbox-close {
  position: absolute;
  top: 18px;
  right: 18px;
  z-index: 10; /* above .lightbox-crop-overlay (z-index:5) -- see that rule */
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  backdrop-filter: blur(6px);
  transition: background-color 0.15s ease;
}
.lightbox-close svg { width: 20px; height: 20px; }
.lightbox-close:hover { background: rgba(255, 255, 255, 0.22); }

.lightbox-nav {
  z-index: 9;
  width: min(22vw, 220px);
}
.lightbox-nav-prev { padding-left: 24px; }
.lightbox-nav-next { padding-right: 24px; }
.lightbox-nav svg { width: 46px; height: 46px; }

.lightbox-tool-rail {
  position: absolute;
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 8px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 22px;
  background: rgba(18, 18, 18, 0.5);
  backdrop-filter: blur(8px);
}

.lightbox-rotate-btn {
  position: relative;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  backdrop-filter: blur(6px);
  transition: background-color 0.15s ease;
}
.lightbox-tool-rail .lightbox-rotate-btn { transform: none; }
.lightbox-rotate-btn svg { width: 28px; height: 28px; }
.lightbox-rotate-btn:hover { background: rgba(255, 255, 255, 0.22); }

.lightbox-actions-row {
  position: absolute;
  bottom: 32px;
  left: 50%;
  z-index: 10; /* above .lightbox-crop-overlay (z-index:5) -- this class is
                  reused for #lightbox-crop-actions, which sits directly
                  over a tall photo's overlay and would otherwise have its
                  clicks swallowed by it (see that rule's own comment) */
  transform: translateX(-50%);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  width: min(94vw, 720px);
  gap: 10px;
}
.lightbox-pill-btn {
  font-family: var(--body);
  font-weight: 800;
  text-transform: uppercase;
  font-size: 13px;
  background: var(--ink);
  color: var(--paper);
  border: none;
  padding: 12px 22px;
  border-radius: 999px;
  cursor: pointer;
}
.lightbox-pill-btn:hover { filter: brightness(1.3); }
.lightbox-pill-btn:disabled { opacity: 0.62; cursor: default; filter: none; }
.lightbox-pill-btn-primary { background: var(--yellow); color: #121212; }
.lightbox-pill-btn-primary:hover { filter: brightness(1.08); }
.lightbox-pill-btn.tool-icon-btn { display: inline-flex; align-items: center; gap: 7px; }
.lightbox-pill-btn.tool-icon-btn svg { width: 16px; height: 16px; flex: 0 0 auto; }
.lightbox-pill-btn.tool-icon-only {
  width: 52px;
  height: 52px;
  justify-content: center;
  padding: 10px;
  border-radius: 16px;
}
.lightbox-pill-btn.tool-icon-only svg { width: 29px; height: 29px; }

.lightbox-photo-status {
  position: absolute;
  bottom: 84px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--body);
  font-weight: 700;
  font-size: 13px;
  color: var(--paper);
}

.lightbox-straighten-panel {
  position: absolute;
  left: 50%;
  bottom: 24px;
  z-index: 10;
  width: min(92vw, 480px);
  padding: 16px 18px;
  transform: translateX(-50%);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 18px;
  background: rgba(20, 20, 20, 0.9);
  color: #fff;
  cursor: default;
  backdrop-filter: blur(14px);
}
.lightbox-straighten-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
  font-family: var(--body);
  font-size: 14px;
  font-weight: 800;
}
#lightbox-straighten-value { color: var(--yellow); font-variant-numeric: tabular-nums; }
#lightbox-straighten-range {
  width: 100%;
  accent-color: var(--yellow);
  cursor: ew-resize;
}
.lightbox-straighten-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 12px;
}

.lightbox-perspective-overlay {
  position: fixed;
  z-index: 6;
  overflow: visible;
  cursor: default;
  touch-action: none;
}
#lightbox-perspective-canvas {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 14px;
}
.lightbox-perspective-guide {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border: 1px solid rgba(255, 216, 61, 0.72);
  border-radius: 14px;
  background:
    linear-gradient(to right, transparent 49.8%, rgba(255,255,255,.22) 50%, transparent 50.2%),
    linear-gradient(to bottom, transparent 49.8%, rgba(255,255,255,.18) 50%, transparent 50.2%);
}
.lightbox-perspective-handle {
  position: absolute;
  z-index: 2;
  top: 0;
  width: 34px;
  height: 34px;
  padding: 0;
  transform: translate(-50%, -50%);
  border: 4px solid #171717;
  border-radius: 50%;
  background: var(--yellow);
  box-shadow: 0 2px 10px rgba(0,0,0,.5);
  cursor: grab;
  touch-action: none;
}
.lightbox-perspective-handle:active { cursor: grabbing; }
.lightbox-perspective-handle-left { left: 0; }
.lightbox-perspective-handle-right { left: 100%; }
.lightbox-perspective-hint {
  position: absolute;
  top: 12px;
  left: 50%;
  width: max-content;
  max-width: calc(100vw - 40px);
  margin: 0;
  padding: 7px 11px;
  transform: translateX(-50%);
  border-radius: 999px;
  background: rgba(20,20,20,.82);
  color: #fff;
  font: 800 12px/1.2 var(--body);
  text-align: center;
  pointer-events: none;
}

@media (max-width: 560px) {
  .lightbox { padding: 72px 14px 132px; }
  .lightbox-tool-rail { gap: 8px; padding: 6px; border-radius: 18px; }
  .lightbox-tool-rail .lightbox-rotate-btn { width: 48px; height: 48px; }
  .lightbox-pill-btn { padding: 11px 15px; font-size: 12px; }
  .lightbox-pill-btn.tool-icon-only { width: 48px; height: 48px; padding: 8px; }
  .lightbox-pill-btn.tool-icon-only svg { width: 27px; height: 27px; }
  .lightbox-actions-row { bottom: calc(18px + env(safe-area-inset-bottom)); }
  .lightbox-straighten-panel { bottom: calc(14px + env(safe-area-inset-bottom)); }
  .lightbox-perspective-handle { width: 38px; height: 38px; }
}

/* ---------- Lightbox photo crop ---------- */
/* Sized/positioned in JS (positionCropOverlay) to exactly match the
   rendered <img>'s box -- overflow:hidden lets the crop box's own giant
   box-shadow (below) double as the darkened "outside the crop" mask
   without a second, separately-cut overlay element. */
.lightbox-crop-overlay {
  position: fixed;
  overflow: hidden;
  z-index: 5;
  cursor: default;
}
.lightbox-crop-box {
  position: absolute;
  border: 2px solid var(--yellow);
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
  touch-action: none;
  cursor: move;
}
.lightbox-crop-handle {
  position: absolute;
  width: 20px;
  height: 20px;
  background: var(--yellow);
  border: 2px solid #121212;
  border-radius: 50%;
  touch-action: none;
}
.lightbox-crop-handle[data-handle="nw"] { top: -10px; left: -10px; cursor: nwse-resize; }
.lightbox-crop-handle[data-handle="ne"] { top: -10px; right: -10px; cursor: nesw-resize; }
.lightbox-crop-handle[data-handle="sw"] { bottom: -10px; left: -10px; cursor: nesw-resize; }
.lightbox-crop-handle[data-handle="se"] { bottom: -10px; right: -10px; cursor: nwse-resize; }

.loading-more {
  font-family: var(--body);
  font-weight: 700;
  font-size: 13px;
  color: var(--ink-soft);
  text-align: center;
  padding: 20px 0 50px;
}
#scroll-sentinel {
  height: 1px;
}

.site-footer {
  padding: 26px 22px 50px;
  color: var(--ink-soft);
  font-family: var(--body);
  font-weight: 600;
  font-size: 13px;
  border-top: 3px solid var(--ink);
}

@media (max-width: 850px) {
  .hero-art { min-height: 340px; }
}

@media (max-width: 560px) {
  .hero-art { min-height: 280px; }
  .hero-issue { width: 170px; height: 240px; }
  .cta-btn { width: 100%; text-align: center; }
}

/* ===========================================================================
   Social post -- shared by the all-listings grid (one card per listing,
   app.js) and the single listing page (listing.js). Moved here from
   listing.css so both pages get the exact same avatar/badge/stats/photo/
   action-bar treatment instead of two diverging copies. See social.js for
   the JS that renders/wires these.
   =========================================================================== */
/* No box, no border, no background -- just two columns: an avatar column
   that holds nothing but the avatar (see .post-avatar-col), and a body
   column with everything else (name/badges/stats/photo/actions) stacked
   in it. Posts in the grid are separated by .grid's own gap, not a per-post
   boundary. */
.listing-post {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  padding: 16px 0;
}

.post-avatar-col {
  display: block;
  position: relative;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  text-decoration: none;
}
.post-avatar-img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  display: block;
  background: var(--paper);
}
.post-avatar-fallback {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--paper);
  border: 1.5px solid var(--hairline);
  color: var(--ink-soft);
}
.post-avatar-fallback svg { width: 22px; height: 22px; }
.post-avatar-lock {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: var(--ink-soft);
  border: 2px solid var(--paper-dim);
  display: flex;
  align-items: center;
  justify-content: center;
}
.post-avatar-lock svg { width: 9px; height: 9px; }

/* ---------- Online-status dot ---------- */
/* Shared across the post header (here), seller profile (seller.css), and
   the messenger (messages.css) -- one definition so all three stay
   visually identical rather than three near-copies drifting apart. Derived
   server-side from User.last_seen_at (see main.py's is_online); the ring
   uses --paper-dim rather than a border so it reads as a notch cut into
   whatever card surface it sits on, same trick as .post-avatar-lock above. */
.online-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #22C55E;
  box-shadow: 0 0 0 2px var(--paper-dim), 0 0 5px 1px rgba(34, 197, 94, 0.65);
  flex-shrink: 0;
}

/* Incoming messages stay in the browsing context: the first poll records
   existing unread threads quietly, then only newly-arrived messages open this
   small reply card. */
.incoming-message-card { position: fixed; right: 16px; bottom: calc(16px + env(safe-area-inset-bottom)); z-index: 320; display: flex; flex-direction: column; width: min(390px, calc(100vw - 32px)); height: min(460px, calc(100dvh - 32px)); overflow: hidden; border: 1px solid var(--hairline); border-radius: 18px; background: var(--paper-dim); box-shadow: 0 18px 48px -16px rgba(0, 0, 0, .55); opacity: 0; transform: translateY(14px) scale(.98); transition: opacity .16s ease, transform .16s ease; }
.incoming-message-card.is-open { opacity: 1; transform: translateY(0) scale(1); }
.incoming-message-header { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--hairline); background: var(--paper); }
.incoming-message-avatar { display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; overflow: hidden; border-radius: 50%; background: var(--yellow); color: #121212; font: 800 14px var(--body); flex: 0 0 auto; }
.incoming-message-avatar img { width: 100%; height: 100%; object-fit: cover; }
.incoming-message-person { display: flex; flex: 1; flex-direction: column; min-width: 0; }
.incoming-message-person strong { overflow: hidden; color: var(--ink); font: 700 14px var(--body); text-overflow: ellipsis; white-space: nowrap; }
.incoming-message-person small { color: var(--ink-soft); font: 12px var(--body); }
.incoming-message-person .online-dot { width: 8px; height: 8px; margin-left: 4px; box-shadow: 0 0 0 2px var(--paper), 0 0 5px 1px rgba(34, 197, 94, .65); vertical-align: 1px; }
.incoming-message-close, .incoming-message-icon { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border: none; border-radius: 50%; background: transparent; color: var(--ink-soft); cursor: pointer; }
.incoming-message-close { width: 30px; height: 30px; }
.incoming-message-close svg, .incoming-message-icon svg { width: 21px; height: 21px; }
.incoming-message-history { display: flex; flex: 1; flex-direction: column; gap: 8px; min-height: 0; overflow-y: auto; padding: 14px; scrollbar-color: var(--hairline) transparent; }
.incoming-message-row { display: flex; flex-direction: column; align-items: flex-start; width: 100%; max-width: none; }
.incoming-message-row.is-mine { align-self: flex-end; align-items: flex-end; }
.incoming-message-bubble-wrap { position: relative; max-width: 86%; }
.incoming-message-bubble { overflow: hidden; border-radius: 16px 16px 16px 5px; padding: 9px 11px; background: var(--paper); color: var(--ink); font: 14px/1.4 var(--body); white-space: pre-wrap; }
.incoming-message-row.is-mine .incoming-message-bubble { border-radius: 16px 16px 5px 16px; background: var(--blue); color: #fff; }
.incoming-message-react-toggle { position: absolute; top: 50%; right: -36px; z-index: 3; display: flex; align-items: center; justify-content: center; width: 30px; height: 30px; border: 1px solid var(--hairline); border-radius: 50%; background: var(--paper); color: var(--ink-soft); cursor: pointer; opacity: 0; pointer-events: none; transform: translateY(-50%); }
.incoming-message-row.is-mine .incoming-message-react-toggle { right: auto; left: -36px; }
.incoming-message-react-toggle::before { content: ""; position: absolute; inset: -8px; }
.incoming-message-row:hover .incoming-message-react-toggle, .incoming-message-row:focus-within .incoming-message-react-toggle { opacity: 1; pointer-events: auto; }
.incoming-message-action-menu { position: absolute; top: 50%; right: -70px; z-index: 3; display: flex; align-items: center; justify-content: center; width: 30px; height: 30px; padding: 0; border: 1px solid var(--hairline); border-radius: 50%; background: var(--paper); color: var(--ink-soft); cursor: pointer; font: 22px/1 var(--body); opacity: 0; pointer-events: none; transform: translateY(-50%); }
.incoming-message-row.is-mine .incoming-message-action-menu { right: auto; left: -70px; }
.incoming-message-row:hover .incoming-message-action-menu, .incoming-message-row:focus-within .incoming-message-action-menu { opacity: 1; pointer-events: auto; }
.incoming-message-actions { position: absolute; right: -70px; bottom: calc(100% + 7px); z-index: 5; min-width: 112px; padding: 4px; border: 1px solid var(--hairline); border-radius: 10px; background: var(--paper); box-shadow: 0 8px 20px -12px rgba(0, 0, 0, .5); }
.incoming-message-row.is-mine .incoming-message-actions { right: auto; left: -70px; }
.incoming-message-actions button { display: block; width: 100%; padding: 7px 9px; border: 0; border-radius: 6px; background: transparent; color: var(--ink); text-align: left; font: 13px var(--body); cursor: pointer; }
.incoming-message-actions button:hover { background: var(--paper-dim); }
.incoming-message-actions .is-danger { color: var(--red); }
.incoming-message-reactions { position: absolute; top: -10px; right: 4px; z-index: 2; display: flex; flex-wrap: wrap; gap: 3px; }
.incoming-message-history-link { position: absolute; right: 4px; bottom: -16px; padding: 0; border: 0; background: transparent; color: var(--ink-soft); font: 10px var(--body); text-decoration: underline; cursor: pointer; }
.incoming-message-reaction, .incoming-message-reaction-picker button { border: 1px solid var(--hairline); border-radius: 999px; background: var(--paper); color: var(--ink); cursor: pointer; }
.incoming-message-reaction { display: inline-flex; align-items: center; gap: 3px; padding: 2px 7px; font: 11px var(--body); }
.incoming-message-reaction-emoji { font-size: 18px; line-height: 1; }
.incoming-message-reaction.is-reacted { border-color: var(--blue); background: color-mix(in srgb, var(--blue) 14%, var(--paper)); }
.incoming-message-reaction-picker { position: absolute; left: 0; bottom: calc(100% + 7px); z-index: 4; display: flex; gap: 2px; padding: 4px; border: 1px solid var(--hairline); border-radius: 999px; background: var(--paper); box-shadow: 0 8px 20px -12px rgba(0, 0, 0, .5); }
.incoming-message-row.is-mine .incoming-message-reaction-picker { right: 0; left: auto; }
.incoming-message-reaction-picker button { padding: 4px 6px; font-size: 19px; line-height: 1; }
.incoming-message-photos { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 3px; margin-bottom: 8px; }
.incoming-message-photos img { display: block; width: 100%; max-height: 150px; border-radius: 9px; object-fit: cover; }
.message-expandable { cursor: zoom-in; }
.message-image-lightbox { position: fixed; inset: 0; z-index: 500; display: grid; place-items: center; overflow: hidden; box-sizing: border-box; padding: 26px; background: rgba(0, 0, 0, .92); }
.message-image-lightbox[hidden] { display: none; }
.message-image-lightbox img { display: block; max-width: calc(100vw - 52px); max-height: calc(100dvh - 52px); object-fit: contain; }
.message-image-lightbox button { position: fixed; top: 18px; right: 18px; width: 42px; height: 42px; border: 0; border-radius: 50%; background: rgba(0, 0, 0, .65); color: #fff; font: 32px/1 var(--body); cursor: pointer; }
.incoming-message-empty { margin: auto; color: var(--ink-soft); font: 13px var(--body); text-align: center; }
.incoming-message-form { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px 10px; border-top: 1px solid var(--hairline); background: var(--paper); }
.incoming-message-picker { align-self: flex-start; padding: 4px; border: 1px solid var(--hairline); border-radius: 12px; background: var(--paper-dim); box-shadow: 0 8px 20px -12px rgba(0, 0, 0, .5); }
.incoming-message-picker button { border: none; border-radius: 7px; padding: 3px 5px; background: transparent; cursor: pointer; font-size: 16px; }
.incoming-message-shell { position: relative; display: flex; align-items: center; width: 100%; box-sizing: border-box; overflow: hidden; border: 1.5px solid var(--hairline); border-radius: 12px; background: var(--paper); }
.incoming-message-shell:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 16%, transparent); }
.incoming-message-input { height: 46px; min-height: 46px; max-height: 115px; min-width: 0; padding: 11px 54px 11px 5px; flex: 1; resize: none; overflow-x: hidden; overflow-y: hidden; border: none; background: var(--paper); color: var(--ink); font: 400 16px/1.45 var(--body); white-space: pre-wrap; overflow-wrap: anywhere; }
.incoming-message-input::-webkit-scrollbar { width: 5px; }
.incoming-message-input::-webkit-scrollbar-track { background: transparent; }
.incoming-message-input::-webkit-scrollbar-thumb { border-radius: 999px; background: var(--hairline); }
.incoming-message-input:focus { outline: none; }
.incoming-message-icon { margin-left: 2px; flex: 0 0 auto; }
.incoming-message-send { position: absolute; right: 3px; top: 50%; display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; transform: translateY(-50%); border: none; border-radius: 50%; background: var(--yellow); color: #121212; cursor: pointer; }
.incoming-message-send svg { width: 20px; height: 20px; }
@media (max-width: 480px) { .incoming-message-card { right: 8px; bottom: calc(8px + env(safe-area-inset-bottom)); width: calc(100vw - 16px); height: min(460px, calc(100dvh - 16px)); } }
@media (prefers-reduced-motion: no-preference) {
  .online-dot { animation: online-dot-pulse 2.4s ease-in-out infinite; }
}
@keyframes online-dot-pulse {
  0%, 100% { box-shadow: 0 0 0 2px var(--paper-dim), 0 0 5px 1px rgba(34, 197, 94, 0.65); }
  50% { box-shadow: 0 0 0 2px var(--paper-dim), 0 0 8px 3px rgba(34, 197, 94, 0.85); }
}

.post-body-col { flex: 1 1 auto; min-width: 0; }

.post-headline { margin-bottom: 14px; }
.post-headrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.post-nameline {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
/* Highest-contrast text in the header, by design -- the seller's name
   (or, locked, their tier band) is the thing meant to draw the eye. */
.post-name {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--body);
  font-weight: 800;
  font-size: 15px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.post-name svg { width: 12px; height: 12px; flex-shrink: 0; }
.post-name-link {
  color: inherit;
  text-decoration: none;
}
.post-name-link:hover { text-decoration: underline; }
.post-badgerow {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

/* Lower-contrast than .post-name/.post-age's ink-soft would suggest --
   deliberately muted so the name (or tier band) stays the visual anchor. */
.post-username {
  display: flex;
  align-items: center;
  gap: 4px;
  font-family: var(--body);
  font-weight: 600;
  font-size: 13px;
  color: var(--ink-soft);
  margin-top: 1px;
  text-decoration: none;
  width: fit-content;
}
.post-username:hover { text-decoration: underline; }
.post-username svg { width: 11px; height: 11px; }

.post-age {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--body);
  font-weight: 600;
  font-size: 13px;
  color: var(--ink-soft);
  white-space: nowrap;
}

/* Under-4h freshness callout, on the timestamp itself -- no separate badge,
   no layout change. */
.post-age-fresh { color: var(--red); font-weight: 800; }
.post-age-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--red);
  box-shadow: 0 0 5px 1px rgba(255, 64, 55, 0.65);
  flex-shrink: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .post-age-dot { animation: post-age-dot-pulse 2.4s ease-in-out infinite; }
}
@keyframes post-age-dot-pulse {
  0%, 100% { box-shadow: 0 0 5px 1px rgba(255, 64, 55, 0.65); }
  50% { box-shadow: 0 0 8px 3px rgba(255, 64, 55, 0.85); }
}

/* ---------- Badges + hover tooltip ---------- */
.badge-icon { position: relative; display: inline-flex; align-items: center; justify-content: center; }
.tier-badge {
  font-family: var(--body);
  font-weight: 800;
  text-transform: uppercase;
  font-size: 9.5px;
  letter-spacing: 0.2px;
  border-radius: 999px;
  padding: 3px 8px;
  line-height: 1.3;
}
.verified-badge svg { width: 15px; height: 15px; display: block; }

.badge-icon[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--body);
  font-weight: 600;
  font-size: 11.5px;
  text-transform: none;
  white-space: nowrap;
  padding: 7px 11px;
  border-radius: 8px;
  box-shadow: 0 6px 16px -4px rgba(0, 0, 0, 0.35);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease, transform 0.12s ease;
  z-index: 30;
}
.badge-icon[data-tooltip]::before {
  content: "";
  position: absolute;
  bottom: calc(100% + 3px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  border: 5px solid transparent;
  border-top-color: var(--ink);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease, transform 0.12s ease;
  z-index: 30;
}
.badge-icon[data-tooltip]:hover::after,
.badge-icon[data-tooltip]:hover::before,
.badge-icon[data-tooltip]:focus-visible::after,
.badge-icon[data-tooltip]:focus-visible::before {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ---------- Post stats (era/grade/price/desc block) ---------- */
.post-stats { margin: 12px 0 2px; }

.listing-photo-card {
  position: relative;
  max-width: 420px;
  border-radius: 16px;
  overflow: hidden;
  background: var(--paper);
  display: block;
  text-decoration: none;
}

/* Shaped like a real shipping/price tag laid on its side: a flat rectangle
   with the left edge cut to a point -- the point itself is chamfered
   (0% 46%/54% instead of a single 0% 50% vertex) so it's blunted rather
   than a knife edge, without going all the way to square. A prior version
   built the border from stacked drop-shadow()s tracing the clip-path
   outline; that read as too faint/inconsistent, so this is a real solid
   frame instead: .post-price-tag is the border-colored outer shape, sized
   by its own padding, with .post-price-tag-fill inset inside it as a
   second, proportionally smaller clip-path in the actual tag color. */
.post-price-tag {
  display: inline-block;
  position: absolute;
  right: 12px;
  bottom: 12px;
  padding: 3px;
  background: var(--tag-border, #7A1610);
  clip-path: polygon(0% 46%, 0% 54%, 18px 100%, 100% 100%, 100% 0%, 18px 0%);
  filter: drop-shadow(3px 3px 0 var(--ink));
}
.post-price-tag-fill {
  display: block;
  padding: 8px 12px 8px 23px;
  font-family: var(--body);
  font-weight: 800;
  font-size: 19px;
  color: #fff;
  white-space: nowrap;
  background: var(--tag);
  clip-path: polygon(0% 46%, 0% 54%, 15px 100%, 100% 100%, 100% 0%, 15px 0%);
}
.post-price-tag-pending { --tag-border: #1a1a1a; }
.post-price-tag-pending .post-price-tag-fill {
  background: var(--ink-soft);
  text-transform: uppercase;
  font-size: 16px;
  letter-spacing: 0.2px;
}

/* Opposite corner from the price tag -- AI key-issue classification (see
   Listing.key_tier). Sharp-cornered ink-bordered tag with a hard shadow,
   matching the site's blocky comic-panel badges (.seller-badge, .sold-tag)
   rather than a soft pill. Colors are hardcoded, not the themed --ink
   token -- this badge is meant to look identical in light and dark mode
   (var(--ink) flips to a light cream in dark mode, which would turn the
   text/border invisible against the yellow background and flip the
   "super" variant's dark chip to a light one). */
.post-key-badge {
  position: absolute;
  top: 12px;
  right: 12px;
  display: inline-block;
  font-family: var(--body);
  font-weight: 800;
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: 0.3px;
  color: #121212;
  background: var(--yellow);
  border: 2px solid #121212;
  padding: 4px 9px;
  box-shadow: 2px 2px 0 #121212;
}
/* Inverted (ink background, gold text) -- reads as a step up from the
   plain key badge instead of just a different color of the same thing. */
.post-key-badge-super {
  background: #121212;
  color: var(--yellow);
  border-color: var(--yellow);
}
.listing-photo-card img {
  width: 100%;
  display: block;
  object-fit: contain;
  max-height: 640px;
  background: var(--paper);
  cursor: zoom-in;
  transition: transform 0.25s ease;
}
.listing-photo-card:hover img { transform: scale(1.015); }

/* ---------- Multi-photo carousel (swipe right on the feed) ----------
   Native horizontal scroll-snap rather than hand-rolled touch handling --
   a real swipe/scroll gesture never fires the surrounding <a>'s click, so
   this works unmodified whether renderPostPhotoHtml wrapped the card in a
   link (feed/grid, opens the detail page) or a lightbox div (detail page
   itself). The dot sync lives in social.js's wirePost. */
.post-photo-multi .post-photo-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  touch-action: pan-x pan-y pinch-zoom;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.post-photo-multi .post-photo-track::-webkit-scrollbar { display: none; }
.post-photo-slide {
  flex: 0 0 100%;
  min-width: 100%;
  scroll-snap-align: start;
}
.post-photo-dots {
  position: absolute;
  left: 50%;
  bottom: 12px;
  transform: translateX(-50%);
  display: flex;
  gap: 5px;
  z-index: 1;
}
.post-photo-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(0, 0, 0, 0.35);
}
.post-photo-dot.is-active { background: #fff; }
.post-photo-nav,
.lightbox-nav {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  width: clamp(58px, 16%, 110px);
  padding: 0;
  border: 0;
  background: transparent;
  color: #fff;
  cursor: pointer;
}
.post-photo-nav[hidden],
.lightbox-nav[hidden] { display: none; }
.post-photo-nav-prev,
.lightbox-nav-prev { left: 0; justify-content: flex-start; padding-left: 14px; }
.post-photo-nav-next,
.lightbox-nav-next { right: 0; justify-content: flex-end; padding-right: 14px; }
.post-photo-nav svg,
.lightbox-nav svg {
  width: 38px;
  height: 38px;
  padding: 7px;
  box-sizing: border-box;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.56);
  fill: none;
  stroke: currentColor;
  stroke-width: 2.3;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.76;
  transition: opacity 0.15s ease, transform 0.15s ease, background-color 0.15s ease;
}
.post-photo-nav:hover svg,
.post-photo-nav:focus-visible svg,
.lightbox-nav:hover svg,
.lightbox-nav:focus-visible svg {
  opacity: 1;
  transform: scale(1.08);
  background: rgba(0, 0, 0, 0.78);
}

/* ---------- Homepage feed only: edge-to-edge photo (Instagram-style) ----------
   Bleed offset itself is set inline via app.js's bleedGridPhotos (has to be
   measured, not guessed in CSS -- see that function's comment). This just
   covers what CSS can own: no cap/rounding on the now-full-bleed box, and no
   fixed height, so the whole cover shows uncropped at its natural aspect
   ratio instead of the capped/letterboxed max-height:640px above.
   listing.html's single-post view is untouched (its .listing-photo-card
   isn't inside #grid). */
#grid .listing-photo-card { max-width: none; border-radius: 0; }
#grid .listing-photo-card img {
  height: auto;
  max-height: none;
}
/* Feed photos open a listing, not an image zoom tool. Keep the detail page's
   zoom cursor/hover treatment, while feed cards use an ordinary link cursor
   and never start the browser's draggable-image rubber-band interaction. */
#grid .listing-photo-card img,
#grid .foryou-thumb-sale img {
  cursor: pointer;
  -webkit-user-drag: none;
  user-select: none;
}
#grid .listing-photo-card:hover img { transform: none; }
/* Feed posts only (not the grid-mode cards above) -- the feed variant is a
   contain-fit PNG on a transparent square canvas (see backend/storage.py's
   LISTING_VARIANTS), so this shows through the letterbox margin instead of
   the plain --paper fill. listing.html's single-post view uses this same
   .listing-post markup but sits outside #grid, so it's untouched. */
/* feedbg.png sits behind the img (not as the img's own background) so its
   opacity can be turned down independently of the cutout photo drawn on
   top -- an img's own CSS background paints at full strength no matter what
   opacity you give it without fading the photo pixels along with it. */
#grid .listing-post .listing-photo-card::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("feedbg.png") center / cover no-repeat;
  opacity: 0.35;
}
#grid .listing-post .listing-photo-card img {
  position: relative;
  background: transparent;
}
/* Cancels .wrap's 22px side padding for the whole post row (avatar, name,
   title/grade, like/comment/view bar) so that metadata lines up with the
   photo's own left edge instead of sitting indented relative to it -- the
   photo already ignores .wrap's padding (see bleedGridPhotos), this just
   brings the surrounding text out to the same edge. */
#grid .listing-post {
  /* 22px would cancel .wrap's padding exactly, flush to the true edge like
     the photo -- pulled in 10px short of that on purpose so the avatar/name
     and title/stats text keep a little breathing room instead of touching
     the screen edge the way the photo itself is meant to. */
  width: calc(100% + 24px);
  margin-left: -12px;
}
/* The name row still sits to the right of the avatar on purpose (same as
   Instagram's own username line). The title/grade/description gets a small
   inset relative to the edge-to-edge photo so dense comic details don't sit
   directly against the screen edge; the action bar stays aligned to the
   feed's visual edge. */
#grid .post-stats {
  margin-left: -48px;
}
#grid .post-actions {
  margin-left: -56px;
}

.post-title-row { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.post-stats h1 {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 19px;
  letter-spacing: -0.4px;
  margin: 0 0 4px;
  line-height: 1.15;
  text-wrap: balance;
}
.post-stats a { text-decoration: none; color: inherit; }
/* Same size as the title, by design -- just muted, not shrunk. */
.listing-issue { color: var(--ink-soft); }

/* Sits on its own line below the grade/cert/era line, truncated to one line
   so a long description can't blow out the card. */
.post-description {
  display: block;
  margin-top: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--body);
  font-weight: 500;
  font-size: 13px;
  color: var(--ink-soft);
}

/* Item detail page (listing.js) -- no card width to protect, so let the full
   description wrap onto as many lines as it needs instead of truncating. */
.post-description-full {
  overflow: visible;
  text-overflow: initial;
  white-space: normal;
}

/* Now the combined "9.2 Universal Bronze Age" small-print line (grade,
   cert type, era) instead of separate pills above the title. */
.listing-meta {
  font-family: var(--body);
  font-weight: 600;
  font-size: 13px;
  color: var(--ink-soft);
}

/* Detail-page-only rows (page quality, cert #, seller remarks) -- omitted
   from feed cards where there's no room. */
.post-detail-extras {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 10px;
}
.post-detail-row {
  font-family: var(--body);
  font-weight: 500;
  font-size: 13px;
  color: var(--ink-soft);
}
.post-detail-label {
  font-weight: 800;
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: 0.2px;
  color: var(--ink);
  margin-right: 6px;
}
.post-stats a.cert-link { color: var(--blue); text-decoration: underline; }
.post-stats a.cert-link:hover { color: var(--ink); }

/* ---------- Post action bar: like / comment / views / wishlist ---------- */
.post-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  padding-top: 10px;
}
.post-action-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  padding: 8px 10px;
  border-radius: 999px;
  font-family: var(--body);
  font-weight: 700;
  font-size: 13px;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.post-action-icon { display: flex; }
.post-action-icon svg { width: 19px; height: 19px; display: block; }
.post-action-btn:hover { background: var(--paper); color: var(--ink); }
.post-action-btn.is-active { color: var(--red); }
.post-action-btn.wishlist-active { color: var(--blue); }
.post-action-static { cursor: default; }
.post-action-static:hover { background: none; color: var(--ink-soft); }
.post-action-end { margin-left: auto; }

/* ---------- Comment drawer ----------
   A bottom sheet, not an inline expand -- opened from any post's comment
   icon (grid card or the single listing page alike), one shared instance
   appended to <body> and re-targeted per listing (see social.js). */
.comment-drawer-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(18, 18, 18, 0.55);
  /* Above .bottom-tab-bar's z-index:200 -- social.js also force-hides that
     bar for as long as the drawer is open, this is just a backstop so it
     can never render on top even for a stray frame. */
  z-index: 210;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
}
.comment-drawer-backdrop.is-open { opacity: 1; pointer-events: auto; }

.comment-drawer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 211;
  width: 100%;
  max-width: 600px;
  margin: 0 auto;
  box-sizing: border-box;
  /* Taller + a dvh-based cap -- vh is measured against the LARGEST
     possible viewport and doesn't shrink when a mobile keyboard opens, so
     a vh-only height can end up taller than what's actually visible above
     the keyboard, reading as the sheet "jumping". dvh tracks the real
     visible viewport; the vh line is just a fallback for older browsers
     that don't support dvh yet. */
  max-height: 88vh;
  max-height: 88dvh;
  background: var(--paper-dim);
  border: 1px solid var(--hairline);
  border-bottom: none;
  border-radius: 20px 20px 0 0;
  box-shadow: 0 -12px 34px -10px rgba(18, 18, 18, 0.4);
  display: flex;
  flex-direction: column;
  transform: translateY(100%);
  transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1);
}
.comment-drawer.is-open { transform: translateY(0); }

.comment-drawer-grabber {
  width: 36px;
  height: 4px;
  border-radius: 999px;
  background: var(--hairline);
  margin: 10px auto 2px;
  flex-shrink: 0;
}

.comment-drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 18px 12px;
  border-bottom: 1px solid var(--hairline);
  flex-shrink: 0;
}
.comment-drawer-title {
  font-family: var(--body);
  font-weight: 800;
  font-size: 15px;
  color: var(--ink);
}
.comment-drawer-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: none;
  background: var(--paper);
  color: var(--ink-soft);
  cursor: pointer;
}
.comment-drawer-close svg { width: 15px; height: 15px; }
.comment-drawer-close:hover { color: var(--ink); }

.comment-drawer-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 14px 18px;
}

.comment-drawer-composer {
  flex-shrink: 0;
  border-top: 1px solid var(--hairline);
  padding: 10px 14px calc(12px + env(safe-area-inset-bottom));
}

.emoji-pick-row {
  display: flex;
  gap: 6px;
  margin-bottom: 8px;
}
.emoji-pick-btn {
  font-size: 18px;
  line-height: 1;
  padding: 5px 8px;
  border-radius: 999px;
  border: 1.5px solid var(--hairline);
  background: var(--paper);
  cursor: pointer;
  transition: transform 0.1s ease, border-color 0.1s ease;
}
.emoji-pick-btn:hover { transform: scale(1.12); border-color: var(--ink-soft); }
.emoji-pick-btn:active { transform: scale(0.95); }

/* ---------- Comment items (used inside the drawer) ---------- */
.comment-list { display: flex; flex-direction: column; gap: 14px; }
.comment-empty {
  font-family: var(--body);
  font-size: 13px;
  color: var(--ink-soft);
  margin: 0;
  text-align: center;
  padding: 20px 0;
}
.comment-row { display: flex; gap: 10px; align-items: flex-start; }
.comment-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--paper);
  flex-shrink: 0;
}
.comment-avatar-fallback {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--paper);
  border: 1.5px solid var(--hairline);
  color: var(--ink-soft);
}
.comment-avatar-fallback svg { width: 16px; height: 16px; }
.comment-body {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  font-family: var(--body);
  font-size: 13.5px;
  flex: 1 1 auto;
  min-width: 0;
}
.comment-author { font-weight: 800; color: var(--ink); }
.comment-text { color: var(--ink); word-break: break-word; width: 100%; }
.comment-age { color: var(--ink-soft); font-size: 12px; }
.comment-like-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin: 3px 0 0;
  padding: 2px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  font: inherit;
  line-height: 1;
}
.comment-like-btn:hover { color: var(--red); background: var(--paper-dim); }
.comment-like-btn:disabled { cursor: default; opacity: 0.65; }
.comment-like-btn [data-el="comment-like-icon"] { display: inline-flex; }
.comment-like-btn svg { width: 16px; height: 16px; display: block; }
.comment-like-btn [data-el="comment-like-count"] { font-size: 12px; }
.comment-owner-actions { display: inline-flex; align-items: center; gap: 8px; margin: 3px 0 0 4px; }
.comment-owner-btn { border: none; padding: 2px; background: transparent; color: var(--ink-soft); cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; }
.comment-owner-btn:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
.comment-owner-delete:hover { color: var(--red); }
.comment-text strong { font-weight: 800; }
.comment-text em { font-style: italic; }
.comment-text a { color: var(--blue); text-decoration: underline; text-underline-offset: 2px; }
.comment-list-item { display: inline-flex; gap: 7px; }
.comment-list-bullet { color: var(--ink-soft); }

.comment-form { display: flex; gap: 8px; min-width: 0; }
.comment-form input {
  flex: 1;
  min-width: 0;
  box-sizing: border-box;
  border: 1.5px solid var(--hairline);
  border-radius: 999px;
  padding: 9px 14px;
  font-family: var(--body);
  /* Must be >= 16px -- iOS Safari auto-zooms the whole page on focus for
     any input under 16px, which is what makes a fixed-position sheet like
     this one appear to "jump" and overflow past the screen edge the
     moment you tap the field. */
  font-size: 16px;
  background: var(--paper);
  color: var(--ink);
}
.comment-form input:focus { outline: none; border-color: var(--ink-soft); }
.comment-submit-btn { padding: 9px 16px; font-size: 12px; flex-shrink: 0; }

@media (max-width: 640px) {
  .listing-post { padding: 12px 0; gap: 9px; }
  .comment-drawer { border-radius: 16px 16px 0 0; }
}

.sales-drawer {
  /* Taller than the base .comment-drawer cap -- there's no composer footer
     to eat into the sheet, so give the sales list more room and keep its
     last row clear of the bottom edge. min-height keeps it from reading as
     a thin strip when a listing only has a few sales to show. */
  min-height: 55vh;
  min-height: 55dvh;
  max-height: 94vh;
  max-height: 94dvh;
}
.sales-drawer .comment-drawer-body {
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
}

.sales-drawer-subtitle {
  font-family: var(--body);
  font-size: 12.5px;
  color: var(--ink-soft);
  margin-top: 2px;
}
.sales-note {
  font-family: var(--body);
  font-size: 12.5px;
  color: var(--ink-soft);
  margin: 0 0 14px;
}
.sales-group + .sales-group { margin-top: 18px; }
.sales-group.is-exact {
  padding: 12px;
  background: #fff9d9;
  border: 1px solid #ead06a;
  border-radius: 12px;
}
:root[data-theme="dark"] .sales-group.is-exact {
  background: rgba(255, 216, 61, 0.14);
  border-color: rgba(255, 216, 61, 0.62);
}
.sales-group-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.sales-grade { font-family: var(--body); font-weight: 800; font-size: 14px; color: var(--ink); }
.sales-relation {
  font-family: var(--body);
  font-weight: 700;
  text-transform: uppercase;
  font-size: 9.5px;
  letter-spacing: 0.2px;
  line-height: 1.3;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--paper-dim);
  color: var(--ink-soft);
  border: 1px solid var(--hairline);
}
.sales-group.is-exact .sales-relation {
  background: var(--yellow);
  color: #121212;
  border-color: #d6b624;
}
.sales-count, .sales-median { font-family: var(--body); font-size: 12.5px; color: var(--ink-soft); }
.sales-median { margin-left: auto; font-weight: 700; color: var(--ink); }
.sales-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-family: var(--body);
  font-size: 13px;
  padding: 6px 0;
  border-top: 1px solid var(--hairline);
}
.sales-row-date { color: var(--ink-soft); flex-shrink: 0; }
.sales-row-cert { color: var(--ink); flex: 1 1 auto; min-width: 0; }
.sales-row-price { color: var(--ink); font-weight: 700; flex-shrink: 0; }

/* ---------- Post owner menu (3-dot: edit / mark as sold / remove) ----------
   Shown only when listing.viewer_is_owner is true (see renderPostHeadlineHtml
   in social.js) -- own listings only, on any page that renders a post.
   .post-headrow-right wraps this alongside <time class="post-age"> so
   .post-headrow keeps exactly 2 top-level children either way -- adding the
   menu as a 3rd direct sibling would break its justify-content:space-between
   layout. Written into style.css (not a page-specific CSS file) because
   index.html, which also renders posts via social.js, loads only this file. */
.post-headrow-right {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

.post-owner-menu { position: relative; flex-shrink: 0; }
.post-owner-menu-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  background: transparent;
  color: var(--ink-soft);
  border-radius: 8px;
  cursor: pointer;
}
.post-owner-menu-btn:hover,
.post-owner-menu-btn[aria-expanded="true"] { background: var(--paper-dim); color: var(--ink); }
.post-owner-menu-icon svg { width: 17px; height: 17px; display: block; }

.post-owner-menu-dropdown {
  position: absolute;
  top: 32px;
  right: 0;
  min-width: 170px;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: 10px;
  box-shadow: 0 8px 24px -6px rgba(18, 18, 18, 0.25);
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  z-index: 20;
}
.post-owner-menu-item {
  font-family: var(--body);
  font-weight: 700;
  font-size: 13px;
  text-align: left;
  border: none;
  background: transparent;
  color: var(--ink);
  padding: 9px 10px;
  cursor: pointer;
  border-radius: 6px;
}
.post-owner-menu-item:hover { background: var(--paper-dim); }
.post-owner-menu-item-danger { color: var(--red); }
.post-owner-menu-item-danger:hover { background: var(--red); color: #fff; }

/* ---------- Mark-as-sold / confirm-delete modals ----------
   Centered modal, shared chrome for both (see social.js's
   ensurePostMarkSoldModal/ensurePostConfirmDeleteModal) -- fully
   self-contained rather than reusing account.css's .verify-required-modal
   / .mark-sold-modal, since pages other than account.html that load
   social.js don't load account.css. Sits above the comment/sales drawers
   (z-index 210/211) and the bottom tab bar (200). */
.post-mark-sold-backdrop {
  position: fixed;
  inset: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(18, 18, 18, 0.55);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
}
.post-mark-sold-backdrop.is-open { opacity: 1; pointer-events: auto; }

.post-mark-sold-modal {
  position: relative;
  width: 100%;
  max-width: 400px;
  max-height: 88vh;
  max-height: 88dvh;
  overflow-y: auto;
  box-sizing: border-box;
  background: var(--paper-dim);
  border: 1px solid var(--hairline);
  border-radius: 20px;
  padding: 30px 24px 24px;
  text-align: left;
  box-shadow: 0 20px 50px -12px rgba(18, 18, 18, 0.5);
  transform: scale(0.96) translateY(6px);
  transition: transform 0.18s ease;
}
.post-mark-sold-backdrop.is-open .post-mark-sold-modal { transform: scale(1) translateY(0); }
.post-confirm-delete-modal { text-align: center; }

.post-modal-close {
  position: absolute;
  top: 14px;
  right: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: none;
  background: var(--paper);
  color: var(--ink-soft);
  cursor: pointer;
}
.post-modal-close svg { width: 14px; height: 14px; }
.post-modal-close:hover { color: var(--ink); }

.post-modal-title {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 21px;
  letter-spacing: -0.3px;
  margin: 0 0 14px;
}

.post-mark-sold-price {
  font-family: var(--body);
  font-weight: 700;
  font-size: 14px;
  color: var(--ink-soft);
  margin: 0 0 18px;
}

#post-mark-sold-form label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-family: var(--body);
  font-weight: 800;
  text-transform: uppercase;
  font-size: 11px;
  color: var(--ink-soft);
}
#post-mark-sold-form input {
  width: 100%;
  box-sizing: border-box;
  margin-top: 2px;
  border: 1.5px solid var(--hairline);
  border-radius: 999px;
  padding: 11px 16px;
  font-family: var(--body);
  font-size: 16px;
  background: var(--paper);
  color: var(--ink);
}
#post-mark-sold-form input:focus { outline: none; border-color: var(--ink-soft); }

.post-field-hint {
  font-family: var(--body);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-soft);
  margin: 8px 0 0;
}
.post-mark-sold-savings { color: #1B8A55; font-weight: 800; }

.post-modal-submit {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin-top: 16px;
  font-family: var(--body);
  font-weight: 800;
  text-transform: uppercase;
  font-size: 13px;
  text-align: center;
  background: var(--ink);
  color: var(--paper);
  border: none;
  border-radius: 999px;
  padding: 12px 20px;
  cursor: pointer;
}
.post-modal-submit:hover { filter: brightness(1.3); }
.post-modal-danger { background: var(--red); }
.post-modal-status {
  display: block;
  font-family: var(--body);
  font-size: 12.5px;
  color: var(--ink-soft);
  margin: 10px 0 0;
}

.post-confirm-delete-actions { display: flex; gap: 10px; margin-top: 20px; }
.post-confirm-delete-actions .post-modal-submit { flex: 1; margin-top: 0; }
.post-modal-cancel {
  flex: 1;
  font-family: var(--body);
  font-weight: 800;
  text-transform: uppercase;
  font-size: 13px;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 12px 20px;
  cursor: pointer;
}
.post-modal-cancel:hover { background: var(--paper-dim); }

/* ---------- Sale/delete celebration ----------
   Full-screen moment shown after a sale is confirmed (or a lighter-weight
   burst after a removal) -- same in-browser confetti/chime treatment as
   account.js's showSaleCelebration/showDeleteBurst (see social.js's
   showPostSaleCelebration/showPostDeleteBurst). Sits above the mark-sold/
   confirm-delete modals (500) since those are already closed by the time
   this shows. */
.post-sale-celebration-overlay {
  position: fixed;
  inset: 0;
  z-index: 600;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 20px;
  background: rgba(18, 18, 18, 0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
.post-sale-celebration-overlay.is-open { opacity: 1; pointer-events: auto; }

.post-sale-celebration-burst {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: clamp(48px, 14vw, 108px);
  line-height: 1;
  color: var(--yellow);
  -webkit-text-stroke: 3px #121212;
  text-shadow: 5px 5px 0 var(--red);
  text-align: center;
  transform: scale(0) rotate(-6deg);
  opacity: 0;
  animation: post-sale-burst-pop 2.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  pointer-events: none;
}
@keyframes post-sale-burst-pop {
  0% { transform: scale(0) rotate(-6deg); opacity: 0; }
  15% { transform: scale(1.15) rotate(-6deg); opacity: 1; }
  30% { transform: scale(1) rotate(-3deg); opacity: 1; }
  70% { transform: scale(1) rotate(-3deg); opacity: 1; }
  100% { transform: scale(0.9) rotate(-3deg); opacity: 0; }
}

.post-sale-celebration-card {
  position: relative;
  width: 100%;
  max-width: 380px;
  box-sizing: border-box;
  background: var(--paper-dim);
  border: 1px solid var(--hairline);
  border-radius: 20px;
  padding: 26px 24px;
  text-align: center;
  box-shadow: 0 20px 50px -12px rgba(18, 18, 18, 0.5);
  opacity: 0;
  transform: translateY(10px);
  animation: post-sale-card-in 0.4s ease 0.5s forwards;
}
@keyframes post-sale-card-in {
  to { opacity: 1; transform: translateY(0); }
}

.post-sale-celebration-listing {
  font-family: var(--body);
  font-weight: 800;
  font-size: 14px;
  color: var(--ink-soft);
  margin: 0 0 10px;
}
.post-sale-celebration-message {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 22px;
  letter-spacing: -0.2px;
  color: var(--ink);
  margin: 0 0 16px;
}
.post-sale-celebration-savings {
  font-family: var(--body);
  font-size: 13.5px;
  line-height: 1.5;
  color: #1B8A55;
  font-weight: 700;
  margin: 0 0 14px;
}
.post-sale-celebration-dismiss { margin-top: 0; }

.post-delete-celebration-burst {
  font-size: clamp(30px, 7.5vw, 40px);
  max-width: 680px;
  line-height: 1.2;
}

.post-sale-confetti-canvas {
  position: fixed;
  inset: 0;
  z-index: 601;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .post-sale-celebration-burst,
  .post-sale-celebration-card { animation: none; opacity: 1; transform: none; }
}
