/*
 * The search page's styling (index.html). A file rather than a <style> block so
 * the page's Content-Security-Policy can refuse inline stylesheets — see
 * PAGE_CONTENT_SECURITY_POLICY.
 */

/* ---- tokens ------------------------------------------------------------ */
:root {
  --bg: #f6f7f9;
  --surface: #fff;
  --surface-2: #f1f3f6;
  --fg: #14161a;
  --muted: #5f6675;
  --faint: #8a90a0;
  --border: #e3e6ec;
  --border-strong: #cdd2dc;
  --accent: #2563eb;
  --accent-fg: #fff;
  --accent-soft: #eaf0ff;
  --danger: #c0332b;
  --warn: #a8611a;
  /* a diff's two colours, and the git verdicts that borrow them */
  --add: #1a7f37;
  --del: #b42318;
  /* The green observer_stats.html marks a synced service with, to the digit.
     Kept apart from --add, a diff's green: the two vocabularies may drift. */
  --ok: #1a7f4b;
  --ring: rgba(37, 99, 235, .35);
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, .05);
  --shadow-md: 0 4px 16px rgba(16, 24, 40, .08);
  --shadow-lg: 0 18px 44px rgba(16, 24, 40, .14);
  --radius: 12px;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --skel: linear-gradient(90deg, var(--surface-2) 25%, #e7eaf0 37%, var(--surface-2) 63%);
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0d0f12;
    --surface: #15181d;
    --surface-2: #1c2028;
    --fg: #e6e8ec;
    --muted: #99a1b0;
    --faint: #6f7787;
    --border: #242932;
    --border-strong: #333a45;
    --accent: #7aa2ff;
    --accent-fg: #0d0f12;
    --accent-soft: #1a2233;
    --danger: #ff8a80;
    --warn: #d99b56;
    --add: #57ab5a;
    --del: #f47067;
    --ok: #6dd39b;
    --ring: rgba(122, 162, 255, .4);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);
    --shadow-md: 0 4px 16px rgba(0, 0, 0, .45);
    --shadow-lg: 0 18px 44px rgba(0, 0, 0, .6);
    --skel: linear-gradient(90deg, var(--surface-2) 25%, #262c36 37%, var(--surface-2) 63%);
  }
}

* { box-sizing: border-box; }
html { overflow-y: scroll; }
html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--fg);
  font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}
body { min-height: 100vh; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.page { max-width: 780px; margin: 0 auto; padding: 0 16px; }

/* ---- hero: centered while idle, a sticky bar once results are up ------- */
.hero { padding-top: 12vh; transition: padding-top .2s ease; }
.hero-inner { display: flex; flex-direction: column; align-items: center; }

.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 25px;
  font-weight: 680;
  letter-spacing: -.022em;
  color: inherit;
  text-decoration: none;
  margin-bottom: 4px;
}
.brand .mark { width: 1.05em; height: 1.05em; color: var(--accent); flex-shrink: 0; }
.brand em { font-style: normal; color: var(--accent); }
.tagline { color: var(--muted); font-size: 13.5px; margin: 0 0 20px; }

.search-wrap { position: relative; width: 100%; }
.search-icon {
  position: absolute; left: 17px; top: 50%; transform: translateY(-50%);
  width: 17px; height: 17px; color: var(--faint); pointer-events: none;
}
#q {
  width: 100%;
  padding: 13px 50px 13px 44px;
  font: inherit;
  font-size: 16px;
  color: var(--fg);
  border: 1px solid var(--border-strong);
  border-radius: 26px;
  outline: none;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  transition: border-color .15s, box-shadow .15s;
  /* An input's single line, restored on a div: `pre` keeps the spaces between
     tokens (a collapsed space stops being a boundary); the overflow scrolls
     sideways with the scrollbar hidden, as an input's does. */
  line-height: 1.6;
  white-space: pre;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  cursor: text;
}
#q::-webkit-scrollbar { display: none; }
/* The field is empty only when searchfield.js has cleared it: a contenteditable
   leaves a <br> behind otherwise, and :empty would never match again. */
#q:empty::before { content: attr(data-placeholder); color: var(--faint); }
#q:hover { border-color: var(--muted); }
#q:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }
#clear {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  display: none; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  border: 0; border-radius: 50%; background: none;
  color: var(--faint); font-size: 17px; line-height: 1; cursor: pointer;
}
#clear:hover { background: var(--surface-2); color: var(--fg); }
body.has-query #clear { display: flex; right: 46px; }

/* Who you are, in the field. Signing out means a new connection: NIP-42 auth
   belongs to the socket. `.pending` is the window between the extension naming
   you and the relay accepting the AUTH: faded, must not look like `.in`, and
   still clickable, unlike `.busy`. */
.me {
  /* Pinned to the field's box, so top, bottom and right stay one gap whatever
     the padding becomes. */
  position: absolute; right: 6px; top: 6px; bottom: 6px;
  width: auto; aspect-ratio: 1 / 1; padding: 0; border: 1px solid var(--border);
  border-radius: 50%; background: var(--surface-2); cursor: pointer;
  display: grid; place-items: center; overflow: hidden;
  color: var(--faint); font-size: 12px; font-weight: 700; line-height: 1;
  transition: border-color .15s, box-shadow .15s;
}
.me:hover { border-color: var(--accent); }
.me .avatar { border: 0; border-radius: 0; }
/* No score on your own face: the lens is you, and the badge would be clipped.
   Written against `.on` because `.score-chip.on` is the rule it has to beat. */
.me .score-chip.on { display: none; }
.me.in { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.me.pending { opacity: .6; }
.me.busy { opacity: .55; pointer-events: none; }

/* ---- the control bar --------------------------------------------------- */
.bar {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px 14px;
  width: 100%;
  margin-top: 12px;
}
.chips { display: flex; gap: 6px; flex: 1 1 auto; min-width: 0; overflow-x: auto; scrollbar-width: none; padding-bottom: 1px; }
.chips::-webkit-scrollbar { display: none; }
.chip {
  flex-shrink: 0;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  border-radius: 999px;
  padding: 4px 12px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.chip:hover { border-color: var(--border-strong); color: var(--fg); }
.chip.on { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.bar-right { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; flex-shrink: 0; }

.select {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--surface) no-repeat right 8px center/9px 6px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 13px center, right 8px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  border-radius: 8px;
  font: inherit;
  font-size: 12.5px;
  padding: 5px 24px 5px 10px;
  color: var(--muted);
  cursor: pointer;
}
.select:hover { border-color: var(--border-strong); color: var(--fg); }

/* ---- the advanced filters, behind a disclosure -------------------------- */
.adv { position: relative; flex-shrink: 0; }
.adv-btn {
  display: inline-flex; align-items: center; gap: 6px;
  list-style: none;               /* the disclosure triangle, Firefox */
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  border-radius: 8px;
  padding: 5px 10px;
  font-size: 12.5px;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
}
.adv-btn::-webkit-details-marker { display: none; }   /* and Safari's */
.adv-btn:hover { border-color: var(--border-strong); color: var(--fg); }
.adv-btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
.adv[open] .adv-btn { border-color: var(--border-strong); color: var(--fg); }
.adv-icon { width: 14px; height: 14px; flex-shrink: 0; }
.adv-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
  background: var(--accent); color: var(--accent-fg);
  font-size: 10.5px; font-weight: 600;
}
/* `hidden` alone loses to the display above on specificity, and the button
   would carry a permanent "0" (web/src/test/js/filters.test.mjs). */
.adv-count[hidden] { display: none; }
.adv-panel {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 45;
  width: min(17rem, calc(100vw - 32px));   /* 32px = .page's gutter, both sides */
  display: flex; flex-direction: column; gap: 11px; padding: 12px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-md);
}
.adv-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
/* the two rows that are labels: the whole row is the control's hit area */
label.adv-row { cursor: pointer; }
.adv-stack { flex-direction: column; align-items: stretch; gap: 6px; }
.adv-label { font-size: 12.5px; color: var(--muted); white-space: nowrap; }

