:root {
  --accent: #2e7d32;
  --accent-dark: #235e27;
  --accent-soft: #e7f3e8;
  --accent-contrast: #ffffff;
  --bg: #fafbfa;
  --surface: #ffffff;
  --text: #1a1f1b;
  --text-muted: #667066;
  --border: #e2e7e2;
}

/* Dark palette — applies automatically when the visitor's OS/browser
   prefers dark (unless THEME=light pins it to light), and always when
   THEME=dark pins it explicitly. See lib/brand.js (getThemeAttr) and
   server.js, which set data-theme="light"/"dark" on <html> from the THEME
   env var, or leave it unset for "system" so this media query decides. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --accent: #eb004e;
    --accent-dark: #bc003e;
    --accent-soft: #3a0f1e;
    --accent-contrast: #ffffff;
    --bg: #1a1216;
    --surface: #221a1e;
    --text: #f4ecee;
    --text-muted: #a89aa0;
    --border: #3a2d32;
  }
}

:root[data-theme="dark"] {
  --accent: #eb004e;
  --accent-dark: #bc003e;
  --accent-soft: #3a0f1e;
  --accent-contrast: #ffffff;
  --bg: #1a1216;
  --surface: #221a1e;
  --text: #f4ecee;
  --text-muted: #a89aa0;
  --border: #3a2d32;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--text);
  background: var(--bg);
  line-height: 1.6;
}

a {
  color: var(--accent);
}

.container {
  max-width: 860px;
  margin: 0 auto;
  padding: 0 20px;
}

/* ---- site header ---- */

header.site-header {
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

header.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 16px;
  padding-bottom: 16px;
}

.brand {
  font-size: 17px;
  font-weight: 700;
  text-decoration: none;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Home page only (index.html) — wraps .brand plus the optional BRAND_TAGLINE
   span so both stay one flex item within the header's own space-between
   layout (header.site-header .container), same as .brand alone was before.
   On every other page the tagline placeholder is never in the markup at
   all, so .brand-wrap there is just an unstyled, harmless wrapper. */
.brand-wrap {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}

.brand-tagline {
  font-size: 13px;
  font-weight: 400;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 640px) {
  .brand-tagline {
    display: none;
  }
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 18px;
  font-size: 14px;
}

.site-nav a {
  text-decoration: none;
  font-weight: 500;
}

/* :not(.btn) on both of these matters — a plain ".site-nav a" or
   ".site-nav a:hover" selector has higher specificity than ".btn-primary"/
   ".btn-primary:hover" (class+element beats class alone), so without this
   exclusion these would repaint a primary nav button's text back to the
   accent color — the same color as its own background (the "Create
   Account" button bug: accent-on-accent text, in both its default and
   hover states). Excluding .btn here lets button-classed nav links fall
   through entirely to the global .btn/.btn-primary rules below instead.
*/
.site-nav a:not(.btn) {
  color: var(--text-muted);
}

.site-nav a:not(.btn):hover {
  color: var(--accent);
}

.site-nav a.btn:not(.btn-primary) {
  color: var(--accent);
}

/* .user-identity is an <a> even before a username is known (falls back to
   plain email text with its href removed — see dashboard.js renderIdentity)
   so it needs its own not-actually-a-link cursor, since :not(.btn) above
   already colors it like every other nav link regardless of href. */
.user-identity:not([href]) {
  cursor: default;
}

.user-identity:not([href]):hover {
  color: var(--text-muted);
}

/* Hamburger toggle for .site-nav — hidden by default (desktop/tablet width,
   where .site-nav already lays out fine as an inline row) and only shown
   below the breakpoint in the media query further down, which is also where
   .site-nav itself switches from "always visible inline row" to "collapsed
   unless toggled open". app.js's initNavToggle() wires the click. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}

.nav-toggle:hover {
  border-color: var(--accent);
  color: var(--accent);
}

@media (max-width: 640px) {
  .nav-toggle {
    display: inline-flex;
  }

  /* flex-wrap lets .site-nav (100% width below) drop to its own row under
     the brand+toggle row instead of needing separate absolute positioning —
     there's no page content beside the header for it to overlap either
     way. */
  header.site-header .container {
    flex-wrap: wrap;
  }

  .site-nav {
    display: none;
    width: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--border);
  }

  /* Toggled by app.js's initNavToggle(), not a CSS-only checkbox hack —
     keeping it a plain class match app.js's other nav-state classes
     (nav-logged-out/nav-logged-in) use for the same "[hidden] is the only
     other thing already fighting .btn's `display` here" reason documented
     below. */
  .site-nav.nav-open {
    display: flex;
  }

  /* :not(.btn) so this doesn't fight .btn's own (larger, pill-shaped)
     padding — same reasoning as the a:not(.btn) color rule above this
     media query. */
  .site-nav a:not(.btn) {
    padding: 8px 4px;
  }

  .site-nav a.btn {
    width: 100%;
  }
}

