/* ============================================================
   Stackpeek side panel — tokens + component styles
   Tokens are the same contract as the marketing site.
   Applied to :root and to any [data-sp-theme] subtree so a
   single page can preview both schemes.
   ============================================================ */

:root,
[data-sp-theme] {
  --sp-font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --sp-font-mono: ui-monospace, "SF Mono", SFMono-Regular, "Cascadia Mono",
    "Roboto Mono", Menlo, Consolas, "Liberation Mono", monospace;

  --sp-fs-micro: 10px;
  --sp-fs-xs: 11px;
  --sp-fs-sm: 12px;
  --sp-fs-md: 13px;
  --sp-fs-lg: 15px;
  --sp-fs-xl: 17px;

  --sp-lh-tight: 1.2;
  --sp-lh-snug: 1.35;
  --sp-lh-normal: 1.55;

  --sp-fw-regular: 400;
  --sp-fw-medium: 500;
  --sp-fw-semibold: 600;

  --sp-tracking-caps: 0.08em;
  --sp-tracking-tight: -0.01em;

  --sp-space-1: 4px;
  --sp-space-2: 8px;
  --sp-space-3: 12px;
  --sp-space-4: 16px;
  --sp-space-5: 24px;
  --sp-space-6: 32px;

  --sp-hairline: 1px;
  --sp-radius-sm: 2px;
  --sp-radius-md: 4px;
  --sp-radius-lg: 6px;

  --sp-bg: #ffffff;
  --sp-bg-subtle: #f7f8f9;
  --sp-bg-inset: #f1f3f5;
  --sp-surface: #ffffff;

  --sp-border: #e3e6ea;
  --sp-border-strong: #c9ced6;

  --sp-text: #14161a;
  --sp-text-muted: #545c6a;
  --sp-text-faint: #6b7280;

  --sp-accent: #2b5ce6;
  --sp-accent-hover: #1f47bd;
  --sp-accent-on: #ffffff;
  --sp-accent-bg: #eef2ff;
  --sp-accent-border: #c3cffa;

  /* Category tints — app-category markers only. Desaturated siblings of
     the accent hue; never used for text, fills, CTAs or status. */
  --sp-tint-1: oklch(62% 0.13 265);
  --sp-tint-2: oklch(64% 0.11 200);
  --sp-tint-3: oklch(66% 0.10 155);
  --sp-tint-4: oklch(68% 0.11 75);
  --sp-tint-5: oklch(62% 0.12 320);

  /* Lightness and chroma for every category marker; the hue comes from
     the [data-sp-cat] rules further down. */
  --sp-cat-l: 64%;
  --sp-cat-c: 0.11;

  --sp-focus-ring: 2px solid var(--sp-accent);
  --sp-focus-offset: 2px;

  --sp-dur-fast: 90ms;
  --sp-dur-med: 160ms;
  --sp-ease: cubic-bezier(0.2, 0, 0.3, 1);

  color-scheme: light;
}

