/* ============================================================
   Ad Maker — Competitors pane
   Same tokens as styles.css (no new colors, no new fonts).
   The Board and the Competitors pane share the right column:
   only one of them is visible at a time.
   ============================================================ */

/* --- the tab bar doubles as the right-column switch on wide screens --- */
.tabbar { display: flex; }

@media (min-width: 1024px) {
  #tabChat { display: none; }                 /* both panes are visible anyway */
  .layout > #chatPane { grid-column: 1; grid-row: 1; }
  .layout > #boardPane,
  .layout > #competitorsPane { grid-column: 2; grid-row: 1; }
}

/* ------------------------------ pane shell ------------------------------ */

.cmp-pane { position: relative; }

.cmp-hd {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding: var(--s3);
  border-bottom: 1px solid var(--border);
  background: var(--panel-2);
}

/* ------------------------------ search ------------------------------ */

.cmp-searchrow { display: flex; gap: var(--s2); position: relative; }
.cmp-searchrow .input { flex: 1 1 auto; min-width: 0; }

.cmp-results {
  position: absolute;
  z-index: 40;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 340px;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--border-2);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
  padding: var(--s1);
}
.cmp-results[hidden] { display: none; }

.cmp-result {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 6px var(--s2);
  border-radius: var(--r-sm);
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  font: inherit;
  color: var(--text);
  cursor: default;
}
.cmp-result:hover { background: var(--panel-3); }
.cmp-result-main { min-width: 0; flex: 1 1 auto; }
.cmp-result-name { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-result-sub { font-size: 11.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-result .btn { flex: 0 0 auto; }
.cmp-results-msg { padding: var(--s2); font-size: 12.5px; color: var(--muted); }

.cmp-avatar {
  flex: 0 0 auto;
  width: 30px; height: 30px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--panel-3);
  border: 1px solid var(--border);
  display: block;
}
.cmp-avatar-ph {
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: var(--muted);
}

/* ------------------------------ saved chips ------------------------------ */

.cmp-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 0; padding: 2px 0 4px; }
.cmp-chips .cmp-stale-hint { flex-basis: 100%; }
.cmp-chips[hidden] { display: none; }

/* The logo strip. Identity only: a row of round marks, overlapped like a
   team. The name floats in on hover as an OVERLAY — transform and opacity,
   never width — so pointing at one moves nothing else (the old chips animated
   max-width on hover and re-laid-out the strip on every pointer move: the
   lag the owner reported). The owner asked for this motion explicitly. */
.cmp-logo {
  position: relative; flex: 0 0 auto;
  width: 34px; height: 34px; padding: 0;
  margin-left: -7px;
  border: 0; border-radius: 50%; background: none; cursor: pointer;
  transition: transform .15s ease;
}
.cmp-logo:first-child { margin-left: 0; }
.cmp-logo .cmp-avatar {
  width: 32px; height: 32px; border-radius: 50%;
  border: 2px solid var(--panel);
  box-shadow: 0 0 0 1px var(--border);
}
.cmp-logo:hover { transform: translateY(-2px) scale(1.08); z-index: 3; }
.cmp-logo:focus-visible { outline: none; box-shadow: var(--focus); z-index: 3; }
@media (prefers-reduced-motion: reduce) { .cmp-logo { transition: none; } .cmp-logo:hover { transform: none; } }

/* chosen = the ring; the accent is a state here, not decoration */
.cmp-logo.is-on { z-index: 2; }
.cmp-logo.is-on .cmp-avatar { box-shadow: 0 0 0 2px var(--accent); }
.cmp-logo.is-error .cmp-avatar { box-shadow: 0 0 0 2px var(--danger); }

/* the name, on demand, floating — no layout participation at all */
.cmp-logo-name {
  position: absolute; top: calc(100% + 6px); left: 50%;
  transform: translate(-50%, -2px);
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-xs); box-shadow: var(--shadow);
  padding: 3px 9px;
  font-size: 11px; font-weight: 600; color: var(--text); white-space: nowrap;
  opacity: 0; pointer-events: none; z-index: 5;
  transition: opacity .12s ease, transform .12s ease;
}
.cmp-logo:hover .cmp-logo-name,
.cmp-logo:focus-visible .cmp-logo-name { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) { .cmp-logo-name { transition: none; } }

/* fetching / failed sit on the mark itself: never hover-gated */
.cmp-logo .cmp-dot {
  position: absolute; top: -1px; right: -1px;
  width: 10px; height: 10px;
  border: 2px solid var(--panel);
}

.cmp-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--muted); flex: 0 0 auto;
}
.cmp-dot-fetching { background: var(--warn); animation: cmp-pulse 1.1s ease-in-out infinite; }
.cmp-dot-error { background: var(--danger); }
@keyframes cmp-pulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .cmp-dot-fetching { animation-duration: 3s; } }

/* ------------------------------ filters ------------------------------ */

.cmp-filters { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cmp-filters .sel, .cmp-filters .input {
  max-width: 190px; font-size: 12.5px; padding: 3px 8px;
}
.cmp-f {
  font: inherit; font-size: 12px;
  background: none; border: 1px solid transparent; border-radius: 999px;
  color: var(--muted); padding: 3px 10px; cursor: pointer;
}
.cmp-f:hover { background: var(--panel-3); color: var(--text); }
.cmp-f.is-active { background: var(--panel-3); color: var(--text); font-weight: 600; }
.cmp-count {
  margin-left: auto; font-family: var(--mono); font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Images vs video sits beside All/Active/Favorites but answers a different
   question, so a hairline keeps the two groups from reading as one row of six. */
.cmp-fsep {
  width: 1px; align-self: stretch; margin: 2px 4px;
  background: var(--border);
}
.cmp-media { display: flex; align-items: center; gap: 4px; }
.cmp-m { font-variant-numeric: tabular-nums; }
.cmp-m[disabled] { opacity: .4; cursor: default; }
.cmp-m[disabled]:hover { background: none; color: var(--muted); }

/* ------------------------------ ad grid ------------------------------ */

.cmp-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--s3); scrollbar-width: thin; }

.cmp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
  gap: var(--s3);
}

