/* InstaSnipe — one hand-written stylesheet, no build step. Light, plain, readable at a glance.
   The palette and faces are the landing page's (site/index.html); DESIGN.md at the repo root is the one spec
   both follow (2026-09-22: the app was blue #1f5eff with cool greys while the site is green). */
/* Self-hosted, never a third-party font CDN: a logged-in page must not hand the reader's IP to another
   company the privacy page does not name. Latin subset (Fontsource 5, OFL — licence text beside the files);
   anything outside the range falls through to the system face. 650 in the rules below resolves to 700. */
@font-face { font-family: "IBM Plex Sans"; src: url("plex-sans-400.woff2") format("woff2"); font-weight: 400; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "IBM Plex Sans"; src: url("plex-sans-500.woff2") format("woff2"); font-weight: 500; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "IBM Plex Sans"; src: url("plex-sans-600.woff2") format("woff2"); font-weight: 600; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "IBM Plex Sans"; src: url("plex-sans-700.woff2") format("woff2"); font-weight: 700; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Manrope"; src: url("manrope-800.woff2") format("woff2"); font-weight: 800; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
/* Metric-matched fallbacks, the landing page's own numbers: the swap to the webfont moves nothing. */
@font-face { font-family: "Manrope Fallback"; src: local("Arial"); size-adjust: 110.81%; ascent-override: 96.20%; descent-override: 27.07%; line-gap-override: 0%; }
@font-face { font-family: "Manrope Fallback Android"; src: local("Roboto"); size-adjust: 111.06%; ascent-override: 95.99%; descent-override: 27.01%; line-gap-override: 0%; }
@font-face { font-family: "Plex Fallback"; src: local("Arial"); size-adjust: 101.17%; ascent-override: 101.32%; descent-override: 27.18%; line-gap-override: 0%; }
@font-face { font-family: "Plex Fallback Android"; src: local("Roboto"); size-adjust: 101.39%; ascent-override: 101.10%; descent-override: 27.12%; line-gap-override: 0%; }
:root {
  /* neutrals carry the landing page's faint green bias so they sit under the accent rather than beside it */
  --bg: #f4f7f5; --surface: #fff; --ink: #12171a; --muted: #5a6670; --line: #e2e8e6; --soft: #edf1ee;
  --accent: #0b7a51; --accent-ink: #fff; --accent-hover: #096440; --accent-soft: #e2f2ea; --focus: #b8ddcb;
  /* accent used AS TEXT (links, summaries). Light: the accent itself (5.4:1 on white). Dark lifts it, below. */
  --accent-text: var(--accent);
  --buy: #0b7a51; --review: #96620c; --pass: #6b7280; --uncovered: #2563eb; --drop: #9aa3ad; --danger: #c62828;
  --pill-ink: #fff;
  --warn-bg: #fff4d6; --warn-ink: #6a4b00; --warn-line: #f1dc9c; --queued: #fff7e6;
  --chip-bg: #fff1de; --chip-ink: #7a4c00; --chip-line: #f3d9a8;
  /* keyword palette (feed/presentation.py PALETTE, by name): --kw-<name> is the chip/stripe colour, -ink its text */
  --kw-gold: #d4af37; --kw-gold-ink: #1a1a2e; --kw-silver: #64748b; --kw-silver-ink: #fff;
  --kw-blue: #2563eb; --kw-blue-ink: #fff; --kw-green: #0e8a5c; --kw-green-ink: #fff;
  --kw-purple: #7c3aed; --kw-purple-ink: #fff; --kw-orange: #ea6a0a; --kw-orange-ink: #fff;
  --kw-pink: #db2777; --kw-pink-ink: #fff; --kw-teal: #0d9488; --kw-teal-ink: #fff;
  --radius: 10px; --gap: 12px;
  --display: "Manrope", "Manrope Fallback", "Manrope Fallback Android", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --body: "IBM Plex Sans", "Plex Fallback", "Plex Fallback Android", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-family: var(--body);
  color-scheme: light;
}
/* Dark mode (by request, 2026-09-20): the ☾/☀ button in the top bar flips it and remembers the choice in
   localStorage("theme"); with no choice stored the system preference decides. base.html sets data-theme on
   <html> before first paint, so this one block is the whole dark palette — every colour above is a token
   and nothing below names a colour directly. --accent is NOT overridden here on purpose: the container
   fork's brand accent (settings.brand_accent) is set on :root and must keep winning in both themes. */
:root[data-theme="dark"] {
  --bg: #0e1114; --surface: #171d1c; --ink: #e9eeeb; --muted: #94a2a0; --line: #29302f; --soft: #1c2322;
  --accent-ink: #fff; --accent-hover: #10875a; --accent-soft: #0f2b20; --focus: #1d4936;
  --buy: #35c78c; --review: #ddab52; --pass: #8b93a0; --uncovered: #5b8cff; --drop: #7d8691; --danger: #ef6b6b;
  /* white on these status fills was 2.3:1; dark ink is 5-9:1 on every one */
  --pill-ink: #0e1114;
  --warn-bg: #3a2f10; --warn-ink: #f2d38a; --warn-line: #5a4715; --queued: #2e2716;
  --chip-bg: #33270f; --chip-ink: #f0cf8a; --chip-line: #4d3a14;
  --kw-gold: #e2c04a; --kw-silver: #94a3b8; --kw-silver-ink: #0f1216; --kw-blue: #5b8cff; --kw-green: #2fbf82;
  --kw-green-ink: #0f1216; --kw-purple: #a78bfa; --kw-purple-ink: #0f1216; --kw-orange: #fb923c;
  --kw-orange-ink: #0f1216; --kw-pink: #f472b6; --kw-pink-ink: #0f1216; --kw-teal: #2dd4bf; --kw-teal-ink: #0f1216;
  color-scheme: dark;
}
/* Dark accent AS TEXT: the accent lifted to the landing page's dark lightness (#0b7a51 -> ~#35c78c, 8.8:1),
   DERIVED from --accent rather than named, so a container brand accent still wins. Without relative-colour
   support it stays the accent itself (3.5:1, what the blue was before). */
:root[data-theme="dark"] { --accent-text: var(--accent); }
@supports (color: oklch(from red max(l, .74) calc(c * 1.33) h)) {
  :root[data-theme="dark"] { --accent-text: oklch(from var(--accent) max(l, .74) calc(c * 1.33) h); }
}
* { box-sizing: border-box; }
html, body { margin: 0; }
body { background: var(--bg); color: var(--ink); font-size: 15px; line-height: 1.45; }
a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; }
h1 { font-family: var(--display); font-size: 22px; font-weight: 800; margin: 0; letter-spacing: -0.02em; }
h1 small, h2 small { font-family: var(--body); color: var(--muted); font-weight: 500; font-size: 14px; margin-left: 6px; }
h2 { font-size: 13px; font-weight: 650; margin: 20px 0 8px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
h3 { font-size: 13px; font-weight: 650; margin: 14px 0 4px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
/* Full-bleed by request 2026-09-12: the feed is a scanning surface, so it uses the whole window rather
   than a centred column. The login sheet keeps its own narrow cap via .sheet.narrow. */
main { margin: 0; padding: 20px 24px 60px; }

/* top bar */
.top { display: flex; align-items: center; gap: 24px; padding: 10px 20px; background: var(--surface); border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 5; }
.brand { position: relative; display: inline-flex; align-items: center; color: var(--ink); font-family: var(--display);
         font-weight: 800; letter-spacing: -0.03em; }
/* The landing page's mark (reticle + spark, design/elements/brand-mark.html), drawn in CSS so no template changes:
   ::before is the --accent tile, ::after the same SVG drawing as a MASK painted --accent-ink, so both follow the
   theme and a container brand accent. The #000 inside the data URI is mask alpha, not a colour anyone sees. */
.brand::before { content: ""; flex: none; width: 22px; height: 22px; border-radius: 5.5px; margin-right: 8px; background: var(--accent); }
.brand::after { content: ""; position: absolute; left: 0; top: 50%; width: 22px; height: 22px; transform: translateY(-50%);
                background: var(--accent-ink);
                -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cg fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round'%3E%3Ccircle cx='16' cy='16' r='8.2'/%3E%3Cpath d='M16 4.5v4.2M16 23.3v4.2M4.5 16h4.2M23.3 16h4.2'/%3E%3C/g%3E%3Cpath d='M17.2 10.8l-4.3 6h3.2l-1.3 4.4 4.4-6.1h-3.2z'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
                mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cg fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round'%3E%3Ccircle cx='16' cy='16' r='8.2'/%3E%3Cpath d='M16 4.5v4.2M16 23.3v4.2M4.5 16h4.2M23.3 16h4.2'/%3E%3C/g%3E%3Cpath d='M17.2 10.8l-4.3 6h3.2l-1.3 4.4 4.4-6.1h-3.2z'/%3E%3C/svg%3E") center / 100% 100% no-repeat; }
.top nav { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.top nav a { color: var(--muted); padding: 6px 10px; border-radius: 8px; font-weight: 500; }
.top nav a.on, .top nav a:hover { background: var(--soft); color: var(--ink); text-decoration: none; }
.top nav form { margin-left: 8px; }
/* The dark-mode switch sits at the far right of the bar on every page, logged in (after the nav) or not
   (alone, so it takes the nav's auto margin). Its label names the theme a click will GIVE you. */
.top .theme { margin-left: auto; padding: 6px 10px; font-weight: 500; color: var(--muted); }
.top nav + .theme { margin-left: 0; }
.top .theme:hover { color: var(--ink); }
.banner { background: var(--warn-bg); color: var(--warn-ink); border-bottom: 1px solid var(--warn-line); padding: 8px 20px; font-size: 14px; }

/* start-a-hunt strip: the first thing on the feed, so a search can be started without leaving the page */
/* keyword strip: the bar stays put at the top; each keyword added becomes a line under it (✕ removes it);
   one Play starts every queued line into the single feed below (by request, 2026-09-12). */
.strip { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 10px; margin-bottom: 14px; }
.start { display: flex; gap: 8px; align-items: center; }
.start input[type=text] { margin: 0; font-size: 16px; flex: 1; min-width: 0; }
.start button, .start .button { white-space: nowrap; }
/* The keyword lines lay out across the strip rather than down it (by request, 2026-09-12: "put the keyword list
   into 2 rows") — a dozen keywords land in about two rows on a wide window, and one column on a narrow one.
   A line whose Filters panel is open takes the full width so the panel is not squeezed into one cell. */
.lines { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 6px;
         grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
/* The whole card is the click target for narrowing the feed (by request, 2026-09-12), so it says so on hover.
   Only a box-shadow, never a background — .queued must keep its amber and .picked its heavier ring. */
.line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 6px 6px 6px 4px; border-radius: 8px; background: var(--soft); cursor: pointer; }
.line:hover { box-shadow: inset 0 0 0 1px var(--accent); }
.line:has(.tune[open]) { grid-column: 1 / -1; }
.line.queued { background: var(--queued); }
/* The keyword is a BOX: type in it to rename the line, click it to narrow the feed below to that keyword. */
.line .edit { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; flex: 1; min-width: 0; }
.line input.kw { font-weight: 650; font-size: 14px; margin: 0; padding: 4px 7px; min-width: 0; flex: 1 1 130px;
                 background: transparent; border: 1px solid transparent; border-radius: 6px; cursor: text; }
.line input.kw:hover { background: var(--surface); border-color: var(--line); }
.line input.kw:focus { background: var(--surface); border-color: var(--accent); outline: none; }
.line.picked, .line:focus-within { background: var(--surface); box-shadow: inset 0 0 0 2px var(--accent); }
.line.picked.queued, .line.queued:focus-within { background: var(--queued); }
.line .state { color: var(--muted); font-size: 13px; min-width: 0; }
.line .state .error { margin: 0; }
.line .tune { font-size: 13px; white-space: nowrap; }
/* The Filters toggle is a chip the size of a button, not a word in the row (by request, 2026-09-12: "filters
   collapsing needs to be larger and easier to see"). Open, it fills in with the accent, so which line's
   panel is showing is readable from across the strip. */
.line .tune summary { cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: 6px;
                      font-size: 14px; font-weight: 600; color: var(--accent-text); padding: 5px 12px;
                      border: 1px solid var(--line); border-radius: 999px; background: var(--surface); }
.line .tune summary:hover { border-color: var(--accent); background: var(--accent-soft); }
.line .tune summary::-webkit-details-marker { display: none; }
.line .tune summary::after { content: "▾"; font-size: 12px; line-height: 1; }
.line .tune[open] summary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.line .tune[open] summary::after { content: "▴"; }
.line .tune summary small { color: var(--muted); font-size: 12px; font-weight: 500; }
.line .tune[open] summary small { color: rgba(255, 255, 255, 0.85); }
/* Open, the panel drops to a full-width row under the line; closed, it is one word in the row. */
.line .tune[open] { order: 9; flex-basis: 100%; white-space: normal; }
.line .tune .fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 6px 10px; align-items: end; margin-top: 6px; padding-top: 8px; border-top: 1px solid var(--line); }
.line .tune .fields label { margin: 0; font-size: 12px; }
.line .tune .fields input, .line .tune .fields select { padding: 5px 8px; font-size: 13px; margin-top: 2px; }
.line .tune .fields .act { display: flex; align-items: center; gap: 10px; white-space: nowrap; font-size: 13px; }
.line .tune .fields .act button { padding: 6px 12px; }
.line form { margin: 0; display: flex; }
.line .x { border: 0; background: none; color: var(--muted); font-size: 16px; padding: 2px 6px; line-height: 1; }
.line .x:hover { color: var(--danger); background: none; }
.strip-foot { display: flex; align-items: center; gap: 12px; margin-top: 10px; }
.strip-foot .tally { color: var(--muted); font-size: 13px; flex: 1; }
.strip-foot form { margin: 0; }
.notice { color: var(--buy); font-weight: 600; margin: 6px 0; }

/* keyword colours: .kw-<name> on a line, a chip, a tile or a group binds the pair every rule below reads. */
.kw-gold { --kw: var(--kw-gold); --kw-ink: var(--kw-gold-ink); }     .kw-silver { --kw: var(--kw-silver); --kw-ink: var(--kw-silver-ink); }
.kw-blue { --kw: var(--kw-blue); --kw-ink: var(--kw-blue-ink); }     .kw-green { --kw: var(--kw-green); --kw-ink: var(--kw-green-ink); }
.kw-purple { --kw: var(--kw-purple); --kw-ink: var(--kw-purple-ink); } .kw-orange { --kw: var(--kw-orange); --kw-ink: var(--kw-orange-ink); }
.kw-pink { --kw: var(--kw-pink); --kw-ink: var(--kw-pink-ink); }     .kw-teal { --kw: var(--kw-teal); --kw-ink: var(--kw-teal-ink); }
.kw-chip { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
           padding: 3px 8px; border-radius: 6px; background: var(--kw); color: var(--kw-ink); white-space: nowrap; max-width: 100%;
           overflow: hidden; text-overflow: ellipsis; vertical-align: middle; }
.kw-chip a { color: inherit; }
.line { box-shadow: inset 4px 0 0 var(--kw); }
.line:hover { box-shadow: inset 4px 0 0 var(--kw), inset 0 0 0 1px var(--accent); }
.line.picked, .line:focus-within { box-shadow: inset 4px 0 0 var(--kw), inset 0 0 0 2px var(--accent); }
/* the swatch picker on a line: a dot in the line's colour; open, the eight swatches + auto */
.swatches { position: relative; }
.swatches summary { list-style: none; cursor: pointer; display: inline-flex; padding: 4px; border-radius: 6px; }
.swatches summary::-webkit-details-marker { display: none; }
.swatches summary:hover { background: var(--surface); }
.swatches form { position: absolute; z-index: 6; top: 100%; left: 0; margin-top: 4px; display: flex; gap: 4px; align-items: center;
                 padding: 6px; background: var(--surface); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12); }
.dot { display: inline-block; width: 16px; height: 16px; border-radius: 50%; background: var(--kw); border: 2px solid var(--surface); padding: 0; cursor: pointer; }
.dot:hover { transform: scale(1.15); }
.dot.on { box-shadow: 0 0 0 2px var(--ink); }
.swatches form .link { padding: 2px 6px; font-size: 12px; }
/* Notify on a line (spec §9): the same disclosure shape as the swatch picker — a summary in the row, a
   panel that drops over what is under it rather than pushing the strip around. Right-anchored, because the
   control sits at the end of the line and a left-anchored panel would hang off the window on the last column. */
.notify { position: relative; font-size: 13px; }
.notify > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
                    padding: 4px 8px; border-radius: 6px; color: var(--muted); white-space: nowrap; }
.notify > summary::-webkit-details-marker { display: none; }
.notify > summary:hover, .notify[open] > summary { background: var(--surface); color: var(--ink); }
.notify > summary small { color: var(--muted); font-size: 12px; }
.notify form { position: absolute; z-index: 6; top: 100%; right: 0; margin-top: 4px; display: flex; gap: 10px;
               align-items: flex-end; white-space: nowrap; padding: 10px; background: var(--surface);
               border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12); }