[data-sp-theme="dark"] {
  --sp-bg: #0e1013;
  --sp-bg-subtle: #14171c;
  --sp-bg-inset: #1a1e24;
  --sp-surface: #12151a;

  --sp-border: #262b33;
  --sp-border-strong: #3a4150;

  --sp-text: #e8ebef;
  --sp-text-muted: #a2aab6;
  --sp-text-faint: #8b939f;

  --sp-accent: #7d9bff;
  --sp-accent-hover: #9ab0ff;
  --sp-accent-on: #0b0e13;
  --sp-accent-bg: #161c2c;
  --sp-accent-border: #2c3a5e;

  --sp-tint-1: oklch(72% 0.13 265);
  --sp-tint-2: oklch(74% 0.11 200);
  --sp-tint-3: oklch(76% 0.10 155);
  --sp-tint-4: oklch(78% 0.11 75);
  --sp-tint-5: oklch(72% 0.12 320);
  --sp-cat-l: 74%;

  color-scheme: dark;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-sp-theme="light"]) {
    --sp-bg: #0e1013;
    --sp-bg-subtle: #14171c;
    --sp-bg-inset: #1a1e24;
    --sp-surface: #12151a;
    --sp-border: #262b33;
    --sp-border-strong: #3a4150;
    --sp-text: #e8ebef;
    --sp-text-muted: #a2aab6;
    --sp-text-faint: #8b939f;
    --sp-accent: #7d9bff;
    --sp-accent-hover: #9ab0ff;
    --sp-accent-on: #0b0e13;
    --sp-accent-bg: #161c2c;
    --sp-accent-border: #2c3a5e;
    --sp-tint-1: oklch(72% 0.13 265);
    --sp-tint-2: oklch(74% 0.11 200);
    --sp-tint-3: oklch(76% 0.10 155);
    --sp-tint-4: oklch(78% 0.11 75);
    --sp-tint-5: oklch(72% 0.12 320);
    --sp-cat-l: 74%;
    color-scheme: dark;
  }
}

/* ============================================================
   Panel shell
   ============================================================ */

.sp-panel {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  min-height: 100%;
  background: var(--sp-bg);
  color: var(--sp-text);
  font-family: var(--sp-font-sans);
  font-size: var(--sp-fs-md);
  line-height: var(--sp-lh-normal);
  -webkit-font-smoothing: antialiased;
}

/* The site's type scale is a step larger at every size, so the panel declares
   its own. Custom properties inherit, so every rule inside resolves against
   these on both surfaces while the marketing page keeps its scale elsewhere.
   Colours are already identical in both files and are deliberately absent here. */
.sp-panel {
  --sp-font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --sp-fs-micro: 10px;
  --sp-fs-xs: 11px;
  --sp-fs-sm: 12px;
  --sp-fs-md: 13px;
  --sp-fs-lg: 15px;
  --sp-fs-xl: 17px;
  --sp-lh-tight: 1.2;
  --sp-lh-normal: 1.55;
  --sp-tracking-tight: -0.01em;
}

.sp-panel *,
.sp-panel *::before,
.sp-panel *::after { box-sizing: border-box; }
.sp-panel p, .sp-panel h1, .sp-panel h2, .sp-panel ul, .sp-panel li { margin: 0; }
.sp-panel ul { padding: 0; list-style: none; }
.sp-panel :where(a, button, input, summary):focus-visible {
  outline: var(--sp-focus-ring);
  outline-offset: var(--sp-focus-offset);
  border-radius: var(--sp-radius-sm);
}

/* ============================================================
   Everything below this line was scoped by this change. (The four rules
   directly above -- .sp-panel *, .sp-panel p/h1/h2/ul/li, .sp-panel ul, and
   .sp-panel :where(...):focus-visible -- were already scoped before it.)

   The marketing site links this same file. Eighteen selectors are defined
   both here and in stackpeek.css, thirteen with different rules, so an
   unscoped rule here silently restyles the marketing page -- or gets
   clobbered by it, depending on link order.

   App.svelte's root element is .sp-panel, so scoping changes nothing in the
   extension while giving the site specificity-based isolation that does not
   depend on load order. panel_scoping.test.ts fails on any rule that
   escapes.
   ============================================================ */

.sp-panel .sp-mono {
  font-family: var(--sp-font-mono);
  font-variant-numeric: tabular-nums;
}

/* Header — persistent chrome */
.sp-panel .sp-hd {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--sp-space-2);
  padding: var(--sp-space-2) var(--sp-space-3);
  border-bottom: var(--sp-hairline) solid var(--sp-border);
  background: var(--sp-bg-subtle);
}
.sp-panel .sp-hd__mark { display: flex; flex: none; }
.sp-panel .sp-hd__domain {
  flex: 1;
  min-width: 0;
  font-family: var(--sp-font-mono);
  font-size: var(--sp-fs-sm);
  color: var(--sp-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  direction: rtl;
  text-align: left;
}
.sp-panel .sp-iconbtn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: var(--sp-hairline) solid transparent;
  border-radius: var(--sp-radius-md);
  background: transparent;
  color: var(--sp-text-muted);
  cursor: pointer;
  transition: background-color var(--sp-dur-fast) var(--sp-ease),
              color var(--sp-dur-fast) var(--sp-ease);
}
.sp-panel .sp-iconbtn:hover { background: var(--sp-bg-inset); color: var(--sp-text); }
.sp-panel .sp-iconbtn svg { width: 14px; height: 14px; display: block; }