.cmp-card {
  /* the favourite star is a SIBLING of the thumb now (AUDIT.md A3), so the
     card is what it positions against */
  position: relative;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.cmp-card:hover { border-color: var(--border-2); }
/* Offscreen cards skip layout+paint — 1,500+ ads made scrolling janky.
   auto lets the browser remember each card's real height after first paint. */
.cmp-card, .cmp-dcard { content-visibility: auto; contain-intrinsic-size: auto 360px; }

.cmp-thumb {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  background: var(--panel-3);
  border: 0;
  padding: 0;
  cursor: zoom-in;
  overflow: hidden;
}
.cmp-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cmp-thumb-ph {
  display: flex; align-items: center; justify-content: center; height: 100%;
  color: var(--muted); font-size: 12px; text-align: center; padding: var(--s3);
}

.cmp-badges { position: absolute; top: 6px; left: 6px; display: flex; gap: 4px; flex-wrap: wrap; }
.cmp-badge {
  font-size: 10px; font-weight: 600; letter-spacing: .02em;
  padding: 1px 6px; border-radius: 999px;
  background: rgba(10,12,16,.72); color: #fff; backdrop-filter: blur(2px);
}
/* AUDIT.md A4 — white on --ok measured 1.94:1 (it is a LIGHT green in dark
   mode, so white all but vanished). Dark ink on the green clears AA. */
/* "Active" is LIVE, not DONE. Green is the app's finished/ok colour and this
   badge was borrowing it, which put "still running" in the same voice as "saved
   successfully". Teal is the fourth hue and carries exactly one meaning:
   money is being spent on this right now. */
.cmp-badge-active { background: var(--live); color: #04231E; }
.cmp-badge-video { background: rgba(10,12,16,.72); }

/* Longevity. Meta publishes no reach or spend for non-EU advertisers, so how long
   an ad has run is the only evidence it earns its budget: past 30 days the
   advertiser is choosing to keep paying, and the badge says so loudly. */
.cmp-badge-run { background: rgba(10,12,16,.72); font-variant-numeric: tabular-nums; }
.cmp-badge-run.is-proven {
  background: linear-gradient(180deg, #f6c453, #e0a32e);
  color: #23180a;
  box-shadow: 0 0 0 1px rgba(255,255,255,.18) inset;
}
.cmp-badge-reach { background: rgba(10,12,16,.72); font-variant-numeric: tabular-nums; }
/* How many variations Meta collated under this one archive id. Quiet on
   purpose: it is supporting evidence next to run length, not the headline. */
.cmp-badge-versions { background: rgba(10,12,16,.72); font-variant-numeric: tabular-nums; }

/* The card shows the mirrored poster frame and the play mark promises the real
   thing: clicking opens the lightbox, where a <video> is waiting. Styled to read
   as a button — solid, ringed, lifting on hover — not as a passive badge. */
.cmp-play {
  position: absolute; inset: 0; margin: auto;
  width: 42px; height: 42px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: rgba(10,12,16,.55);
  box-shadow: 0 0 0 1.5px rgba(255,255,255,.7) inset, 0 2px 10px rgba(0,0,0,.35);
  color: #fff; line-height: 0;
  padding-left: 2px; /* optically centre the triangle */
  pointer-events: none;
  transition: background .12s ease, transform .12s ease;
}
.cmp-thumb:hover .cmp-play,
.cmp-thumb:focus-visible .cmp-play { background: rgba(10,12,16,.85); transform: scale(1.1); }
/* A playable card gets the zoom cursor swapped for a pointer — the old zoom
   cursor was the whole complaint: it promised a bigger picture, not playback. */
.cmp-thumb.is-video { cursor: pointer; }
@media (prefers-reduced-motion: reduce) {
  .cmp-play { transition: none; }
  .cmp-thumb:hover .cmp-play { transform: none; }
}

.cmp-stale-hint {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11.5px; color: var(--muted);
}

.cmp-star {
  position: absolute; top: 5px; right: 5px;
  width: 26px; height: 26px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: rgba(10,12,16,.6);
  color: #fff; line-height: 1; cursor: pointer;
}
.cmp-star:hover { background: rgba(10,12,16,.85); }
.cmp-star .ico { width: 14px; height: 14px; }
/* Fixed red, not a theme token: like the badge inks, this sits on the card's
   own dark scrim over a photograph, the same in both themes. Filled = saved. */
.cmp-star.is-on { color: #ff4d6d; }
.cmp-star.is-on .ico { fill: currentColor; }
@keyframes cmp-heart-pop {
  0% { transform: scale(1); }
  40% { transform: scale(1.4); }
  70% { transform: scale(.9); }
  100% { transform: scale(1); }
}
.cmp-star.is-pop .ico { animation: cmp-heart-pop .34s ease; }
@media (prefers-reduced-motion: reduce) { .cmp-star.is-pop .ico { animation: none; } }

.cmp-body {
  flex: 1 1 auto;
  padding: var(--s2) var(--s3) var(--s3);
  display: flex; flex-direction: column; gap: 5px; min-width: 0;
}
.cmp-who { display: flex; align-items: baseline; gap: 6px; font-size: 11.5px; color: var(--muted); }
.cmp-who-name { font-weight: 600; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-who-date { margin-left: auto; white-space: nowrap; font-variant-numeric: tabular-nums; }

/* The headline is the one line that differs between 255 ads, so it leads the
   card. The body copy moved to the lightbox entirely — see createCard(). */
.cmp-headline {
  font-size: 13.5px; font-weight: 600; line-height: 1.35; color: var(--text);
  overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
/* ---- the Ads / Tracking subcategory ---- */
.cmp-subnav {
  display: flex; align-items: center; gap: var(--s1);
}
.cmp-subtabs {
  display: flex; align-items: center; gap: var(--s1);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--s3);
}
.cmp-subnav-spacer { flex: 1 1 auto; }
.cmp-sub {
  font: inherit; font-size: 13px; font-weight: 500;
  color: var(--muted); background: none; border: 0;
  padding: 6px 12px; margin-bottom: 2px; border-radius: var(--r-sm); cursor: pointer;
}
.cmp-sub:hover { color: var(--text); background: var(--panel-3); }
.cmp-sub.is-sel { color: var(--text); background: var(--panel-3); font-weight: 600; }
.cmp-sub:focus-visible { outline: none; box-shadow: var(--focus); }

/* ---- card actions: one button, then icon-only tools ---- */
.cmp-icon-btn {
  width: 30px; height: 30px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm); cursor: pointer; padding: 0;
  background: var(--panel); border: 1px solid var(--border);
  color: var(--text-2); text-decoration: none;
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}
.cmp-icon-btn .ico { width: 15px; height: 15px; }
.cmp-icon-btn:hover { background: var(--panel-3); color: var(--text); border-color: var(--border-2); }
.cmp-icon-btn:focus-visible { outline: none; box-shadow: var(--focus); }
.cmp-icon-btn:disabled { opacity: .5; cursor: default; }
.cmp-icon-btn.is-busy .ico { animation: spin 1s linear infinite; }
@media (prefers-reduced-motion: reduce) { .cmp-icon-btn.is-busy .ico { animation: none; } }

/* ---- the transcript, in the lightbox's copy column ---- */
.cmp-lb-transcript { margin-top: var(--s4); border-top: 1px solid var(--border); padding-top: var(--s3); }
.cmp-lb-transcript h4 {
  margin: var(--s3) 0 6px; font-size: 12px; font-weight: 600; color: var(--text);
}
.cmp-lb-transcript h4:first-child { margin-top: 0; }
.cmp-lb-transcript p {
  margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--text-2);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.cmp-lb-tmeta, .cmp-lb-tnote, .cmp-lb-tmodel {
  font-size: 11px !important; color: var(--muted) !important;
}
.cmp-lb-tnote { margin-top: 4px !important; font-style: italic; }

/* The one piece of colour this panel earns. An unclear transcript that LOOKS
   clean is the failure mode that matters: the owner would paste a hook that was
   never said. Warn tone, because it is a caveat and not a broken thing. */
.cmp-lb-twarn {
  margin: 6px 0 0 !important;
  padding: 6px 8px;
  border: 1px solid var(--warn-line);
  border-radius: var(--r-sm);
  background: var(--warn-soft);
  color: var(--warn) !important;
  font-size: 11.5px !important;
}

/* A prompt to go and listen, not an alarm: this is a hint built from two
   imperfect readings of the same ad, so it must not look like a defect report. */
.cmp-lb-tcheck {
  margin: 6px 0 0 !important;
  font-size: 11.5px !important;
  color: var(--muted) !important;
  line-height: 1.6;
}

.cmp-lb-tlines { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; }

/* Each line is a button that seeks the video. Stripped back to text: it has to
   read as a transcript, not as a list of controls. */
.cmp-lb-tline {
  display: flex;
  gap: 8px;
  align-items: baseline;
  width: 100%;
  padding: 4px 6px;
  border: 0;
  border-inline-start: 2px solid transparent;
  border-radius: var(--r-sm);
  background: none;
  font: inherit;
  text-align: start;
  cursor: pointer;
  transition: background .12s ease, color .12s ease;
}
.cmp-lb-tline:hover { background: var(--panel-3); }
.cmp-lb-tline:focus-visible { outline: none; box-shadow: var(--focus); }

/* The line being spoken. Dimming the rest is what makes this readable while
   the audio runs — the eye needs one place to be, the way a lyric display
   works. The accent is earned here: it tracks something true and live. */
.cmp-lb-tlines:has(.is-now) .cmp-lb-tline { color: var(--muted); }
.cmp-lb-tline.is-now {
  border-inline-start-color: var(--accent);
  background: var(--accent-soft);
}
.cmp-lb-tline.is-now .cmp-lb-ttext { color: var(--text); font-weight: 500; }
.cmp-lb-tline.is-now .cmp-lb-tstart { color: var(--accent-ink); }
.cmp-lb-tstart {
  flex: 0 0 auto; min-width: 34px;
  font-family: var(--mono); font-size: 10.5px; color: var(--muted);
}
.cmp-lb-ttext { flex: 1 1 auto; min-width: 0; }
.cmp-lb-tlang {
  flex: 0 0 auto; font-size: 10px; color: var(--muted);
  border: 1px solid var(--border); border-radius: var(--r-pill); padding: 1px 6px;
}
/* A single half-heard WORD, kept in place rather than dropped. Underlined and
   warm-toned: the sentence still reads, but the doubtful word is impossible to
   mistake for a confident one. */
.cmp-lb-guess {
  color: var(--warn);
  text-decoration: underline wavy var(--warn);
  text-underline-offset: 3px;
  cursor: help;
}

/* Unsure lines read as unsure — dimmed and dashed, not presented as fact. */
.cmp-lb-tline.is-low .cmp-lb-ttext {
  color: var(--muted); border-bottom: 1px dashed var(--border-2);
}
.cmp-lb-tline.is-medium .cmp-lb-ttext { color: var(--text-2); }

.cmp-lb-tscreen { display: flex; flex-direction: column; gap: 4px; }
.cmp-lb-tscreen p { font-size: 12px !important; color: var(--muted) !important; }

.cmp-lb-tfoot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s2); margin-top: var(--s3);
}

.cmp-actions { display: flex; gap: 5px; margin-top: auto; padding-top: 3px; }
.cmp-actions .btn { font-size: 12px; padding: 5px 9px; flex: 1 1 auto; }
.cmp-link { font-size: 11px; color: var(--muted); text-decoration: none; align-self: center; padding: 0 4px; }
.cmp-link:hover { color: var(--accent-ink); }

/* ------------------------------ empty states ------------------------------ */

.cmp-empty { text-align: center; padding: var(--s6) var(--s4); color: var(--muted); }
.cmp-empty[hidden] { display: none; }
.cmp-empty-title { margin: 0 0 var(--s1); font-weight: 600; color: var(--text-2); }
.cmp-empty-sub { margin: 0 auto; font-size: 13px; max-width: 46ch; line-height: 1.5; }
.cmp-empty-sub code {
  font-family: var(--mono); font-size: 12px;
  background: var(--panel-3); border: 1px solid var(--border);
  border-radius: var(--r-xs); padding: 0 4px;
}
.cmp-empty-sub a { color: var(--accent-ink); }

/* ------------------------------ lightbox ------------------------------ */

.cmp-lb { position: fixed; inset: 0; z-index: 160; }
.cmp-lb[hidden] { display: none; }
.cmp-lb-backdrop { position: absolute; inset: 0; background: rgba(8,10,14,.84); backdrop-filter: blur(2px); }
.cmp-lb-shell {
  position: relative;
  height: 100%;
  display: flex;
  align-items: stretch;
  gap: var(--s4);
  padding: var(--s5) var(--s5) var(--s5) var(--s5);
  pointer-events: none;
}
.cmp-lb-shell > * { pointer-events: auto; }
.cmp-lb-stage { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; justify-content: center; }
.cmp-lb-stage img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: var(--r); }

/* The player. `preload="none"` in the markup is what keeps the MP4 off the wire
   until the owner presses play; the poster is the JPEG the grid already has, so
   the stage never flashes empty. */
.cmp-lb-stage video {
  max-width: 100%; max-height: 100%;
  border-radius: var(--r);
  background: #000;
  outline: none;
}

/* Meta signs its media for ~105 h. When that lapses we say so and offer the two
   things that actually help, instead of a broken player. */
.cmp-lb-gone {
  max-width: 420px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s5);
  text-align: center;
}
.cmp-gone-title { margin: 0 0 var(--s2); font-size: 15px; font-weight: 600; color: var(--text); }
.cmp-gone-sub { margin: 0 0 var(--s4); font-size: 13px; color: var(--text-2); line-height: 1.5; }
.cmp-gone-actions { display: flex; gap: var(--s2); justify-content: center; flex-wrap: wrap; }
.cmp-lb-side {
  flex: 0 0 320px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s4);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}