/* The [hidden] attribute is how hydrateSiteNav() (app.js) toggles
   .nav-logged-out/.nav-logged-in — it relies on the browser's default
   `[hidden] { display: none }` UA rule. That default loses to ANY author
   rule that sets `display` on the same element regardless of specificity
   (author styles beat UA styles at equal importance), so `.btn`'s own
   `display: inline-flex` below was silently overriding it — a button-styled
   nav link like "Create Account" (.btn.btn-primary.nav-logged-out) stayed
   visible even while actually hidden="", because the .btn rule, not the
   browser default, was what actually painted it. This reinstates `hidden`
   as authoritative for every element, button-styled or not. */
[hidden] {
  display: none !important;
}

/* ---- buttons ---- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
  padding: 10px 18px;
  cursor: pointer;
  text-decoration: none;
  font-family: inherit;
}

.btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.btn-primary:hover {
  background: var(--accent-dark);
  border-color: var(--accent-dark);
  color: var(--accent-contrast);
}

.btn-block {
  width: 100%;
}

/* ---- hero / landing ---- */

.hero {
  padding: 72px 0 56px;
}

.hero h1 {
  font-size: 38px;
  margin: 0 0 14px;
  max-width: 16ch;
}

.hero p.lede {
  font-size: 17px;
  color: var(--text-muted);
  max-width: 52ch;
  margin: 0 0 28px;
}

.hero-actions {
  display: flex;
  gap: 12px;
  margin-bottom: 8px;
}

.how-it-works {
  padding: 8px 0 64px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}

@media (max-width: 680px) {
  .how-it-works { grid-template-columns: 1fr; }
}

.how-step {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 20px;
}

.how-step .step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 12px;
}

.how-step h3 {
  font-size: 15px;
  margin: 0 0 6px;
}

.how-step p {
  font-size: 13.5px;
  color: var(--text-muted);
  margin: 0;
}

.footnote {
  color: var(--text-muted);
  font-size: 13.5px;
  padding-bottom: 60px;
}

/* ---- support page (/support) ---- */

.support-section {
  max-width: 680px;
  margin: 0 0 36px;
}

.support-section h2 {
  font-size: 18px;
  margin: 0 0 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}

.support-section h3 {
  font-size: 14.5px;
  margin: 20px 0 6px;
}

.support-section p,
.support-section li {
  font-size: 14px;
  color: var(--text-muted);
}

.support-section ul {
  padding-left: 20px;
  margin: 0;
}

.support-section li {
  margin-bottom: 8px;
}

/* ---- site footer ---- */

/* Shared across every regular-chrome page (see lib/brand.js's
   getFooterHtml(), spliced in via server.js's {{SITE_FOOTER}} and
   lib/publicPages.js's layout()) — NOT shown on privacy-policy.html or
   terms.html, which are self-contained documents with their own
   single-purpose footer already, or on frame.html, a full-viewport iframe
   viewer with no room for one. */
.site-footer {
  margin-top: 48px;
  border-top: 1px solid var(--border);
  padding: 28px 0 36px;
}

.site-footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* flex-start, not space-between — space-between was spreading the row's
     two groups (nav links, copyright) to the far left and far right edges
     even with only 14px of actual content between them once the window was
     wide, which read as the copyright line floating off on its own rather
     than being part of the same footer. flex-start keeps everything
     clustered together, left-aligned, the same direction .footer-copyright
     itself already reads in. */
  justify-content: flex-start;
  gap: 14px;
}

/* Three stacked rows — social icon(s), then nav links, then copyright —
   each on its own line (flex: 1 0 100% forces a break both before and after
   a 100%-basis item), in that order both visually (order: 0/1/2) and in the
   markup itself (lib/brand.js's getFooterHtml). Used to be two rows (links
   + social sharing row 1, copyright alone on row 2) — split apart because a
   lone social icon sharing a row with the text links read as visually
   unbalanced/cluttered; three clean rows reads calmer. */
.footer-social {
  display: flex;
  gap: 12px;
  flex: 1 0 100%;
  order: 0;
}

.footer-social-link {
  display: inline-flex;
  align-items: center;
  color: var(--text-muted);
  text-decoration: none;
  font-size: 16px;
  line-height: 1;
}

.footer-social-link:hover {
  color: var(--accent);
}