/* Sections */
.sp-panel .sp-body { flex: 1; }
/* The panel is showing a store the user stepped away from on purpose, by
   opening a theme or app listing from one of our own links. It sits above the
   first section and borrows that section's hairline, so it reads as a note
   attached to the results rather than a section of its own -- it has no
   heading and nothing to collapse. Inset rather than tinted: this is not a
   warning, and colouring it as one would put the user back where they were,
   afraid to leave the panel open. */
.sp-panel .sp-held {
  padding: var(--sp-space-2) var(--sp-space-3);
  border-bottom: var(--sp-hairline) solid var(--sp-border);
  background: var(--sp-bg-inset);
  font-size: var(--sp-fs-xs);
  color: var(--sp-text-muted);
  text-wrap: pretty;
}
.sp-panel .sp-sec {
  border-bottom: var(--sp-hairline) solid var(--sp-border);
  padding: var(--sp-space-3);
}
.sp-panel .sp-sec__hd {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-space-2);
  margin-bottom: var(--sp-space-3);
}
/* <details> nests inside .sp-sec rather than replacing it, so the section
   landmark and its aria-labelledby survive. Both elements are layout-
   transparent, so .sp-sec__hd keeps its baseline-spread layout by moving to
   the summary. */
.sp-panel .sp-sec > details { display: block; }
.sp-panel summary.sp-sec__hd { cursor: pointer; list-style: none; }
.sp-panel summary.sp-sec__hd::-webkit-details-marker { display: none; }
/* The caret is the only new affordance. It is drawn on the summary rather than
   supplied by the UA marker so it can sit at the end of the flex row. */
.sp-panel summary.sp-sec__hd::after {
  content: "";
  width: 6px;
  height: 6px;
  border-right: var(--sp-hairline) solid var(--sp-text-faint);
  border-bottom: var(--sp-hairline) solid var(--sp-text-faint);
  transform: rotate(45deg);
  transition: transform var(--sp-dur-fast) var(--sp-ease);
}
.sp-panel details[open] > summary.sp-sec__hd::after { transform: rotate(-135deg); }
/* A collapsible head has three items where every other head has two, and
   space-between strands the count in the middle of the row. The count belongs
   where it sits in the four sections that do not collapse -- hard right -- and
   the caret is an affordance, so it takes the edge. An auto margin claims the
   free space before justify-content can distribute it. */
.sp-panel summary.sp-sec__hd .sp-count { margin-left: auto; }
.sp-panel .sp-label {
  font-family: var(--sp-font-mono);
  font-size: var(--sp-fs-micro);
  letter-spacing: var(--sp-tracking-caps);
  text-transform: uppercase;
  /* Weight and full-strength colour so a section head outranks the rows
     beneath it; at 10px uppercase mono, weight alone is too slight. */
  font-weight: var(--sp-fw-semibold);
  color: var(--sp-text);
}
.sp-panel .sp-count {
  font-family: var(--sp-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--sp-fs-xs);
  color: var(--sp-text-faint);
}

/* ============================================================
   ThemeCard — the one accent-carrying element
   ============================================================ */