.cmp-lb-side h3 { margin: 0; font-size: 15px; }

/* who + where in the deck: name, status chip, and the counter in mono */
.cmp-lb-hd { display: flex; align-items: center; gap: var(--s2); }
.cmp-lb-status {
  flex: none; font-size: 10.5px; font-weight: 600;
  border-radius: var(--r-pill); padding: 1px 8px;
  background: var(--panel-3); color: var(--muted);
  border: 1px solid var(--border-2);
}
.cmp-lb-status.is-on { background: var(--ok-soft); color: var(--ok); border-color: var(--ok-line); }
.cmp-lb-count { flex: none; font-family: var(--mono); font-size: 11px; color: var(--muted); }

/* the ad's own words: direction follows the text, long captions fold */
.cmp-lb-copy {
  font-size: 13px; color: var(--text-2); line-height: 1.6;
  white-space: pre-wrap; overflow-wrap: anywhere;
  unicode-bidi: plaintext; text-align: start;
}
.cmp-lb-copy.is-clamped {
  display: -webkit-box; -webkit-line-clamp: 7; -webkit-box-orient: vertical;
  overflow: hidden;
}
.cmp-lb-more { align-self: flex-start; margin-top: -4px; }

/* measured facts: one hairline row each, micro-label left, value right —
   the preview panel's fact grammar, not a loose stack of grey words */
.cmp-lb-facts {
  display: flex; flex-direction: column;
  border-top: 1px solid var(--border);
  margin-top: var(--s1);
}
.cmp-lb-row {
  display: flex; align-items: baseline; gap: var(--s2);
  padding: 6px 0; border-bottom: 1px solid var(--border);
  font-size: 12px;
}
.cmp-lb-k {
  flex: 0 0 84px;
  font-size: 10px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted);
}
.cmp-lb-v { color: var(--text-2); overflow-wrap: anywhere; margin-left: auto; text-align: right; }
.cmp-lb-v-mono { font-family: var(--mono); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }

/* the ways out, in the app's quiet-link voice */
.cmp-lb-quiet { display: flex; align-items: center; gap: var(--s3); padding: 2px 0; }
.cmp-lb-close {
  position: absolute; top: 10px; right: 10px;
  width: 34px; height: 34px; border: 0; border-radius: 50%;
  background: rgba(255,255,255,.14); color: #fff; font-size: 21px; line-height: 1; cursor: pointer;
  z-index: 2;
}
.cmp-lb-close:hover { background: rgba(255,255,255,.24); }

@media (max-width: 860px) {
  .cmp-lb-shell { flex-direction: column; padding: var(--s3); }
  .cmp-lb-side { flex: 0 0 auto; max-height: 42vh; }
}

/* ============================================================
   Competitor tracker — the bar, the eye, the bell, the NEW badge.
   Same tokens as everything above; no new colors, no new fonts.
   The bell lives in THIS pane's header because the global header
   belongs to app.js. Its badge is mirrored into document.title,
   which is what carries the count when the window is behind
   something else. No browser Notification API on purpose.
   ============================================================ */

/* Tracking is its own tab now, so it gets a page rather than a full-bleed
   strip with a screen of nothing under it. Bounded width: these are settings,
   and settings stretched to 1400px read as a toolbar you cannot dismiss. */
.cmp-trackpage {
  /* a sibling of .cmp-scroll, not a child of the header — so it carries the
     same gutter and does its own scrolling */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--s3);
  padding: var(--s3);
}
/* Was 640px, sized for when the creative rail took the right third of the
   screen. The rail now stays in the Studio, so a 640px list left half the page
   empty at 1440. It uses the width — capped where a row's name and its Watch
   button would drift too far apart to read as one line. */
.cmp-trackpage > * { max-width: 1080px; width: 100%; flex: none; }
.cmp-track-card {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding: var(--s3);
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--panel);
}
.cmp-track-main { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; min-width: 0; }
.cmp-track-main .sel { max-width: 150px; }
.cmp-track-state {
  margin: 0;
  font-size: 11.5px;
  color: var(--muted);
}

/* ---------------------------- watched roster ---------------------------- */

.cmp-track-h {
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--muted);
}
/* one bordered list, hairlines between rows — the res-docs grammar, not a
   stack of individually framed cards */
.cmp-track-list {
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--panel); overflow: hidden;
}
.cmp-track-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s3);
  border-top: 1px solid var(--border);
}
.cmp-track-row:first-child { border-top: 0; }
.cmp-track-tools { display: flex; align-items: center; gap: 2px; flex: none; }
.cmp-track-tools .cmp-icon-btn.is-danger:hover { color: var(--danger); background: var(--danger-soft); }
/* Watching is a chosen state: fill + weight, like every chosen thing */
.cmp-track-row .btn.is-on { background: var(--panel-3); font-weight: 600; }
.cmp-track-row .cmp-avatar { width: 26px; height: 26px; }
.cmp-track-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
/* A phone: the name and figures get the row's full width, and the controls
   (tools, View ads, Watch) move to a line of their own beneath them. With
   View ads added they had crushed the name to a word per line. */
@media (max-width: 600px) {
  .cmp-track-row { flex-wrap: wrap; row-gap: var(--s2); }
  .cmp-track-who { flex: 1 1 calc(100% - 40px); }
  .cmp-track-row .cmp-track-tools { margin-left: 34px; }
  .cmp-track-row .cmp-track-view { margin-left: auto; }
}
.cmp-track-name { font-size: 13px; font-weight: 600; color: var(--text); }
.cmp-track-when { font-size: 11.5px; color: var(--muted); }
/* Labelled figures: each one names itself. An estimate is set apart — dashed
   underline, italic, the "(est.)" in its label — so it never reads as one of
   our exact counts. */
.cmp-track-stats {
  display: flex; flex-wrap: wrap; gap: 2px 14px; margin: 2px 0 0;
  font-size: 11.5px; line-height: 1.45;
}
.cmp-stat { display: flex; gap: 4px; min-width: 0; }
.cmp-stat dt { color: var(--muted); }
.cmp-stat dt::after { content: ':'; }
.cmp-stat dd { margin: 0; color: var(--text-2); font-variant-numeric: tabular-nums; }
.cmp-stat.is-est dd {
  font-style: italic; color: var(--muted);
  text-decoration: underline dashed; text-underline-offset: 3px; text-decoration-thickness: 1px;
}
.cmp-track-empty-acts { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); }
.cmp-track-empty { margin: 0; font-size: 12.5px; color: var(--muted); }

/* The one sentence this feature owes the owner. It is not a tooltip and it is
   not fine print: a tracker that stayed quiet about the half it cannot see
   would read as "they have launched nothing", which is a lie. */
.cmp-track-note {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--muted);
}

/* ------------------------------ the switch ------------------------------ */

.cmp-switch { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; user-select: none; }
.cmp-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.cmp-switch-track {
  width: 34px; height: 19px;
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--panel-3);
  border: 1px solid var(--border-2);
  position: relative;
  transition: background .14s ease, border-color .14s ease;
}
.cmp-switch-knob {
  position: absolute; top: 2px; left: 2px;
  width: 13px; height: 13px;
  border-radius: 50%;
  background: var(--panel);
  box-shadow: var(--shadow-xs);
  transition: transform .14s ease;
}
.cmp-switch input:checked + .cmp-switch-track { background: var(--accent-btn); border-color: var(--accent-btn); }
.cmp-switch input:checked + .cmp-switch-track .cmp-switch-knob { transform: translateX(15px); }
.cmp-switch input:focus-visible + .cmp-switch-track { box-shadow: var(--focus); }
.cmp-switch-text { font-size: 12.5px; font-weight: 600; color: var(--text); white-space: nowrap; }
@media (prefers-reduced-motion: reduce) {
  .cmp-switch-track, .cmp-switch-knob { transition: none; }
}

/* -------------------------------- the bell ------------------------------- */

.cmp-bellwrap { position: relative; flex: 0 0 auto; margin-left: auto; }
.cmp-bell {
  position: relative;
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  line-height: 1;
  cursor: pointer;
}
.cmp-bell:hover { background: var(--panel-3); }
.cmp-bell:focus-visible { box-shadow: var(--focus); outline: none; }
.cmp-bell.has-unread { border-color: var(--accent-line); background: var(--accent-soft); }
.cmp-bell-ico { width: 15px; height: 15px; color: var(--muted); }
.cmp-bell.has-unread .cmp-bell-ico { color: var(--text); }
.cmp-bell-badge {
  position: absolute; top: -6px; right: -6px;
  min-width: 17px; height: 17px;
  padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px;
  background: var(--accent-btn);
  color: var(--accent-fg);
  font-size: 10px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--panel-2);
}
.cmp-bell-badge[hidden] { display: none; }