/* Only Tumblr's own icon is an inline SVG (X's is a plain Unicode glyph,
   which needs none of this) — stroke=currentColor already makes it follow
   .footer-social-link's own color/hover color above, this just keeps its
   box the same 16px both link types otherwise share so the two sit on the
   same visual baseline in the row instead of the SVG's own default
   intrinsic sizing making it a slightly different size. */
.footer-social-link svg {
  width: 16px;
  height: 16px;
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  flex: 1 0 100%;
  order: 1;
}

.footer-links a {
  color: var(--text-muted);
  text-decoration: none;
  font-size: 13.5px;
}

.footer-links a:hover {
  color: var(--text);
  text-decoration: underline;
}

.footer-copyright {
  color: var(--text-muted);
  font-size: 12.5px;
  margin: 0;
  flex: 1 0 100%;
  order: 2;
}

/* ---- auth forms ---- */

.auth-wrap {
  max-width: 380px;
  margin: 64px auto;
  padding: 0 20px;
}

.auth-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 28px;
}

.auth-card h1 {
  font-size: 21px;
  margin: 0 0 6px;
}

.auth-card .auth-sub {
  color: var(--text-muted);
  font-size: 13.5px;
  margin: 0 0 22px;
}

/* ---- age gate ---- */
/* Reuses .auth-wrap/.auth-card's centered-card layout (public/age-gate.html)
   rather than a new one — same page shape as login/register, just with two
   stacked buttons instead of a form. */

.age-gate-brand {
  font-weight: 700;
  font-size: 16px;
  margin-bottom: 14px;
}

.age-gate-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.field {
  margin-bottom: 14px;
}

.field label {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-muted);
  margin-bottom: 5px;
}

.field input,
.field textarea {
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  font-family: inherit;
}

.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--accent);
}

/* Wraps a password <input> once app.js's wirePasswordToggle() runs — plain
   .field's own margin/label styling above is untouched, this just gives
   the input a positioning context for the icon button below. */
.password-field {
  position: relative;
}

/* Same box as any other .field input (the selector below matches that
   rule's own specificity, class+type+class vs. class+type, so this wins
   without !important) — just reserves room on the right so typed text
   never renders under the icon button. */
.field input.password-field-input {
  padding-right: 40px;
}

/* Deliberately NOT var(--text-muted)/var(--border) here, unlike most icon
   buttons elsewhere in this app — .field input above has no background/
   color rules of its own, so every text field on this site (this one
   included) renders with the browser's plain native white box regardless
   of site theme; --text-muted flips to a PALE color in dark mode (meant
   for reading against dark surfaces, not a white input box), which made
   this icon nearly invisible sitting on top of one. Fixed, theme-independent
   colors instead — chosen to read clearly on the light input box this
   button always sits on, in either theme. */
.password-toggle-btn {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  width: 30px;
  height: 30px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: #667066;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.password-toggle-btn:hover {
  color: #1a1f1b;
  background: #e2e7e2;
}

.password-toggle-btn svg {
  width: 18px;
  height: 18px;
}

.field-checkbox {
  margin-bottom: 16px;
}

/* Scoped as ".field-checkbox .checkbox-label" rather than bare
   ".checkbox-label" — this label sits inside a ".field" div (for the
   auth-form's existing field spacing), and plain ".field label" (a class +
   an element = higher specificity than a single class alone) would
   otherwise win and silently override this rule's display/color/weight,
   same class of bug as the nav button fix above. */
.field-checkbox .checkbox-label {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
}

.field-checkbox .checkbox-label input[type="checkbox"] {
  width: auto;
  margin-top: 2px;
  flex: 0 0 auto;
}

.form-error {
  color: #c62828;
  font-size: 13px;
  min-height: 18px;
  margin: -4px 0 10px;
}

.auth-switch {
  text-align: center;
  font-size: 13.5px;
  color: var(--text-muted);
  margin-top: 16px;
}

/* Right-aligned under the password field on login.html, and reused as-is
   by forgot-password.html/reset-password.html/verify-email.html for their
   own "back to log in" links — same muted, small-print treatment as
   .auth-switch above, just right-aligned instead of centered since this one
   sits directly under a field rather than below the whole form. */
.auth-forgot {
  text-align: right;
  font-size: 13px;
  margin: -8px 0 14px;
}

/* forgot-password.html/reset-password.html/verify-email.html's success
   state — same red-for-error convention .form-error already uses, just
   green for "this worked." Not reused for .form-error itself since an
   error and a success message are never shown at the same time on these
   pages (see each page's own script), so there's no need for one class
   that switches color. */
.auth-success {
  color: #2e7d32;
  font-size: 13.5px;
  margin: 0 0 14px;
}