.sp-panel .sp-theme {
  display: flex;
  flex-direction: column;
  gap: var(--sp-space-1);
  padding: var(--sp-space-3);
  border: var(--sp-hairline) solid var(--sp-accent-border);
  border-left: 2px solid var(--sp-accent);
  border-radius: var(--sp-radius-sm);
  background: var(--sp-accent-bg);
}
.sp-panel .sp-theme__top {
  display: flex;
  align-items: baseline;
  gap: var(--sp-space-2);
  flex-wrap: wrap;
}
.sp-panel .sp-theme__name {
  font-size: var(--sp-fs-lg);
  font-weight: var(--sp-fw-semibold);
  letter-spacing: var(--sp-tracking-tight);
  color: var(--sp-text);
}
.sp-panel a.sp-theme__name {
  text-decoration: underline;
  text-decoration-color: var(--sp-accent-border);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: text-decoration-color var(--sp-dur-fast) var(--sp-ease);
}
.sp-panel a.sp-theme__name:hover { text-decoration-color: var(--sp-accent); }
.sp-panel .sp-theme__version {
  font-family: var(--sp-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--sp-fs-sm);
  color: var(--sp-accent);
}
.sp-panel .sp-theme__meta {
  display: flex;
  align-items: baseline;
  gap: var(--sp-space-2);
  flex-wrap: wrap;
  font-family: var(--sp-font-mono);
  font-size: var(--sp-fs-xs);
  color: var(--sp-text-muted);
}
.sp-panel .sp-theme__meta .sp-sep { color: var(--sp-border-strong); }
.sp-panel .sp-theme__mod {
  font-family: var(--sp-font-mono);
  font-size: var(--sp-fs-micro);
  letter-spacing: 0.04em;
  color: var(--sp-text);
  border: var(--sp-hairline) solid var(--sp-accent-border);
  border-radius: var(--sp-radius-sm);
  padding: 0 4px;
  background: var(--sp-bg);
}

/* custom / headless — deliberate non-answers, no accent */
.sp-panel .sp-theme--plain {
  border: var(--sp-hairline) solid var(--sp-border-strong);
  border-left: 2px solid var(--sp-border-strong);
  background: var(--sp-bg-subtle);
}
.sp-panel .sp-theme--plain .sp-theme__name { color: var(--sp-text); }
.sp-panel .sp-theme--dashed {
  border: 1px dashed var(--sp-border-strong);
  background: transparent;
}
.sp-panel .sp-theme__note {
  font-size: var(--sp-fs-sm);
  color: var(--sp-text-muted);
  text-wrap: pretty;
}

/* ============================================================
   AppList
   ============================================================ */

.sp-panel .sp-cats { display: flex; flex-direction: column; gap: var(--sp-space-3); }
.sp-panel .sp-cat { display: flex; flex-direction: column; gap: var(--sp-space-1); }
.sp-panel .sp-cat + .sp-cat {
  padding-top: var(--sp-space-3);
  border-top: var(--sp-hairline) solid var(--sp-border);
}
.sp-panel .sp-cat__label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--sp-font-mono);
  font-size: var(--sp-fs-micro);
  letter-spacing: var(--sp-tracking-caps);
  text-transform: uppercase;
  color: var(--sp-text-faint);
}
.sp-panel .sp-cat__label::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 1px;
  background: var(--sp-cat-tint, var(--sp-border-strong));
}
/* Category markers. Every category carries a hue; lightness and chroma are one
   pair of tokens, so the whole set reads as one system and switches with the
   scheme automatically. Hues are spaced so categories that display next to each
   other land at least 40 degrees apart -- that separation is what the eye
   actually uses, since neighbouring groups are what get compared.

   Keyed to the category, never to DOM position, so a category keeps its colour
   whether or not the others are present. A category with no rule here sets no
   --sp-cat-tint at all, so .sp-cat__label::before falls back to the neutral
   marker -- which is why these are written out per category rather than composed
   from a hue variable: an unset hue makes the colour function invalid, and an
   invalid substitution drops the background entirely instead of falling back.

   Written one declaration per line because colour literals belong on token
   lines; check_colour_literals enforces it.

   The first five hues are the original --sp-tint-1..5 values, unchanged, so the
   established categories keep the colours the design settled on.
   Shared convention: same attribute values in stackpeek.css. */
