/* ============================================================================
 * Listing detail — v4 shared shell
 * Ported from Sigrix_new/marketplace/Marketplace - Listing Image Prompt v4.html
 * and Sigrix_new/marketplace/Marketplace - Listing Skill v4.html, whose CSS is
 * byte-identical across the two files for everything in this sheet. The design
 * ships one shell and a per-type variant block; this is the shell.
 *
 * Every rule here is scoped under `body.listing-v4-page`, which each v4 detail
 * template carries alongside its own type class. A type variant sheet
 * (`sx-listing-image-prompt.css`, `sx-listing-skill.css`) loads after this one
 * and owns two things only: its accent tokens, and the markup no other type
 * renders.
 *
 * A variant's own token block is written `body.listing-v4-page.<type>-page`
 * — (0,2,1) against this sheet's (0,1,1) — so it wins on specificity rather
 * than on load order. CLAUDE.md's admin.css note is the reason: a same-
 * specificity override that happens to load later is a rule nobody can read
 * off either file.
 *
 * This sheet deliberately does NOT re-scope the whole --sx-* token set onto the
 * body the way persona/skill/agent/mcp v2 pages do. Those pages have to re-flip
 * four tokens the global dark rule skips (see CLAUDE.md), and every one of them
 * has shipped that gap at least once. Reading the shared tokens straight off
 * `.sx` — which base.html already puts on <body> and sx-home.css already flips
 * for dark — removes that whole class of bug. Only the page's own --ig-*
 * accents are declared here, and the dark block below re-flips every one of
 * them.
 * ==========================================================================*/