/* ---- dashboard ---- */

.dash-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 28px 0 20px;
}

.dash-header h1 {
  font-size: 22px;
  margin: 0;
}

.dash-user {
  font-size: 13.5px;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 12px;
}

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 18px 20px;
  margin-bottom: 22px;
}

.panel h2 {
  font-size: 15px;
  margin: 0 0 6px;
}

.panel .panel-hint {
  font-size: 13px;
  color: var(--text-muted);
  margin: 0 0 14px;
}

.banner-panel {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* Dashboard's three headline stat boxes (Views/Bookmarks/Comments — see
   dashboard.html/.js). A plain 3-up flex row of .stat-box cards, each
   built from the same background/border/radius as .panel above rather than
   its own new surface treatment, so they read as part of the same page
   rather than a visually distinct new component. Equal-width via flex: 1
   so the row always fills the container edge-to-edge regardless of digit
   count in any one box. */
.stats-row {
  display: flex;
  gap: 14px;
  margin-bottom: 22px;
}

.stat-box {
  flex: 1;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 2px;
}

.stat-value {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--text);
}

.stat-label {
  font-size: 12.5px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Narrow phone screens: three boxes at the desktop's roomier padding get
   cramped fast, so this drops to a single column instead of shrinking each
   box's padding/font down to illegible sizes — same breakpoint the rest of
   this app's mobile rules already use (see browse.css's own 640px queries). */
@media (max-width: 640px) {
  .stats-row {
    flex-direction: column;
  }
}

/* Admin page — one .admin-slot-card (a .panel) per registered ad slot, see
   public/admin.js's slotCardHtml(). */
.admin-embed-input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  resize: vertical;
  margin-bottom: 10px;
}

.admin-embed-input:focus {
  outline: none;
  border-color: var(--accent);
}

.admin-slot-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  margin-bottom: 14px;
}

.admin-slot-toggle input[type="checkbox"] {
  width: auto;
}

.admin-slot-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.admin-slot-status {
  font-size: 13px;
}

.admin-slot-status-ok {
  color: var(--accent);
}

.admin-slot-status-error {
  color: #c62828;
}

.admin-slot-updated {
  font-size: 12px;
  color: var(--text-muted);
  margin: 10px 0 0;
}

/* Admin page — the ad-slot card's "Custom code" vs "Ad Rotator" radio pair
   (public/admin.js's slotCardHtml) and the size dropdown that appears when
   Rotator is picked. The two radio labels reuse .admin-slot-toggle for
   their own icon+text+cursor styling (same class the plain Enabled/Full
   width checkboxes above use) — this wraps THEM in a row together instead
   of the vertical stack .admin-slot-toggle's own display:flex would give
   two of them back to back with nothing else grouping them. */
.admin-slot-mode {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  margin-bottom: 14px;
}

.admin-slot-mode .admin-slot-toggle {
  margin-bottom: 0;
}

.admin-slot-mode input[type="radio"] {
  width: auto;
}

.admin-slot-embed-field,
.admin-slot-rotator-field {
  margin-bottom: 0;
}

.admin-slot-rotator-field select {
  width: 100%;
  box-sizing: border-box;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font-size: 14px;
  margin-bottom: 10px;
}

.admin-slot-rotator-field select:focus {
  outline: none;
  border-color: var(--accent);
}

/* Admin page — the Ad Rotator tab, one .admin-creative-card (a .panel) per
   creative in a size's pool (public/admin.js's creativeCardHtml). Reuses
   .admin-embed-input/.admin-slot-toggle/.admin-slot-row/.admin-slot-status
   from the ad-slot cards above rather than its own copies — same textarea,
   Enabled checkbox, and Save-button-plus-status-message shape, just one
   level smaller (a creative, not a whole slot). margin-bottom: 0 on the
   card itself hands all the spacing between cards to the list's own gap
   instead of doubling up with .panel's normal 22px. */
.admin-creatives-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-bottom: 14px;
}

.admin-creative-card {
  margin-bottom: 0;
}

/* Admin page — the Users panel's search box, styled the same as every
   other page's own search input (.toolbar input[type="text"] above) even
   though this one isn't inside a .toolbar — the panel here only ever has
   this one control, so the flex row .toolbar exists for isn't needed. */
#userSearch {
  width: 100%;
  box-sizing: border-box;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font-size: 14px;
}

#userSearch:focus {
  outline: none;
  border-color: var(--accent);
}

/* Admin page — the Users panel, one .admin-user-card (a .panel) per
   matching account — see public/admin.js's userCardHtml(). Same
   .admin-slot-toggle class as the ad-slot cards' own Enabled checkbox
   (above) for the "Ads enabled" one here, just laid out on the opposite
   end of the row instead of stacked underneath, since there's nothing else
   on this card to stack it under. */