/* "as" — whose web of trust ranks the results */
.obsbox { position: relative; display: flex; align-items: center; gap: 6px; }
.obscurrent { font-size: 13px; font-weight: 600; max-width: 7rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.obsbox.active .obscurrent { color: var(--accent); }
.obsfilter { flex: 1 1 auto; min-width: 0; font: inherit; font-size: 13px; padding: 3px 7px; border-radius: 8px;
             border: 1px solid var(--border); background: var(--surface); color: var(--fg); }
.obsfilter:focus { outline: 2px solid var(--ring); outline-offset: 1px; }
.obsreset { display: none; border: 0; background: none; color: var(--muted); cursor: pointer; font-size: 15px; line-height: 1; }
.obsbox.active .obsreset { display: inline; }
/* Capped: the rows are `nowrap` and a display name is whatever its owner typed.
   The name ellipsises; the row still carries the npub as its title. */
.obslist { position: absolute; top: calc(100% + 4px); left: 0; z-index: 40; margin: 0; padding: 4px;
           list-style: none; min-width: 16rem; max-width: min(20rem, calc(100vw - 32px));
           max-height: 15rem; overflow-y: auto;
           background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
           box-shadow: var(--shadow-md); }
.obslist:empty { display: none; }
.obslist li { padding: 5px 8px; border-radius: 8px; cursor: pointer; font-size: 13px;
              white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.obslist li:hover { background: var(--surface-2); }
.obslist li.muted { color: var(--faint); cursor: default; }
.switch { display: inline-flex; align-items: center; }
#spam { position: absolute; opacity: 0; width: 0; height: 0; }
.toggle-track { position: relative; width: 32px; height: 19px; border-radius: 10px; background: var(--border-strong); flex-shrink: 0; transition: background .15s; }
.toggle-thumb { position: absolute; top: 2px; left: 2px; width: 15px; height: 15px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.3); transition: transform .15s; }
#spam:checked + .toggle-track { background: var(--accent); }
#spam:checked + .toggle-track .toggle-thumb { transform: translateX(13px); }
#spam:focus-visible + .toggle-track { box-shadow: 0 0 0 3px var(--ring); }

/* ---- the syntax sheet --------------------------------------------------
   A <dialog> opened with showModal(): Escape, the backdrop, the focus trap and
   the inert page are the element's. Its button borrows .adv-btn; what a
   <button> does not inherit from that <summary> is the font. */
button.adv-btn { font: inherit; font-size: 12.5px; }
/* The mark alone, so the row does not wrap: square padding, and an icon a shade
   larger than the labelled button's. */
#helpbtn { padding: 5px 7px; }
#helpbtn .adv-icon { width: 15px; height: 15px; }

/* `[open]`, or a `display` on the dialog itself replaces the UA's `display: none`
   for a closed one. The column lets the head stay put while the body scrolls. */
.help-sheet[open] { display: flex; flex-direction: column; }
.help-sheet {
  width: min(40rem, calc(100vw - 32px));
  max-height: min(80vh, 44rem);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--fg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;                       /* the rounding; the body scrolls */
}
.help-sheet::backdrop { background: rgba(16, 24, 40, .45); }
@media (prefers-color-scheme: dark) { .help-sheet::backdrop { background: rgba(0, 0, 0, .6); } }
/* Fixed by the column rather than by `position`, so the close button is on
   screen at every scroll position of the body. */
.help-head {
  flex: 0 0 auto;
  display: flex; align-items: baseline; gap: 10px;
  padding: 14px 16px 10px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.help-head h2 { margin: 0; font-size: 15px; font-weight: 650; }
.help-head p { margin: 0; flex: 1 1 auto; font-size: 12px; color: var(--muted); }
.help-close {
  flex-shrink: 0; border: 0; background: none; color: var(--muted);
  font: inherit; font-size: 19px; line-height: 1; cursor: pointer; padding: 0 2px;
}
.help-close:hover { color: var(--fg); }
.help-close:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 6px; }
/* `min-height: 0`, or the flex item refuses to shrink below its content and
   the sheet grows past its own max-height instead of scrolling. */
.help-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px 16px 16px; }
.help-sec { margin-top: 14px; }
.help-sec h3 { margin: 0 0 2px; font-size: 12px; font-weight: 650; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.help-sec > p { margin: 0 0 6px; font-size: 12.5px; color: var(--muted); }
/* Two columns while a token can keep its own: one wrapping mid-
   `podcast:item:guid:` is the one thing this sheet must not do. */
.help-rows { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px 14px; align-items: baseline; }
.help-rows dt { min-width: 0; }
.help-rows dd { margin: 0; font-size: 12.5px; color: var(--muted); }
.help-rows dd b { color: var(--fg); font-weight: 600; }
@media (max-width: 560px) {
  .help-rows { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .help-rows dd { margin-bottom: 6px; }
  /* The one line the head can afford here; the sections say it again anyway. */
  .help-head p { display: none; }
  /* With the subtitle gone, the title holds the close button out at the edge. */
  .help-head h2 { white-space: nowrap; flex: 1 1 auto; }
}
/* The same monospace the json panel and the props rows use: a thing you type
   reads as a thing you type. */
.tok {
  font-family: var(--mono); font-size: 12px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 6px; padding: 1px 5px;
  white-space: nowrap;
}
.tok em { font-style: normal; color: var(--faint); }
.help-note { margin: 14px 0 0; font-size: 12px; color: var(--faint); }
.help-note a { color: var(--accent); text-decoration: none; }
.help-note a:hover { text-decoration: underline; }

.whoami { font-size: 12px; color: var(--muted); margin-top: 8px; width: 100%; }
.whoami:empty { display: none; }
.whoami b { color: var(--fg); font-weight: 600; }
.whoami .err { color: var(--danger); }

/* ---- "is search ready for you yet" ------------------------------------ */
/* One card, seven states. The state is a dot beside the sentence that states
   it, not a tinted rail down the card: the severity belongs next to the words. */
.readiness { width: 100%; margin-top: 12px; }
.readiness[hidden] { display: none; }
.rdy {
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px 16px 15px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  text-align: left;
}
.rdy-top { display: flex; align-items: flex-start; gap: 10px; }
.rdy-dot {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; margin-top: 6px;
  background: var(--faint);
}
.rdy.is-blocked .rdy-dot { background: var(--danger); }
.rdy.is-working .rdy-dot { background: var(--accent); }
.rdy.is-partial .rdy-dot { background: var(--warn); }
.rdy.is-ok .rdy-dot { background: var(--ok); }
.rdy-headline { margin: 0; flex: 1 1 auto; font-size: 15px; font-weight: 620; letter-spacing: -.01em; }
.rdy-state {
  padding-top: 4px; white-space: nowrap;
  font: 500 10.5px/1 var(--mono); letter-spacing: .09em; text-transform: uppercase;
  color: var(--faint);
}
.rdy-x {
  border: 0; background: none; padding: 0 0 0 4px; margin-top: -1px;
  color: var(--faint); font-size: 16px; line-height: 1; cursor: pointer;
}
.rdy-x:hover { color: var(--fg); }
/* 19px = the dot's 9 plus the row's 10 gap: everything under the headline
   hangs off the headline's own left edge. */
.rdy-body { margin: 0 0 0 19px; color: var(--muted); font-size: 13.5px; max-width: 62ch; }
.rdy-body b { color: var(--fg); font-weight: 600; }
.rdy-body code, .rdy-figures code {
  font-family: var(--mono); font-size: .9em;
  background: var(--surface-2); border-radius: 4px; padding: .5px 4px;
}
.rdy-hint { margin: 0 0 0 19px; color: var(--faint); font-size: 12.5px; max-width: 60ch; }

.rdy-meter { margin-left: 19px; display: flex; flex-direction: column; gap: 6px; }
.rdy-track { height: 5px; border-radius: 3px; background: var(--surface-2); overflow: hidden; }
/* Warn until parity and green only at it, so "partly there" never reads as "done". */
.rdy-fill { height: 100%; border-radius: 3px; background: var(--warn); }
.rdy.is-working .rdy-fill { background: var(--accent); }
.rdy.is-ok .rdy-fill { background: var(--ok); }
.rdy-figures {
  margin: 0 0 0 19px; display: flex; flex-wrap: wrap; gap: 4px 10px;
  font: 12px/1.4 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint);
}
.rdy-meter .rdy-figures { margin-left: 0; }
.rdy-figures b { color: var(--muted); font-weight: 600; }

.rdy-actions { margin-left: 19px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.rdy-field {
  flex: 1 1 15rem; min-width: 0; font: inherit; font-size: 13.5px; padding: 8px 11px;
  border: 1px solid var(--border-strong); border-radius: 8px;
  background: var(--surface); color: var(--fg);
}
.rdy-field::placeholder { color: var(--faint); }
.rdy-field:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }
.rdy-btn {
  display: inline-flex; align-items: center; gap: 6px;
  font: inherit; font-size: 13.5px; font-weight: 600;
  padding: 8px 14px; border: 1px solid transparent; border-radius: 8px;
  background: var(--accent); color: var(--accent-fg);
  cursor: pointer; white-space: nowrap; text-decoration: none;
}
.rdy-btn:hover { filter: brightness(1.06); }
.rdy-btn.quiet {
  background: var(--surface); color: var(--muted); border-color: var(--border); font-weight: 500;
}
.rdy-btn.quiet:hover { color: var(--fg); border-color: var(--border-strong); filter: none; }
.rdy-ext { font-size: 11px; opacity: .75; }
.rdy-more {
  margin-left: 19px; align-self: flex-start;
  font: inherit; font-size: 12.5px; color: var(--muted);
  background: none; border: 0; padding: 0; cursor: pointer;
}
.rdy-more:hover { color: var(--fg); }

/* The gates are a dependency chain (relay list, scores list, the service it
   names), so the list draws one: solid down to the link that broke, dashed
   below it, because everything under a break is waiting, not failing. */
.rdy-chain { margin: 2px 0 0 19px; display: flex; flex-direction: column; }
.rdy-chain[hidden] { display: none; }
.rdy-link { display: grid; grid-template-columns: 16px 1fr auto; gap: 0 10px; align-items: start; }
.rdy-rail { position: relative; width: 16px; align-self: stretch; }
.rdy-rail::before {
  content: ""; position: absolute; left: 7px; top: 14px; bottom: -4px; width: 2px;
  background: var(--ok);
}
.rdy-link:last-child .rdy-rail::before { display: none; }
/* The segment below a link takes that link's colour: a green rail under an
   amber mark would read as "everything past here is fine". */
.rdy-link.partial .rdy-rail::before, .rdy-link.aside .rdy-rail::before { background: var(--warn); }
.rdy-link.waiting .rdy-rail::before,
.rdy-link.broken .rdy-rail::before { background: none; width: 0; border-left: 2px dashed var(--border-strong); }
.rdy-mark {
  position: absolute; left: 0; top: 4px; width: 16px; height: 16px; border-radius: 50%;
  display: grid; place-items: center; font-size: 10px; font-weight: 700; line-height: 1;
  background: var(--ok); color: var(--surface);
}
.rdy-link.broken .rdy-mark { background: var(--danger); }
.rdy-link.partial .rdy-mark, .rdy-link.aside .rdy-mark { background: var(--warn); }
.rdy-link.waiting .rdy-mark { background: none; border: 2px dashed var(--border-strong); }
.rdy-what { padding: 2px 0 12px; font-size: 13.5px; }
.rdy-what small { display: block; color: var(--muted); font-size: 12.5px; }
.rdy-link.waiting .rdy-what, .rdy-link.waiting .rdy-what small { color: var(--faint); }
.rdy-verdict {
  padding-top: 3px; text-align: right; white-space: nowrap;
  font: 12px/1.4 var(--mono); font-variant-numeric: tabular-nums; color: var(--muted);
}
.rdy-link.broken .rdy-verdict { color: var(--danger); }
.rdy-link.partial .rdy-verdict { color: var(--warn); }
.rdy-link.waiting .rdy-verdict { color: var(--faint); }
.rdy-link.aside { opacity: .82; }

.rdy-found { margin: 0 0 0 19px; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 5px; }
.rdy-found li { display: flex; align-items: baseline; gap: 8px; font-size: 13px; color: var(--muted); }
.rdy-found b { color: var(--fg); font-weight: 600; }
.rdy-found .tick { color: var(--ok); font-weight: 700; width: 1em; }
.rdy-found .cross { color: var(--warn); font-weight: 700; width: 1em; }
.rdy-found .dash { color: var(--faint); width: 1em; }
.rdy-found .kind { font: 11.5px/1 var(--mono); color: var(--faint); }

.rdy-spinner {
  width: 13px; height: 13px; border-radius: 50%; flex: 0 0 auto; margin-top: 4px;
  border: 2px solid var(--border-strong); border-top-color: var(--accent);
  animation: rdy-spin .8s linear infinite;
}
@keyframes rdy-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .rdy-spinner { animation-duration: 3s; } }