.cmp-bell-menu {
  position: absolute;
  z-index: 45;
  top: calc(100% + 6px);
  right: 0;
  width: min(380px, calc(100vw - 32px));
  max-height: 460px;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--border-2);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
  padding: var(--s2);
}
.cmp-bell-menu[hidden] { display: none; }
.cmp-bell-hd {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s2);
  padding: 2px var(--s1) var(--s2);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--s1);
}
.cmp-bell-title { font-size: 12.5px; font-weight: 600; color: var(--text); }
.cmp-bell-empty { padding: var(--s3) var(--s2); font-size: 12.5px; color: var(--muted); line-height: 1.5; }
.cmp-bell-note {
  padding: var(--s2) var(--s1) 2px;
  font-size: 11px;
  line-height: 1.45;
  color: var(--muted);
  border-top: 1px solid var(--border);
  margin-top: var(--s1);
}
.cmp-bell-empty .cmp-bell-note { border: 0; padding-left: 0; padding-right: 0; }
.cmp-bell-list { display: flex; flex-direction: column; }

.cmp-notif {
  display: flex; align-items: flex-start; gap: var(--s2);
  width: 100%;
  padding: var(--s2);
  border: 0; border-radius: var(--r-sm);
  background: none;
  font: inherit; text-align: left; color: var(--text);
  cursor: pointer;
}
.cmp-notif:hover { background: var(--panel-3); }
.cmp-notif:focus-visible { box-shadow: var(--focus); outline: none; }
.cmp-notif .cmp-avatar { width: 26px; height: 26px; }
.cmp-notif-dot {
  flex: 0 0 auto;
  width: 7px; height: 7px; margin-top: 9px;
  border-radius: 50%;
  background: var(--accent);
}
.cmp-notif-dot.is-read { background: transparent; }
.cmp-notif-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.cmp-notif-head {
  font-size: 12.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmp-notif.is-unread .cmp-notif-head { color: var(--text); }
.cmp-notif:not(.is-unread) .cmp-notif-head { color: var(--text-2); font-weight: 500; }
.cmp-notif-sub {
  font-size: 11.5px; color: var(--text-2); line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.cmp-notif-meta { font-size: 10.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.cmp-notif-thumb {
  flex: 0 0 auto;
  width: 40px; height: 50px;
  object-fit: cover;
  border-radius: var(--r-xs);
  background: var(--panel-3);
  border: 1px solid var(--border);
}
.cmp-notif.is-error .cmp-notif-head { color: var(--danger); }
.cmp-notif.is-error .cmp-notif-dot { background: var(--danger); }

/* ------------------------------- NEW badge ------------------------------- */

/* The only badge on a card that means "this is news": the tracker watched it
   appear between two scans, less than 48 hours ago. */
.cmp-badge-new {
  background: var(--accent-btn);
  color: var(--accent-fg);
  box-shadow: 0 0 0 1px rgba(255,255,255,.22) inset;
}

@media (max-width: 640px) {
  .cmp-track-main { align-items: flex-start; }
}

/* `.btn-sm` is not in styles.css (which app.js owns) — the tracker bar needs one,
   so it is defined here, matching .btn's shape at the bar's 12.5px scale. */
.cmp-track-card .btn-sm { font-size: 12px; padding: 4px 9px; }

/* The bell, once app.js's sidebar offers it a home (see dockBell()): a narrow
   rail has no room for a 380px panel hanging off its right edge, so the
   dropdown opens the other way. */
.cmp-bellwrap.is-docked { margin-left: 0; }
.cmp-bellwrap.is-docked .cmp-bell-menu { left: 0; right: auto; }

/* The Saved / Discover switch. It sits above the search row because it decides
   what the search row MEANS — a brand name in one, a keyword in the other. */
.cmp-scope { display: inline-flex; gap: 4px; margin-bottom: var(--s2); }
.cmp-scope[hidden] { display: none; }

.cmp-searchrow[hidden], .cmp-filters[hidden], .cmp-discover[hidden],
.cmp-scroll[hidden], .cmp-dscroll[hidden] { display: none; }

/* ------------------------------- discover ---------------------------------
   Keyword + country, live from Meta's public library. It borrows the saved
   gallery's card shape on purpose — the same eye reads both — but keeps its own
   scroller, because these cards are a live view and those are your archive. */

.cmp-discover { display: flex; flex-direction: column; gap: var(--s2); }

/* Search, then five filters, then a count. On a narrow pane that is more than
   one row's worth, and `min-width: 0` let the search shrink to a sliver rather
   than wrap — the box was 4px wide beside the preview rail. It now keeps a
   usable width and the filters drop to a second line instead. */
.cmp-dsearch { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
.cmp-dsearch .input { flex: 1 1 260px; min-width: 200px; }
.cmp-dsearch .sel { flex: 0 1 auto; min-width: 0; }
.cmp-dsearch .cmp-count { flex: 0 0 auto; margin-left: auto; }

.cmp-dfilters[hidden] { display: none; }

.cmp-dview { display: inline-flex; gap: 4px; }

/* The Discover panel owns its scroll: the saved grid's scroller is hidden while
   this tab is up, so without this the results would grow the whole pane. */
.cmp-dscroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--s3);
  scrollbar-width: thin;
}

.cmp-dgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
  gap: var(--s3);
}
/* Advertiser rows carry a name and two numbers, not a headline — they read
   better a little wider and shorter than an ad card. */
.cmp-dgrid.is-advertisers { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }

/* ------------------------------- the masonry -------------------------------
   Equal columns, unequal cards. The placement is the script's job — see
   layoutMasonry for why neither CSS columns nor CSS grid can do it — so all the
   stylesheet owes it is a positioning context. The script sets width, left, top
   and the container's height. */
.cmp-masonry {
  position: relative;
  width: 100%;
  /* The footer's height, pinned. layoutMasonry adds exactly this to every
     card's media height instead of measuring, so the two must agree: 12px text
     at 1.2 line-height, plus 6px of padding, plus a 2px rounding allowance. */
  --foot-h: 26px;
}
.cmp-masonry .cmp-dcard {
  position: absolute;
  top: 0;
  left: 0;
  /* The script sets width, height, left and top. The card must not grow past
     what it was given, or it would overlap its neighbour again — which is the
     exact bug this layout replaced. */
  overflow: hidden;
}
/* The media fills whatever the card has left after the footer. The ratio lives
   in the SCRIPT now, not in an inline aspect-ratio: one source of truth, and a
   box that cannot disagree with the position it was placed at. */
.cmp-masonry .cmp-dthumb {
  aspect-ratio: auto;
  height: calc(100% - var(--foot-h));
  flex: none;
}
.cmp-masonry .cmp-dfoot {
  height: var(--foot-h);
  box-sizing: border-box;
}

.cmp-dcard {
  position: relative;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.cmp-dcard:hover { border-color: var(--border-2); }

/* In the masonry the card chrome all but disappears: 200 bordered boxes read as
   a spreadsheet, and the creatives are the thing you came to look at. The
   border arrives on hover, so the card is still a target you can see. */
.cmp-masonry .cmp-dcard {
  background: transparent;
  border-color: transparent;
  transition: transform .12s ease, box-shadow .12s ease;
}
.cmp-masonry .cmp-dcard:hover {
  border-color: transparent;
  transform: translateY(-2px);
}
.cmp-masonry .cmp-dcard:hover .cmp-dthumb {
  box-shadow: 0 6px 18px rgb(0 0 0 / 14%);
}

.cmp-dthumb {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  background: var(--panel-3);
  overflow: hidden;
}
.cmp-dadvert .cmp-dthumb { aspect-ratio: 16 / 10; }
.cmp-dthumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The masonry card already reserves the creative's real ratio, so the image
   fits INSIDE its box rather than being cropped to fill it. `contain` is what
   keeps a 9:16 story whole; `cover` is only still here for the few ads whose
   dimensions could not be read, where a crop beats a letterbox. */
.cmp-masonry .cmp-dthumb {
  border-radius: var(--r);
  transition: box-shadow .12s ease;
}
.cmp-masonry .cmp-dthumb img { object-fit: contain; }

/* The footer is a name, a number and a link. Nothing else fits in 232px
   without turning the grid into a table. */
.cmp-dfoot {
  display: flex;
  align-items: center;
  /* Tight between the icons: each .ib already carries its own padding inside
     a 24px box, and the 6px gap on top cost the advertiser's name the room it
     needed once the targets grew to the WCAG minimum. */
  gap: 0;
  padding: 6px 2px 0;
  min-width: 0;
}
.cmp-dfoot .cmp-dbrand, .cmp-dfoot .cmp-dage { margin-right: 6px; }
/* A narrow card (two columns on a phone): the advertiser's name takes its own
   line instead of shrinking to one letter beside three buttons. The footer's
   height for this is set by layoutMasonry (--foot-h), never guessed here. */
.cmp-masonry[data-narrow="1"] .cmp-dfoot { flex-wrap: wrap; align-content: flex-start; row-gap: 0; }
.cmp-masonry[data-narrow="1"] .cmp-dbrand { flex: 1 0 100%; margin-right: 0; line-height: 16px; }
.cmp-masonry[data-narrow="1"] .cmp-dage { margin-right: auto; }
.cmp-dbrand {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 1 1 auto;
}
.cmp-dage { font-size: 11px; color: var(--muted); white-space: nowrap; }
/* .cmp-dlink, .cmp-dtrack and .cmp-dsave are .ib icon buttons (styles.css);
   only what differs from the primitive lives here. */
.cmp-dlink:hover { color: var(--accent); }

/* Skeletons keep the columns at roughly the right height while a page is in
   flight, so the feed does not collapse and snap back on every scroll. */
.cmp-dcard.is-skeleton { pointer-events: none; }
.cmp-dcard.is-skeleton .cmp-dthumb {
  background: linear-gradient(100deg, var(--panel-3) 30%, var(--panel-2) 50%, var(--panel-3) 70%);
  background-size: 300% 100%;
  animation: cmp-shimmer 1.4s linear infinite;
}
@keyframes cmp-shimmer { to { background-position: -150% 0; } }
@media (prefers-reduced-motion: reduce) {
  .cmp-dcard.is-skeleton .cmp-dthumb { animation: none; }
  .cmp-masonry .cmp-dcard { transition: none; }
}

/* The Explore card carries the ad's own headline — the hook is the thing you
   are here to borrow. Two lines maximum, so a grid still reads down. */
.cmp-dhead {
  margin: 2px 0 0;
  font-size: 12px;
  line-height: 1.35;
  color: var(--text-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Badges sit ON the creative, bottom-left, so the run length is readable in a
   grid of two hundred without adding a line to every card. */
.cmp-dbadges {
  position: absolute;
  left: 6px;
  bottom: 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  max-width: calc(100% - 12px);
  pointer-events: none;
}
.cmp-dbadge {
  font-size: 10.5px;
  line-height: 1;
  padding: 4px 6px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  color: var(--text-2);
  backdrop-filter: blur(3px);
  white-space: nowrap;
}
/* 90 days is the line where an ad stops being a test and starts being a
   business. That is the whole signal this pane exists to surface. */
.cmp-dbadge.is-hot {
  background: var(--accent-soft);
  color: var(--accent-ink);
  border: 1px solid var(--accent-line);
  padding: 3px 5px;
}
.cmp-dbadge.is-off { opacity: 0.75; }

.cmp-dmore {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  padding: var(--s3) 0 var(--s2);
  flex-wrap: wrap;
}
.cmp-dmore[hidden] { display: none; }
.cmp-dmore-note { font-size: 12px; color: var(--muted); }
/* Shown once the fan-out is spent: an owner who keeps pressing a button that
   adds nothing concludes the tool is broken, not that Meta is done. */
.cmp-dspent { text-align: center; margin: 0 auto var(--s3); max-width: 60ch; }
.cmp-dspent[hidden] { display: none; }

/* ===================== Store Directory (Websites tab) =====================
   A full-width intelligence table: page header, view tabs, one filter
   toolbar, a results summary, then a real data table. Full width with a
   sensible cap; columns, not floating flex. Tokens only, one grammar for
   both themes. */
.cmp-dir {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-width: thin;
  /* breakpoints key off THIS pane's width, not the viewport — a right-hand
     preview panel narrows the pane well below the window width */
  container-type: inline-size;
}
.dir-wrap {
  width: 100%; max-width: 1500px; margin: 0 auto;
  padding: var(--s3) var(--s4) var(--s5);
  display: flex; flex-direction: column; gap: var(--s3);
}

/* A · page header */
.dir-hd { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; }
.dir-title { margin: 0; font-size: 20px; font-weight: 650; color: var(--text); line-height: 1.2; }
.dir-sub { margin: 3px 0 0; font-size: 13px; color: var(--muted); max-width: 62ch; }
.dir-hd-act { display: flex; align-items: center; gap: var(--s2); flex: none; }
.dir-progress { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.dir-progress[hidden] { display: none; }

/* B · view tabs — a quiet underline segmented control */
.dir-tabs { display: flex; align-items: stretch; gap: var(--s3); border-bottom: 1px solid var(--border); }
.dir-tab {
  font: inherit; font-size: 13.5px; font-weight: 550; color: var(--muted);
  background: none; border: 0; border-bottom: 2px solid transparent;
  padding: 8px 2px; margin-bottom: -1px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
}
.dir-tab:hover { color: var(--text); }
.dir-tab.is-active { color: var(--text); border-bottom-color: var(--text); }
.dir-tab:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-xs); }
.dir-tab-n {
  font-family: var(--mono); font-size: 11px; font-variant-numeric: tabular-nums;
  color: var(--muted); background: var(--panel-3); border-radius: var(--r-pill); padding: 0 6px; line-height: 16px;
}
.dir-tab.is-active .dir-tab-n { color: var(--text-2); }

/* C · toolbar — one row, consistent control height */
.dir-toolbar { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.dir-search { flex: 1 1 260px; min-width: 200px; display: block; }
.dir-search input {
  width: 100%; height: 34px; padding: 0 12px;
  font: inherit; font-size: 13px; color: var(--text);
  background: var(--panel); border: 1px solid var(--border-2); border-radius: var(--r-sm);
}
.dir-search input::placeholder { color: var(--muted); }
.dir-search input:focus-visible { outline: none; box-shadow: var(--focus); border-color: var(--border-2); }
.dir-toolbar .dir-sel { height: 34px; flex: none; max-width: 220px; }
.dir-toolbar .btn { height: 34px; flex: none; }

/* D · results summary */
.dir-summary { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2); }
.dir-count { font-size: 13px; color: var(--text-2); font-weight: 550; }
.dir-split { display: flex; align-items: center; gap: 6px; }
/* Confirmed = a verified four-platform storefront; Other = a reachable TN
   storefront we checked and found is NOT on the four platforms (WooCommerce,
   PrestaShop, custom) — a settled bucket, not a to-do, so it reads neutral
   (--muted), not amber. Confirmed keeps the --ok accent so the two pills still
   split at a glance (DESIGN.md "Status"). */
.dir-pill-confirmed { background: var(--ok-soft); color: var(--ok); border-color: var(--ok-line); }
.dir-pill-candidate { background: var(--panel-3); color: var(--muted); border-color: var(--border-2); }
.dir-updated { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; margin-left: auto; }

/* E · the data table */
.dir-table { border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--panel); overflow: hidden; }
.dir-thead, .dir-row {
  display: grid;
  /* "Running ads" needs room for its own name on ONE line; the 68px it had fit
     "Tracked" and nothing truer, and 96px wrapped it. Platform and Category
     give up the difference — both are short words with room to spare. */
  grid-template-columns: minmax(212px, 2.4fr) 146px 124px 112px 100px 132px;
  align-items: center; gap: var(--s3);
  padding: 0 var(--s3);
}
.dir-thead {
  min-height: 40px; background: var(--panel-2);
  border-bottom: 1px solid var(--border);
}
.dir-thead .dir-c { font-size: 11.5px; font-weight: 600; color: var(--muted); letter-spacing: 0.02em; }
.dir-row { min-height: 66px; border-top: 1px solid var(--border); }
.dir-tbody .dir-row:first-child { border-top: 0; }
.dir-row:hover { background: var(--panel-2); }
.dir-c-ads, .dir-thead .dir-c-ads { justify-self: end; text-align: right; }
.dir-c-act { justify-self: end; }

/* Store column */
.dir-c-store { display: flex; align-items: center; gap: var(--s2); min-width: 0; }
.dir-logo {
  width: 34px; height: 34px; flex: none; border-radius: var(--r-sm);
  object-fit: contain; background: var(--panel-3);
  border: 1px solid var(--border);
}
.dir-store-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.dir-name-line { display: flex; align-items: center; gap: 6px; min-width: 0; }
.dir-name {
  font-size: 14px; font-weight: 600; color: var(--text); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.dir-name:hover { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 2px; }
.dir-sub-txt { font-size: 12.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dir-sub-link { color: var(--accent-ink); text-decoration: none; }
.dir-sub-link:hover { text-decoration: underline; }
.cmp-site-flag { flex: none; width: 18px; height: 13.5px; display: block; border-radius: 2px; outline: 1px solid var(--border-2); outline-offset: -1px; }
.cmp-site-cc {
  flex: none; font-family: var(--mono); font-size: 9.5px; font-weight: 600; letter-spacing: 0.04em;
  color: var(--muted); border: 1px solid var(--border-2); border-radius: var(--r-xs); padding: 0 4px; line-height: 15px;
}

/* Platform column */
.dir-c-platform { display: flex; align-items: center; gap: 8px; min-width: 0; }
.dir-plogo { width: 18px; height: 18px; flex: none; border-radius: var(--r-xs); display: block; }
.dir-plogo-none { background: var(--panel-3); border: 1px solid var(--border-2); }
.dir-plabel { font-size: 13px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Category column — one calm label, no competing outline */
.dir-cat { font-size: 13px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dir-cat-none { color: var(--muted); }
.dir-muted { color: var(--muted); }

/* Ads / First seen — figures, mono, aligned */
.dir-c-ads {
  font-family: var(--mono); font-size: 13px; color: var(--text-2);
  font-variant-numeric: tabular-nums;
  display: flex; flex-direction: column; align-items: flex-end; gap: 1px;
}
/* The live count leads the cell: it is what the directory exists to produce and
   what the sort above is set to. It reads at full size in the live hue, with
   the saved count beneath it as the supporting line — the reverse of the old
   arrangement, where the ranking figure was the small grey one. */
.dir-ads-live {
  font-size: 15px; font-weight: 600; color: var(--live-ink); line-height: 1.15;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.dir-ads-none { font-size: 15px; color: var(--border-2); line-height: 1.15; }
.dir-ads-held { font-size: 10.5px; color: var(--muted); white-space: nowrap; }
/* An estimate for a shared advertiser page — dimmer and italic so it never
   reads as the exact count. It drops back to grey: an estimate is not a
   measurement, and only measurements get the live colour. */
.dir-meta-est { font-style: italic; opacity: 0.8; color: var(--muted); font-weight: 500; }
.dir-c-seen { font-family: var(--mono); font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Actions — Track + open, right-aligned */
.dir-c-act { display: flex; align-items: center; justify-content: flex-end; gap: 6px; }
.dir-track {
  display: inline-flex; align-items: center; gap: 5px;
  height: 28px; padding: 0 10px; font: inherit; font-size: 12px; font-weight: 550;
  color: var(--text-2); background: var(--panel); border: 1px solid var(--border-2);
  border-radius: var(--r-sm); cursor: pointer; white-space: nowrap;
}
.dir-track:hover { color: var(--text); background: var(--panel-3); border-color: var(--border-2); }
.dir-track:focus-visible { outline: none; box-shadow: var(--focus); }
.dir-track .ico { width: 14px; height: 14px; }
.dir-track.is-tracked { color: var(--muted); border-color: transparent; background: transparent; }
.dir-track.is-tracked:hover { color: var(--text); background: var(--panel-3); }
.dir-open {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: var(--r-sm); color: var(--muted); flex: none;
}
.dir-open:hover { color: var(--text); background: var(--panel-3); }
.dir-open:focus-visible { outline: none; box-shadow: var(--focus); }
.dir-open .ico { width: 16px; height: 16px; }

.dir-empty { padding: var(--s5) var(--s3); text-align: center; font-size: 13px; color: var(--muted); }
.dir-divider {
  padding: 8px var(--s3); text-align: left; font-size: 11.5px;
  background: var(--panel-2); border-top: 1px solid var(--border);
}

/* narrow pane (preview panel open) — drop First seen, keep Store · Platform ·
   Category · Ads · Actions; the Track/open buttons go icon-only to fit.
   THIS is the layout the owner actually sees: the preview panel is open by
   default, so the pane sits at ~770px and never reaches the rule above. The ads
   column had 60px here, which is why "Running ads" wrapped to two lines no
   matter what the wide layout said. Platform and Category fund the difference. */
@container (max-width: 940px) {
  .dir-thead, .dir-row { grid-template-columns: minmax(180px, 2.4fr) 130px 116px 108px 72px; }
  .dir-c-seen { display: none; }
  .dir-track-lbl { display: none; }
  .dir-track { padding: 0; width: 28px; justify-content: center; }
}
/* pages under the table: the range in the middle, one step either side */
.dir-pager {
  display: flex; align-items: center; justify-content: center; gap: var(--s3);
  margin-top: var(--s4);
}
.dir-pager-range { font-size: 13px; color: var(--text-2); font-variant-numeric: tabular-nums; }
@media (pointer: coarse) { .dir-pager .btn { min-height: 40px; } }

/* mobile-width pane — a deliberate compact row: store, platform mark, ads,
   and the ACTIONS on a line of their own beneath. They used to be hidden here,
   which left a phone with no way to track a store or open it at all. Two real
   buttons, labelled, 40px tall: no menu to discover, nothing to reinvent for a
   keyboard or a screen reader. */
@container (max-width: 620px) {
  .dir-thead, .dir-row { grid-template-columns: minmax(0, 1fr) 30px 44px; gap: var(--s2); }
  .dir-c-cat { display: none; }
  .dir-thead .dir-c-act { display: none; }
  .dir-row .dir-c-act {
    grid-column: 1 / -1; justify-self: stretch; justify-content: flex-start; gap: var(--s2);
    padding-bottom: var(--s1);
  }
  .dir-row .dir-track { width: auto; min-height: 40px; padding: 0 var(--s3); }
  .dir-row .dir-track-lbl { display: inline; }
  .dir-row .dir-c-act a { min-width: 40px; min-height: 40px; display: inline-flex; align-items: center; justify-content: center; }
  .dir-plabel { display: none; }
}
@media (max-width: 640px) {
  .dir-wrap { padding: var(--s3) var(--s2) var(--s4); }
  .dir-toolbar .dir-sel { max-width: none; flex: 1 1 40%; }
}

/* ═══════════════════════════ the ad details modal ═════════════════════════
   Two columns: the creative on the left with room to be whatever shape it is,
   and everything we know about it on the right. The right column used to be a
   tall white panel with a few dates floating in it and the actions stranded at
   the bottom; it is now a fixed stack — brand, tabs, scrolling content, action
   bar — so nothing has to be scrolled past to be reached. */
.av-back {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: var(--s4);
  background: color-mix(in srgb, #000 58%, transparent);
  backdrop-filter: blur(2px);
}
body.av-locked { overflow: hidden; }

.av-panel {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  width: min(1240px, 100%);
  /* A DEFINITE height, not a max: with max-height the grid row sizes itself to
     the creative, so the stage grows and the media's `height: 100%` resolves
     against a stage that has already been pushed past the panel. */
  height: min(86vh, 880px);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: 0 24px 64px rgb(0 0 0 / 34%);
  overflow: hidden;
}

/* The stage centres the creative and lets it be whatever shape it is. A 9:16
   story goes tall and narrow, a 1:1 square stays square; neither is cropped. */
.av-stage {
  position: relative;
  display: grid;
  place-items: center;
  background: var(--panel-3);
  padding: var(--s4);
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}
/* Previous / next through the gallery the viewer was opened from — present
   only when the source lists neighbours (a tracked advertiser's gallery). */
.av-step {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px; display: grid; place-items: center;
  border: 1px solid var(--border); border-radius: 50%; cursor: pointer;
  font-size: 22px; line-height: 1; color: var(--text);
  background: color-mix(in srgb, var(--panel) 88%, transparent);
}
.av-step-prev { left: var(--s3); }
.av-step-next { right: var(--s3); }
.av-step:disabled { opacity: .35; cursor: default; }
.av-step:not(:disabled):hover { border-color: var(--text-2); }
.av-count {
  /* top, not bottom: at the bottom it sat on a video's progress bar */
  position: absolute; left: 50%; top: var(--s3); transform: translateX(-50%);
  padding: 2px 8px; border-radius: var(--r-sm);
  font-size: 12px; font-variant-numeric: tabular-nums; color: var(--text-2);
  background: color-mix(in srgb, var(--panel) 88%, transparent);
}
.av-media {
  position: relative;
  height: 100%;
  width: auto;
  max-width: 100%;
  max-height: 100%;
  border-radius: var(--r);
  overflow: hidden;
  background: var(--panel-2);
}
.av-media img { display: block; width: 100%; height: 100%; object-fit: contain; }
.av-nomedia {
  display: grid; place-items: center; width: 100%; height: 100%;
  padding: var(--s5); text-align: center; font-size: 13px; color: var(--muted);
}
.av-vtag {
  position: absolute; left: 10px; bottom: 10px;
  padding: 5px 9px; border-radius: var(--r-sm);
  font-size: 11.5px; color: var(--text-2);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(3px);
}

/* Four rows: brand, tabs, content, actions. Only the third scrolls, so the
   action bar is always on screen and the tabs never drift away. */
.av-side {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  min-width: 0;
  min-height: 0;
  border-left: 1px solid var(--border);
}

.av-head {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s4) var(--s5);
  padding-right: 46px;   /* clear of the close button */
}
.av-mark {
  display: grid; place-items: center; flex: none; overflow: hidden;
  width: 36px; height: 36px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent-ink);
  font-size: 15px; font-weight: 600;
}
.av-mark.has-img { background: var(--panel-2); border: 1px solid var(--border); }
.av-mark-img { width: 100%; height: 100%; object-fit: contain; }
.av-head-txt { display: flex; flex-direction: column; min-width: 0; }
.av-brand {
  font-size: 15px; font-weight: 600; line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.av-facts { font-size: 11.5px; color: var(--muted); }

.av-tabs {
  display: flex; gap: 2px;
  padding: 0 var(--s5);
  border-bottom: 1px solid var(--border);
}
.av-tab {
  border: 0; background: none; cursor: pointer;
  padding: 7px 2px 9px; margin-right: var(--s4);
  font: inherit; font-size: 13px; color: var(--muted);
  border-bottom: 2px solid transparent;
}
.av-tab:hover { color: var(--text-2); }
.av-tab.is-on { color: var(--text); border-bottom-color: var(--accent); }

.av-content { overflow-y: auto; scrollbar-width: thin; min-height: 0; }
.av-tabpane { padding: var(--s4) var(--s5) var(--s5); }

.av-headline { margin: 0 0 var(--s3); font-size: 14px; font-weight: 600; line-height: 1.35; }

/* Captions carry their own line breaks and emoji — both are kept. Long ones
   are clamped rather than turning the panel into a wall of text. */
.av-caption {
  margin: 0; font-size: 13px; line-height: 1.55; color: var(--text-2);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.av-caption.is-missing { color: var(--muted); font-style: italic; }
.av-capwrap.is-clamped .av-caption {
  display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden;
}
.av-capwrap.is-clamped.is-open .av-caption { display: block; -webkit-line-clamp: none; }
.av-more {
  margin-top: 4px; padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 12px; color: var(--accent);
}
.av-caprow { display: flex; flex-wrap: wrap; gap: var(--s2); margin: var(--s3) 0 var(--s4); }

/* Label/value rows, not a card per value. */
.av-rows { display: flex; flex-direction: column; gap: 7px; }
.av-row { display: flex; gap: var(--s3); font-size: 12px; align-items: baseline; }
.av-k { flex: 0 0 92px; color: var(--muted); }
.av-v { min-width: 0; overflow-wrap: anywhere; color: var(--text-2); }
.av-v.is-link { color: var(--accent); }

.av-links { display: flex; flex-wrap: wrap; gap: var(--s3); margin-top: var(--s4); }

/* A quiet action: readable text, no filled button competing with Save. */
.av-quiet {
  padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 12.5px; color: var(--text-2);
  text-decoration: none;
}
.av-quiet:hover:not(:disabled) { color: var(--accent); }
.av-quiet:disabled { color: var(--muted); cursor: default; }

/* ------------------------------- transcript ------------------------------ */
.av-langs { display: flex; gap: 2px; margin-bottom: var(--s3); }
.av-lang {
  padding: 4px 9px; border: 1px solid var(--border); background: var(--panel);
  cursor: pointer; font: inherit; font-size: 12px; color: var(--text-2);
  border-radius: var(--r-sm);
}
.av-lang.is-on { border-color: var(--accent); color: var(--accent-ink); background: var(--accent-soft); }
/* Not fetched yet: reads as an action to take, not a tab already sitting there. */
.av-lang.is-offer { border-style: dashed; color: var(--muted); }

.av-tbody { display: flex; flex-direction: column; gap: 2px; }
.av-tseg {
  display: flex; gap: var(--s3); align-items: baseline;
  padding: 4px 6px; margin: 0 -6px; border-radius: var(--r-sm);
}
.av-tseg:hover { background: var(--panel-2); }
/* A line the model was unsure of is marked, not hidden: the owner can listen
   for themselves rather than trusting it. */
.av-tseg.is-unsure .av-tline { color: var(--muted); }
.av-tstamp {
  flex: none; padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 11px; font-variant-numeric: tabular-nums;
  color: var(--accent); min-width: 38px; text-align: left;
}
.av-tstamp:disabled { color: var(--border-2); cursor: default; }
.av-tline { margin: 0; font-size: 13px; line-height: 1.5; color: var(--text-2); overflow-wrap: anywhere; }
/* Arabic reads right to left inside its own line; the panel does not turn. */
.av-tline[dir="rtl"], .av-caption[dir="rtl"], .av-tscreen[dir="rtl"] { text-align: right; }

.av-th { margin: var(--s4) 0 var(--s2); font-size: 11.5px; font-weight: 600; color: var(--muted); }
.av-tscreen {
  margin: 0 0 4px; padding: 5px 8px; border-radius: var(--r-sm);
  background: var(--panel-2); font-size: 12.5px; color: var(--text-2);
}
.av-tempty { margin: 0; font-size: 13px; line-height: 1.5; color: var(--muted); }
.av-tnote { margin: var(--s3) 0 0; font-size: 11.5px; line-height: 1.45; color: var(--muted); }
.av-tnote.is-bad { color: var(--danger); }

/* -------------------------------- actions -------------------------------- */
.av-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3);
  padding: var(--s4) var(--s5);
  border-top: 1px solid var(--border);
}
.btn.is-done { background: var(--ok-soft); border-color: var(--ok-line); color: var(--ok); }
.av-note { flex: 1 0 100%; font-size: 11.5px; color: var(--muted); }
.av-note.is-bad { color: var(--danger); }

.av-x {
  position: absolute; top: 8px; right: 10px;
  width: 30px; height: 30px; display: grid; place-items: center;
  border: 0; border-radius: var(--r-sm); cursor: pointer;
  font-size: 22px; line-height: 1; color: var(--text-2);
  background: color-mix(in srgb, var(--panel) 80%, transparent);
}
.av-x:hover { color: var(--text); }

/* Below the split it becomes one column with the creative on top — the phone
   shape, where a 400px information rail has nowhere to go. */
@media (max-width: 900px) {
  .av-back { padding: 0; }
  .av-panel {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr);
    width: 100%; height: 100%;
    border: 0; border-radius: 0;
  }
  .av-stage { padding: var(--s3); height: 42vh; }
  .av-side { border-left: 0; border-top: 1px solid var(--border); }
  .av-head, .av-tabs, .av-tabpane, .av-actions {
    padding-left: var(--s4); padding-right: var(--s4);
  }
  .av-head { padding-right: 46px; }
}

/* --------------------------- the viewer's player ---------------------------
   A real <video>, served through our own origin because fbcdn refuses the
   browser's cross-origin request. Ratio comes from the poster, so the frame is
   the right shape before a single byte of video arrives. */
.av-video { display: block; width: 100%; height: 100%; background: #000; }
.av-vfail { position: absolute; inset: 0; display: grid; place-items: center; }
.av-vfail img { width: 100%; height: 100%; object-fit: contain; }
.av-vfail-note {
  position: absolute; left: 12px; right: 12px; bottom: 12px; margin: 0;
  padding: 8px 10px; border-radius: var(--r-sm);
  font-size: 12px; line-height: 1.4; color: var(--text-2); text-align: center;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(3px);
}

/* ═══════════════════ the page: one container, one left edge ═══════════════
   The complaint was that the search bar started against the sidebar and
   nothing lined up with anything. The cause was three toolbars each with its
   own padding, stacked. Now one variable sets the gutter, and the title, the
   search field, the filters and the first column of cards all inherit it. It
   is a padding on a shared container — not a margin compensating for the
   sidebar, so it stays correct when the sidebar collapses. */
.cmp-pane { --page-gutter: clamp(16px, 2.2vw, 32px); }

.cmp-pagehead {
  padding: var(--s5) var(--page-gutter) 0;
}
.cmp-h1 {
  margin: 0;
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.012em;
}
.cmp-sub-hint {
  margin: 4px 0 0;
  font-size: 13px;
  color: var(--muted);
}

/* The old tab strips still drive the panes; they are simply not a second
   navigation any more. The bell has to stay reachable, so the row survives
   with its tabs removed rather than being deleted. */
.cmp-subtabs { display: none; }
.cmp-subnav { padding: 0 var(--page-gutter); min-height: 0; border-bottom: 0; }
.cmp-scope { display: none; }

.cmp-dsearch, .cmp-bar { padding-left: var(--page-gutter); padding-right: var(--page-gutter); }
.cmp-dscroll { padding-left: var(--page-gutter); padding-right: var(--page-gutter); }

/* ───────────────────────────── the toolbar ─────────────────────────────────
   One control height, one radius, one type size, one focus ring. The previous
   row was six dropdowns of equal weight, each shouting a row count. */
.cmp-bar {
  --ctl-h: 34px;
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  padding-top: var(--s4);
}

.cmp-field {
  position: relative;
  flex: 1 1 280px;
  min-width: 200px;
  max-width: 420px;
}
.cmp-field-ico {
  position: absolute;
  left: 9px; top: 50%;
  transform: translateY(-50%);
  width: 15px; height: 15px;
  color: var(--muted);
  pointer-events: none;
}
.cmp-input {
  width: 100%;
  height: var(--ctl-h);
  padding: 0 10px 0 30px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}
.cmp-input::placeholder { color: var(--muted); }
.cmp-input:focus-visible,
.cmp-select:focus-visible,
.cmp-ghost:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.cmp-select {
  height: var(--ctl-h);
  padding: 0 26px 0 10px;
  font: inherit;
  font-size: 13px;
  color: var(--text-2);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  max-width: 190px;
}
.cmp-select:hover { border-color: var(--border-2); }

.cmp-ghost {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: var(--ctl-h);
  padding: 0 11px;
  font: inherit;
  font-size: 13px;
  color: var(--text-2);
  cursor: pointer;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}
.cmp-ghost .ico { width: 15px; height: 15px; }
.cmp-ghost:hover, .cmp-ghost.is-on { border-color: var(--border-2); color: var(--text); }
/* A filter hidden inside a popover must still announce itself. */
.cmp-ghost-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
}

.cmp-pop-anchor { position: relative; }
.cmp-pop {
  position: absolute;
  right: 0; top: calc(100% + 6px);
  z-index: 20;
  width: 264px;
  padding: var(--s4);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: 0 10px 28px rgb(0 0 0 / 12%);
}
.cmp-pop[hidden] { display: none; }
.cmp-pop-row { display: flex; flex-direction: column; gap: 5px; margin-bottom: var(--s3); }
.cmp-pop-row > span { font-size: 12px; color: var(--muted); }
.cmp-pop-row .cmp-select { max-width: none; width: 100%; }
.cmp-pop-note { margin: 0; font-size: 11.5px; line-height: 1.45; color: var(--muted); }

/* The count is a fact about the feed, not a control. It gets its own quiet
   line rather than a seventh slot in the toolbar. */
/* Tracked ads: the Meta brand search opens under the toolbar, on the same
   left edge as everything else. */
.cmp-tsearch { margin-top: var(--s2); padding: 0 var(--page-gutter); }
.cmp-tsearch .input { max-width: 560px; height: 34px; font-size: 13px; }
.cmp-tsearch .btn { height: 34px; }
#cmpChips { display: none; }
/* The tracked toolbar sits inside the header's own 12px padding; Explore's
   does not. Undo it so title, search and cards share one left edge. */
#cmpSubAds .cmp-tbar, #cmpSubAds .cmp-tline, #cmpSubAds .cmp-tsearch {
  margin-left: calc(-1 * var(--s3)); margin-right: calc(-1 * var(--s3));
}
.cmp-resultline {
  margin: var(--s2) 0 0;
  padding: 0 var(--page-gutter);
  font-size: 12px;
  color: var(--muted);
  min-height: 1em;
}

@media (max-width: 720px) {
  /* Search keeps the first row to itself; the filters wrap under it rather
     than being squeezed until their labels collide. */
  .cmp-field { flex: 1 1 100%; max-width: none; }
  .cmp-select { max-width: none; flex: 1 1 calc(50% - var(--s2)); }
}

/* The page head is a row: title and subtitle on the left, the bell on the
   right. The bell used to sit in the old tab strip, which is gone, and it was
   left floating over the top of the feed. */
.cmp-pagehead { display: flex; align-items: flex-start; gap: var(--s4); }
.cmp-pagehead-txt { flex: 1 1 auto; min-width: 0; }
/* "← Back to Stores / Tracking" above a page reached by a link */
.cmp-back {
  display: inline-block; margin: 0 0 6px; padding: 2px 0;
  font: inherit; font-size: 13px; color: var(--accent-ink, var(--accent));
  background: none; border: 0; cursor: pointer;
}
.cmp-back:hover { text-decoration: underline; }
.cmp-back:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-xs); }
.cmp-track-hrow { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }
.cmp-subnav { position: relative; margin-top: -34px; pointer-events: none; }
.cmp-subnav .cmp-bellwrap { pointer-events: auto; }
/* That pull-up drags .cmp-hd's own tint 34px up behind the subtitle: a band
   of lighter grey started halfway through the heading block, with the bell
   straddling the join. The header area takes the page's colour instead — its
   bottom border still closes the toolbar off from the cards. */
.cmp-pane .cmp-hd { background: transparent; }
/* …and the text keeps clear of the bell, which on a phone sat on the subtitle. */
.cmp-pagehead-txt { padding-right: 44px; }

/* Track, beside Save, on every card. Two small monochrome affordances that
   only colour when they are on — the alternative was leaving the grid, finding
   the brand in a different page's search box, and hoping. */
/* .cmp-dtrack: see .ib in styles.css. */

/* M-02: the way out of an over-filtered Saved page */
.cmp-empty-clear { margin-top: var(--s3); }

/* ───────────────────────────── Instagram ──────────────────────────────────
   Reels DM'd to @mycrea.ads (public/instagram.js). Explore's grammar — the
   same toolbar, result line and gutter — plus a row of folders. Reels are all
   9:16, so a plain grid does what the library needs a masonry for. */
.ig-page { flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-width: thin; }
.ig-page[hidden] { display: none; }
.ig-root { padding-bottom: var(--s5); }

.ig-folders {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: var(--s3);
  padding: 0 var(--page-gutter);
}
.ig-folder {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 12px;
  font: inherit; font-size: 13px; color: var(--text-2);
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-pill);
  cursor: pointer;
}
.ig-folder:hover { border-color: var(--border-2); color: var(--text); }
.ig-folder.is-on { background: var(--text); border-color: var(--text); color: var(--bg); }
.ig-folder:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ig-folder-n { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.ig-folder.is-on .ig-folder-n { color: color-mix(in srgb, var(--bg) 70%, transparent); }
.ig-folder.is-add { border-style: dashed; color: var(--muted); }
.ig-namewrap { display: inline-flex; }
.ig-namewrap .ig-name { height: 30px; width: 170px; padding: 0 12px; border-radius: var(--r-pill); }

.ig-line .ig-linkbtn {
  padding: 0; font: inherit; color: var(--accent-ink, var(--accent));
  background: none; border: 0; cursor: pointer;
}
.ig-line .ig-linkbtn:hover { text-decoration: underline; }
.ig-line.is-bad { color: var(--danger, #b42318); }

.ig-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(176px, 1fr));
  gap: var(--s4) var(--s3);
  margin-top: var(--s3);
  padding: 0 var(--page-gutter);
}
.ig-card { position: relative; min-width: 0; display: flex; flex-direction: column; }
.ig-thumb {
  position: relative; display: block; width: 100%; aspect-ratio: 9 / 16;
  padding: 0; border: 0; cursor: pointer; overflow: hidden;
  background: var(--panel-3); border-radius: var(--r);
  transition: transform .12s ease, box-shadow .12s ease;
}
.ig-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ig-card:hover .ig-thumb { transform: translateY(-2px); box-shadow: 0 6px 18px rgb(0 0 0 / 14%); }
.ig-thumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ig-thumb.is-failed { cursor: default; }
.ig-dur { position: absolute; left: 6px; bottom: 6px; }
.ig-state {
  position: absolute; inset: auto 8px 8px 8px;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 6px 8px; font-size: 12px; color: var(--text);
  background: color-mix(in srgb, var(--panel) 86%, transparent);
  backdrop-filter: blur(4px); border-radius: var(--r-sm);
}
.ig-state.is-bad { color: var(--danger, #b42318); }
.ig-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
  animation: ig-pulse 1.1s ease-in-out infinite;
}
@keyframes ig-pulse { 50% { opacity: .25; } }
.ig-thumb.is-busy img { filter: saturate(.6) brightness(.92); }
@media (prefers-reduced-motion: reduce) {
  .ig-dot { animation: none; }
  .ig-thumb, .ig-card:hover .ig-thumb { transition: none; transform: none; }
}

.ig-foot { display: flex; align-items: flex-start; gap: 2px; padding-top: 7px; min-width: 0; }
.ig-foot-txt { flex: 1 1 auto; min-width: 0; }
.ig-title {
  margin: 0; font-size: 12.5px; font-weight: 500; line-height: 1.35; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.ig-meta {
  margin: 2px 0 0; font-size: 11.5px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ig-more { flex: none; margin-top: -2px; }
.ig-src { margin-top: 4px; font-size: 12px; color: var(--accent-ink, var(--accent)); }

.ig-menu {
  position: absolute; right: 0; top: calc(100% - 4px); z-index: 30;
  min-width: 190px; padding: 6px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r);
  box-shadow: 0 10px 28px rgb(0 0 0 / 14%);
}
.ig-mh { margin: 4px 8px 4px; font-size: 11.5px; color: var(--muted); }
.ig-mi {
  display: block; width: 100%; padding: 7px 8px; text-align: left;
  font: inherit; font-size: 13px; color: var(--text-2);
  background: none; border: 0; border-radius: var(--r-sm); cursor: pointer;
}
.ig-mi:hover, .ig-mi:focus-visible { background: var(--panel-2); color: var(--text); outline: none; }
.ig-mi.is-on { color: var(--text); font-weight: 500; }
.ig-mi.is-on::after { content: " \2713"; color: var(--accent); }
.ig-mi.is-bad { color: var(--danger, #b42318); }
.ig-msep { height: 1px; margin: 5px 4px; background: var(--border); }

/* Connect: three numbered steps, the code as the one thing that shouts. */
.ig-connect {
  margin: var(--s4) var(--page-gutter) 0;
  padding: var(--s4) var(--s5);
  max-width: 760px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r);
}
.ig-connect-hd { display: flex; align-items: flex-start; gap: var(--s3); }
.ig-connect-ico {
  flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: var(--r);
  color: #fff; background: linear-gradient(45deg, #f9ce34, #ee2a7b 55%, #6228d7);
}
.ig-connect-ico .ico { width: 18px; height: 18px; }
.ig-connect-h { margin: 0; font-size: 15px; font-weight: 600; color: var(--text); }
.ig-steps { margin: var(--s4) 0 0; padding: 0; list-style: none; counter-reset: ig; display: grid; gap: var(--s3); }
.ig-steps > li {
  counter-increment: ig; position: relative;
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2) var(--s3);
  padding-left: 34px; min-height: 30px;
}
.ig-steps > li::before {
  content: counter(ig); position: absolute; left: 0; top: 3px;
  display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%;
  font-size: 12px; font-weight: 600; color: var(--accent-ink, var(--accent));
  background: var(--accent-soft); border: 1px solid var(--accent-line, transparent);
}
.ig-step-t { font-size: 13.5px; color: var(--text); }
.ig-step-note { margin: 2px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--muted); }
.ig-step-note .ico { display: inline-block; width: 13px; height: 13px; vertical-align: -2px; margin: 0 2px; }
.ig-steps .ig-step-note { flex: 1 1 100%; margin: 0; }
.ig-code { display: inline-flex; align-items: baseline; gap: var(--s3); }
.ig-code-n {
  font-size: 24px; font-weight: 600; letter-spacing: .08em; font-variant-numeric: tabular-nums;
  color: var(--text); user-select: all;
}
.ig-code-note { font-size: 12px; color: var(--muted); }
.ig-setup {
  margin: var(--s4) 0 0; padding: 8px 10px; font-size: 12.5px; color: var(--text-2);
  background: var(--panel-2); border-radius: var(--r-sm);
}

.ig-empty { margin: 48px var(--page-gutter) 0; text-align: center; }
.ig-empty-h { margin: 0; font-size: 15px; font-weight: 600; color: var(--text); }
.ig-empty-note { margin: 6px var(--page-gutter) 0; font-size: 13px; color: var(--muted); }
.ig-empty .ig-empty-note { margin: 6px 0 0; }

/* The folder picker in the viewer's action bar sits at button height. */
.av-actions .ig-move { height: 30px; max-width: 170px; }

@media (max-width: 720px) {
  .ig-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ig-connect { padding: var(--s4); }
  .ig-step-t { min-width: 0; flex: 1 1 100%; }
  .ig-folders { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .ig-folder { flex: none; }
}