body.listing-v4-page {
  /* Release the shell's content wrapper. base.html wraps its content block in
     `.container`, which is `max-width: var(--container-max)` = 1200px. The
     prototype has no such wrapper — its `.ig-main` is full-bleed and
     `.ig-container` (1280px) is what centres the content — so on this page the
     wrapper did two wrong things at once: it squeezed every section 40px
     narrower per side than the design draws them, and it clipped the hero's
     radial glow to that column, painting it as a tinted rectangle with hard
     vertical edges against the page either side.

     `--container-max` is the design system's own knob and `.container` is its
     only reader, so releasing it here is one inherited token rather than a
     specificity fight with a shared selector. Safe on this page specifically
     because every top-level block under `main.ig-page` supplies its own
     `.ig-container` — the hero, the single wrapper `.ig-main` puts around all
     five numbered sections, and the sticky bar — so nothing goes full-bleed
     except the backgrounds that are meant to. The nav and footer are outside
     this wrapper in base.html and are unaffected. */
  --container-max: none;

  /* Graphic prompts read as the teal/persona accent in the prototype, and this
     is the shell's default for any v4 type that declares no accent of its own.

     It used to be `var(--sx-accent)`, which looks like the tidy answer — one
     shared token, theme-flipped for free, no fifth hardcoded palette. It was
     the wrong one, because `--sx-accent` is a **fill**: `#14B8A6` measures
     2.49:1 on white and 2.19:1 on its own 14% tint, and the shell writes
     `--ig-accent` as *text* in seven places (`.ig-kind`, `.ig-sig .ico`,
     `.ig-editable`, `.ig-varnote code`, `.ig-open-note code`, and the graphic
     variant's `.ig-pre .v` / `.ig-card-cap .k`). `.ig-editable` is the worst
     of them — accent ink on an accent-soft background, so it read at the 2.19
     figure. That is CLAUDE.md's "a fill is not an ink" rule one level up from
     a literal: a token can be sound in every use it was written for and unsound
     the moment a new consumer paints it as text.

     So the pair is declared here instead. Light is the same hue (173°) taken
     down to the lightest value that clears AA on every light surface a chip can
     land on — 5.96:1 on `--sx-surface`, 5.72 on `--sx-bg`, 5.62 on
     `--sx-surface-2`, and 4.87 / 4.69 / 4.60 on its own tint over each. Dark is
     byte-identical to what `--sx-accent` already resolved to, so that theme does
     not move at all. Measured, not guessed — see the contrast test.

     `--ig-accent-soft` / `-line` derive from `var(--ig-accent)` rather than from
     `--sx-accent` so the tint tracks the ink; per the `--pp-accent` note in
     CLAUDE.md, that `var()` re-resolves against whichever value wins on this
     same element, so the dark block below needs no copy of them. */
  --ig-accent: #0c7065;
  --ig-accent-soft: color-mix(in srgb, var(--ig-accent) 14%, transparent);
  --ig-accent-line: color-mix(in srgb, var(--ig-accent) 34%, transparent);
  --ig-accent-ink: #06413a;
  --ig-brand: var(--sx-brand-3, #3d4dff);
  --ig-brand-soft: color-mix(in srgb, var(--sx-brand-3, #3d4dff) 12%, transparent);
  --ig-brand-line: color-mix(in srgb, var(--sx-brand-3, #3d4dff) 40%, transparent);
  --ig-ok: var(--sx-ok, #1f9d55);
  --ig-ok-soft: color-mix(in srgb, var(--sx-ok, #1f9d55) 14%, transparent);
  --ig-ok-line: color-mix(in srgb, var(--sx-ok, #1f9d55) 38%, transparent);
  --ig-warn: var(--sx-warn, #c77800);
  --ig-warn-soft: color-mix(in srgb, var(--sx-warn, #c77800) 12%, transparent);
  /* The ink counterparts. `--ig-ok` / `--ig-warn` above are **fills**, and the
     shell paints them as *text* in five places — the grade chip, the warn
     verdict, the assurance ticks, the "you own this" label and the review-done
     glyph — all of which measured 2.58:1 to 3.29:1 in light mode: present,
     correctly styled, unreadable. Dark was fine throughout, which is the whole
     reason it looked sound for as long as it did.

     Exactly the split `--sx-brand-2` / `--sx-brand-text` already draws in
     base.html, and the same trap #5213 fixed one token over for `--ig-accent`.
     The rule these encode: **a fill is never a `color:`** — the contract test
     enforces that rather than trusting the next reader to notice.

     Solved against every background each is painted on, not just white: the
     plain surfaces, `--ig-ok-soft`'s 14%, `.rv-done .ico`'s 22%, and the 12/13%
     tint the badge chips mix from the ink itself. The 22% case is what makes
     this darker than the badge-only pair it replaces. Measured, not guessed. */
  --ig-ok-text: #16703c;
  --ig-warn-text: #925800;
  --ig-shot-badge-bg: rgba(8, 10, 16, 0.62);
  --ig-shot-badge-line: rgba(255, 255, 255, 0.18);
  --ig-shot-badge-ink: #ffffff;
  --ig-hero-glow-a: color-mix(in srgb, var(--sx-brand-3, #3d4dff) 10%, transparent);
  --ig-hero-glow-b: color-mix(in srgb, var(--sx-accent, #14b8a6) 8%, transparent);
  --ig-lock-wash: linear-gradient(
    160deg,
    color-mix(in srgb, var(--sx-brand-3, #3d4dff) 12%, var(--sx-surface-2, #f7f8fb)) 0%,
    var(--sx-surface-2, #f7f8fb) 58%
  );
  --ig-mono: var(--sx-font-mono, 'JetBrains Mono', ui-monospace, monospace);
  --ig-display: var(--sx-font-display, 'Space Grotesk', 'Geist', ui-sans-serif, system-ui, sans-serif);
}

/* Dark re-flip. Every --ig-* token declared above appears here; the ones built
   with color-mix() re-resolve for free because --sx-accent / --sx-brand-3 are
   themselves flipped on `[data-theme="dark"] .sx`, so only the tokens carrying
   a literal need restating. `--ig-accent-ink` is one of those: it is ink *on*
   the accent, so it has to darken as the accent brightens. */
[data-theme="dark"] body.listing-v4-page {
  /* Exactly what `var(--sx-accent)` resolved to here before the light value was
     derived, so dark does not move. It passes comfortably as ink already —
     11.69:1 on the surface, 8.52:1 on its own tint — which is the whole reason
     the old shared-token spelling looked fine for as long as it did: only one
     of the two themes was ever wrong. */
  --ig-accent: #7ce3d4;
  --ig-accent-ink: #04211d;
  /* What `--sx-ok` / `--sx-warn` already resolve to here, restated so the pair
     is declared once per theme rather than half-derived — the light values are
     the ones that had to move (6.20:1 and 6.95:1 worst here, so dark never
     needed the walk-down). */
  --ig-ok-text: #6ee7a1;
  --ig-warn-text: #f5b454;
  --ig-shot-badge-bg: rgba(4, 6, 11, 0.7);
  --ig-shot-badge-line: rgba(255, 255, 255, 0.14);
  --ig-shot-badge-ink: #ffffff;
  --ig-hero-glow-a: color-mix(in srgb, var(--sx-brand-3, #6b78ff) 14%, transparent);
  --ig-hero-glow-b: color-mix(in srgb, var(--sx-accent, #7ce3d4) 9%, transparent);
  --ig-lock-wash: linear-gradient(
    160deg,
    color-mix(in srgb, var(--sx-brand-3, #6b78ff) 16%, var(--sx-surface-2, #1a1f28)) 0%,
    var(--sx-surface-2, #1a1f28) 58%
  );
}

/* base.css gives every `main` 34px of top padding. Here that padding sits
   *above* the hero, so the glow — whose densest point is its own top edge —
   began at full strength 34px below the nav and drew a hard horizontal seam
   across the page. The prototype's `.ig-main` sets `padding-bottom` only, so
   its hero starts flush under the nav and the gradient's top edge lands on the
   header's own border where there is nothing to see. `.ig-hero` carries its
   own 26px of top padding, so the content spacing is unchanged. */
body.listing-v4-page main.ig-page {
  padding-top: 0;
}

body.listing-v4-page .ig-container {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 32px;
}
@media (max-width: 760px) {
  body.listing-v4-page .ig-container {
    padding: 0 20px;
  }
}

body.listing-v4-page .ig-mono {
  font-family: var(--ig-mono);
  font-feature-settings: 'zero', 'ss02';
  letter-spacing: -0.01em;
}

/* ── owner bar ──────────────────────────────────────────────────────────── */
.ig-ownerbar {
  position: relative;
  z-index: 5;
  border-bottom: 1px solid var(--sx-line);
  background: color-mix(in srgb, var(--sx-brand-3, #3d4dff) 9%, var(--sx-bg-2, #f4f5f8));
}
.ig-ownerbar--preview {
  background: color-mix(in srgb, var(--ig-warn) 12%, var(--sx-bg-2, #f4f5f8));
}
.ig-ownerbar-in {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 50px;
  padding-top: 9px;
  padding-bottom: 9px;
  flex-wrap: wrap;
}
.ig-ownerbar-msg {
  flex: 1 1 auto;
  min-width: 0;
}
.ig-ownerbar .ig-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ig-brand);
  flex: none;
}
.ig-ownerbar--preview .ig-dot {
  background: var(--ig-warn);
}
.ig-ownerbar .t {
  font-size: 13.5px;
  font-weight: 600;
}
.ig-ownerbar .s {
  font-size: 12.5px;
  color: var(--sx-text-2);
}
.ig-ownerbar .ig-acts {
  display: flex;
  gap: 8px;
  margin-left: auto;
  flex: none;
}

/* ── shared small controls ──────────────────────────────────────────────── */
/* css/responsive.css applies a site-wide `min-height: 44px` tap-target floor to
   every <button>. These controls are deliberately shorter than that, so each
   opts out explicitly — without it a 32px button renders 32x44 beside a
   visually identical 32px <a>. tests/test_image_prompt_listing_v4_page.py pins
   the opt-outs, and fails loudly if the global floor is ever removed. */
.ig-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 40px;
  min-height: 0;
  padding: 0 16px;
  border-radius: var(--sx-r-md, 10px);
  border: 1px solid var(--sx-line);
  background: var(--sx-surface);
  color: var(--sx-text);
  font-family: inherit;
  font-weight: 500;
  font-size: 14px;
  letter-spacing: -0.005em;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, transform 0.05s;
}
.ig-btn:hover {
  background: var(--sx-surface-2);
}
.ig-btn:active {
  transform: translateY(1px);
}
.ig-btn--primary {
  background: linear-gradient(135deg, var(--sx-brand-3, #6b78ff) 0%, var(--sx-brand-2, #1a1f6b) 100%);
  color: var(--sx-brand-ink, #fff);
  border-color: transparent;
  font-weight: 600;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.18) inset,
    0 6px 18px color-mix(in srgb, var(--sx-brand-2, #1a1f6b) 32%, transparent);
}
.ig-btn--primary:hover {
  background: linear-gradient(135deg, var(--sx-brand, #3d4dff) 0%, var(--sx-brand-2, #1a1f6b) 100%);
}
.ig-btn--sm {
  height: 32px;
  min-height: 0;
  padding: 0 12px;
  font-size: 13px;
  border-radius: 8px;
}
.ig-btn--lg {
  height: 48px;
  min-height: 0;
  padding: 0 22px;
  font-size: 15px;
  border-radius: 12px;
  font-weight: 600;
}
.ig-icon-btn {
  width: 32px;
  height: 32px;
  min-height: 0;
  border-radius: 8px;
  border: 1px solid var(--sx-line);
  background: var(--sx-surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--sx-text-2);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.ig-icon-btn:hover {
  background: var(--sx-surface-2);
  border-color: var(--sx-line-2);
  color: var(--sx-text);
}
.ig-eyebrow-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--ig-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sx-text-3);
}

/* ── hero ───────────────────────────────────────────────────────────────── */
.ig-hero {
  position: relative;
  padding: 26px 0 8px;
  overflow: hidden;
}
.ig-hero-bg {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(900px 420px at 18% 0%, var(--ig-hero-glow-a) 0%, transparent 70%),
    radial-gradient(700px 360px at 92% 12%, var(--ig-hero-glow-b) 0%, transparent 72%);
}
.ig-hero > .ig-container {
  position: relative;
}
.ig-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(380px, 460px);
  gap: 48px;
  align-items: start;
  margin-top: 20px;
}
/* "No samples" state (handoff §4): the hero drops to one column and the
   quality card moves into the right rail, rather than leaving a half-empty
   proof frame where the mosaic would have been. */
.ig-grid.is-proofless {
  grid-template-columns: minmax(0, 1fr);
  max-width: 760px;
}

.ig-mosaic-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 12px;
  flex-wrap: wrap;
}
.ig-varnote {
  font-size: 12.5px;
  color: var(--sx-text-2);
}
.ig-varnote code {
  font-family: var(--ig-mono);
  font-size: 12px;
  color: var(--ig-accent);
}
.ig-jump {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ig-brand);
}
.ig-jump:hover {
  text-decoration: underline;
}

/* ── hero identity ──────────────────────────────────────────────────────── */
.ig-eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.ig-kind {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--ig-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ig-accent);
}
.ig-kind .ig-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ig-accent);
}
.ig-model {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 24px;
  padding: 0 10px;
  border-radius: 999px;
  border: 1px solid var(--ig-brand-line);
  background: var(--ig-brand-soft);
  color: var(--ig-brand);
  font-family: var(--ig-mono);
  font-size: 11.5px;
  font-weight: 500;
}
/* The automated-test verdict chip (#3117). A pass keeps the brand chip above;
   a warn has to *read* differently, not merely carry a different attribute —
   a badge that says "with warnings" only in its title text is a distinction
   nobody sees. `min-height: 0` because the chip is 24px and the site-wide
   44px tap-target floor in responsive.css matches every element, not only
   buttons (CLAUDE.md). */
.ig-verdict[data-testing-verdict="warn"] {
  border-color: color-mix(in srgb, var(--ig-warn) 38%, transparent);
  background: var(--ig-warn-soft);
  color: var(--ig-warn-text);
}
.ig-new {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 10px;
  border-radius: 999px;
  border: 1px solid var(--sx-line-2);
  background: var(--sx-surface);
  color: var(--sx-text-2);
  font-family: var(--ig-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
}
.ig-title {
  font-family: var(--ig-display);
  font-weight: 700;
  font-size: clamp(30px, 3.2vw, 44px);
  line-height: 1.04;
  letter-spacing: -0.032em;
  margin: 0 0 16px;
  text-wrap: balance;
}
.ig-desc {
  margin: 0;
  font-size: 15.5px;
  line-height: 1.62;
  color: var(--sx-text-2);
  max-width: 50ch;
  text-wrap: pretty;
}
.ig-facts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px 28px;
  margin: 26px 0;
  padding: 22px 0;
  border-top: 1px solid var(--sx-line);
  border-bottom: 1px solid var(--sx-line);
}
.ig-fact .l {
  display: block;
  font-family: var(--ig-mono);
  font-size: 10px;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--sx-text-3);
  margin-bottom: 7px;
}
.ig-fact .v {
  font-size: 14.5px;
  font-weight: 500;
  color: var(--sx-text);
}
.ig-fact .v .sub {
  color: var(--sx-text-2);
  font-weight: 400;
}
.ig-fact-seller {
  display: flex;
  align-items: center;
  gap: 9px;
}
.ig-av {
  width: 24px;
  height: 24px;
  border-radius: 7px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--sx-brand-3, #3d4dff) 0%, var(--sx-brand-2, #1a1f6b) 100%);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  flex: none;
}
.ig-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 26px;
}
.ig-tag {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 11px;
  border-radius: 999px;
  border: 1px solid var(--sx-line);
  background: var(--sx-surface);
  color: var(--sx-text-2);
  font-family: var(--ig-mono);
  font-size: 11.5px;
}
.ig-tag:hover {
  border-color: var(--sx-line-2);
  color: var(--sx-text);
}

/* ── hero commerce ──────────────────────────────────────────────────────── */
.ig-buy {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 18px 20px;
  border-radius: 16px;
  border: 1px solid var(--sx-line-2);
  background: var(--sx-surface);
  box-shadow: var(--sx-shadow-2);
}
.ig-price {
  font-family: var(--ig-display);
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
  white-space: nowrap;
}
.ig-price .cur {
  font-size: 19px;
  vertical-align: 0.28em;
  margin-right: 1px;
  color: var(--sx-text-2);
}
.ig-buy-cta {
  flex: 1;
  min-width: 0;
}
.ig-buy-cta .ig-btn {
  width: 100%;
}
.ig-assure {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin: 12px 2px 0;
  padding: 0;
  list-style: none;
}
.ig-assure li {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--sx-text-2);
  list-style: none;
}
.ig-assure svg {
  color: var(--ig-ok-text);
  flex: none;
}

/* ── listing quality ────────────────────────────────────────────────────── */
.ig-signals {
  margin-top: 26px;
  border: 1px solid var(--sx-line);
  border-radius: 16px;
  background: var(--sx-surface);
  overflow: hidden;
}
.ig-signals-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 18px;
  border-bottom: 1px solid var(--sx-line);
}
.ig-signals-head .t {
  font-family: var(--ig-mono);
  font-size: 10.5px;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--sx-text-2);
}
.ig-grade {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 22px;
  padding: 0 9px;
  border-radius: 999px;
  background: var(--ig-ok-soft);
  border: 1px solid var(--ig-ok-line);
  color: var(--ig-ok-text);
  font-family: var(--ig-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
}
.ig-sigs {
  display: grid;
  grid-template-columns: 1fr 1fr;
}
.ig-sig {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  padding: 12px 18px;
  border-top: 1px solid var(--sx-line);
}
.ig-sigs .ig-sig:nth-child(-n + 2) {
  border-top: 0;
}
.ig-sigs .ig-sig:nth-child(even) {
  border-left: 1px solid var(--sx-line);
}
.ig-sig .ico {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: 1px;
  background: var(--ig-accent-soft);
  color: var(--ig-accent);
}
/* An unmet check is muted with an info glyph, never a warning colour — it
   states what this listing has not shown yet, not that something is wrong. */
.ig-sig.is-off .ico {
  background: var(--sx-surface-2);
  color: var(--sx-text-3);
}
.ig-sig .t {
  font-size: 13.5px;
  font-weight: 500;
}
.ig-sig .s {
  display: block;
  font-size: 12.5px;
  color: var(--sx-text-2);
  margin-top: 2px;
}
.ig-sig.is-off .t {
  color: var(--sx-text-2);
}

/* ── sections ───────────────────────────────────────────────────────────── */
.ig-main {
  padding-bottom: 40px;
}
.ig-sec {
  margin-top: 96px;
  scroll-margin-top: 80px;
}
.ig-sec-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 28px;
  padding-bottom: 18px;
  margin-bottom: 26px;
  border-bottom: 1px solid var(--sx-line);
}
.ig-sec-head h2 {
  font-family: var(--ig-display);
  font-size: 27px;
  font-weight: 700;
  letter-spacing: -0.025em;
  margin: 8px 0 0;
  line-height: 1.15;
}
.ig-sec-head .sub {
  margin: 8px 0 0;
  font-size: 14px;
  color: var(--sx-text-2);
  max-width: 56ch;
}
.ig-sec-aux {
  font-family: var(--ig-mono);
  font-size: 11.5px;
  color: var(--sx-text-2);
  white-space: nowrap;
  padding-bottom: 4px;
}

/* ── 01 · the artifact ──────────────────────────────────────────────────── */
.ig-locked {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(320px, 0.9fr);
  border: 1px solid var(--sx-line);
  border-radius: 20px;
  overflow: hidden;
  background: var(--sx-surface);
}
.ig-locked-l {
  padding: 34px 36px;
}
.ig-locked-l h3 {
  font-family: var(--ig-display);
  font-size: 20px;
  letter-spacing: -0.02em;
  margin: 0 0 8px;
}
.ig-locked-l > p {
  margin: 0 0 22px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--sx-text-2);
  max-width: 46ch;
}
.ig-incl {
  display: grid;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.ig-incl li {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: 12px;
  align-items: start;
  padding: 11px 0;
  list-style: none;
}
.ig-incl li + li {
  border-top: 1px solid var(--sx-line);
}
.ig-incl .ico {
  width: 20px;
  height: 20px;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--ig-brand-soft);
  color: var(--ig-brand);
  margin-top: 1px;
}
.ig-incl .t {
  display: block;
  font-size: 14px;
  font-weight: 500;
}
.ig-incl .s {
  display: block;
  font-size: 12.5px;
  color: var(--sx-text-2);
  margin-top: 2px;
  overflow-wrap: anywhere;
}
.ig-locked-r {
  position: relative;
  padding: 34px;
  border-left: 1px solid var(--sx-line);
  background: var(--ig-lock-wash);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 16px;
}
.ig-lock-ico {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--sx-surface);
  border: 1px solid var(--sx-line-2);
  color: var(--ig-brand);
}
.ig-locked-r .t {
  font-family: var(--ig-display);
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.ig-locked-r .s {
  font-size: 13.5px;
  color: var(--sx-text-2);
  line-height: 1.55;
  max-width: 34ch;
}
.ig-locked-price {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
.ig-locked-price .p {
  font-family: var(--ig-display);
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.03em;
}
.ig-locked-price .n {
  font-family: var(--ig-mono);
  font-size: 11.5px;
  color: var(--sx-text-2);
}

/* The artifact, open — what a viewer with access reads instead of the
   unlock panel above. Shell rather than variant: every v4 type has a
   deliverable to hand over, and only what goes *inside* the <pre> differs
   (a graphic prompt highlights its variables, a skill prints its SKILL.md
   body). Those per-type rules stay in the variant sheets. */
.ig-open {
  border: 1px solid var(--sx-line);
  border-radius: 20px;
  background: var(--sx-surface);
  overflow: hidden;
}
.ig-open-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 22px;
  border-bottom: 1px solid var(--sx-line);
  flex-wrap: wrap;
}
.ig-owned {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--ig-ok-text);
}
.ig-pre {
  margin: 0;
  padding: 24px 26px;
  font-family: var(--ig-mono);
  font-size: 13px;
  line-height: 1.85;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--sx-text-2);
  overflow-x: auto;
}
.ig-open-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 0 26px 24px;
}
.ig-open-note {
  flex: 1 1 100%;
  margin: 2px 0 0;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--sx-text-2);
}
.ig-open-note code {
  font-family: var(--ig-mono);
  font-size: 12px;
  color: var(--ig-accent);
}

/* ── 03 · details ───────────────────────────────────────────────────────── */
.ig-det {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: 64px;
  align-items: start;
}
.ig-spec {
  margin: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0 24px;
}
.ig-spec dt {
  font-family: var(--ig-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--sx-text-3);
  padding: 11px 0;
  border-top: 1px solid var(--sx-line);
}
.ig-spec dd {
  margin: 0;
  padding: 11px 0;
  border-top: 1px solid var(--sx-line);
  font-size: 13.5px;
  text-align: right;
  color: var(--sx-text);
  min-width: 0;
  overflow-wrap: anywhere;
}
.ig-spec dt:first-of-type,
.ig-spec dd:first-of-type {
  border-top: 0;
}
.ig-spec .ig-mono {
  font-size: 12.5px;
  color: var(--sx-text-2);
  /* The dd allows anywhere-breaks so a long value cannot overflow the column,
     but a parameter string must not break *inside* a token — `--v 8.2` split
     across two lines reads as a typo in the flags, which is the one thing this
     row exists to state precisely. Wrap at the spaces between flags instead. */
  overflow-wrap: normal;
  word-break: normal;
}
.ig-spec dd .note {
  display: block;
  margin-top: 5px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--sx-text-2);
}
.ig-editable {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: 8px;
  height: 18px;
  padding: 0 7px;
  border-radius: 999px;
  border: 1px solid var(--ig-accent-line);
  background: var(--ig-accent-soft);
  color: var(--ig-accent);
  font-family: var(--ig-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
}
/* ── Shop badges ──────────────────────────────────────────────────────────
   Rendered by `v4_shop_badges` in components/listing_v4.html, which both v4
   pages can call — so the rules live in the shell rather than in the variant
   of whichever page opted in first. A class rendered by a shared macro but
   styled in one variant sheet is the failure this shell exists to avoid: 200,
   correct markup, no styling.

   `--trust` and `--role` are deliberately different colours. A role chip says
   who published the listing; a trust chip says what the platform checked. Both
   are read as *text on their own tint*, so both take the badge ink pair above
   rather than the `--ig-ok` / `--ig-warn` fills — see the note there. */
.ig-shopb-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 21px;
  /* The floor in responsive.css matches `button` but not `span`, so this row
     needs no opt-out today — stated because a future revision that makes a
     chip a <button> would silently become 44px tall beside its neighbours. */
  padding: 0 8px;
  border-radius: 999px;
  border: 1px solid var(--sx-line-2);
  background: var(--sx-surface-2);
  color: var(--sx-text-2);
  font-family: var(--ig-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.ig-shopb-chip svg {
  flex: none;
}
/* Tint and border derive from the ink, so a theme that restates only the ink
   re-resolves both for free (the `--pp-accent` rule in CLAUDE.md). */
.ig-shopb-chip--trust {
  border-color: color-mix(in srgb, var(--ig-ok-text) 40%, transparent);
  background: color-mix(in srgb, var(--ig-ok-text) 12%, transparent);
  color: var(--ig-ok-text);
}
.ig-shopb-chip--role {
  border-color: color-mix(in srgb, var(--ig-warn-text) 44%, transparent);
  background: color-mix(in srgb, var(--ig-warn-text) 13%, transparent);
  color: var(--ig-warn-text);
}
.ig-shopb-list {
  display: grid;
  gap: 9px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--sx-line);
}
.ig-shopb-item {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.ig-shopb-item .n {
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--sx-text-2);
  text-wrap: pretty;
}
/* ── Tokens & cost ────────────────────────────────────────────────────────
   The stacked bar, its legend and the three cost cards, shared by both v4
   pages. The prototypes spell this twice — `.ip-*` in the graphic prompt file
   and `.sk-*` in the skill one — with the cost cards and the note byte-
   identical between them and the legend differing only in its row rhythm. One
   copy lives here for the same reason the rest of the shell does: a token
   section rendered on a page whose variant sheet lacked the rule would return
   200 with every element present and no styling at all.

   What genuinely differs per type is the legend row — the graphic prompt
   carries a description line under each segment name, the skill carries a mono
   filename — so the variants tune `.ig-tok-leg` rather than restating the box.

   No control in here is focusable, so the 44px tap-target floor in
   responsive.css never applies and nothing needs to opt out of it. */
.ig-tok-bar {
  display: flex;
  gap: 2px;
  height: 12px;
  border-radius: 999px;
  overflow: hidden;
  margin: 2px 0 20px;
}
.ig-tok-seg {
  display: block;
  min-width: 6px;
}
/* Five fills, cycled by the template (`loop.index0 % 5 + 1`). Every one is an
   existing shell token or a mix of two, so this block declares no literal and
   needs no dark counterpart. They are read as *fills* only — never as ink —
   which is what makes `--ig-accent` safe here where the contrast note in
   CLAUDE.md warns against it as text.

   Three were enough while the section had at most three rows (a skill divides
   into two spans, a graphic prompt into three). A persona divides into one row
   per authored block — up to eight — so a three-colour cycle put the same
   swatch on rows 1 and 4 of the same legend. Four and five avoid `--ig-warn`
   deliberately: it is an amber, and a type whose accent is also an amber would
   then draw two segments a reader has to compare by lightness alone. */
.ig-tok-seg--1 {
  background: var(--ig-accent);
}
.ig-tok-seg--2 {
  background: var(--ig-brand);
}
.ig-tok-seg--3 {
  background: var(--ig-ok);
}
.ig-tok-seg--4 {
  background: color-mix(in srgb, var(--ig-brand) 55%, var(--ig-ok));
}
.ig-tok-seg--5 {
  background: var(--sx-text-3);
}
.ig-tok-legend {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px 36px;
}
.ig-tok-leg {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 13.5px;
  min-width: 0;
}
.ig-tok-leg i {
  width: 9px;
  height: 9px;
  border-radius: 3px;
  flex: none;
  margin-top: 5px;
}
.ig-tok-leg .t {
  color: var(--sx-text);
  min-width: 0;
}
.ig-tok-leg .d {
  display: block;
  margin-top: 3px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--sx-text-2);
}
.ig-tok-leg .v {
  margin-left: auto;
  flex: none;
  font-family: var(--ig-mono);
  font-size: 12px;
  color: var(--sx-text-3);
  white-space: nowrap;
}
.ig-tok-cost {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  margin-top: 26px;
}
.ig-tok-cost .c {
  padding: 16px 18px;
  border: 1px solid var(--sx-line);
  border-radius: 14px;
  background: var(--sx-surface);
}
.ig-tok-cost .k {
  display: block;
  font-family: var(--ig-mono);
  font-size: 10px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--sx-text-3);
}
.ig-tok-cost .n {
  display: block;
  margin-top: 7px;
  font-family: var(--ig-display);
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.ig-tok-cost .s {
  display: block;
  margin-top: 3px;
  font-size: 12px;
  color: var(--sx-text-2);
}
.ig-tok-note {
  margin: 24px 0 0;
  padding-top: 18px;
  border-top: 1px dashed var(--sx-line-2);
  font-size: 13px;
  line-height: 1.6;
  color: var(--sx-text-2);
  max-width: 78ch;
  text-wrap: pretty;
}

.ig-changelog {
  margin-top: 22px;
  border: 1px solid var(--sx-line);
  border-radius: 14px;
  background: var(--sx-surface);
  padding: 0 16px;
}
.ig-changelog > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 0;
  cursor: pointer;
  list-style: none;
  font-size: 14px;
  font-weight: 600;
}
.ig-changelog > summary::-webkit-details-marker {
  display: none;
}
.ig-changelog dl {
  margin: 0;
  padding: 0 0 16px;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 6px 16px;
  font-size: 13px;
}
.ig-changelog dt {
  font-family: var(--ig-mono);
  font-size: 11.5px;
  color: var(--sx-text-3);
}
.ig-changelog dd {
  margin: 0;
  color: var(--sx-text-2);
}

.ig-seller {
  margin-top: 28px;
  border: 1px solid var(--sx-line);
  border-radius: 16px;
  padding: 20px;
  background: var(--sx-surface);
}
.ig-seller-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.ig-seller-av {
  width: 40px;
  height: 40px;
  border-radius: 11px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--sx-brand-3, #3d4dff) 0%, var(--sx-brand-2, #1a1f6b) 100%);
  color: #fff;
  font-weight: 700;
  font-size: 16px;
  flex: none;
}
.ig-seller-id {
  flex: 1 1 140px;
  min-width: 0;
}
.ig-seller-name {
  font-weight: 600;
  font-size: 14.5px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.ig-seller-sub {
  font-family: var(--ig-mono);
  font-size: 11.5px;
  color: var(--sx-text-2);
  margin-top: 1px;
}
.ig-seller p {
  margin: 14px 0 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--sx-text-2);
}
.ig-seller-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 14px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ig-brand);
}
.ig-seller-link:hover {
  text-decoration: underline;
}
.ig-seller-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

.ig-faq {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--sx-line);
}
.ig-faq li {
  border-bottom: 1px solid var(--sx-line);
  list-style: none;
}
.ig-faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 18px 2px;
  cursor: pointer;
  list-style: none;
  font-size: 15px;
  font-weight: 500;
}
.ig-faq summary::-webkit-details-marker {
  display: none;
}
.ig-faq summary:hover {
  color: var(--ig-brand);
}
.ig-faq-ico {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 1px solid var(--sx-line);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--sx-text-2);
  flex: none;
  transition: transform 0.18s;
}
.ig-faq details[open] .ig-faq-ico {
  transform: rotate(180deg);
}
.ig-faq p {
  margin: 0 0 20px;
  padding-right: 44px;
  font-size: 14px;
  line-height: 1.65;
  color: var(--sx-text-2);
  max-width: 64ch;
  white-space: pre-line;
}

/* ── related ────────────────────────────────────────────────────────────
   The seller's other image prompts. Cover-led on purpose: this page sells a
   look, so the next listing has to show one. */
.ig-rel {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
.ig-rel-card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--sx-surface);
  border-radius: var(--sx-r-lg, 14px);
  box-shadow: 0 0 0 1px var(--sx-line) inset;
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  transition: background 0.15s, box-shadow 0.15s, transform 0.12s;
}
.ig-rel-card:hover {
  background: var(--sx-surface-2);
  box-shadow:
    0 0 0 1px var(--sx-line-2) inset,
    0 12px 30px -16px rgba(0, 0, 0, 0.4);
  transform: translateY(-2px);
}
.ig-rel-cover {
  position: relative;
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--sx-bg-2);
}
.ig-rel-cover img,
.ig-rel-cover-empty {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.ig-rel-cover-empty {
  background: linear-gradient(135deg, var(--sx-surface-2) 0%, var(--sx-bg-2) 100%);
}
/* Both chips sit on the seller's own artwork, which can be any colour, so they
   carry their own dark scrim in both themes rather than reading off the page
   tokens — the one place on this page where a fixed dark surface is correct. */
.ig-rel-tag,
.ig-rel-price {
  position: absolute;
  top: 10px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  height: 22px;
  border-radius: 999px;
  background: rgba(11, 13, 18, 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14) inset;
}
.ig-rel-tag {
  left: 10px;
  gap: 6px;
  padding: 0 9px;
  color: #d8def0;
  font-family: var(--ig-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.ig-rel-tag::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ig-accent);
  flex: none;
}
.ig-rel-price {
  right: 10px;
  padding: 0 8px;
  color: #fff;
  font-size: 11.5px;
  font-weight: 600;
}
.ig-rel-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 14px 16px 16px;
}
.ig-rel-name {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.35;
  margin-bottom: 6px;
}
.ig-rel-foot {
  /* No divider rule: only *some* cards in a mixed row carry a byline, and a
     rule on those alone reads as four cards built to two different designs.
     `margin-top: auto` still floors it, so the row's bylines line up with each
     other and the cards keep equal height. */
  margin-top: auto;
  padding-top: 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.ig-rel-author {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--sx-text-2);
  min-width: 0;
}
.ig-rel-author > span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ig-rel-av {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--sx-brand-3, #3d4dff) 0%, var(--sx-brand-2, #1a1f6b) 100%);
  color: #fff;
  font-size: 10px;
  font-weight: 600;
}
.ig-rel-desc {
  font-size: 13px;
  line-height: 1.5;
  color: var(--sx-text-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── sticky buy bar ─────────────────────────────────────────────────────── */
.ig-stick {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--sx-bg-2, #f4f5f8) 92%, transparent);
  backdrop-filter: blur(16px) saturate(130%);
  -webkit-backdrop-filter: blur(16px) saturate(130%);
  border-top: 1px solid var(--sx-line);
  transform: translateY(120%);
  transition: transform 0.26s cubic-bezier(0.2, 0.8, 0.3, 1);
}
.ig-stick.is-on {
  transform: none;
}
/* The bar ships `hidden` so a reader with no JavaScript never gets one stuck
   across the foot of the page; the page script unhides it and the transform
   above does the reveal.

   base.css now carries a global `[hidden] { display: none !important }`
   (#5017), so this rule is belt-and-braces rather than load-bearing — but the
   bar is `position: fixed` with its own transform, which is the shape that
   goes wrong most visibly if that global rule is ever narrowed, and stating it
   here costs one declaration. */
.ig-stick[hidden] {
  display: none;
}
.ig-stick-in {
  display: flex;
  align-items: center;
  gap: 20px;
  height: 64px;
}
.ig-stick-t {
  font-weight: 600;
  font-size: 14.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ig-stick-m {
  font-family: var(--ig-mono);
  font-size: 11.5px;
  color: var(--sx-text-3);
  white-space: nowrap;
}
.ig-stick-r {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-left: auto;
}
.ig-stick-p {
  font-family: var(--ig-display);
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -0.02em;
}

/* ── reviews (section 04) ───────────────────────────────────────────────
   The v4 review module, ported from the design repo's portable
   `<style id="sx-reviews-css">` block in
   `Sigrix_new/marketplace/Marketplace - Listing Image Prompt v4.html`.

   Two columns. The right one is the write form and the cards — roughly what
   the shared `_reviews_panel.html` already rendered. The left one is the
   reason the module exists: an average, a star row, a tally and a 5→1
   distribution. The shared panel renders a count and a list and nothing else,
   so a listing's rating *spread* was not visible on any surface in the app.

   Prototype tokens map onto the page's own. This sheet reads `--sx-*` straight
   off `.sx` rather than re-scoping a token set (see the file header), so the
   module inherits dark mode for free: `--text-mute` → `--sx-text-2`,
   `--text-soft`/`--text-faint` → `--sx-text-3` and the derivation below,
   `--line-strong` → `--sx-line-2`, `--bg-1` → `--sx-bg-2`, `--good` →
   `--ig-ok`. The two new tokens are both `color-mix()` over a shared token
   rather than literals, so the dark block has nothing to re-flip for them —
   which is what `test_the_dark_block_re_flips_every_page_local_token_that_carries_a_literal`
   checks.

   Prefix note: `rv-` is this page's alone. The sheet loads on no other page,
   and nothing else in `src/static/css/` defines an `rv-*` selector — the
   `.pp-locked` collision in CLAUDE.md is what that check exists to avoid. */
.rv {
  --ig-star: var(--sx-warn, #c77800);
  /* The prototype's fourth text tier (--text-faint, alpha .32 dark / .38 light)
     is below AA at the sizes this module uses it — measured in Chromium at
     3.27:1 light and 3.82:1 dark on the "/ 5", the bar counts, the side note
     and the review date, all of which are 11-12.5px. Those read --sx-text-3
     instead (5.35:1 / 5.84:1), and the faint tone survives only where it is
     drawing the *unfilled star* rather than words: an empty star and a star
     button at rest are non-text UI, which WCAG 1.4.11 puts at 3:1, and both
     clear it. Naming the token for that job is what stops it drifting back
     onto text — the failure is silent, since the rule applies and the element
     renders either way. */
  --ig-star-off: color-mix(in srgb, var(--sx-text-3, #6b7382) 76%, transparent);
  display: grid;
  grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
  gap: 64px;
  align-items: start;
}

/* ── summary rail ─────────────────────────────────────────────────────── */
.rv-main {
  min-width: 0;
}
.rv-sum {
  display: grid;
  gap: 18px;
}
.rv-avg {
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.rv-avg .n {
  font-family: var(--ig-display);
  font-size: 46px;
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 0.9;
}
.rv-avg .of {
  font-family: var(--ig-mono);
  font-size: 12px;
  color: var(--sx-text-3);
}
.rv-stars {
  display: inline-flex;
  gap: 3px;
  color: var(--ig-star);
  line-height: 1;
  font-size: 14px;
  letter-spacing: 2px;
}
.rv-stars .off {
  color: var(--ig-star-off);
}
.rv-avg-stars {
  margin-top: 10px;
}
.rv-tally {
  font-size: 12.5px;
  color: var(--sx-text-3);
}
.rv-bars {
  display: grid;
  gap: 7px;
}
.rv-bar {
  display: grid;
  grid-template-columns: 10px 1fr 22px;
  gap: 10px;
  align-items: center;
  font-family: var(--ig-mono);
  font-size: 11px;
  color: var(--sx-text-3);
}
.rv-bar .track {
  height: 5px;
  border-radius: 999px;
  background: var(--sx-surface-2);
  overflow: hidden;
}
.rv-bar .fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--ig-star);
}
.rv-bar .c {
  text-align: right;
  color: var(--sx-text-3);
}
/* No ratings yet: the rail keeps its shape so the server and product_detail.js
   are updating the same DOM rather than building two different ones, and the
   bars — which would all read zero — are the one part that says nothing. */
.rv-sum[data-empty='true'] .rv-bars,
.rv-sum[data-empty='true'] .rv-avg .of,
.rv-sum[data-empty='true'] .rv-stars {
  display: none;
}
.rv-sum[data-empty='true'] .rv-avg .n {
  color: var(--sx-text-3);
}
.rv-side-note {
  font-size: 12px;
  line-height: 1.55;
  color: var(--sx-text-3);
  padding-top: 16px;
  margin: 0;
  border-top: 1px solid var(--sx-line);
}

/* ── write form ───────────────────────────────────────────────────────── */
.rv-form {
  border: 1px solid var(--sx-line);
  border-radius: var(--sx-r-lg, 14px);
  background: var(--sx-bg-2);
  padding: 22px;
  box-shadow: var(--sx-shadow-2, 0 8px 28px rgba(0, 0, 0, 0.16));
}
.rv-form h3,
.rv-form h4 {
  font-family: var(--ig-display);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0 0 4px;
}
.rv-form .lead {
  margin: 0 0 20px;
  font-size: 13.5px;
  color: var(--sx-text-3);
  max-width: 52ch;
}
.rv-lbl {
  display: block;
  font-family: var(--ig-mono);
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sx-text-3);
  margin-bottom: 9px;
}
/* The label is 10.5px uppercase with wide tracking; "(optional)" is an aside
   inside it and reads as noise in the same treatment. */
.rv-lbl-note {
  text-transform: none;
  letter-spacing: 0;
}
.rv-help {
  display: block;
  font-size: 12.5px;
  color: var(--sx-text-3);
  margin: -4px 0 9px;
}
.rv-field {
  display: block;
  margin-bottom: 18px;
}
.rv-pick {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}
.rv-pick .set {
  display: flex;
  gap: 2px;
}
/* min-height opt-out: css/responsive.css floors every <button> at 44px
   site-wide, so a 32px star button renders 32x44 without this (CLAUDE.md).
   Selected by class rather than as `.rv-pick button` so the floor sweep in
   tests/test_image_prompt_listing_v4_page.py can see it — that check reads
   class names off the markup, and a bare element selector is invisible to it. */
.rv-star {
  width: 32px;
  height: 32px;
  min-height: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ig-star-off);
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
  transition: color 0.12s, background 0.12s;
}
.rv-star:hover:not([disabled]) {
  background: var(--sx-surface-2);
}
/* `is-selected` / `is-hovered` are what product_detail.js toggles, and it sets
   them on every star up to the value — `aria-checked` marks only the exact one,
   so keying the fill off that would light a single star instead of a row. */
.rv-star.is-selected,
.rv-star.is-hovered {
  color: var(--ig-star);
}
.rv-star[disabled] {
  cursor: not-allowed;
}
.rv-pick .word {
  font-size: 12.5px;
  color: var(--sx-text-2);
}
.rv-in {
  width: 100%;
  background: var(--sx-bg);
  border: 1px solid var(--sx-line);
  border-radius: var(--sx-r-md, 10px);
  color: var(--sx-text);
  font: inherit;
  font-size: 14px;
  padding: 11px 13px;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.rv-in::placeholder {
  color: var(--sx-text-3);
}
.rv-in:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--sx-brand-3, #6b78ff) 60%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--sx-brand, #3d4dff) 16%, transparent);
}
textarea.rv-in {
  min-height: 112px;
  resize: vertical;
  line-height: 1.6;
}
.rv-form-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 16px;
  flex-wrap: wrap;
}
.rv-hint {
  font-size: 12px;
  color: var(--sx-text-3);
  margin: 0;
}
.rv-hint--block {
  margin-bottom: 16px;
}
.rv-hint .num {
  font-family: var(--ig-mono);
  color: var(--sx-text-3);
}
.rv-acts-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.rv-status:empty {
  display: none;
}
.rv-status {
  margin: 14px 0 0;
  font-size: 13px;
  color: var(--sx-text-2);
}
.rv-optin {
  display: grid;
  gap: 8px;
  margin-bottom: 18px;
}
.rv-optin-toggle {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  font-size: 13px;
  color: var(--sx-text-2);
  line-height: 1.5;
}
.rv-optin-toggle input {
  min-height: 0;
  width: 15px;
  height: 15px;
  margin-top: 2px;
  flex: none;
}
.rv-optin .rv-hint {
  font-size: 11.5px;
}

/* ── posted / blocked states ──────────────────────────────────────────── */
.rv-done {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  border: 1px solid var(--ig-ok-line);
  background: var(--ig-ok-soft);
  border-radius: var(--sx-r-lg, 14px);
  padding: 16px 18px;
}
.rv-done .ico {
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--ig-ok) 22%, transparent);
  color: var(--ig-ok-text);
  font-size: 11px;
  margin-top: 1px;
}
.rv-done .t {
  font-weight: 600;
  font-size: 13.5px;
}
.rv-done .s {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  color: var(--sx-text-3);
}
.rv-gate {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  border: 1px dashed var(--sx-line-2);
  border-radius: var(--sx-r-lg, 14px);
  padding: 18px 20px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--sx-text-2);
}
.rv-gate .ico {
  flex: none;
  color: var(--sx-text-3);
  margin-top: 1px;
}

/* ── the cards ────────────────────────────────────────────────────────── */
.rv-list {
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
}
/* The prototype puts a monogram in its own 34px column beside the header.
   `author_chip` already renders the reviewer's real avatar next to their name
   and carries the data-* the author-card popover reads, so the avatar rides in
   the header row instead — same adjacency, and it keeps a shipped feature the
   monogram would have replaced with a placeholder. */
.rv-item {
  padding: 24px 0;
  border-top: 1px solid var(--sx-line);
}
.rv-hd {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.rv-hd .ac-chip {
  padding: 0;
  border: 0;
  background: transparent;
}
.rv-when {
  font-family: var(--ig-mono);
  font-size: 11px;
  color: var(--sx-text-3);
}
.rv-title {
  margin: 9px 0 0;
  font-weight: 600;
  font-size: 14px;
  color: var(--sx-text);
}
.rv-body {
  margin: 9px 0 0;
  font-size: 14.5px;
  line-height: 1.62;
  color: var(--sx-text-2);
  max-width: 66ch;
  text-wrap: pretty;
}
.rv-body > :first-child {
  margin-top: 0;
}
.rv-body > :last-child {
  margin-bottom: 0;
}
.rv-item-acts {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 13px;
}
/* min-height opt-out, same floor as .rv-star above. */
.rv-mini {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 27px;
  min-height: 0;
  padding: 0 11px;
  border-radius: 8px;
  border: 1px solid var(--sx-line);
  background: transparent;
  color: var(--sx-text-3);
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.rv-mini:hover {
  background: var(--sx-surface-2);
  color: var(--sx-text);
}
.rv-none {
  border: 1px dashed var(--sx-line-2);
  border-radius: var(--sx-r-lg, 14px);
  padding: 30px 24px;
  margin-top: 28px;
  text-align: center;
  list-style: none;
}
.rv-none .t {
  display: block;
  font-family: var(--ig-display);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.rv-none .s {
  display: block;
  margin: 6px auto 0;
  font-size: 13.5px;
  color: var(--sx-text-3);
  max-width: 42ch;
}


/* ── item manifest ───────────────────────────────────────────────────────
   The list under section 01's CTA, so a buyer counts what they get in the same
   glance as the price. Two types render it and mean different things by a row:
   a skill's is its SKILL.md, a persona's is one authored block. What they
   share is every declaration here, the label included — the *one* property
   that differs is its typeface, since a filename is set in mono and a block
   name is prose, and the skill variant restates that one line at (0,3,1) so it
   wins on specificity rather than on which sheet loaded last. */
.ig-files {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--sx-line);
  border-radius: 12px;
  overflow: hidden;
}
.ig-file {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--sx-bg-2);
  color: var(--sx-text-2);
}
.ig-file + .ig-file {
  border-top: 1px solid var(--sx-line);
}
.ig-file .n {
  font-size: 12.5px;
  color: var(--sx-text);
}
.ig-file .z {
  margin-left: auto;
  font-family: var(--ig-mono);
  font-size: 11.5px;
  color: var(--sx-text-3);
}

/* ── owner stats ─────────────────────────────────────────────────────────
   Replaces the buy column for the seller's own view. Three numbers the
   platform genuinely holds — both prototypes ask for "installs / views",
   which are not tracked per listing, so likes / versions / reviews take their
   place rather than rendering three zeros. Byte-identical in the skill and
   persona prototypes, which is why it is here rather than copied into each. */
.ig-owner-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  padding: 18px 20px;
  border: 1px solid var(--sx-line-2);
  border-radius: 16px;
  background: var(--sx-surface);
  box-shadow: var(--sx-shadow-md, 0 8px 24px rgba(15, 18, 26, 0.06));
}
.ig-owner-stats .v {
  display: block;
  font-family: var(--ig-display);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.ig-owner-stats .l {
  display: block;
  margin-top: 2px;
  font-family: var(--ig-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sx-text-3);
}

/* ── related cards with no cover ─────────────────────────────────────────
   Emitted by the shared `v4_related` macro for any caller passing a
   `cover_fallback`, which is what puts it in the shell: a rule reachable from
   shared markup cannot live in one type's sheet, or the *other* caller renders
   it unstyled at HTTP 200. A skill's argument is not imagery and a persona's
   is its voice, so both name the artifact instead of drawing an empty frame.
   Hatched rather than flat so it reads as a placeholder and not as a cover
   that failed to load. */
.ig-rel-cover--mono {
  display: flex;
  align-items: center;
  justify-content: center;
  background: repeating-linear-gradient(
    135deg,
    var(--sx-surface-2) 0 10px,
    var(--sx-bg-2) 10px 20px
  );
}
.ig-rel-cover--mono .g {
  padding: 6px 12px;
  border-radius: 8px;
  background: var(--sx-bg);
  border: 1px solid var(--sx-line);
  font-family: var(--ig-mono);
  font-size: 12.5px;
  color: var(--sx-text-2);
}

/* ── Versions ─────────────────────────────────────────────────────────────
   Releases are the one thing a buyer re-reads after installing, so both
   prototypes promote them out of the details disclosure into a numbered
   section of their own. The skill file spells them `.sk-ver*` and the persona
   file `.pv-ver*`; the declarations between those two names are identical, so
   this is one component named twice rather than two components. (It did start
   in `sx-listing-skill.css`, with a comment saying only that page had them.
   The second page is what moved it.)

   `subgrid` is what keeps the version, its chip, its weight and its date in
   four aligned columns while each row's note spans the rest — a per-row grid
   would let every row pick its own column widths. */
.ig-vers {
  display: grid;
  grid-template-columns: auto auto minmax(0, auto) 1fr auto;
  margin: 0;
  list-style: none;
  border: 1px solid var(--sx-line);
  border-radius: 16px;
  background: var(--sx-surface);
  padding: 6px 22px;
}
.ig-ver {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: center;
  gap: 0 12px;
  padding: 17px 0;
  list-style: none;
}
.ig-ver + .ig-ver {
  border-top: 1px solid var(--sx-line);
}
.ig-ver .dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--sx-line-2);
}
.ig-ver.is-cur .dot {
  background: var(--ig-accent);
  box-shadow: 0 0 0 4px var(--ig-accent-soft);
}
.ig-ver .v {
  font-family: var(--ig-mono);
  font-size: 13.5px;
}
.ig-ver .z,
.ig-ver .d {
  font-family: var(--ig-mono);
  font-size: 11.5px;
  color: var(--sx-text-3);
  white-space: nowrap;
}
.ig-ver .d {
  text-align: right;
}
.ig-ver .s {
  grid-column: 4 / -1;
  margin: 6px 0 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--sx-text-2);
  text-wrap: pretty;
}
.ig-ver-cur {
  display: inline-flex;
  align-items: center;
  height: 19px;
  padding: 0 8px;
  border-radius: 999px;
  border: 1px solid var(--ig-accent-line);
  background: var(--ig-accent-soft);
  color: var(--ig-accent);
  font-family: var(--ig-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

@media (max-width: 900px) {
  .rv {
    grid-template-columns: 1fr;
    gap: 34px;
  }
  .rv-form {
    padding: 18px;
  }
}

@media (max-width: 1080px) {
  .ig-grid {
    grid-template-columns: 1fr;
    gap: 36px;
  }
  .ig-det {
    grid-template-columns: 1fr;
    gap: 40px;
  }
  .ig-locked {
    grid-template-columns: 1fr;
  }
  .ig-locked-r {
    border-left: 0;
    border-top: 1px solid var(--sx-line);
  }
  .ig-rel {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 760px) {
  .ig-rel {
    grid-template-columns: repeat(2, 1fr);
  }
  .ig-owner-stats {
    grid-template-columns: 1fr;
    gap: 10px;
  }
  /* subgrid gives up at this width — five columns in 360px puts the date under
     the version number. One flowing column per row keeps every field readable
     and the note still last. */
  .ig-vers {
    display: block;
    padding: 6px 18px;
  }
  .ig-ver {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
  }
  .ig-ver .d {
    margin-left: auto;
  }
  .ig-ver .s {
    flex: 1 1 100%;
    margin-top: 2px;
  }
  /* Both drop to one column rather than shrinking: the legend row ends with a
     nowrap `N · NN%` value pushed right by `margin-left: auto`, and the cost
     card's number is a 20px display face — neither survives a third of 360px. */
  .ig-tok-legend {
    grid-template-columns: 1fr;
  }
  .ig-tok-cost {
    grid-template-columns: 1fr;
  }
  .ig-facts {
    grid-template-columns: 1fr;
  }
  .ig-sigs {
    grid-template-columns: 1fr;
  }
  .ig-sigs .ig-sig:nth-child(2) {
    border-top: 1px solid var(--sx-line);
  }
  .ig-sigs .ig-sig:nth-child(even) {
    border-left: 0;
  }
  .ig-sec {
    margin-top: 64px;
  }
  .ig-sec-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
  .ig-locked-l {
    padding: 26px 22px;
  }
  .ig-buy {
    flex-wrap: wrap;
  }
  /* Handoff §8.4: on mobile the bar keeps only the CTA. The title and meta are
     already on screen above it, and a three-part bar at 360px leaves the button
     too narrow to hit. */
  .ig-stick-m,
  .ig-stick-t {
    display: none;
  }
  .ig-stick-r {
    margin-left: 0;
    width: 100%;
  }
  .ig-stick-r .ig-btn {
    flex: 1;
  }
}