.notify form label { margin: 0; font-size: 12px; }
.notify form select { width: auto; margin-top: 2px; padding: 5px 8px; font-size: 13px; }
.notify form button { padding: 6px 12px; }
/* A channel the provider has stopped accepting: the badge on the line, in the settings list and nowhere else. */
.broken { color: var(--danger); font-weight: 650; font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }

/* page chrome */
.bar { display: flex; align-items: center; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
.bar > a:last-child, .bar .filters { margin-left: auto; }
.filters { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.filters select { margin: 0; width: auto; }
/* The verdict menu (by request, 2026-09-22): a summary that sits in the bar like the selects beside it, and a
   panel of checkboxes that drops over the rows rather than pushing the bar around — the Notify panel's shape,
   but LEFT-anchored: it is the bar's first control, so on a phone the wrapped summary sits at the window's
   left edge and a right-anchored panel hung off it (measured at 390px). */
.verdicts { position: relative; }
.verdicts > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
                      padding: 8px 10px; font: inherit; color: var(--ink); background: var(--surface);
                      border: 1px solid var(--line); border-radius: 8px; white-space: nowrap; }
.verdicts > summary::-webkit-details-marker { display: none; }
.verdicts > summary::after { content: "▾"; font-size: 12px; line-height: 1; }
.verdicts[open] > summary::after { content: "▴"; }
.verdicts > summary:hover, .verdicts[open] > summary { border-color: var(--accent); }
.verdicts .menu { position: absolute; z-index: 6; top: 100%; left: 0; margin-top: 4px; min-width: 170px;
                  display: flex; flex-direction: column; gap: 2px; padding: 10px; background: var(--surface);
                  border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12); }