.sp-panel [data-sp-cat="email-sms"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 265);
}
.sp-panel [data-sp-cat="page-builder"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 200);
}
.sp-panel [data-sp-cat="reviews"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 155);
}
.sp-panel [data-sp-cat="upsell"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 75);
}
.sp-panel [data-sp-cat="subscriptions"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 320);
}
.sp-panel [data-sp-cat="sales"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 0);
}
.sp-panel [data-sp-cat="marketing"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 133);
}
.sp-panel [data-sp-cat="store-design"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 293);
}
.sp-panel [data-sp-cat="customer-service"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 67);
}
.sp-panel [data-sp-cat="social-media"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 227);
}
.sp-panel [data-sp-cat="analytics"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 13);
}
.sp-panel [data-sp-cat="advertising"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 147);
}
.sp-panel [data-sp-cat="discounts"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 307);
}
.sp-panel [data-sp-cat="payments"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 93);
}
.sp-panel [data-sp-cat="shipping-delivery"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 240);
}
.sp-panel [data-sp-cat="orders"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 27);
}
.sp-panel [data-sp-cat="product-management"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 173);
}
.sp-panel [data-sp-cat="search-navigation"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 333);
}
.sp-panel [data-sp-cat="localization"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 107);
}
.sp-panel [data-sp-cat="loyalty"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 253);
}
.sp-panel [data-sp-cat="trust-security"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 40);
}
.sp-panel [data-sp-cat="privacy-compliance"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 187);
}
.sp-panel [data-sp-cat="dropshipping"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 347);
}
.sp-panel [data-sp-cat="seo"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 120);
}
.sp-panel [data-sp-cat="store-management"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 280);
}
.sp-panel [data-sp-cat="performance"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 53);
}
.sp-panel [data-sp-cat="accessibility"] {
  --sp-cat-tint: oklch(var(--sp-cat-l) var(--sp-cat-c) 213);
}
.sp-panel .sp-items { display: flex; flex-direction: column; }
.sp-panel .sp-item {
  display: flex;
  align-items: baseline;
  gap: var(--sp-space-2);
  min-height: 22px;
  font-size: var(--sp-fs-md);
  color: var(--sp-text);
}
.sp-panel .sp-item__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Recognized apps link to their listing. Accent stays reserved for the
   theme, so the affordance is a hairline underline, not a colour. */
.sp-panel a.sp-item__name {
  color: var(--sp-text);
  text-decoration: underline;
  text-decoration-color: var(--sp-border-strong);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  transition: text-decoration-color var(--sp-dur-fast) var(--sp-ease);
}
.sp-panel a.sp-item__name:hover { text-decoration-color: var(--sp-text); }
.sp-panel .sp-flag {
  flex: none;
  font-family: var(--sp-font-mono);
  font-size: var(--sp-fs-micro);
  line-height: 15px;
  color: var(--sp-text-faint);
  border: var(--sp-hairline) solid var(--sp-border-strong);
  border-radius: var(--sp-radius-sm);
  padding: 0 4px;
  cursor: help;
}
/* No rule above it. A hairline is how this design separates one section from
   the next -- .sp-sec carries that border -- so repeating it inside a section
   reads as a second boundary and splits the section in two. Space alone is
   enough to set a footnote apart from what it annotates. */
.sp-panel .sp-foot-note {
  margin-top: var(--sp-space-3);
  font-size: var(--sp-fs-xs);
  color: var(--sp-text-faint);
  text-wrap: pretty;
}
/* A footnote reporting an indeterminate wait, so it can carry the header's
   spinner beside its text. A modifier rather than a change to .sp-foot-note
   itself: that class is shared with the Apps section's static note and the
   "Show N more" button, and neither is a row. */