.admin-user-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 14px 18px;
}

.admin-user-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.admin-user-info .text-muted {
  font-size: 13px;
}

.admin-user-card .admin-slot-toggle {
  margin-bottom: 0;
  white-space: nowrap;
}

/* Paid Membership's Grant/Revoke row (public/admin.js's membershipBlockHtml)
   — flex-basis: 100% drops it onto its own line below .admin-user-info/
   .admin-slot-toggle within the card's own flex-wrap row above, rather than
   fighting those two for space on the same line. */
.admin-user-membership {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  flex-basis: 100%;
}

.admin-user-membership .btn {
  padding: 4px 10px;
  font-size: 13px;
}

/* Account Settings page (public/account.html/js). .admin-slot-toggle
   (above) is reused as-is for the Public profile checkbox; these are the
   two pieces that page needs of its own. */
.account-radio-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 6px;
}

.account-radio {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
}

.account-radio input[type="radio"] {
  width: auto;
}

.account-setting-status {
  font-size: 13px;
  min-height: 16px;
}

.account-setting-status-ok {
  color: var(--accent);
}

.account-setting-status-error {
  color: #c62828;
}

/* Account Settings' Email panel — the Verified/Not verified badge next to
   the address itself. Same warm-red-for-a-problem convention
   .row-dead-link already uses for a broken bookmark link, and the same
   accent-green .auth-success uses for "this worked" — reused colors, not
   new ones, so this doesn't introduce a third meaning for either hue. */
.verified-badge-yes {
  color: #2e7d32;
  font-weight: 600;
}

.verified-badge-no {
  color: #c62828;
  font-weight: 600;
}

/* Paid Membership's pink checkmark — rendered next to a username wherever
   it appears as a link to that account (profile page heading, bookmark
   listing bylines, the bookmark permalink page's byline, comment authors,
   and the signed-in visitor's own nav identity), backed by every public
   API response's `is_member`/`isMember` field (see lib/publicBookmarks.js's
   PUBLIC_FIELDS and lib/comments.js's IS_MEMBER_SQL, both intentionally
   mirroring lib/membership.js's isActive()). Deliberately a fixed pink,
   independent of --accent/THEME_COLOR — the whole point (X.com-style) is a
   consistent, recognizable "this account is verified/paying" color that
   doesn't shift with a white-labeled deployment's own branding. Built with
   a plain circle + Unicode checkmark rather than an SVG so this same tiny
   snippet is trivial to reproduce identically from both server-rendered
   HTML (lib/publicPages.js) and client-side script (public/app.js,
   public/browse.js, public/comments.js) without sharing a build step. */
.member-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  margin-left: 3px;
  border-radius: 50%;
  background: #ec2899;
  color: #fff;
  font-size: 10px;
  line-height: 1;
  vertical-align: middle;
  flex-shrink: 0;
}

/* The profile page's own "Member" pill (public/browse.js's profileBadge) —
   distinct from .member-check above: this is a standalone, textual badge
   shown once in the profile-meta row (alongside "Joined <Month Year>" and
   follower/following counts), not a small mark repeated next to every
   occurrence of the name. Same pink as .member-check, so the two read as
   "the same feature" despite the different shape. */
.member-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 10px;
  border-radius: 999px;
  background: #ec2899;
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}

/* Account Settings' Cancel Account panel — the one destructive action on
   this page, so it gets a visually distinct treatment from every other
   .panel here (a muted red border instead of the default neutral one)
   purely so a skimming visitor's eye catches "this one's different" before
   reading a word of it, the same reason a browser's own "delete" dialogs
   are rarely styled identically to an "OK" one. */
.panel-danger {
  border-color: #c62828;
}

.panel-danger .field {
  max-width: 320px;
}

.btn-danger {
  background: #c62828;
  border-color: #c62828;
  color: #fff;
}

.btn-danger:hover {
  background: #a92121;
  border-color: #a92121;
  color: #fff;
}

/* Admin page — the tab bar switching between the Reports and Ad slots
   panels (public/admin.js's tab-click listener). Plain buttons, not real
   `<a href>` tabs — there's nothing to deep-link to, both panels already
   load together, this is purely a same-page show/hide toggle. */
.admin-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 20px;
}

.admin-tab {
  padding: 10px 16px;
  border: none;
  background: none;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px; /* sits flush on top of .admin-tabs' own border */
}

.admin-tab:hover {
  color: var(--text);
}