.verdicts .menu .check { margin: 0; padding: 4px 2px; cursor: pointer; white-space: nowrap; }
.verdicts .menu button { margin-top: 6px; padding: 6px 12px; }
.check.inline { margin: 0; white-space: nowrap; font-size: 14px; }
.sheet { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 22px; }
.sheet.narrow { max-width: 460px; margin: 40px auto; }
/* Settings carries the delivery section now: a webhook row is a name, a tail and three buttons, which does
   not fit the login sheet's width. After .narrow, so it wins on equal specificity. */
.sheet.settings { max-width: 640px; }
.sheet h1 { margin-bottom: 14px; }
.empty { color: var(--muted); padding: 24px; text-align: center; background: var(--surface); border: 1px dashed var(--line); border-radius: var(--radius); }
.error { color: var(--danger); margin: 6px 0; }
.ok { color: var(--buy); margin: 6px 0; }
.meta, .hint, .fine, .counters { color: var(--muted); font-size: 13px; }
.fine { margin-top: 14px; }

/* forms */
label { display: block; font-size: 13px; color: var(--muted); margin: 10px 0 0; }
input[type=text], input[type=password], select { display: block; width: 100%; margin-top: 4px; padding: 8px 10px; font: inherit; color: var(--ink); background: var(--surface); border: 1px solid var(--line); border-radius: 8px; }
input:focus, select:focus { outline: 2px solid var(--focus); border-color: var(--accent); }
button, .button { font: inherit; font-weight: 600; font-size: 14px; padding: 8px 14px; border-radius: 8px; border: 1px solid var(--line); background: var(--surface); color: var(--ink); cursor: pointer; display: inline-block; line-height: 1.2; }
button:hover, .button:hover { background: var(--soft); text-decoration: none; }
button.primary, .button.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
button.primary:hover { background: var(--accent-hover); }
button.danger { color: var(--danger); }
button.link { border: 0; background: none; color: var(--muted); font-weight: 500; padding: 6px 10px; }
button.link:hover { color: var(--ink); }
.sheet form button { margin-top: 16px; }
.pct { display: flex; align-items: center; gap: 8px; }
.pct input { width: 120px; }
.pair { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* builder */
.builder .keyword { display: flex; gap: 8px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 10px; }
.builder .keyword input { margin: 0; font-size: 16px; }
.builder .keyword button { white-space: nowrap; }
.panel { display: grid; grid-template-columns: 290px 1fr; gap: var(--gap); margin-top: var(--gap); align-items: start; }
.builder.htmx-request .panel { opacity: 0.55; }
.builder.htmx-request .preview::after { content: "…"; }
.rail { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px 16px; position: sticky; top: 64px; max-height: calc(100vh - 80px); overflow-y: auto; }
.rail .allotment { margin: 0 0 4px; font-size: 13px; color: var(--muted); }
.rail label { margin-top: 12px; }
.aspect { border-top: 1px solid var(--line); padding: 8px 0; }
.aspect summary { cursor: pointer; font-size: 14px; font-weight: 600; list-style: none; display: flex; align-items: center; }
.aspect summary::after { content: "+"; margin-left: auto; color: var(--muted); }
.aspect[open] summary::after { content: "–"; }
.aspect summary b { color: var(--accent-text); margin-left: 6px; font-weight: 600; }
.check { display: flex; align-items: center; gap: 8px; margin: 6px 0 0; color: var(--ink); font-size: 14px; }
.check input { margin: 0; }
.check .count { margin-left: auto; color: var(--muted); font-size: 12px; }
.rows { min-width: 0; }
.counters { margin: 0 0 8px; }
.counters.warn { color: var(--ink); background: var(--soft); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; }
.counters.warn b { color: var(--danger); }
.rail .hint { margin: 4px 0 0; font-size: 12px; }

/* rows */
.row { display: grid; grid-template-columns: 420px 1fr; gap: 16px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px; margin-bottom: 12px; }
.row.v-drop { opacity: 0.55; }
.gallery { display: flex; gap: 5px; overflow-x: auto; scrollbar-width: thin; }
/* The whole photo, letterboxed, never a crop: a cropped product shot hides the piece the card is about. */
.gallery img { height: 160px; width: auto; max-width: 260px; object-fit: contain; border-radius: 6px; cursor: zoom-in; background: var(--soft); flex: 0 0 auto; }
/* A listing eBay sent no photo for: hold the column open and say so, rather than collapse to nothing and
   read as an image that failed to load. */
.gallery.none { align-items: center; justify-content: center; height: 160px; border: 1px dashed var(--line); border-radius: 6px; background: var(--soft); }
.gallery.none span { color: var(--muted); font-size: 13px; }
.body { min-width: 0; }
.head { display: flex; align-items: baseline; gap: 10px; }
.head .title { color: var(--ink); font-weight: 600; font-size: 16px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.head .price { font-weight: 700; font-size: 19px; white-space: nowrap; }
.pill { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; padding: 3px 8px; border-radius: 999px; color: var(--pill-ink); background: var(--pass); white-space: nowrap; }
.pill.buy { background: var(--buy); }
.pill.review { background: var(--review); }
.pill.pass { background: var(--pass); }
.pill.uncovered { background: var(--uncovered); }
.pill.drop { background: var(--drop); }
.facts { display: flex; flex-wrap: wrap; gap: 4px 22px; margin: 8px 0 0; }
.facts div { display: flex; gap: 6px; align-items: baseline; }
.facts dt { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }
.facts dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }
.facts dd small { color: var(--muted); font-weight: 500; }
.facts.wide { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 16px; gap: 6px 28px; }
.blurb { margin: 8px 0 0; font-size: 13px; line-height: 1.45; color: var(--muted); }
.chips { list-style: none; padding: 0; margin: 8px 0 0; display: flex; flex-wrap: wrap; gap: 6px; }
.chips li { font-size: 12px; background: var(--chip-bg); color: var(--chip-ink); border: 1px solid var(--chip-line); border-radius: 6px; padding: 2px 8px; }
.signals { margin: 8px 0 0; font-size: 13px; color: var(--muted); display: flex; flex-wrap: wrap; gap: 4px 14px; }
.signals .red { color: var(--danger); font-weight: 600; }
.foot { display: flex; align-items: center; gap: 12px; margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
.foot .more { margin-left: auto; font-size: 13px; }
.outcome { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.outcome form { display: flex; align-items: center; gap: 6px; }
.outcome button { padding: 5px 10px; font-size: 13px; }
.outcome .sold { display: flex; align-items: center; gap: 4px; }
.outcome .sold input { width: 80px; margin: 0; padding: 5px 8px; font-size: 13px; }
.outcome .marked { font-size: 13px; font-weight: 600; color: var(--buy); }
.outcome .error { font-size: 13px; margin: 0; }

/* layouts (by request, 2026-09-20): the list is today's rows; 2 / 4 are a grid of tiles after the shop's
   daily-deals cards — a FIXED two or four columns (by request, 2026-09-22: "go back to the single row, 2 row,
   4 row grid options", which reverted 3cb44e1's auto-fill large / compact grid). A grid narrows with the
   window: 4 -> 2 -> 1. Every tile is the same height (by request, 2026-09-22): the rows are 1fr, so each takes
   the tallest tile's height, and a two-line title and a one-line price keep the rest of the body at the same
   place in every tile. */
.rows.layout-2 .cards, .rows.layout-4 .cards { display: grid; gap: 20px; grid-auto-rows: 1fr; }
.rows.layout-2 .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.rows.layout-4 .cards { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1200px) { .rows.layout-4 .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 700px) { .rows.layout-2 .cards, .rows.layout-4 .cards { grid-template-columns: 1fr; } }
.tile { display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); overflow: hidden; min-width: 0; }
.tile.v-drop { opacity: 0.55; }
.tile .photo { position: relative; display: block; aspect-ratio: 4 / 3; background: var(--soft); }
/* Absolute, so the frame stays 4:3 whatever the photo's shape: in flow, a portrait photo's own height overrode
   the aspect-ratio and stretched its tile, and with equal-height rows every tile beside it. */
.tile .photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; }
.tile .photo .kw-chip { position: absolute; top: 10px; left: 10px; }
.tile .photo .age { position: absolute; top: 10px; right: 10px; font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 999px;
                    background: rgba(20, 24, 30, 0.72); color: #fff; }
.tile .tbody { flex: 1; display: flex; flex-direction: column; gap: 8px; padding: 12px 14px 14px; min-width: 0; }
.tile .title { color: var(--ink); font-weight: 600; font-size: 15px; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2;
               -webkit-box-orient: vertical; overflow: hidden; min-height: 2.6em; }
.tile .price { font-weight: 700; font-size: 22px; letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tile .price small { font-weight: 500; font-size: 12px; color: var(--muted); }
.tile .badge { align-self: flex-start; font-size: 12px; font-weight: 600; padding: 3px 8px; border-radius: 6px; background: var(--soft); color: var(--ink); }
.tile .pill { align-self: flex-start; }
.tile .blurb { margin: 0; }
.tile .go { text-align: center; margin-top: auto; }
.tile .foot { margin-top: 2px; flex-wrap: wrap; }
.tile .outcome form { flex-wrap: wrap; }
.tile .outcome .sold input { width: 64px; }
/* "AI recommends" on the pill: the prefix is quieter than the verdict word it introduces */
.pill small { font-weight: 600; letter-spacing: 0.04em; opacity: 0.85; text-transform: none; }
/* grouped: one section per keyword, its heading in the keyword's colour */
.group { margin-bottom: 22px; }
.group-head { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; text-transform: none; letter-spacing: 0; font-size: 14px;
              padding-bottom: 8px; border-bottom: 2px solid var(--kw); }
.group-head .kw-chip { font-size: 12px; }
.group-head small { color: var(--muted); font-weight: 500; }

/* card detail */
.detail { margin-top: 18px; }
.flags { margin: 0; padding-left: 18px; }

/* searches list */
.searches { list-style: none; padding: 0; margin: 0; }
.search { display: flex; gap: 16px; align-items: center; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 16px; margin-bottom: 8px; }
.search.paused { opacity: 0.7; }
.search .main { flex: 1; min-width: 0; }
.search .kw { color: var(--ink); font-weight: 650; font-size: 16px; }
.search .summary { margin: 2px 0 0; font-size: 13px; color: var(--muted); display: flex; flex-wrap: wrap; gap: 4px 12px; }
.search .meta { margin: 4px 0 0; }
.search .actions { display: flex; gap: 6px; align-items: center; }

/* image viewer */
/* The viewer shows eBay's 1600px rendition as large as the window allows — full size, not a bigger thumbnail. */
dialog#viewer { background: var(--surface); color: var(--ink); border: 0; border-radius: var(--radius); padding: 6px; max-width: 98vw; max-height: 98vh; }
dialog#viewer::backdrop { background: rgba(20, 24, 30, 0.8); }
dialog#viewer img { width: auto; height: auto; max-width: 96vw; max-height: 90vh; display: block; border-radius: 6px; }
dialog#viewer form { text-align: right; margin-top: 8px; }

@media (max-width: 900px) {
  .panel { grid-template-columns: 1fr; }
  .rail { position: static; max-height: none; }
  .row { grid-template-columns: 1fr; }
  .head .title { white-space: normal; }
  .search { flex-direction: column; align-items: stretch; }
}

/* what the lane read about the piece itself (form · marks · stones · traits) and eBay's own specifics */
.details { margin: 8px 0 0; font-size: 13px; color: var(--ink); }
.specifics { list-style: none; padding: 0; margin: 6px 0 0; display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 12px; color: var(--muted); }
.specifics b { color: var(--ink); font-weight: 600; }
.description { margin: 8px 0 0; font-size: 13px; line-height: 1.5; white-space: pre-wrap; color: var(--ink); }

/* card notes: the operator's own words on one card, read back as fix prompts */
/* What the listing says (by request, 2026-09-22): four lines, and "More" opens the rest — plus, on a tile, the
   flag chips the row shows inline. The summary IS the clamped text, so a click anywhere on it opens it too. */
.about { margin: 8px 0 0; }
.about > summary { list-style: none; cursor: pointer; }
.about > summary::-webkit-details-marker { display: none; }
.about .blurb { margin: 0; display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
.about[open] .blurb { display: block; -webkit-line-clamp: unset; overflow: visible; white-space: pre-line; }
.about .toggle { display: inline-block; margin-top: 2px; font-size: 12px; font-weight: 600; color: var(--accent); }
.about .toggle::after { content: "More ▾"; }
.about[open] .toggle::after { content: "Less ▴"; }
.about .chips { margin-top: 6px; }
.tile .about { margin: 0; }
/* The feed card's feedback (by request, 2026-09-22): thumbs up / down for "was this analysis correct?", then
   the notes box. The lit thumb is the card's latest answer — green for right, red for wrong. */
.feedback { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; flex: 1 1 auto; min-width: 0; }
.feedback .thumbs { display: inline-flex; gap: 4px; }
.feedback .thumb { display: inline-flex; align-items: center; padding: 4px 8px; color: var(--muted); background: var(--surface);
                   border: 1px solid var(--line); }
.feedback .thumb.down svg { transform: rotate(180deg); }
.feedback .thumb:hover { color: var(--ink); border-color: var(--accent); }
.feedback .thumb.up.on { color: var(--buy); border-color: var(--buy); }
.feedback .thumb.down.on { color: var(--danger); border-color: var(--danger); }
.feedback input[name=body] { flex: 1 1 220px; min-width: 0; max-width: 520px; margin: 0; padding: 5px 8px; font-size: 13px; }
.feedback .save { padding: 5px 10px; font-size: 13px; }
.feedback .saved, .feedback .count { font-size: 12px; color: var(--muted); }
.feedback .saved { color: var(--buy); font-weight: 600; }
.feedback .error { font-size: 13px; margin: 0; }
/* A tile is narrow: thumbs and the note count on one line, the box and Save on the next (the ::before is the
   line break, ordered between them). */
.tile .feedback::before { content: ""; flex-basis: 100%; order: 2; }
.tile .feedback .saved, .tile .feedback .count, .tile .feedback .error { order: 1; }
.tile .feedback input[name=body] { flex: 1 1 0; max-width: none; order: 3; }
.tile .feedback .save { order: 4; }
/* An unseen first submit: Enter in a notes box files a plain note, never the thumb / Yes that would otherwise
   be the form's default button. */
.implicit { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0);
            border: 0; }
.notebox { display: flex; align-items: center; gap: 6px; }
.notebox input { padding: 5px 8px; font-size: 13px; width: 240px; max-width: 40vw; }
.notebox button { padding: 5px 10px; font-size: 13px; }
.notes { display: block; }
.notes ul { list-style: none; padding: 0; margin: 0 0 8px; display: flex; flex-direction: column; gap: 6px; }
.notes li { font-size: 13px; background: var(--soft); border: 1px solid var(--line); border-radius: 6px; padding: 6px 10px; }
.notes li time { color: var(--muted); font-size: 12px; margin-left: 6px; }
/* The CTA ("Was this analysis correct?", 2026-09-22) stacks above the note field inside ONE form, so the
   form itself becomes a column and each row keeps the old inline layout. */
.notes form { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.notes form .line { display: flex; align-items: center; gap: 6px; }
.notes .ask { margin: 0; font-size: 13px; font-weight: 600; }
.notes .say { min-width: 52px; }
.notes li .said { font-size: 11px; text-transform: uppercase; letter-spacing: .4px; margin-right: 6px; }
.notes li .said.yes { color: var(--buy); }
.notes li .said.no { color: var(--danger); }
.notes .error { color: var(--danger); font-size: 13px; }

/* Settings → Alerts: the tenant's own delivery endpoints (spec §9). One row per webhook — its name, the
   last characters of its token (the URL itself is a credential and never reaches this page) and the three
   actions — then the add form and the email address under it. */
.hooks { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 6px; }
.hooks li { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 10px; background: var(--soft); border-radius: 8px; }
.hooks .name { font-weight: 650; }
.hooks .tail { color: var(--muted); font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.hooks .act { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; }
.hooks form { margin: 0; }
.hooks button { padding: 5px 10px; font-size: 13px; }
.addhook { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) auto; gap: 8px; align-items: end; margin-top: 10px; }
.addhook label { margin: 0; }
.addhook button { white-space: nowrap; }
.mailtest { margin-top: 8px; }
details.help { margin-top: 10px; }
details.help summary { cursor: pointer; color: var(--accent-text); font-size: 13px; }
details.help ol { margin: 8px 0 0; padding-left: 20px; color: var(--muted); font-size: 13px; }
details.help b { color: var(--ink); }
code { background: var(--soft); border-radius: 4px; padding: 1px 5px; font-size: 12px; }
@media (max-width: 620px) {
  .addhook { grid-template-columns: 1fr; }
  .hooks .act { margin-left: 0; }
}

/* The legal footer: muted, narrow, under the page; main already pads 60px at the bottom. */
footer.legal { max-width: 62em; margin: 0; padding: 0 24px 24px; font-size: 12px; line-height: 1.5; color: var(--muted); }
footer.legal a { color: var(--muted); text-decoration: underline; }
/* The legal pages are prose: give paragraphs and lists reading room inside the narrow sheet. */
.legal p, .legal li { line-height: 1.55; }
.sheet.legal h2 { text-transform: none; letter-spacing: 0; font-size: 15px; color: var(--ink); margin-top: 22px; }
/* /disclosure, /terms and /privacy are prose, not a login form: .sheet.narrow caps at 460px (:166), which
   wraps every sentence to a sliver. Specificity 0-3-0 beats .sheet.narrow's 0-2-0 without touching it or
   the templates' `narrow` class (controller ruling 2026-09-22, task 4). */
.sheet.narrow.legal { max-width: 640px; }