.sp-panel .sp-foot-note--busy {
  display: flex;
  align-items: center;
  gap: var(--sp-space-2);
}
/* "Show 20 more" reuses .sp-foot-note's spacing/typography but, unlike the
   Apps section's static footnote, is a real <button> — reset UA chrome (which
   .sp-foot-note alone leaves untouched: no bare `button` rule exists in this
   file) so it renders as bare, right-aligned text, matching
   BestSellers.svelte's borderless, flex-end `.more`, not a platform button. */
.sp-panel button.sp-foot-note {
  display: block;
  width: fit-content;
  margin-left: auto;
  padding: 0;
  border: 0;
  background: transparent;
  font-family: var(--sp-font-sans);
  cursor: pointer;
}

/* ============================================================
   PixelBadges
   ============================================================ */

.sp-panel .sp-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-space-1);
}
.sp-panel .sp-badge {
  font-family: var(--sp-font-mono);
  font-size: var(--sp-fs-xs);
  color: var(--sp-text);
  border: var(--sp-hairline) solid var(--sp-border-strong);
  border-radius: var(--sp-radius-sm);
  padding: 2px var(--sp-space-2);
  background: var(--sp-surface);
}
.sp-panel .sp-quiet {
  margin-top: var(--sp-space-2);
  font-family: var(--sp-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--sp-fs-xs);
  color: var(--sp-text-faint);
}

/* ============================================================
   Best sellers — ranked list
   ============================================================ */

.sp-panel .sp-bs { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; }
.sp-panel .sp-bs li {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: var(--sp-space-2);
}
.sp-panel .sp-rank {
  font-family: var(--sp-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--sp-fs-micro);
  color: var(--sp-text-faint);
  min-width: 1.5em;
  text-align: right;
}
.sp-panel .sp-bs-name {
  font-family: var(--sp-font-sans);
  font-size: var(--sp-fs-sm);
  color: var(--sp-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sp-panel .sp-price {
  font-family: var(--sp-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--sp-fs-xs);
  color: var(--sp-text);
}

/* Export progress rule — track and fill, never the accent */
.sp-panel .sp-track { height: 2px; border-radius: var(--sp-radius-sm); background: var(--sp-border); overflow: hidden; }
.sp-panel .sp-fill { height: 100%; background: var(--sp-text-faint); }

/* ============================================================
   StoreIntelligence — one container, two states
   ============================================================ */

.sp-panel .sp-si { display: flex; flex-direction: column; gap: var(--sp-space-3); }
.sp-panel .sp-si__copy {
  font-size: var(--sp-fs-sm);
  color: var(--sp-text-muted);
  text-wrap: pretty;
}
/* Product digest — borderless key/value rows.
   Deliberately not .sp-metric*: .sp-metrics is defined in this file AND in
   stackpeek.css with different children and a different display, and the
   site's copy is live on the store-intelligence section. Two files, one
   selector, two meanings is the collision this name avoids. */
.sp-panel .sp-facts { display: grid; gap: var(--sp-space-1); }
.sp-panel .sp-fact {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-space-3);
  align-items: baseline;
  font-size: var(--sp-fs-sm);
}
.sp-panel .sp-fact__k { color: var(--sp-text-muted); }
.sp-panel .sp-fact__v {
  font-family: var(--sp-font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--sp-text);
}

.sp-panel .sp-si-form { display: flex; flex-wrap: wrap; gap: var(--sp-space-2); }
.sp-panel .sp-si-input {
  flex: 1 1 140px;
  min-width: 0;
  padding: 6px var(--sp-space-2);
  border: var(--sp-hairline) solid var(--sp-border-strong);
  border-radius: var(--sp-radius-md);
  background: var(--sp-surface);
  color: var(--sp-text);
  font-family: var(--sp-font-mono);
  font-size: var(--sp-fs-sm);
}
.sp-panel .sp-si-input::placeholder { color: var(--sp-text-faint); }
.sp-panel .sp-hint { flex: 1 0 100%; font-size: var(--sp-fs-xs); color: var(--sp-text-faint); }

/* ============================================================
   Buttons
   ============================================================ */

.sp-panel .sp-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-space-2);
  padding: 6px var(--sp-space-3);
  border: var(--sp-hairline) solid transparent;
  border-radius: var(--sp-radius-md);
  font-family: var(--sp-font-sans);
  font-size: var(--sp-fs-sm);
  font-weight: var(--sp-fw-medium);
  line-height: var(--sp-lh-tight);
  cursor: pointer;
  transition: background-color var(--sp-dur-fast) var(--sp-ease),
              color var(--sp-dur-fast) var(--sp-ease);
}
.sp-panel .sp-btn--primary { background: var(--sp-accent); color: var(--sp-accent-on); }
.sp-panel .sp-btn--primary:hover { background: var(--sp-accent-hover); }
.sp-panel .sp-btn--quiet {
  background: transparent;
  border-color: var(--sp-border-strong);
  color: var(--sp-text);
}
.sp-panel .sp-btn--quiet:hover:not(:disabled) { background: var(--sp-bg-inset); }
/* The export control sits directly above the Trackers badges, and .sp-btn--quiet
   shares their fill, border colour and centred text -- differing only by 2px of
   radius and 4px of padding, so it read as a fourth badge sitting alone.
   Badges are inline pills that hug their text; a full-bleed filled bar cannot be
   mistaken for one. The separation is shape and fill, never accent: an accent
   export button would make the panel's brightest object a control rather than
   the detection result. */