@media (max-width: 620px) {
  .rdy-state { display: none; }
  .rdy-actions .rdy-btn { flex: 1 1 auto; justify-content: center; }
}

/* ---- type-ahead popup --------------------------------------------------- */
.popup {
  position: absolute;
  top: calc(100% + 8px);
  left: 0; right: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  z-index: 30;
  display: none;
  max-height: 62vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.popup.open { display: block; }
.popup-head {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em; color: var(--faint);
  padding: 9px 14px; background: var(--surface); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; display: flex; justify-content: space-between; z-index: 1;
}
.popup-head .timing { font-variant-numeric: tabular-nums; }
.popup-item {
  display: flex; gap: 11px; align-items: center;
  padding: 9px 14px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  border-left: 2px solid transparent;
}
.popup-item:last-child { border-bottom: 0; }
.popup-item:hover { background: var(--surface-2); }
.popup-item.active { background: var(--surface-2); border-left-color: var(--accent); }
.row-main { flex: 1; min-width: 0; }
.row-name { font-weight: 550; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-about { color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Not a result: the picker saying what it is doing. A row would be clickable. */
.popup-note { padding: 13px 14px; color: var(--faint); font-size: 12.5px; }

/* ---- a person, inside the text field ------------------------------------ */
/* The field's value is still `from:npub1…`; this is only how it draws. Sized
   in em so it follows the field into the sticky toolbar, where the font shrinks. */
.mention {
  display: inline-flex; align-items: center; gap: .35em;
  vertical-align: middle;
  max-width: 15em;
  padding: .1em .6em .1em .2em;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: .86em; font-weight: 600;
  white-space: nowrap;
  user-select: none;
}
/* The one face measured in em (av-xs is its px fallback), for the same reason.
   No border: the pill is the frame. */
.mention .av-wrap { --av: 1.5em; }
.mention .avatar { border: 0; }
/* The badge hangs further off the corner so the digits clear the name beside
   them; its ring is cut out of the pill's background, not the card surface. */
.mention .score-chip {
  right: calc(var(--chip) * -.45); bottom: calc(var(--chip) * -.3);
  box-shadow: 0 0 0 calc(var(--chip) * .16) var(--accent-soft);
}
/* The pill's left padding is cut for a face, which fills a rounded corner by
   itself; `from` / `to` is square text and gets the room back on the label. */
.mention-kind { opacity: .65; font-weight: 700; margin-left: .35em; }
.mention-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* ---- a hashtag, inside the text field ----------------------------------
   Not a .mention: `#nostr` is already the word it means, so the pill only says
   the word left the search string and became a tag filter. An outline, no
   avatar, no lookup; em-sized like the person chip. */
.hashtag {
  display: inline-flex; align-items: center;
  vertical-align: middle;
  max-width: 15em;
  padding: .1em .55em;
  border-radius: 999px;
  border: 1px solid var(--accent-soft);
  background: transparent;
  color: var(--accent);
  font-size: .86em; font-weight: 600;
  white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
  user-select: none;
}

/* ---- a date, inside the text field --------------------------------------
   An outline, like a hashtag: `since:2026-08-06` is readable. The pill respells
   the ISO token the reader's way. */
.datepill {
  display: inline-flex; align-items: baseline; gap: .4em;
  vertical-align: middle;
  padding: .1em .6em;
  border-radius: 999px;
  border: 1px solid var(--accent-soft);
  color: var(--accent);
  font-size: .86em; font-weight: 600;
  white-space: nowrap;
  user-select: none;
}
.datepill b { font-weight: 700; opacity: .65; }

/* ---- a NIP-73 scope, inside the text field -------------------------------
   The date pill's outline and split, the prefix bold. The value can be a whole
   url, so it clips like a person's name: min-width 0 and the ellipsis on the
   value span, never on the flex container. The token stays intact underneath. */
.scopepill {
  display: inline-flex; align-items: baseline; gap: .15em;
  vertical-align: middle;
  max-width: 18em;
  padding: .1em .6em;
  border-radius: 999px;
  border: 1px solid var(--accent-soft);
  color: var(--accent);
  font-size: .86em; font-weight: 600;
  white-space: nowrap;
  user-select: none;
}
.scopepill b { font-weight: 700; opacity: .65; }
.scopepill .scope-id { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* A group is a scope pill filled in: its value was picked rather than typed,
   like a person's. */
.grouppill { background: var(--accent-soft); border-color: transparent; }

/* ---- a group, inside the picker ---------------------------------------- */
/* Yours: it came out of your own kind 10009, the only source that knows a relay
   url. Reads "private" for the encrypted half of that list; same chip. */
.group-mine {
  margin-left: .5em; padding: .05em .45em;
  border-radius: 999px; background: var(--accent-soft); color: var(--accent);
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  vertical-align: middle;
}
/* A host named by its own kind 0 rather than by a tagged url is a claim, drawn
   as prose; an exact url keeps `.mono`. */
.group-host { font-style: italic; }
/* The id is carried by more than one group here and an `h` filter cannot tell
   them apart. A note, not a warning: the search still works. */
.group-warn {
  flex-shrink: 0; padding: .1em .5em;
  border: 1px solid var(--border-strong); border-radius: 999px;
  color: var(--muted); font-size: 10.5px; white-space: nowrap;
}
/* The footer about the encrypted half of a group list. Under the rows, never
   among them: the arrows walk `.popup-item`, and a picked notice would splice
   a group that does not exist. */
.group-lock { border-top: 1px solid var(--border); }
/* The one action in the list is a row like the groups above it, so the arrows
   walk onto it; tinted rather than shaped differently. */
.group-unlock .row-name { color: var(--accent); }

/* ---- the calendar `since:` and `until:` open ----------------------------
   Inside the popup box the people picker uses; never open together, since one
   caret is inside one token. In px, not em: it is not inside the field. */
.cal { padding: 10px 12px 12px; }
.cal-nav { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.cal-month { font-weight: 600; font-size: 13px; }
.cal-step {
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
  border-radius: 8px; width: 27px; height: 27px; padding: 0;
  font: inherit; font-size: 15px; line-height: 1; cursor: pointer;
}
.cal-step:hover { border-color: var(--accent); color: var(--accent); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-dow {
  text-align: center; padding: 5px 0;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--faint);
}
.cal-day {
  border: 0; background: none; color: var(--fg);
  padding: 6px 0; border-radius: 8px;
  font: inherit; font-size: 12.5px; font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.cal-day:hover { background: var(--surface-2); }
/* Today is a ring, the keyboard's day is filled: both must be visible at once
   on the day they coincide. */
.cal-day.today { box-shadow: inset 0 0 0 1px var(--accent-soft); color: var(--accent); }
.cal-day.active { background: var(--accent-soft); color: var(--accent); font-weight: 650; }
/* A future bound is legal (created_at is the author's claim), so a day ahead is
   muted, not faint: half a month is usually in the future. */
.cal-day.ahead { color: var(--muted); }
.cal-quick {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border);
}
.cal-pick {
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
  border-radius: 999px; padding: 3px 10px; font: inherit; font-size: 12px; cursor: pointer;
}
.cal-pick:hover { border-color: var(--accent); color: var(--accent); }

.list-right { display: inline-flex; align-items: baseline; gap: 12px; }
.export {
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
  border-radius: 7px; padding: 3px 10px; font: inherit; font-size: 12px; cursor: pointer;
}
.export:hover { border-color: var(--accent); color: var(--accent); }

/* The raw event, for whoever needs it, invisible to whoever does not. */
.raw { margin-top: 10px; }
.raw-toggle {
  border: 0; background: none; padding: 0; cursor: pointer;
  color: var(--faint); font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
}
.raw-toggle:hover { color: var(--muted); text-decoration: underline; }
.raw-body {
  margin: 8px 0 0; padding: 10px; max-height: 320px; overflow: auto;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  font: 11px/1.5 var(--mono); color: var(--fg);
  white-space: pre-wrap; word-break: break-word;
}

/* A nip05 is a claim until the domain agrees; the chip is the answer. */
.nip05 { display: inline-flex; align-items: center; gap: 5px; }
.n5chip {
  display: inline-grid; place-items: center; min-width: 15px; height: 15px; padding: 0 4px;
  border-radius: 8px; font-size: 10px; font-weight: 800; line-height: 1;
  background: var(--surface-2); color: var(--faint);
}
.n5chip.ok { background: #128a4b; color: #fff; }
.n5chip.bad { background: var(--danger); color: #fff; }
.n5chip.unknown { background: var(--surface-2); color: var(--muted); }

/* ---- avatars ------------------------------------------------------------ */
/* The face table. Every picture of a person comes out of shared/avatar.js,
   which names a size and leaves the pixels here. `--av` is the face, `--chip`
   the score badge, `--chip-text` its digits: the badge scales with the face
   and the digits less, so they stay legible on the small faces. In px, unlike
   the pills in the field: a badge is a fixed affordance, not running text. */
.av-wrap { position: relative; display: inline-flex; flex: 0 0 auto; --av: 34px; --chip: 15px; --chip-text: 9px; }
.av-xs   { --av: 20px; --chip: 10px;   --chip-text: 7.5px; }   /* the field's from:/to: pill */
.av-sm   { --av: 22px; --chip: 11px;   --chip-text: 7.5px; }   /* a card's byline */
.av-md   { --av: 30px; --chip: 13px;   --chip-text: 8.5px; }   /* a face strip */
.av-lg   { --av: 34px; --chip: 15px;   --chip-text: 9px; }     /* a result row, a card's own picture */
.av-xl   { --av: 46px; --chip: 18px;   --chip-text: 10.5px; }  /* the profile card's header */
.av-xxl  { --av: 64px; --chip: 24px;   --chip-text: 12px; }    /* a people grid cell */
/* As big as its box: the toolbar's "me" button, pinned to the field's height. */
.av-fill { --av: 100%; display: block; width: 100%; height: 100%; }
.score-chip {
  position: absolute; display: none;
  right: calc(var(--chip) * -.2); bottom: calc(var(--chip) * -.2);
  min-width: var(--chip); height: var(--chip); padding: 0 calc(var(--chip) * .2);
  border-radius: calc(var(--chip) / 2); background: var(--accent); color: var(--accent-fg);
  font-size: var(--chip-text); font-weight: 800; line-height: var(--chip); text-align: center;
  box-shadow: 0 0 0 calc(var(--chip) * .14) var(--surface);
}
.score-chip.on { display: block; }
.avatar {
  width: var(--av); height: var(--av);
  border-radius: 50%;
  background: var(--surface-2);
  object-fit: cover;
  flex-shrink: 0;
  border: 1px solid var(--border);
}
.avatar.gen { background-image: linear-gradient(140deg, hsl(var(--h) 62% 72%), hsl(calc(var(--h) + 45) 58% 60%)); }

/* ---- the kind badge, tinted by family so a mixed list stays scannable --- */
.kind-badge {
  --h: 225; --s: 9%;
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .055em;
  color: hsl(var(--h) var(--s) 34%);
  background: hsl(var(--h) var(--s) 96%);
  border: 1px solid hsl(var(--h) var(--s) 88%);
  border-radius: 999px;
  padding: 1.5px 8px;
  white-space: nowrap;
}
@media (prefers-color-scheme: dark) {
  .kind-badge {
    color: hsl(var(--h) var(--s) 76%);
    background: hsl(var(--h) calc(var(--s) / 2) 17%);
    border-color: hsl(var(--h) calc(var(--s) / 2) 26%);
  }
}
[data-tone="people"]   { --h: 265; --s: 62%; }
[data-tone="note"]     { --h: 220; --s: 62%; }
[data-tone="article"]  { --h: 35;  --s: 68%; }
[data-tone="media"]    { --h: 330; --s: 58%; }
[data-tone="code"]     { --h: 152; --s: 48%; }
[data-tone="live"]     { --h: 5;   --s: 65%; }
[data-tone="market"]   { --h: 185; --s: 52%; }
[data-tone="social"]   { --h: 285; --s: 40%; }
[data-tone="list"]     { --h: 95;  --s: 40%; }
[data-tone="apps"]     { --h: 250; --s: 30%; }
[data-tone="record"]   { --h: 68;  --s: 50%; }

/* ---- the room a chat was said in, beside the badge that says it is one ---
   A link into this page's own `group:` search. The accent, not the badge's
   family tint, so "what this is" and "where it was said" stay two colours.
   `flex-shrink: 2`: a stranger's room name yields before the author beside
   it, since an equal shrink let a long room name squeeze the author away. */
.byline .group-pill {
  flex-shrink: 2;
  max-width: 14em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 11px;
  font-weight: 600;
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: 999px;
  padding: 1.5px 8px;
  text-decoration: none;
}
.byline a.group-pill:hover { text-decoration: underline; }

/* ---- results ------------------------------------------------------------ */
.results { padding: 20px 0 64px; }
.list-head {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 0 2px 8px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 14px;
}
.list-title { font-weight: 620; font-size: 14px; letter-spacing: -.01em; }
.list-stats { font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }

.result {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 10px;
  box-shadow: var(--shadow-sm);
  transition: border-color .12s, box-shadow .12s, transform .12s;
}
/* The lift belongs only to the cards that open something: `data-href` is the
   attribute app.js navigates on, so the promise and the behaviour are one
   condition. A permalink card carries none. */
.result[data-href] { cursor: pointer; }
.result[data-href]:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); transform: translateY(-1px); }
/* Where j/k are, drawn as focus is drawn: the page has one way of saying "the
   keyboard is here". */
.result.cursor { border-color: var(--border-strong); box-shadow: var(--shadow-md); outline: 2px solid var(--accent); outline-offset: 2px; }

.result-header { display: flex; gap: 12px; align-items: center; margin-bottom: 8px; }
.result-header .who { flex: 1; min-width: 0; }
.result-name { font-size: 16.5px; font-weight: 650; letter-spacing: -.012em; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.result-name a { color: inherit; text-decoration: none; }
.result-name a:hover { color: var(--accent); }
.result-display { color: var(--muted); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.byline { display: flex; gap: 7px; align-items: center; margin-bottom: 8px; }
.byline .dot { margin: 0 -2px; }
.byline .by-name { font-size: 12.5px; font-weight: 550; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.byline .by-date { font-size: 12px; color: var(--faint); flex-shrink: 0; }
/* The date is the card's permalink, the keyboard-reachable half of the
   clickable card. It keeps the date's voice: a timestamp, not a call to action. */
.byline a.by-date { text-decoration: none; }
.byline a.by-date:hover { color: var(--accent); text-decoration: underline; }
.byline .dot { color: var(--border-strong); }
.byline .spacer { flex: 1; }

/* centered so a title-only card doesn't leave a hole beside its thumbnail */
.result-main { display: flex; gap: 14px; align-items: center; }
.result-main .text { flex: 1; min-width: 0; }
.result-title { font-size: 16px; font-weight: 620; letter-spacing: -.012em; line-height: 1.35; margin: 0 0 4px; }
.result-title a { color: inherit; text-decoration: none; }
.result-title a:hover { color: var(--accent); }
.result-body { font-size: 13.5px; margin: 0; overflow-wrap: anywhere; white-space: pre-wrap; }
.result-body.muted { color: var(--muted); }
.result-body.clamp { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; white-space: normal; }
.thumb {
  width: 92px; height: 92px; flex-shrink: 0;
  object-fit: cover; border-radius: 10px;
  background: var(--surface-2); border: 1px solid var(--border);
}
/* An article's image is a cover the author framed, not an avatar: wide and
   top-aligned. `height: auto` is load-bearing: an explicit height beats
   aspect-ratio, and .thumb's square one is inherited here. */
.thumb.cover { width: 168px; height: auto; aspect-ratio: 16 / 10; align-self: flex-start; }

.props { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 4px 14px; font-size: 12.5px; margin: 10px 0 0; }
.props dt { color: var(--faint); }
.props dd { margin: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.props .mono { font-family: var(--mono); font-size: 12px; }
.props a { color: var(--accent); text-decoration: none; }
.props a:hover { text-decoration: underline; }

/* ---- the hero's feed ---------------------------------------------------
   The same cards as a results list, under the landing page's search box. The
   hero gives some height back: a feed nobody sees without scrolling is a feed
   nobody reads. */
.feed-preview { padding: 26px 0 56px; }
body.has-feed-preview .hero { padding-top: 7vh; }
.feed-more { font-size: 12.5px; font-weight: 550; color: var(--accent); text-decoration: none; white-space: nowrap; }
.feed-more:hover { text-decoration: underline; }
/* The feed sends no NIP-50 search string, so the three controls behind Filters
   have nothing to ride on. The chips stay: `kinds` is a field of the plain
   read this view does send. The panel, not the row: the Syntax button beside
   it explains the search box, which is on this page too. */
body.feed #adv { display: none; }

/* ---- the pager ---------------------------------------------------------
   Numbers rather than an endless scroll: a card opens a page of its own, and
   coming back to result 63 is what `?page=2` can do and infinite scroll
   cannot. Wraps on a phone rather than scrolling sideways. */
.pager {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: 6px; margin: 22px 0 4px;
}
.pg-nums { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; }
.pg {
  min-width: 32px; padding: 5px 9px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); color: var(--muted);
  font: inherit; font-size: 12.5px; font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.pg:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
/* The page you are on is a label, not an offer: the button's shape, so the row
   does not reflow, and no longer a target. */
.pg.on {
  background: var(--accent-soft); border-color: var(--accent);
  color: var(--accent); font-weight: 650; cursor: default;
}
/* Prev at the start and Next at the end stay on the page rather than
   disappearing: a row whose ends come and go moves every number under the pointer. */
.pg:disabled { opacity: .4; cursor: default; }
.pg-nav { font-weight: 550; }
.pg-note {
  margin: 10px auto 0; max-width: 46em;
  text-align: center; font-size: 12.5px; color: var(--faint);
}
.pg-note code { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }

.empty, .error { padding: 40px 16px; text-align: center; color: var(--muted); font-size: 13.5px; }
.error { color: var(--danger); }
.empty b { display: block; color: var(--fg); font-size: 15px; font-weight: 600; margin-bottom: 4px; }

/* ---- entity (permalink) pages and the richer card bodies ---------------- */
/* Links inside a card body follow the page's link voice, not the browser's default. */
.result-body a { color: var(--accent); text-decoration: none; }
.result-body a:hover { text-decoration: underline; }
/* A summary followed by the full body (the article permalink) needs air, or
   the muted line reads as the body's first paragraph. */
.result .result-body + .result-body { margin-top: 10px; }
/* Who a reply answers, above the words. Quiet, but the name carries the link
   voice; one line, so a long display name truncates. */
.reply-line { font-size: 12.5px; color: var(--muted); margin: 0 0 6px;
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.reply-line a { color: var(--accent); text-decoration: none; font-weight: 550; }
.reply-line a:hover { text-decoration: underline; }
.reply-line a.mono { font-family: var(--mono); font-size: 12px; font-weight: 400; }
/* The permalink is a destination: its title carries the page. */
.result.full .result-title { font-size: 20px; }
.result.full .result-name { font-size: 20px; }
.entity-head { display: flex; align-items: center; gap: 12px; padding: 16px 2px 10px; font-size: 13px; }
/* Where a hint-fetched event came from and this relay's verdict on it; in
   .warn, that the reader stepped outside their lens. */
.prov { margin: 0 2px 10px; font-size: 12.5px; color: var(--muted); }
/* Why an event is in this page, as pills; no standing label in front of them.
   web/provenance.js has the rules. */
.prov.pills { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 8px; }
.prov-pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; line-height: 1.5; padding: .16em .6em;
  border-radius: 999px; border: 1px solid transparent;
  background: var(--surface-2); color: var(--muted);
  text-decoration: none; max-width: 24ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.prov-pill:hover { border-color: var(--border-strong); color: var(--fg); }
/* Delegated: the reader's own provider list named this signer for this kind.
   --accent-soft is the tone this page spends on "yours" (.grouppill, .group-mine). */
.prov-pill.vouched { background: var(--accent-soft); color: var(--accent); }
.prov-pill.vouched:hover { border-color: var(--accent); }
/* Open: a NIP-32 label anyone may publish. Never the delegated tone: a card
   must not imply the reader's web of trust endorsed what it never gated. */
.prov-pill.open { background: transparent; border-color: var(--border); }
.prov-pill .n { font-variant-numeric: tabular-nums; font-size: 10.5px; opacity: .7; font-weight: 600; }
/* The attribution face rides at the pill's own scale and drops the score chip:
   a second round trip for a number nobody asked for. */
.prov-pill .av-wrap { --av: 15px; }
.prov-pill .score-chip { display: none; }
.prov-pile { display: inline-flex; align-items: center; }
.prov-pile .av-wrap + .av-wrap { margin-left: -6px; }
.prov-pile .avatar { box-shadow: 0 0 0 1.5px var(--surface); }
.prov-more {
  font-size: 11.5px; color: var(--faint); background: none; border: 0;
  cursor: pointer; padding: .16em .3em; font-family: inherit;
}
.prov-more:hover { color: var(--accent); text-decoration: underline; }
.prov .mono { font-family: var(--mono); font-size: 11.5px; }
.prov.warn { color: var(--warn); }
/* The entity page narrates its wait: the pulse says "working", the text on what. */
.entity-stage { text-align: center; color: var(--faint); font-size: 12.5px; padding: 2px 0 18px;
                animation: stage-pulse 1.4s ease-in-out infinite alternate; }
@keyframes stage-pulse { from { opacity: .45; } to { opacity: 1; } }
.reveal-btn { margin-top: 14px; border: 1px solid var(--border-strong); background: var(--surface);
              color: var(--fg); border-radius: 8px; padding: 7px 16px; font: inherit; font-size: 13px; cursor: pointer; }
.reveal-btn:hover { border-color: var(--accent); color: var(--accent); }
.entity-head a { color: var(--muted); text-decoration: none; white-space: nowrap; }
.entity-head a:hover { color: var(--accent); }
.entity-head .ident { flex: 1; text-align: center; color: var(--faint); font: 12px var(--mono);
                      overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.byline a.by-name { font-size: 12.5px; font-weight: 550; color: var(--muted); text-decoration: none;
                    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.byline a.by-name:hover { color: var(--accent); }
.embed { margin: 4px 0 10px; }
.embed img, .embed video { display: block; max-width: 100%; max-height: 70vh; border-radius: 10px;
                           border: 1px solid var(--border); background: var(--surface-2); }
.embed audio { width: 100%; }
/* A video plays in the results list (cards/media.js). The renderer puts the
   event's `dim` on this box as an aspect-ratio, so the space is reserved at
   the right shape before the first byte. Centred with auto margins: a portrait
   video is narrower than the card. The two caps are the card's two depths. */
.media-frame { position: relative; display: flex; justify-content: center; margin: 4px auto 10px;
               width: fit-content; max-width: 100%; max-height: 360px; overflow: hidden; border-radius: 12px;
               border: 1px solid var(--border); background: var(--surface-2); }
.result.full .media-frame { max-height: 70vh; }
.media-frame video, .media-frame img { display: block; max-width: 100%; max-height: inherit; }
/* `.sized` knows its shape from `dim` and the video fills it; without one the
   frame hugs whatever the video turns out to be. */
.media-frame.sized { width: auto; }
.media-frame.sized video { width: 100%; height: 100%; object-fit: contain; background: #000; }
/* `contain`, like the video: nothing is letterboxed unless `dim` lied, and
   `cover` would crop a stranger's photo to a number the same stranger got wrong. */
.media-frame.sized img { width: 100%; height: 100%; object-fit: contain; }
/* An album: NIP-68 gives a picture post one imeta per image. Square cells,
   because a grid of mixed aspect ratios reads as a mistake. */
.media-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px;
              margin: 4px auto 10px; max-width: 460px; }
.media-grid img { display: block; width: 100%; aspect-ratio: 1 / 1; object-fit: cover;
                  border-radius: 10px; border: 1px solid var(--border); background: var(--surface-2); }
/* "and 2 more" belongs to the grid above it, not to the caption below. */
.media-grid + .muted-note { text-align: center; margin-bottom: 8px; }
/* The length, out of the way of the controls, which own the bottom edge on hover. */
.media-chip { position: absolute; top: 8px; right: 8px; padding: 1px 7px; border-radius: 999px;
              background: rgba(0,0,0,.72); color: #fff; font-size: 11.5px; font-weight: 600;
              letter-spacing: .02em; pointer-events: none; }
.codeblock { margin: 6px 0 0; padding: 12px; overflow: auto; max-height: 60vh;
             background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
             font: 12px/1.5 var(--mono); white-space: pre; }
/* ---- code & git ---------------------------------------------------------
   A file's name belongs on the file, as a bar across the top of the block. */
.code-head { display: flex; align-items: baseline; gap: 10px; margin: 6px 0 0; padding: 6px 12px;
             background: var(--surface-2); border: 1px solid var(--border); border-bottom: 0;
             border-radius: 8px 8px 0 0; font-size: 12px; }
.code-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
             font-family: var(--mono); font-weight: 620; }
.code-lang { margin-left: auto; flex: none; color: var(--faint); }
.codeblock.headed { margin-top: 0; border-radius: 0 0 8px 8px; }
/* A diff's two voices as text colour, never a filled row: the block scrolls
   sideways, and a background would stop at the fold. */
.codeblock.diff .d-add { color: var(--add); }
.codeblock.diff .d-del { color: var(--del); }
.codeblock.diff .d-hunk { color: var(--accent); }
.codeblock.diff .d-file { color: var(--muted); font-weight: 620; }
/* How big the change is, in the one line a reviewer reads first. */
.diffstat { display: flex; gap: 12px; margin: 6px 0 0; font-size: 12px; color: var(--faint);
            font-family: var(--mono); }
.diffstat .d-add { color: var(--add); }
.diffstat .d-del { color: var(--del); }
/* Which repository an event belongs to, in the reply line's quiet voice: where
   this card sits, not what it says. */
.repo-line, .meta-line { font-size: 12.5px; color: var(--muted);
                         overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.repo-line { margin: 0 0 6px; }
.meta-line { margin: 8px 0 0; }
.repo-line a, .meta-line a { color: var(--accent); text-decoration: none; font-weight: 550; }
.repo-line a:hover, .meta-line a:hover { text-decoration: underline; }
.repo-line .mono, .meta-line .mono { font-family: var(--mono); font-size: 12px; }
/* A ref carries its commit: same chip, the id trailing in the quiet voice. */
.ref-chip .mono { margin-left: 7px; color: var(--faint); font-family: var(--mono); }
.ref-chip.head { border-color: var(--border-strong); color: var(--fg); }
/* ---- what a permalink shows under its card (web/related.js) -------------
   The same cards a search draws, under headings that count them. The rule is
   visible on purpose: everything below it is about the card above, not more of it. */
.related { margin-top: 22px; border-top: 1px solid var(--border); }
.rel-section + .rel-section { margin-top: 20px; }
.rel-section .section-head { margin: 18px 2px 8px; }
/* The verdict on an issue or a patch sits against the card, not in the list. */
.resolution { display: flex; align-items: center; flex-wrap: wrap; gap: 2px;
              margin: 14px 2px 0; font-size: 13px; color: var(--muted); }
.resolution a { color: var(--accent); text-decoration: none; }
.resolution a:hover { text-decoration: underline; }
.quote { margin: 0 0 6px; padding: 8px 12px; border-left: 3px solid var(--accent);
         background: var(--surface-2); border-radius: 0 8px 8px 0; font-size: 13.5px; white-space: pre-wrap; overflow-wrap: anywhere; }
.face-strip { display: flex; align-items: center; margin-top: 8px; }
.face-strip .av-wrap + .av-wrap { margin-left: -9px; }
/* Overlapping faces need a rim to read as separate ones. */
.face-strip .avatar { box-shadow: 0 0 0 2px var(--surface); }
.face-strip .face-more { margin-left: 8px; font-size: 12px; color: var(--faint); }
/* The people a list holds, a cell each. The column count is a divisor of the
   cell caps (base.js: 6 and 24), never `auto-fill`, so every row is full at
   every width and the `+N more` cell always closes one. `minmax(0, 1fr)` with
   `min-width: 0` lets a long name ellipse rather than widen its column.
   `full` is the permalink's density. */
.people-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
               gap: 14px 8px; margin-top: 10px; }
.people-grid.full { gap: 18px 10px; }
.person-cell { display: flex; flex-direction: column; align-items: center; gap: 7px;
               min-width: 0; text-align: center; text-decoration: none; color: inherit; }
.person-name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
               font-size: 12px; font-weight: 600; line-height: 1.25; }
.people-grid.full .person-name { font-size: 13px; }
/* The npub fallback is quieter than a name: it is the absence of one. */
.person-name.mono { font-family: var(--mono);
                    font-size: 11px; font-weight: 500; color: var(--muted); }
.people-grid.full .person-name.mono { font-size: 11.5px; }
a.person-cell:hover .person-name { color: var(--accent); }
/* Who did not fit, as the last cell, so the row stays rectangular. Sized off
   `--av` so it tracks the faces beside it at either depth. */
.person-cell.more .person-name { color: var(--faint); font-weight: 500; }
.more-face { display: flex; align-items: center; justify-content: center;
             background: var(--surface-2); border-style: dashed; color: var(--muted);
             font-size: calc(var(--av) * .26); font-weight: 700; letter-spacing: -.02em; }
/* Its own breakpoint, below the page's. The step can only be to a divisor of
   the caps, so it is a halving; the max-width stops three columns sprawling. */
@media (max-width: 525px) {
  .people-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); max-width: 400px; }
}
.relay-list { margin: 6px 0 0; padding: 0; list-style: none; font-size: 12.5px; }
.relay-list li { padding: 2px 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.relay-list .mono { font-family: var(--mono); font-size: 12px; }
.relay-list a { color: var(--accent); text-decoration: none; }
.muted-note { color: var(--faint); font-size: 11.5px; }
.emoji-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.emoji-grid img { width: 26px; height: 26px; object-fit: contain; }
/* A 30031 sticker is a picture to send, not a glyph inside a line of text. */
.emoji-grid.stickers img { width: 64px; height: 64px; }
/* A 36767 theme drawn as itself: its background, a line in its text color, its primary.
   The colors are the event's own, and reach `style` only through themeColors()'s strict match. */
.theme-swatch { display: flex; align-items: center; gap: 10px; margin-top: 8px; padding: 10px 12px;
                border: 1px solid var(--border); border-radius: 10px; }
.theme-sample { font-size: 20px; font-weight: 700; }
.theme-primary { width: 22px; height: 22px; border-radius: 999px; flex: none; }
.theme-hexes { margin-left: auto; font-family: var(--mono); font-size: 11.5px; opacity: .8; }
.status-pill { display: inline-block; vertical-align: middle; margin-left: 6px; padding: 1px 8px;
               border-radius: 999px; font-size: 11px; font-weight: 700; text-transform: uppercase;
               letter-spacing: .04em; background: var(--surface-2); color: var(--muted); }
.status-pill.live { background: var(--danger); color: #fff; }
/* the NIP-52 rsvp answers, which are the whole content of a 31925 */
.status-pill.accepted { background: var(--accent); color: var(--accent-fg); }
.status-pill.declined { background: var(--danger); color: #fff; }
.status-pill.tentative { background: var(--warn); color: #fff; }
/* The NIP-34 verdicts. `--accent-fg`, not #fff: these fills invert with the
   theme, and it is the token for text on a saturated background. */
.status-pill.open { background: var(--add); color: var(--accent-fg); }
.status-pill.merged { background: var(--accent); color: var(--accent-fg); }
.status-pill.closed { background: var(--del); color: var(--accent-fg); }
/* A NIP-53 room only some people may enter. */
.status-pill.private { background: var(--warn); color: #fff; }
/* A pill that leads its line, rather than trailing a title. */
.status-pill.lead { margin-left: 0; margin-right: 4px; }
/* The row a card falls back to when it has pills but no title. */
.pill-row { margin: 0 0 4px; }
.pill-row .status-pill:first-child { margin-left: 0; }
.price-line { margin: 2px 0 6px; font-size: 15px; font-weight: 650; }
/* A score out of five: the glyphs place it at a glance, the number is what was
   actually published, since a half star cannot be drawn in a character. */
.stars { display: inline-flex; align-items: baseline; gap: 5px; vertical-align: middle; }
.stars .star-glyphs { color: var(--warn); font-size: 14px; letter-spacing: 1px; }
.stars .star-n { color: var(--muted); font-size: 12.5px; font-weight: 650; }
.rank-big { margin: 2px 0; font-size: 28px; font-weight: 700; color: var(--accent); }
/* what a list points at: e tags to notes, a tags to addressable entities */
.ref-list { margin: 6px 0 0; padding: 0; list-style: none; font-size: 12.5px; }
.ref-list li { padding: 2px 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ref-list a { color: var(--accent); text-decoration: none; }
.ref-list a:hover { text-decoration: underline; }
.ref-list .mono { font-family: var(--mono); font-size: 12px; }
/* A table of contents nests: a part holds chapters. The parser clamps the published
   level to 1..6, so these five rules cover every indent that can reach the page. */
.ref-list li.lv2 { padding-left: 14px; }
.ref-list li.lv3 { padding-left: 28px; }
.ref-list li.lv4 { padding-left: 42px; }
.ref-list li.lv5 { padding-left: 56px; }
.ref-list li.lv6 { padding-left: 70px; }
/* Short values (hashtags, branches, NIPs) as a wrapped row. Not `.chip`: that
   is the filter tab above, and this rule sits later in the sheet. */
.chip-row { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.tag-chip { padding: 1.5px 9px; border-radius: 999px; font-size: 11.5px; max-width: 100%;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
            color: var(--muted); background: var(--surface-2); border: 1px solid var(--border); }
.tag-chip.more { color: var(--faint); background: none; border-color: transparent; }
/* A hashtag chip is a link to the search for it: a real anchor that does not shout. */
a.tag-chip { text-decoration: none; }
a.tag-chip:hover { color: var(--accent); border-color: var(--accent); }
/* a list at permalink depth names each section it holds */
.list-section + .list-section { margin-top: 12px; }
.section-head { font-size: 11.5px; font-weight: 600; text-transform: uppercase;
                letter-spacing: .04em; color: var(--faint); }
.react-glyph { font-size: 18px; vertical-align: middle; }
.react-emoji { width: 22px; height: 22px; vertical-align: middle; object-fit: contain; }

/* ---- loading skeletons -------------------------------------------------- */
.skel-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 15px 16px; margin-bottom: 10px; }
.skel-row { display: flex; gap: 11px; align-items: center; padding: 12px 14px; border-bottom: 1px solid var(--border); }
.skel-line, .skel-dot {
  background: var(--skel); background-size: 400% 100%;
  animation: shimmer 1.3s linear infinite;
  border-radius: 5px; height: 10px;
}
.skel-card .skel-line + .skel-line { margin-top: 9px; }
.skel-dot { width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0; }
@keyframes shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ---- searching: the hero collapses into a sticky toolbar ---------------- */
body.searching .hero {
  position: sticky; top: 0; z-index: 20;
  padding: 10px 0 9px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
@supports (backdrop-filter: blur(8px)) {
  body.searching .hero { background: color-mix(in srgb, var(--bg) 82%, transparent); backdrop-filter: blur(12px); }
}
body.searching .hero-inner {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: 16px;
  row-gap: 9px;
}
body.searching .brand { font-size: 15.5px; gap: 7px; margin: 0; }
body.searching .brand .full { display: none; }
body.searching .tagline { display: none; }
body.searching #q { padding-top: 9px; padding-bottom: 9px; font-size: 14.5px; }
body.searching .search-icon { left: 15px; width: 15px; height: 15px; }
body.searching .bar { grid-column: 1 / -1; margin-top: 0; }
body.searching .whoami { grid-column: 1 / -1; margin-top: 0; }
/* Every row below the first has to say `1 / -1`. Auto-placed, the panel lands
   in the brand's `auto` track, which then takes the whole line and collapses
   the search field beside it to a stub. */
body.searching .readiness { grid-column: 1 / -1; margin-top: 0; }

@media (max-width: 620px) {
  .hero { padding-top: 7vh; }
  /* the chip row takes the whole line, so the button wraps below it at the
     left, and a panel hung off its right edge would run off screen */
  .adv-panel { left: 0; right: auto; }
  /* the chip row scrolls sideways; fade its right edge so that reads as more */
  .chips {
    flex: 1 1 100%; margin: 0 -16px; padding: 0 16px 1px;
    mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
  }
  body.searching .hero-inner { grid-template-columns: minmax(0, 1fr); }
  body.searching .brand { display: none; }
  .av-xl { --av: 40px; --chip: 16px; --chip-text: 10px; }
  /* A 64px face wants a column a third of a phone wide. */
  .av-xxl { --av: 52px; --chip: 20px; --chip-text: 11px; }
  .people-grid { gap: 12px 6px; }
  .people-grid.full { gap: 14px 8px; }
  .thumb { width: 72px; height: 72px; }
  /* .thumb.cover outranks .thumb at any viewport, so the narrow frame is named
     too. Width only: the ratio is what makes the covers read as a set. */
  .thumb.cover { width: 116px; }
  .props { grid-template-columns: minmax(0, 1fr); gap: 0 0; }
  .props dt { margin-top: 6px; font-size: 11px; }
}