.admin-tab-active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* Admin page — the Reports panel, one .report-card (a .panel) per open (or,
   after the "Show resolved" toggle, resolved) report — see
   public/admin.js's reportCardHtml(). */
.report-card {
  padding: 14px 18px;
}

/* A report flagging content as involving a minor gets a visibly different
   treatment from the rest of the queue — a left accent bar in a distinct
   color, not just the same badge everything else gets — so it can't get
   lost between a run of "Broken or doesn't work" reports waiting their
   turn. This is a queue-ordering aid for an admin, not a moderation
   decision on its own — see lib/reports.js's own comment on why this
   reason exists. */
.report-card-urgent {
  border-left: 3px solid #c62828;
}

.report-card-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}

.report-reason-badge {
  display: inline-block;
  font-size: 12px;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: 100px;
  background: var(--accent-soft);
  color: var(--accent);
  white-space: nowrap;
}

.report-card-urgent .report-reason-badge {
  background: #fdeaea;
  color: #c62828;
}

.report-meta {
  font-size: 13px;
  color: var(--text-muted);
}

.report-note {
  font-size: 13.5px;
  margin: 0 0 12px;
  padding: 10px 12px;
  background: var(--bg);
  border-radius: 8px;
}

.report-card-actions {
  display: flex;
  align-items: center;
}

.report-resolved-label {
  font-size: 13px;
  color: var(--text-muted);
}

.text-muted {
  color: var(--text-muted);
}

.username-form {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.username-form input {
  flex: 1 1 220px;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  font-family: inherit;
}

.username-form input:focus {
  outline: none;
  border-color: var(--accent);
}

.bookmarklet-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.bookmarklet-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--accent-soft);
  color: var(--accent-dark);
  border: 1px dashed var(--accent);
  border-radius: 8px;
  padding: 9px 16px;
  font-size: 13.5px;
  font-weight: 700;
  text-decoration: none;
  cursor: grab;
  user-select: none;
}

.bookmarklet-regenerate {
  font-size: 12.5px;
  color: var(--text-muted);
  background: none;
  border: none;
  cursor: pointer;
  text-decoration: underline;
  font-family: inherit;
  padding: 0;
}

.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 16px;
}

.toolbar input[type="text"] {
  flex: 1;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
}

.toolbar input[type="text"]:focus {
  outline: none;
  border-color: var(--accent);
}

/* Activity page's three modules (public/activity.html/js) — a plain button
   row, not a full tab widget: activity.js toggles which .activity-panel is
   hidden and which button carries aria-selected="true", styled here purely
   off that attribute rather than a separate "active" class so the two can
   never drift out of sync with each other. */
.activity-tabs {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--border);
}

.activity-tab {
  padding: 10px 16px;
  border: none;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--text-muted);
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.activity-tab:hover {
  color: var(--text);
}

.activity-tab[aria-selected="true"] {
  color: var(--text);
  border-bottom-color: var(--accent);
}

.count {
  color: var(--text-muted);
  font-size: 13px;
  margin-bottom: 12px;
}