.sp-panel .sp-btn--export {
  display: flex;
  width: 100%;
  padding: var(--sp-space-2) var(--sp-space-3);
  background: var(--sp-bg-inset);
  border-color: var(--sp-border-strong);
  color: var(--sp-text);
}
.sp-panel .sp-btn--export:hover:not(:disabled) { background: var(--sp-border); }
.sp-panel .sp-btn--export svg { width: 12px; height: 12px; flex: none; }
.sp-panel .sp-btn:disabled { cursor: default; color: var(--sp-text-faint); }
/* A control sitting directly in a section carries its own rhythm. .sp-sec has no
   gap of its own, and giving it one would double-space every section head --
   .sp-sec__hd already brings margin-bottom. Scoped to direct children so the
   buttons inside .sp-state, which sit in that container's own flex gap, are
   untouched. */
.sp-panel .sp-sec > .sp-btn {
  margin-top: var(--sp-space-3);
  margin-bottom: var(--sp-space-2);
}

/* ============================================================
   Terminal states (3–6) and loading
   ============================================================ */

.sp-panel .sp-state {
  display: flex;
  flex-direction: column;
  gap: var(--sp-space-3);
  align-items: flex-start;
  padding: var(--sp-space-5) var(--sp-space-3);
}
.sp-panel .sp-state__code {
  font-family: var(--sp-font-mono);
  font-size: var(--sp-fs-micro);
  letter-spacing: var(--sp-tracking-caps);
  text-transform: uppercase;
  color: var(--sp-text-faint);
  border: var(--sp-hairline) solid var(--sp-border);
  border-radius: var(--sp-radius-sm);
  padding: 1px 5px;
}
.sp-panel .sp-state__title {
  font-size: var(--sp-fs-xl);
  font-weight: var(--sp-fw-semibold);
  letter-spacing: var(--sp-tracking-tight);
  line-height: var(--sp-lh-snug);
}
.sp-panel .sp-state__body {
  font-size: var(--sp-fs-sm);
  color: var(--sp-text-muted);
  text-wrap: pretty;
}
.sp-panel .sp-state__list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-space-1);
  width: 100%;
  padding-top: var(--sp-space-2);
  border-top: var(--sp-hairline) solid var(--sp-border);
  font-family: var(--sp-font-mono);
  font-size: var(--sp-fs-xs);
  color: var(--sp-text-faint);
}
.sp-panel .sp-state__row {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-space-2);
}

/* Loading — skeleton of the eventual layout */
.sp-panel .sp-scan {
  display: flex;
  align-items: center;
  gap: var(--sp-space-2);
  /* Matches .sp-iconbtn's height. The two take turns in the same header slot --
     this while scanning, the rescan button once the last round settles -- and
     the header is a flex row sized by its tallest child. Without this the slot
     collapses to the text's 17px line box while scanning and springs back to
     the button's 26px after, jogging the whole header by 7.4px at the exact
     moment the user is watching it. */
  min-height: 26px;
  font-family: var(--sp-font-mono);
  font-size: var(--sp-fs-xs);
  color: var(--sp-text-muted);
}
.sp-panel .sp-spinner {
  width: 12px;
  height: 12px;
  border: 1.5px solid var(--sp-border-strong);
  border-top-color: var(--sp-accent);
  border-radius: 50%;
  animation: sp-spin 700ms linear infinite;
}
@keyframes sp-spin { to { transform: rotate(360deg); } }

.sp-panel .sp-skel {
  border-radius: var(--sp-radius-sm);
  background: var(--sp-bg-inset);
  animation: sp-pulse 1200ms var(--sp-ease) infinite alternate;
}
@keyframes sp-pulse { from { opacity: 1; } to { opacity: 0.45; } }
.sp-panel .sp-skel--theme { height: 62px; border-radius: var(--sp-radius-sm); }
.sp-panel .sp-skel--row { height: 12px; }
.sp-panel .sp-skel-rows { display: flex; flex-direction: column; gap: var(--sp-space-2); }

@media (prefers-reduced-motion: reduce) {
  .sp-panel .sp-spinner,
  .sp-panel .sp-skel { animation: none; }
  .sp-panel .sp-spinner { border-top-color: var(--sp-accent); }
  .sp-panel * { transition-duration: 0.01ms !important; }
}

/* Footer */
.sp-panel .sp-ft {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-space-2);
  padding: var(--sp-space-2) var(--sp-space-3);
  border-top: var(--sp-hairline) solid var(--sp-border);
  background: var(--sp-bg-subtle);
  font-size: var(--sp-fs-xs);
  color: var(--sp-text-faint);
}
.sp-panel .sp-ft a { color: var(--sp-text-muted); text-decoration: none; }
.sp-panel .sp-ft a:hover { color: var(--sp-text); text-decoration: underline; }
.sp-panel .sp-ft__v { font-family: var(--sp-font-mono); }

/* The export deck sits above the footer. Reuses .sp-btn--export rather than
   adding a second button style -- these do the same job as the catalogue
   export button, so they should not look like a different kind of control.
   --export is full-bleed on its own; wrapping gives the longer translated copy
   action a full row in narrow panels while CSV and JSON share the row below.

   Sits outside .sp-sec deliberately: it is not a finding, it is what you do
   with the findings, and giving it a section head would put "Export" in the
   same visual rank as "Apps". */
.sp-panel .sp-deck {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-space-2);
  padding: var(--sp-space-3);
  border-top: var(--sp-hairline) solid var(--sp-border);
}
.sp-panel .sp-deck__btn { flex: 1; min-width: 0; }
/* The copy button carries the longest label in every locale. Give it the full
   first row in a narrow panel so the two short format actions stay compact. */
.sp-panel .sp-deck__btn:first-child { flex: 1 0 100%; }

/* Wider panel: the user dragged it out. Density stays; lines get longer. */
@container (min-width: 440px) {
  .sp-panel .sp-sec { padding: var(--sp-space-3) var(--sp-space-4); }
  .sp-panel .sp-hd, .sp-panel .sp-ft { padding-left: var(--sp-space-4); padding-right: var(--sp-space-4); }
  .sp-panel .sp-cats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-space-3) var(--sp-space-4); }
  .sp-panel .sp-cat + .sp-cat { padding-top: 0; border-top: 0; }
  .sp-panel .sp-deck { flex-wrap: nowrap; }
  .sp-panel .sp-deck__btn:first-child { flex: 2; }
}