.list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.row {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

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

.row-title-line {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.row-favicon {
  width: 16px;
  height: 16px;
  border-radius: 3px;
  flex: 0 0 auto;
}

.row-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.row-title:hover {
  text-decoration: underline;
}

.row-sitename {
  font-size: 11.5px;
  color: var(--text-muted);
  background: var(--accent-soft);
  padding: 1px 6px;
  border-radius: 999px;
  flex: 0 0 auto;
}

/* View counter — shown on the dashboard's own rows (next to the title,
   since there's no meta/date line there to fold into) and on the public
   /browse row list and /bookmark/:id permalink page (browse.css's
   .row-meta/.bm-meta, which this inherits its muted/small look from). See
   lib/views.js for why this is per-bookmark, not shared across every
   bookmark row for the same URL the way vote scores are. */
.row-views {
  font-size: 11.5px;
  color: var(--text-muted);
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Dashboard-only click-through to a bookmark's public /bookmark/:id
   permalink (public/dashboard.js) — only ever rendered for a row that's
   currently public AND not flagged as a dead link (see .row-dead-link
   below), so there's no disabled/broken-link state to style here. Small and
   muted like .row-views right before it, not accent-colored like
   .row-title — this is a secondary path off the row, not its main action
   (that's still the title/thumbnail's own /view/:id link). */
.row-permalink {
  font-size: 11.5px;
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
  flex: 0 0 auto;
}

.row-permalink:hover {
  color: var(--accent);
  text-decoration: underline;
}

/* Dashboard-only "this URL's last check came back 404" indicator
   (public/dashboard.js, lib/urls.js's is_dead_link) — a fixed warm-red tint
   regardless of theme (not var(--accent), which itself flips between green
   and pink across this app's own theme variants — a warning shouldn't ride
   along with a color that isn't reliably read as one) so it reads as a
   caution state at a glance rather than blending in with this row's other
   muted metadata. Never shown for a row that isn't currently flagged dead —
   see dashboard.js's own comment on why. */
.row-dead-link {
  font-size: 11.5px;
  font-weight: 600;
  color: #c62828;
  background: rgba(198, 40, 40, 0.12);
  padding: 1px 7px;
  border-radius: 999px;
  white-space: nowrap;
  flex: 0 0 auto;
}

/* Same small-text-button look as .row-field-edit right below, so this reads
   as "one more small inline action on the row" rather than a visually
   distinct new kind of control. */
.row-recheck-link {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
  padding: 0;
  font-family: inherit;
  white-space: nowrap;
  flex: 0 0 auto;
}

.row-recheck-link:hover {
  text-decoration: underline;
}

.row-recheck-link:disabled {
  color: var(--text-muted);
  cursor: default;
  text-decoration: none;
}

.row-url-line {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  margin: 2px 0 8px;
}

.row-url {
  font-size: 12px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.row-description {
  font-size: 12.5px;
  color: var(--text-muted);
  margin: 0 0 8px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.row-thumb {
  width: 72px;
  height: 72px;
  object-fit: cover;
  border-radius: 8px;
  flex: 0 0 auto;
  border: 1px solid var(--border);
}

.row-tags,
.row-notes {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 5px 8px;
  font-size: 12.5px;
  font-family: inherit;
  margin-top: 4px;
  resize: vertical;
}

.row-tags:focus,
.row-notes:focus {
  outline: none;
  border-color: var(--accent);
}

/* .tag-pill lives here (not browse.css) because the dashboard's read/edit
   tag display (server/public/dashboard.js) reuses the exact same look as
   the public pages' clickable tag pills — both are links now: the public
   ones go to /tag/NAME, the dashboard's own filter its own list in place
   (see dashboard.js's setActiveTag) since a dashboard row can be private
   and has no public /tag/NAME page to send you to. One visual style,
   shared by both contexts. */
.tag-pill {
  display: inline-block;
  background: var(--accent-soft);
  color: var(--accent-dark);
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 12px;
  font-weight: 600;
  text-decoration: none;
  margin: 2px 4px 2px 0;
}

.tag-pill:hover {
  background: var(--accent);
  color: var(--accent-contrast);
}

/* .tag-banner also lives here (not browse.css) for the same reason as
   .tag-pill above — the dashboard's own tag filter (dashboard.js) reuses
   this exact "Filtered/Tagged #X ✕ clear" banner look, not just the public
   /tag/NAME and /bookmark/:id pages (browse.js / lib/publicPages.js). */
.tag-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--accent-soft);
  color: var(--accent-dark);
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 14px;
  font-size: 13.5px;
}

.tag-banner-clear {
  color: var(--accent-dark);
  font-weight: 600;
  text-decoration: none;
}

.tag-banner-clear:hover {
  text-decoration: underline;
}

/* Dashboard-only: each editable field (tags, notes) defaults to a compact
   read display (pills / plain text) with a small "Edit" toggle, rather than
   always showing the raw input/textarea the way this row used to. Clicking
   the toggle swaps that one field's display <-> input and flips the
   toggle's own label between "Edit"/"Done" — see dashboard.js's
   wireFieldToggle(). */
.row-field {
  margin-top: 6px;
}

.row-field-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.row-field-label {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  font-weight: 700;
}

.row-field-edit {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  padding: 0;
  font-family: inherit;
}

.row-field-edit:hover {
  text-decoration: underline;
}

.row-tags-display {
  margin-top: 3px;
}

.row-notes-display {
  margin: 3px 0 0;
  font-size: 12.5px;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.row-field-empty {
  color: var(--text-muted);
  font-style: italic;
}

.row-visibility {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-muted);
  cursor: pointer;
  width: fit-content;
}

.row-visibility input[type="checkbox"] {
  width: auto;
  margin: 0;
}

.row-delete {
  background: none;
  border: none;
  color: #bbb;
  font-size: 14px;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 6px;
}

.row-delete:hover {
  color: #fff;
  background: #c62828;
}

.empty {
  color: var(--text-muted);
  text-align: center;
  padding: 40px 0;
}

/* Quick Hide (public/app.js's initQuickHide) — a floating button plus a
   full-viewport cover, injected on every page that loads app.js when
   QUICK_HIDE_ENABLED is on. The z-index on both is a deliberately extreme
   value (the common "nuclear option" max-int, same one cookie-consent
   banners/paywalls use) rather than just "higher than frame.css's own
   highest, 1000" — an ad creative this app has no control over the markup
   of (see ads.js's own comments on treating embed code as opaque) could set
   its own arbitrarily high z-index, and Quick Hide covering the page is the
   one thing here that has to win against literally everything else on it,
   including that. */
.quick-hide-btn {
  position: fixed;
  bottom: 16px;
  right: 16px;
  z-index: 2147483647;
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
  cursor: pointer;
  opacity: 0.85;
}

.quick-hide-btn:hover,
.quick-hide-btn:focus-visible {
  opacity: 1;
  color: var(--text);
  border-color: var(--accent);
}

/* Kept visually low-key on purpose — muted colors, no alarming red/icon —
   so it doesn't itself draw a passerby's eye or read as "this button hides
   something sensitive" at a glance; the button still needs to be easy to
   FIND on purpose (fixed corner, always present, a title tooltip on hover),
   just not easy to notice for anyone who isn't the visitor themselves. */

.quick-hide-overlay {
  position: fixed;
  inset: 0;
  z-index: 2147483647;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
}

.quick-hide-overlay[hidden] {
  display: none;
}

/* A plain spinner, nothing else — no text, no branding, nothing that reads
   as anything other than "this page is loading," which is both the most
   boring thing to glance at and, since Quick Hide's redirect really is in
   flight underneath it, not even inaccurate. */
.quick-hide-overlay-spinner {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 3px solid var(--border);
  border-top-color: var(--text-muted);
  animation: quick-hide-spin 0.8s linear infinite;
}

@keyframes quick-hide-spin {
  to {
    transform: rotate(360deg);
  }
}

/* Collections page (public/collections.html/.js). One .panel (existing
   class, above) per collection, in a plain vertical stack — .collections-
   list, not .list, since these cards are much taller/denser than a bookmark
   .row and have no grid-view equivalent (unlike /browse and /dashboard,
   this page never offers a grid layout toggle). */
.collections-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.collection-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}

.collection-card-title {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.collection-card-title h2 {
  margin: 0;
}

.collection-card-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* Same pill shape as .row-sitename (browse.css) — reused, not redefined —
   just with the two semantic colors this app already uses elsewhere for
   "this is out in the open" vs. "this is just for you": accent-green for
   public (.verified-badge-yes / .auth-success's own green) and a plain
   muted tone for private, since private is this page's default/unremarkable
   state and doesn't need to read as a warning the way .row-dead-link's red
   does. */
.collection-badge {
  font-size: 11.5px;
  font-weight: 600;
  padding: 2px 9px;
  border-radius: 999px;
  flex: 0 0 auto;
}

.collection-badge-public {
  color: #2e7d32;
  background: rgba(46, 125, 50, 0.12);
}

.collection-badge-private {
  color: var(--text-muted);
  background: var(--accent-soft);
}

.collection-card-count {
  margin-bottom: 6px;
}

.collection-card-description {
  margin-bottom: 10px;
}

.collection-public-link {
  display: inline-block;
  font-size: 13px;
  color: var(--accent);
  text-decoration: none;
  margin-bottom: 10px;
}

.collection-public-link:hover {
  text-decoration: underline;
}

.collection-edit-form {
  border-top: 1px solid var(--border);
  padding-top: 14px;
  margin-top: 4px;
}

.collection-manage {
  border-top: 1px solid var(--border);
  padding-top: 14px;
  margin-top: 4px;
}

.manage-count {
  margin-bottom: 10px;
}

/* One collection-manage-row per bookmark — .row (styles.css) plus a drag
   handle prepended and .row-delete (existing "✕" button) appended, same
   markup shape /dashboard's own rows already use for their own trailing
   delete button. */
.collection-manage-row {
  cursor: default;
}

.drag-handle {
  cursor: grab;
  color: var(--text-muted);
  font-size: 16px;
  line-height: 1;
  padding: 2px 4px;
  flex: 0 0 auto;
  user-select: none;
}

.drag-handle:active {
  cursor: grabbing;
}

/* Applied to the one row actually being dragged (public/collections.js's
   wireDragAndDrop) — dimmed and slightly scaled down so it reads as "lifted
   out of the list" while the other rows visually reflow around it. */
.collection-manage-row.dragging {
  opacity: 0.4;
}

.collection-add-bookmark {
  margin-top: 14px;
}

.add-bookmark-search {
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  font-family: inherit;
  margin-bottom: 8px;
}

.add-bookmark-search:focus {
  outline: none;
  border-color: var(--accent);
}

.collection-add-results {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.collection-add-result-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
}

.collection-add-result-row .row-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
