{% extends "base.html" %}
{% block title %}{{ item_name or inner_name or plugin_name }} — {{ config.INSTANCE_NAME }}{% endblock %}

{% block content %}
<style>
  .item-detail {
    --bg: var(--background);
    --warn-color: #b45309;
  }
  /* Per-kind accents — driven from data-kind. `--kind-from` / `--kind-to`
     are the gradient endpoints for the dark hero, mirroring how
     plugin_detail uses #0073D1 → #0056A3. The lighter shade leads
     (top-left), darker trails (bottom-right). */
  .item-detail[data-kind="skill"] {
    --kind: #0e9b6a;
    --kind-from: #10b77f;
    --kind-to:   #047857;
    --kind-bg: rgba(16, 183, 127, 0.14);
    --kind-shadow: rgba(16, 183, 127, 0.22);
  }
  .item-detail[data-kind="agent"] {
    --kind: #6d28d9;
    --kind-from: #7c3aed;
    --kind-to:   #5b21b6;
    --kind-bg: rgba(124, 58, 237, 0.14);
    --kind-shadow: rgba(124, 58, 237, 0.22);
  }

  /* ── Hero — full kind-coloured gradient (parity with plugin detail) ─ */
  .item-detail .hero {
    position: relative;
    background: linear-gradient(135deg, var(--kind-from) 0%, var(--kind-to) 100%);
    border-radius: 14px;
    padding: 22px 28px 28px;
    margin-bottom: 24px;
    box-shadow: 0 4px 16px var(--kind-shadow);
    color: #fff;
  }

  /* Breadcrumbs — moved INSIDE hero, white-tinted (parity with plugin detail). */
  .item-detail .hero .crumbs {
    display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
    font-size: 12px; color: rgba(255,255,255,0.78);
    margin-bottom: 18px;
  }
  .item-detail .hero .crumbs a { color: #fff; opacity: 0.92; text-decoration: none; }
  .item-detail .hero .crumbs a:hover { text-decoration: underline; }
  .item-detail .hero .crumbs .sep { opacity: 0.5; }
  .item-detail .hero .crumbs .current { color: #fff; font-weight: var(--font-medium); }

  .item-detail .hero .head {
    display: grid;
    grid-template-columns: 380px minmax(0, 1fr) 300px;
    gap: 24px;
    align-items: start;
  }
  @media (max-width: 1100px) {
    .item-detail .hero .head { grid-template-columns: 380px minmax(0, 1fr); }
  }
  @media (max-width: 720px) {
    .item-detail .hero .head { grid-template-columns: 1fr; }
  }

  /* Hero window — macOS-style frame around the skill/agent cover photo.
     Mirrors the plugin-detail hero window so the curated/flea detail
     pages share a consistent visual language. The window's dark chrome
     stays constant; only the surrounding hero gradient changes per kind
     (skill=green, agent=purple). The body has aspect-ratio 715/310 so
     curator-uploaded covers never crop to a square. */
  .item-detail .hero .hero-window {
    width: 380px;
    background: #1e293b;
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid rgba(255, 255, 255, 0.10);
    box-shadow: 0 12px 30px rgba(15, 23, 42, 0.40),
                0 2px 6px rgba(0, 0, 0, 0.18);
    flex-shrink: 0;
    align-self: start;
  }
  .item-detail .hero .hero-window-titlebar {
    background: linear-gradient(180deg, #2a3445 0%, #1e293b 100%);
    height: 26px;
    display: flex; align-items: center; gap: 6px;
    padding: 0 10px;
    border-bottom: 1px solid rgba(0, 0, 0, 0.25);
  }
  .item-detail .hero .hwdot {
    width: 11px; height: 11px; border-radius: 50%;
    box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.18);
    flex-shrink: 0;
  }
  .item-detail .hero .hwdot.red    { background: #ff5f56; }
  .item-detail .hero .hwdot.yellow { background: #ffbd2e; }
  .item-detail .hero .hwdot.green  { background: #27c93f; }
  .item-detail .hero .hero-window-label {
    margin: 0 auto;
    padding-right: 46px;
    font-size: 10.5px; color: rgba(255, 255, 255, 0.55);
    font-family: var(--font-mono);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    max-width: 100%;
  }
  .item-detail .hero .hero-window-body {
    aspect-ratio: 715 / 310;
    overflow: hidden;
    background: linear-gradient(135deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.04) 100%);
    display: flex; align-items: center; justify-content: center;
    color: #fff; font-size: 44px; font-weight: var(--font-bold);
    letter-spacing: 1px;
  }
  .item-detail .hero .hero-window-body img {
    width: 100%; height: 100%; object-fit: cover; display: block;
  }

  .item-detail .hero .meta {
    min-width: 0;
  }
  .item-detail .hero .badges {
    display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
    margin-top: 12px;
  }
  .item-detail .type-badge {
    display: inline-block;
    padding: 3px 10px; border-radius: 999px;
    background: rgba(255,255,255,0.22); color: #fff;
    font-size: 11px; font-weight: var(--font-semibold);
    text-transform: uppercase; letter-spacing: 0.5px;
  }
  .item-detail .source-badge {
    font-size: 11px; padding: 3px 10px; border-radius: 999px;
    font-weight: var(--font-semibold);
  }
  .item-detail .source-badge.curated { background: #FEF3C7; color: #B45309; }
  .item-detail .source-badge.flea    { background: #EDE9FE; color: #6D28D9; }
  .item-detail .cat-badge {
    font-size: 11px; padding: 3px 10px; border-radius: 999px;
    background: rgba(255,255,255,0.16); color: #fff;
  }

  .item-detail .hero h1 {
    margin: 0 0 6px; font-size: 28px; font-weight: var(--font-bold);
    letter-spacing: -0.4px; color: #fff;
    word-wrap: break-word;
  }
  .item-detail .hero .meta-row {
    display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
    font-size: 13px; color: rgba(255,255,255,0.85);
  }
  .item-detail .hero .meta-row strong { color: #fff; font-weight: var(--font-semibold); }
  .item-detail .hero .meta-row a { color: #fff; text-decoration: none; }
  .item-detail .hero .meta-row a:hover { text-decoration: underline; }
  .item-detail .hero .meta-row .dot { color: rgba(255,255,255,0.4); }

  /* Invocation block — flea only. Lives inside the Description panel so
     "what it does" sits next to "how to call it". The chip itself matches
     the .code-block convention from /setup (Catppuccin Mocha palette, mono,
     green `/` prompt + Copy button) so it reads as a real terminal command. */
  .item-detail .invocation-block { margin-top: 18px; }
  .item-detail .invocation-label {
    font-size: 12px; font-weight: var(--font-semibold);
    color: var(--text-secondary);
    text-transform: uppercase; letter-spacing: 0.6px;
    margin: 0 0 8px;
  }
  .item-detail .invocation {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 14px;
    background: #1e1e2e;
    border-radius: 8px;
    font-family: var(--font-mono); font-size: 13px;
    color: #cdd6f4;
    width: 100%;
    line-height: 1.5;
    box-sizing: border-box;
  }
  .item-detail .invocation .prompt {
    color: #a6e3a1;
    user-select: none;
    flex-shrink: 0;
    font-weight: var(--font-bold);
  }
  .item-detail .invocation .cmd { flex: 1; min-width: 0; overflow-wrap: anywhere; }
  .item-detail .invocation .btn-copy {
    appearance: none; cursor: pointer;
    padding: 4px 10px;
    background: transparent;
    border: 1px solid #45475a;
    color: #cdd6f4;
    border-radius: 6px;
    font-size: 11px; font-weight: var(--font-medium);
    font-family: var(--font-primary);
    transition: all 0.15s ease;
    flex-shrink: 0;
  }
  .item-detail .invocation .btn-copy:hover {
    border-color: #89b4fa; color: #89b4fa;
    background: rgba(137, 180, 250, 0.08);
  }
  .item-detail .invocation .btn-copy.copied {
    border-color: #a6e3a1; color: #a6e3a1;
    background: rgba(166, 227, 161, 0.08);
  }

  .item-detail .hero .actions {
    /* Absolute on .hero — anchored at the top-right corner with equal
       22px offsets from top and right. Hint card stacks below the
       button row inside this absolute container, so it stays inside
       the hero gradient and toggling it doesn't shift body content. */
    position: absolute; top: 22px; right: 22px;
    width: 300px;
    display: flex; flex-direction: column; gap: 10px;
    align-items: stretch;
    z-index: 1;
  }
  @media (max-width: 1100px) {
    .item-detail .hero .actions {
      position: static; width: auto; margin-top: 18px;
      align-items: flex-end;
    }
  }
  .item-detail .hero .actions .actions-row {
    display: flex; flex-direction: row; align-items: center;
    justify-content: flex-end; gap: 12px;
  }
  .item-detail .hero .actions [hidden] { display: none !important; }
  .item-detail .hero .actions .btn {
    appearance: none; cursor: pointer;
    padding: 9px 16px; border-radius: 8px;
    font-size: 12px; font-weight: var(--font-semibold); font-family: inherit;
    border: 1px solid rgba(255,255,255,0.28);
    background: rgba(255,255,255,0.12);
    color: #fff;
    text-decoration: none;
    transition: all 0.15s ease;
  }
  .item-detail .hero .actions .btn:hover {
    /* Darken instead of lightening — white text on a brighter white
       background was washing out. */
    background: rgba(0, 0, 0, 0.2);
    border-color: rgba(255, 255, 255, 0.55);
    color: #fff;
  }
  .item-detail .hero .actions .btn.primary {
    background: #fff; color: var(--kind); border-color: #fff;
  }
  .item-detail .hero .actions .btn.primary:hover {
    /* Darken-glass — same formula as the secondary .btn:hover above so
       primary and secondary hero actions feel consistent. The kind
       gradient shows through the 20% black tint, the white border + text
       supply contrast on either green (skill) or purple (agent) heroes. */
    background: rgba(0, 0, 0, 0.2);
    border-color: rgba(255, 255, 255, 0.55);
    color: #fff;
  }
  /* Status label — inline text, NOT a button. Communicates "currently
     in stack" sitting before the Remove button. No border, no fill —
     visual weight intentionally below the adjacent action so the
     user's eye lands on the button. */
  .item-detail .hero .actions .status-pill {
    display: inline-flex; align-items: center;
    font-size: 13px; font-weight: 500;
    color: #fff;
    cursor: default; user-select: none;
  }
  /* Remove from stack — red border by default so the destructive
     intent is announced before hover; full red fill on hover commits
     to it. */
  .item-detail .hero .actions .btn-remove {
    appearance: none; cursor: pointer;
    /* Padding + font-size match .btn.primary so the install / remove
       buttons render at the same height across state transitions. */
    padding: 9px 16px; border-radius: 8px;
    font-size: 12px; font-weight: var(--font-semibold); font-family: inherit;
    background: transparent; color: #fecaca;
    border: 1px solid rgba(248, 113, 113, 0.7);
    transition: all 0.15s ease;
  }
  .item-detail .hero .actions .btn-remove:hover {
    background: rgba(220, 38, 38, 0.85); color: #fff;
    border-color: rgba(220, 38, 38, 0.95);
  }
  .item-detail .hero .actions .btn-remove:focus-visible {
    outline: 2px solid rgba(254, 202, 202, 0.85); outline-offset: 2px;
  }
  .item-detail .hero .actions .helper {
    font-size: 11px; color: rgba(255,255,255,0.78); line-height: 1.45;
    text-align: right; max-width: 260px;
    /* Anchor the 260px box at the right edge of the 300px .actions column
       so the helper's right edge lines up with the button's right edge —
       without this the parent's `align-items: stretch` puts the box flush
       LEFT, and text-align: right only right-aligns within the 260px box,
       which sits ~40px short of the button. */
    align-self: flex-end;
  }
  .item-detail .hero .actions .helper strong {
    color: #fff; font-weight: var(--font-semibold);
  }

  /* ── Post-add hint panel (flea standalone only) ──────────────────────
     Mirrors the panel on marketplace_plugin_detail.html. Lives inside
     the Description panel below the invocation chip so the user sees
     "what it does → how to call it → what to do to make it callable"
     in the natural reading order. */
  /* Glass-on-gradient inside the hero — translucent white over the
     kind gradient (green for skill, purple for agent) reads as
     "elevated tile of the same hero". White text + dismiss outline
     work on any of the three kind palettes without per-kind branching. */
  .item-detail .stack-hint {
    padding: 10px 12px;
    background: rgba(255, 255, 255, 0.14);
    border: 1px solid rgba(255, 255, 255, 0.28);
    border-radius: 10px;
    font-size: 12px;
    color: rgba(255, 255, 255, 0.96);
    line-height: 1.5;
    backdrop-filter: blur(6px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
  }
  .item-detail .stack-hint .head {
    display: flex; align-items: center; justify-content: space-between;
    gap: 8px; margin-bottom: 4px;
  }
  .item-detail .stack-hint .title {
    font-weight: var(--font-semibold);
    color: #fff;
    font-size: 12px;
  }
  .item-detail .stack-hint .dismiss {
    appearance: none; background: transparent;
    border: 1px solid rgba(255, 255, 255, 0.30);
    color: rgba(255, 255, 255, 0.82); font-size: 10px; cursor: pointer;
    padding: 2px 7px; border-radius: 5px;
    font-family: inherit;
    white-space: nowrap;
  }
  .item-detail .stack-hint .dismiss:hover {
    color: #fff; background: rgba(255, 255, 255, 0.14);
    border-color: rgba(255, 255, 255, 0.50);
  }
  .item-detail .stack-hint ol {
    margin: 6px 0 0; padding-left: 20px;
    color: var(--text-secondary);
  }
  .item-detail .stack-hint ol li { margin: 4px 0; }
  .item-detail .stack-hint ol li strong { color: var(--text-primary); font-weight: var(--font-semibold); }
  .item-detail .stack-hint .cmd-chip {
    display: inline-flex; align-items: center; gap: 8px;
    margin-top: 6px;
    padding: 6px 10px;
    background: #1e1e2e;
    border-radius: 6px;
    font-family: var(--font-mono); font-size: 12px;
    color: #cdd6f4;
  }
  .item-detail .stack-hint .cmd-chip .prompt {
    color: #a6e3a1; user-select: none; font-weight: var(--font-bold);
  }
  .item-detail .stack-hint .cmd-chip .btn-copy {
    appearance: none; cursor: pointer;
    padding: 2px 8px;
    background: transparent;
    border: 1px solid #45475a;
    color: #cdd6f4;
    border-radius: 4px;
    font-size: 10px; font-weight: var(--font-medium);
    font-family: var(--font-primary);
    transition: all 0.15s ease;
  }
  .item-detail .stack-hint .cmd-chip .btn-copy:hover {
    border-color: #89b4fa; color: #89b4fa;
    background: rgba(137, 180, 250, 0.08);
  }
  .item-detail .stack-hint .cmd-chip .btn-copy.copied {
    border-color: #a6e3a1; color: #a6e3a1;
  }
  .item-detail .stack-hint .learn-more {
    display: inline-block; margin-top: 8px;
    font-size: 12px; color: var(--primary); text-decoration: none;
  }
  .item-detail .stack-hint .learn-more:hover { text-decoration: underline; }

  /* ── Top row: Description + Details ───────────────────────────────── */
  .item-detail .top-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 20px;
    margin-bottom: 20px;
    align-items: stretch;
  }
  @media (max-width: 900px) {
    .item-detail .top-row { grid-template-columns: 1fr; }
  }

  .item-detail .panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 1px 2px rgba(0,0,0,0.04);
    padding: 22px 26px;
  }
  .item-detail .panel h2 {
    font-size: 15px; font-weight: var(--font-semibold);
    margin: 0 0 14px;
    text-transform: uppercase; letter-spacing: 0.6px;
    color: var(--text-secondary);
  }
  .item-detail .panel .lead {
    font-size: 14.5px; line-height: 1.65; color: var(--text-primary);
    white-space: pre-wrap;
  }
  /* Hero tagline — single-line friendly subtitle below the h1. Same
     placement contract as the plugin-detail hero tagline. */
  .item-detail .hero .hero-tagline {
    font-size: 14.5px; line-height: 1.5;
    color: rgba(255,255,255,0.92);
    margin: -2px 0 8px;
  }

  /* Rich-content rendering helpers (parity with marketplace_plugin_detail).
     `.lead-rendered` switches the lead block from plain-text mode (which
     uses `white-space: pre-wrap` so plain frontmatter descriptions retain
     line breaks) to HTML mode where paragraph breaks come from <p> tags. */
  .item-detail .lead-rendered { white-space: normal; }
  .item-detail .lead-rendered > *:first-child { margin-top: 0; }
  .item-detail .lead-rendered > *:last-child { margin-bottom: 0; }
  .item-detail .lead-rendered p { margin: 0 0 12px; }
  .item-detail .lead-rendered h2,
  .item-detail .lead-rendered h3,
  .item-detail .lead-rendered h4 {
    font-size: 14.5px; font-weight: 600;
    margin: 14px 0 6px;
    text-transform: none; letter-spacing: 0; color: var(--text-primary);
  }
  .item-detail .lead-rendered ul,
  .item-detail .lead-rendered ol { margin: 0 0 12px 22px; padding: 0; }
  .item-detail .lead-rendered code {
    background: var(--surface-alt, #f4f4f5); border-radius: 4px;
    padding: 1px 5px; font-size: 0.92em;
    font-family: var(--font-mono);
  }
  .item-detail .lead-rendered pre {
    background: #1e1e2e; color: #cdd6f4;
    border-radius: 8px; padding: 12px 14px;
    font-family: var(--font-mono); font-size: 12.5px;
    overflow-x: auto; margin: 8px 0 14px;
  }
  .item-detail .lead-rendered pre code {
    background: transparent; padding: 0; color: inherit;
  }
  .item-detail .lead-rendered a { color: var(--primary); text-decoration: none; }
  .item-detail .lead-rendered a:hover { text-decoration: underline; }

  /* Spacing between the new rich-content panels — same vertical rhythm
     as the existing .section blocks below. */
  .item-detail .section-block { margin-top: 24px; }

  /* Use-cases grid — 3-column on wide, 2 on tablet, 1 on phone. */
  .item-detail .use-cases-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 14px;
  }
  @media (max-width: 1100px) {
    .item-detail .use-cases-grid { grid-template-columns: repeat(2, 1fr); }
  }
  @media (max-width: 700px) {
    .item-detail .use-cases-grid { grid-template-columns: 1fr; }
  }
  .item-detail .use-case-card {
    border: 1px solid var(--border); border-radius: 10px;
    padding: 14px 16px;
    background: var(--surface-alt, #fafafa);
    display: flex; flex-direction: column; gap: 8px;
  }
  .item-detail .use-case-card h3 {
    margin: 0; font-size: 13.5px; font-weight: 600;
    color: var(--text-primary); letter-spacing: 0;
    text-transform: none;
  }
  .item-detail .use-case-card p {
    margin: 0; font-size: 12.5px; line-height: 1.5;
    color: var(--text-secondary);
  }
  .item-detail .use-case-prompt {
    margin: 4px 0 0;
    background: #1e1e2e; color: #a6e3a1;
    border-radius: 6px;
    padding: 8px 10px;
    font-family: var(--font-mono); font-size: 12px;
    line-height: 1.4;
    overflow-x: auto;
    white-space: pre-wrap; word-break: break-word;
  }

  /* Sample interaction — Claude Code Catppuccin Mocha dark transcript.
     Identical visual treatment as plugin-detail's sample block: one dark
     panel, monospace user row with green `>` prompt, sans-serif assistant
     body with markdown formatting. */
  .item-detail .sample-interaction {
    background: #1e1e2e;
    border: 1px solid rgba(255,255,255,0.06);
    border-radius: 10px;
    overflow: hidden;
    display: flex; flex-direction: column;
  }
  .item-detail .sample-user,
  .item-detail .sample-assistant { padding: 14px 18px; }
  .item-detail .sample-user {
    border-bottom: 1px solid rgba(255,255,255,0.08);
    background: rgba(255,255,255,0.015);
  }
  .item-detail .sample-label {
    /* visually-hidden — `>` prefix is the speaker indicator */
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0,0,0,0);
    white-space: nowrap; border: 0;
  }
  .item-detail .sample-user > div:last-child {
    font-family: var(--font-mono);
    color: #cdd6f4;
    font-size: 13.5px; line-height: 1.55;
    white-space: pre-wrap; word-break: break-word;
  }
  .item-detail .sample-user > div:last-child::before {
    content: "> ";
    color: #a6e3a1; font-weight: 700;
    user-select: none;
  }
  .item-detail .sample-assistant-body {
    font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif);
    color: #cdd6f4;
    font-size: 13.5px; line-height: 1.65;
  }
  .item-detail .sample-assistant-body > *:first-child { margin-top: 0; }
  .item-detail .sample-assistant-body > *:last-child { margin-bottom: 0; }
  .item-detail .sample-assistant-body p { margin: 0 0 10px; }
  .item-detail .sample-assistant-body strong { color: #fab387; }
  .item-detail .sample-assistant-body em { color: #f9e2af; font-style: italic; }
  .item-detail .sample-assistant-body code {
    background: rgba(255,255,255,0.06);
    color: #f5c2e7;
    border-radius: 4px; padding: 1px 5px;
    font-size: 0.92em; font-family: var(--font-mono);
  }
  .item-detail .sample-assistant-body pre {
    background: #181825;
    border: 1px solid rgba(255,255,255,0.06);
    border-radius: 8px;
    padding: 12px 14px;
    font-family: var(--font-mono); font-size: 12.5px; line-height: 1.5;
    color: #cdd6f4;
    overflow-x: auto;
    margin: 8px 0;
  }
  .item-detail .sample-assistant-body pre code {
    background: transparent; padding: 0; color: inherit;
  }
  .item-detail .sample-assistant-body ul,
  .item-detail .sample-assistant-body ol { margin: 0 0 10px 22px; padding: 0; }
  .item-detail .sample-assistant-body li { margin: 2px 0; }
  .item-detail .sample-assistant-body a {
    color: #89b4fa; text-decoration: none;
  }
  .item-detail .sample-assistant-body a:hover { text-decoration: underline; }
  .item-detail .sample-assistant-body blockquote {
    border-left: 3px solid #585b70;
    margin: 8px 0; padding: 4px 0 4px 12px;
    color: #bac2de;
  }
  .item-detail .sample-assistant-body h2,
  .item-detail .sample-assistant-body h3,
  .item-detail .sample-assistant-body h4 {
    color: #cdd6f4;
    font-size: 14px; font-weight: 600;
    margin: 12px 0 6px;
    text-transform: none; letter-spacing: 0;
  }

  .item-detail .details dl { margin: 0; }
  .item-detail .details .row {
    display: grid; grid-template-columns: max-content 1fr;
    gap: 12px;
    padding: 10px 0;
    border-bottom: 1px solid var(--border-light);
    font-size: 13px;
  }
  .item-detail .details .row:last-child { border-bottom: none; }
  .item-detail .details dt {
    color: var(--text-secondary); margin: 0;
    font-weight: var(--font-medium);
  }
  .item-detail .details dd {
    margin: 0; color: var(--text-primary); font-weight: var(--font-medium);
    text-align: right; word-break: break-word;
  }
  .item-detail .details dd.mono { font-family: var(--font-mono); font-size: 12px; }
  .item-detail .details dd .todo { color: var(--warn-color); font-style: italic; font-weight: 400; }

  /* ── Section card (Docs / Files) ─────────────────────────────────── */
  .item-detail .section {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 1px 2px rgba(0,0,0,0.04);
    margin-bottom: 18px; overflow: hidden;
  }
  .item-detail .section-head {
    display: flex; align-items: center; gap: 10px;
    padding: 18px 24px 0;
  }
  .item-detail .section-head h2 {
    margin: 0; font-size: 15px; font-weight: var(--font-semibold);
    color: var(--text-primary);
  }
  .item-detail .section-head .count {
    font-size: 12px; color: var(--text-secondary);
    background: var(--border-light);
    padding: 2px 8px; border-radius: 999px;
  }
  .item-detail .section-body { padding: 12px 24px 22px; }

  .item-detail .file-list { font-family: var(--font-mono); font-size: 12.5px; }
  .item-detail .file-list .file {
    display: flex; justify-content: space-between; gap: 16px;
    padding: 8px 0; border-bottom: 1px dashed var(--border-light);
    color: var(--text-primary);
    text-decoration: none;
  }
  .item-detail .file-list .file:last-child { border-bottom: none; }
  .item-detail .file-list a.file:hover .name { color: var(--kind); text-decoration: underline; }
  .item-detail .file-list .file .name {
    display: flex; align-items: center; gap: 8px; min-width: 0;
  }
  .item-detail .file-list .file .name .icon {
    width: 14px; height: 14px; flex-shrink: 0; color: var(--text-secondary);
  }
  .item-detail .file-list a.file .name .icon { color: var(--kind); }
  .item-detail .file-list .file .size {
    color: var(--text-secondary); flex-shrink: 0;
    font-variant-numeric: tabular-nums;
  }

  .item-detail .empty-msg {
    color: var(--text-secondary); font-size: 13px; font-style: italic;
    text-align: center; padding: 32px 16px;
  }
</style>

<div class="item-detail page-shell" id="root"
     data-source="{{ source }}"
     data-kind="{{ kind }}"
     data-marketplace-id="{{ marketplace_id or '' }}"
     data-plugin-name="{{ plugin_name or '' }}"
     data-entity-id="{{ entity_id or '' }}"
     data-inner-name="{{ inner_name or '' }}"
     data-visibility="{{ entity.visibility_status if entity else 'approved' }}">

  {# v32+ quarantine banner. Owner / admin only when non-approved. #}
  {% include "_quarantine_banner.html" %}

  {% if entity and (is_owner or is_admin) %}
  <style>
    .item-detail .owner-actions {
      display: flex; gap: 10px; margin: 0 0 16px 0; justify-content: flex-end;
    }
    .item-detail .owner-actions a,
    .item-detail .owner-actions button {
      padding: 6px 14px; border-radius: 8px;
      font-size: 13px; font-weight: 500; font-family: var(--font-primary);
      text-decoration: none; border: 1px solid var(--border, #e5e7eb);
      background: var(--surface, #fff); color: var(--text, #111827);
      cursor: pointer;
    }
    .item-detail .owner-actions .delete { color: #b91c1c; }
    .item-detail .owner-actions button:disabled,
    .item-detail .owner-actions a[aria-disabled="true"] {
      color: #9ca3af !important; border-color: #e5e7eb !important;
      background: #f3f4f6 !important; cursor: not-allowed;
    }
  </style>
  <div class="owner-actions">
    {% if edit_in_flight %}
    <a href="#" aria-disabled="true"
       title="Wait for the in-flight review to finish before editing.">
      Edit (review in flight)
    </a>
    {% else %}
    <a href="/marketplace/flea/{{ entity.id }}/edit"
       title="Edit metadata or upload a new version.">
      Edit
    </a>
    {% endif %}
    {# Same v35 delete UX as the plugin detail page — see comment there. #}
    {% if is_admin %}
      {% if entity.visibility_status == 'approved' %}
        <button class="delete" id="owner-archive-btn" type="button"
                title="Soft delete: hides from browse + blocks new installs. Existing user_store_installs continue serving the bundle.">
          Archive
        </button>
      {% elif entity.visibility_status == 'archived' %}
        <button class="delete" type="button" disabled
                title="Already archived. Hidden from browse; existing installs still served.">
          Archived
        </button>
      {% else %}
        <button class="delete" type="button" disabled
                title="Archive is only available for approved entities. Use Override (in quarantine banner) to publish, Rescan to re-evaluate, or Hard delete to purge.">
          Archive (not applicable while {{ entity.visibility_status }})
        </button>
      {% endif %}
      <button class="delete" id="owner-hard-delete-btn" type="button"
              style="border-color: rgba(185,28,28,0.45);"
              title="Hard delete: drops the bundle + removes existing installs. Use only for legal / privacy removals.">
        Hard delete (admin)
      </button>
    {% elif entity.visibility_status == 'approved' %}
      <button class="delete" id="owner-archive-btn" type="button"
              title="Soft delete: hides from browse + blocks new installs. Existing user_store_installs continue serving the bundle.">
        Archive
      </button>
    {% elif entity.visibility_status == 'archived' %}
      <button class="delete" type="button" disabled
              title="Already archived. Hidden from browse; existing installs still served.">
        Archived
      </button>
    {% elif entity.visibility_status == 'pending' %}
      <button class="delete" type="button" disabled
              title="Submission is under review — Delete is locked until checks finish.">
        Delete (locked — under review)
      </button>
    {% else %}
      <button class="delete" type="button" disabled
              title="Submission is quarantined. Only an admin can delete it. Edit + re-upload to fix.">
        Delete (locked — quarantined)
      </button>
    {% endif %}
  </div>

  {% include "_flea_versions.html" %}

  <script>
  (function(){
    const root = document.getElementById('root');
    if (root.dataset.source !== 'flea' || !root.dataset.entityId) return;
    function bindDel(id, opts){
      const b = document.getElementById(id);
      if (!b) return;
      b.addEventListener('click', async () => {
        if (!confirm(opts.confirm)) return;
        const url = `/api/store/entities/${encodeURIComponent(root.dataset.entityId)}${opts.hard ? '?hard=true' : ''}`;
        const r = await fetch(url, {method: 'DELETE'});
        if (!r.ok) { alert((opts.hard ? 'Hard delete' : 'Archive') + ' failed (' + r.status + ')'); return; }
        window.location = '/marketplace?tab=flea';
      });
    }
    bindDel('owner-archive-btn', {hard: false, confirm: 'Archive this entity? Disappears from browse; existing installs keep working.'});
    bindDel('owner-hard-delete-btn', {hard: true, confirm: 'HARD DELETE — drops bundle + removes ALL existing installs. Continue?'});
  })();
  </script>
  {% endif %}

  <!-- Hero (full kind-coloured gradient, parity with plugin detail) -->
  <div class="hero">
    <div class="crumbs" id="crumbs">
      <a href="/marketplace">Marketplace</a>
      <span class="sep">›</span>
      <span id="crumb-loading">Loading…</span>
    </div>
    <div class="head">
      <div class="hero-window" id="hero-photo" aria-hidden="true">
        <div class="hero-window-titlebar">
          <span class="hwdot red"></span>
          <span class="hwdot yellow"></span>
          <span class="hwdot green"></span>
          <span class="hero-window-label" id="hero-window-label">{{ inner_name or item_name or plugin_name }}</span>
        </div>
        <div class="hero-window-body" id="hero-window-body">{{ 'SK' if kind == 'skill' else 'AG' }}</div>
      </div>
      <div class="meta">
        <h1 id="hero-name">{{ inner_name or item_name or plugin_name }}</h1>
        <div class="hero-tagline" id="hero-tagline" hidden></div>
        <div class="meta-row" id="hero-meta-row"></div>
        <div class="badges" id="hero-badges">
          <span class="type-badge">{{ kind }}</span>
          <span class="source-badge {{ source }}">{{ 'Curated' if source == 'curated' else 'Flea' }}</span>
        </div>
      </div>
    </div>
    <!-- Actions absolutely anchored at the hero's top-right corner.
         Action-row holds the install/remove buttons (JS writes into
         #hero-actions); stack-hint stacks below as a glass card. -->
    <div class="actions">
      <div class="actions-row" id="hero-actions"></div>
      <div class="helper" id="hero-helper" hidden></div>
      <div class="stack-hint" id="stack-hint" hidden>
        <div class="head">
          <span class="title" id="stack-hint-title">✓ Added to your stack</span>
          <button class="dismiss" id="stack-hint-dismiss" type="button">Don’t show again</button>
        </div>
        <div>Run in Claude Code:</div>
        <div class="cmd-chip">
          <span class="prompt">/</span>
          <span class="cmd">update-agnes-plugins</span>
          <button class="btn-copy" id="stack-hint-copy" type="button">Copy</button>
        </div>
      </div>
    </div>
  </div>

  <div class="top-row">
    <div class="panel">
      <h2>Description</h2>
      <div class="lead" id="description-body">Loading…</div>
      <div class="invocation-block" id="invocation-block" hidden>
        <h3 class="invocation-label">How to call it</h3>
        <div class="invocation" id="invocation" title="Run in Claude Code">
          <span class="prompt">/</span>
          <span class="cmd" id="invocation-cmd"></span>
          <button class="btn-copy" id="invocation-copy" type="button">Copy</button>
        </div>
      </div>
    </div>
    <aside class="panel details">
      <h2>Details</h2>
      <dl id="details-list">
        <div class="row"><dt>Type</dt><dd>{{ kind | capitalize }}</dd></div>
      </dl>
    </aside>
  </div>

  <!-- Use cases — populated from marketplace-metadata.json use_cases[].
       Hidden until the curator has supplied at least one card. -->
  <div class="panel section-block" id="panel-use-cases" hidden>
    <h2>When to use it</h2>
    <div class="use-cases-grid" id="panel-use-cases-grid"></div>
  </div>

  <!-- Sample interaction — Claude Code-style dark transcript panel. -->
  <div class="panel section-block" id="panel-sample" hidden>
    <h2>Example</h2>
    <div class="sample-interaction">
      <div class="sample-user"><span class="sample-label">You</span><div id="sample-user"></div></div>
      <div class="sample-assistant"><span class="sample-label">Claude</span><div id="sample-assistant" class="sample-assistant-body"></div></div>
    </div>
  </div>

  <!-- When-to-use disambiguation: rendered markdown ("Use X for Y, see Z
       for the alternative…"). Hidden until populated. -->
  <div class="panel section-block" id="panel-when-to-use" hidden>
    <h2>When to use this</h2>
    <div class="lead" id="when-to-use-body"></div>
  </div>

  <div class="section" id="docs-section" hidden>
    <div class="section-head">
      <h2>Documentation</h2>
      <span class="count" id="docs-count">0</span>
    </div>
    <div class="section-body">
      {# v32: plain link list matching the plugin detail page. No file
         icons, no kind chips — every entry is downloadable PDF / Markdown /
         plain text by contract (sync drops anything else). #}
      <ul class="doc-link-list" id="docs-list" style="list-style:none;padding:0;margin:0;display:grid;gap:8px;"></ul>
    </div>
  </div>

  <div class="section" id="files-section" hidden>
    <div class="section-head">
      <h2>Files</h2>
      <span class="count" id="files-count">0</span>
    </div>
    <div class="section-body">
      <div class="file-list" id="files-list"></div>
    </div>
  </div>

  <div id="error-msg" class="empty-msg" hidden></div>
</div>

<script>
'use strict';
(async function () {
  {% include "_marketplace_video_embed.html" %}
  const root = document.getElementById('root');
  const source = root.dataset.source;          // 'curated' | 'flea'
  const kind = root.dataset.kind;              // 'skill' | 'agent'
  const marketplaceId = root.dataset.marketplaceId;
  const pluginName = root.dataset.pluginName;
  const entityId = root.dataset.entityId;
  const innerName = root.dataset.innerName;

  // Curated nested → /api/marketplace/curated/{mp}/{plugin}/{kind}/{name}
  // Flea standalone → /api/marketplace/flea/{id}/detail
  const apiURL = source === 'curated'
    ? `/api/marketplace/curated/${encodeURIComponent(marketplaceId)}/${encodeURIComponent(pluginName)}/${kind}/${encodeURIComponent(innerName)}`
    : `/api/marketplace/flea/${encodeURIComponent(entityId)}/detail`;

  const ICON_DOC = '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';
  const ICON_DIR  = '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>';
  const ICON_CODE = '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>';
  const CODE_EXTS = new Set(['.py', '.js', '.ts', '.sh', '.bash', '.zsh', '.rb', '.go', '.rs', '.java', '.c', '.cpp', '.h', '.hpp']);

  function esc(s) {
    return String(s ?? '').replace(/[&<>"']/g, ch => (
      {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[ch]));
  }
  function fmtBytes(n) {
    if (n == null) return '—';
    if (n < 1024) return n + ' B';
    if (n < 1024*1024) return (n/1024).toFixed(2) + ' KB';
    if (n < 1024*1024*1024) return (n/(1024*1024)).toFixed(2) + ' MB';
    return (n/(1024*1024*1024)).toFixed(2) + ' GB';
  }
  function fmtRelative(iso) {
    if (!iso) return '—';
    const t = new Date(iso);
    if (isNaN(t)) return iso;
    const days = Math.floor((Date.now() - t.getTime()) / 86400000);
    if (days <= 0) return 'today';
    if (days === 1) return 'yesterday';
    if (days < 30) return days + ' days ago';
    if (days < 365) return Math.floor(days/30) + ' months ago';
    return Math.floor(days/365) + ' years ago';
  }
  function iconFor(path) {
    const lower = path.toLowerCase();
    if (lower.endsWith('/')) return ICON_DIR;
    const dot = lower.lastIndexOf('.');
    const ext = dot >= 0 ? lower.slice(dot) : '';
    if (CODE_EXTS.has(ext)) return ICON_CODE;
    return ICON_DOC;
  }
  function fileRow(path, size, opts) {
    const tag = opts && opts.href ? 'a' : 'div';
    const href = opts && opts.href ? ` href="${esc(opts.href)}" target="_blank" rel="noopener"` : '';
    const sizeStr = size == null ? '—' : fmtBytes(size);
    return `<${tag} class="file"${href}><span class="name">${iconFor(path)}${esc(path)}</span><span class="size">${sizeStr}</span></${tag}>`;
  }

  function showError(status) {
    document.getElementById('description-body').textContent = '';
    const err = document.getElementById('error-msg');
    if (status === 403)      err.textContent = 'You do not have access to this plugin. Ask your admin to grant your group access.';
    else if (status === 404) err.textContent = 'Not found.';
    else                     err.textContent = 'Failed to load (' + status + ').';
    err.hidden = false;
  }

  let res;
  try { res = await fetch(apiURL); }
  catch { showError(0); return; }
  if (!res.ok) { showError(res.status); return; }
  const d = await res.json();

  // ── Title resolution per source ─────────────────────────────────────
  // Curated: marketplace-metadata.json `display_name` wins, else frontmatter `name`.
  // Flea standalone skill/agent reuses PluginDetailResponse — `display_name`
  // populated by the same on-demand parser path, otherwise `plugin_name`
  // (the entity name; manifest_name is the suffixed `<name>-by-<username>`).
  const heroTitle = d.display_name
    || (source === 'curated' ? (d.name || innerName) : (d.plugin_name || ''));

  document.title = `${heroTitle} — Marketplace`;

  // ── Breadcrumbs ────────────────────────────────────────────────────
  const crumbs = document.getElementById('crumbs');
  if (source === 'curated') {
    crumbs.innerHTML =
      `<a href="/marketplace?tab=curated">Marketplace</a>
       <span class="sep">›</span>
       <a href="/marketplace/curated/${esc(marketplaceId)}/${esc(pluginName)}">${esc(pluginName)}</a>
       <span class="sep">›</span>
       <span class="current">${esc(heroTitle)}</span>`;
  } else {
    crumbs.innerHTML =
      `<a href="/marketplace?tab=flea">Marketplace</a>
       <span class="sep">›</span>
       <a href="/marketplace?tab=flea">Flea Market</a>
       <span class="sep">›</span>
       <span class="current">${esc(d.manifest_name || heroTitle)}</span>`;
  }

  // ── Hero name ──────────────────────────────────────────────────────
  document.getElementById('hero-name').textContent = heroTitle;

  // Hero window — macOS-style frame around the cover. The titlebar label
  // carries the skill/agent name (heroTitle); the body holds the cover
  // photo (or a placeholder gradient + 'SK'/'AG' initials when no cover).
  const labelEl = document.getElementById('hero-window-label');
  if (labelEl) labelEl.textContent = heroTitle;

  // Cover photo — flea may have one; curated v32 may also have one when
  // marketplace-metadata.json sub-tree references a skill/agent cover via
  // either an internal path (resolved to /asset/) or an external URL.
  if (d.cover_photo_url) {
    const bodyEl = document.getElementById('hero-window-body');
    // Initials fallback already lives inside #hero-window-body (rendered by
    // the server-side template as 'SK' / 'AG'). Capture before swapping so
    // a 404 on the cover restores the original glyph.
    const initials = bodyEl.textContent || (kind === 'skill' ? 'SK' : 'AG');
    bodyEl.innerHTML = `<img src="${esc(d.cover_photo_url)}" alt=""
        onerror="this.parentElement.classList.add('photo-failed');
                 this.parentElement.textContent=this.dataset.fallback;"
        data-fallback="${esc(initials)}">`;
  }

  // Badges — keep type + source (already server-rendered), append category.
  const badges = document.getElementById('hero-badges');
  const sourceBadge = source === 'curated'
    ? '<span class="source-badge curated">Curated</span>'
    : '<span class="source-badge flea">Flea</span>';
  const cat = d.category ? `<span class="cat-badge">${esc(d.category)}</span>` : '';
  badges.innerHTML = `<span class="type-badge">${esc(kind)}</span>${sourceBadge}${cat}`;

  // Invocation block — lives inside the Description panel so the "how to
  // call it" cue sits right under "what it does". Flea entities ship as
  // their own plugin (or the agnes-store-bundle), so the manifest_name IS
  // the slash invocation. Curated skills/agents live inside a parent
  // plugin, so Claude Code namespaces them as /<plugin>:<inner-name>.
  const invBlock = document.getElementById('invocation-block');
  // Invocation resolution:
  //   1. marketplace-metadata.json `invocation` (curator-provided literal —
  //      may include args like "/my-plugin:tool <your question>",
  //      and may start with @ for agent invocations). Used verbatim.
  //   2. Computed `<manifest_name>:<inner_name>` (legacy behaviour) —
  //      prefixed with "/" at display + copy time.
  let invokeText = null;        // what the chip displays (after the "/" prompt)
  let invokeCopyText = null;    // what gets put in the clipboard on Copy
  if (d.invocation) {
    // Curator override. Hide the hardcoded "/" prompt so the curator's
    // string is shown literally (covering both / and @ forms cleanly).
    const promptEl = document.querySelector('#invocation .prompt');
    if (promptEl) promptEl.hidden = true;
    invokeText = d.invocation;
    invokeCopyText = d.invocation;
  } else if (source === 'flea' && d.manifest_name) {
    invokeText = d.manifest_name;
    invokeCopyText = '/' + d.manifest_name;
  } else if (source === 'curated' && d.manifest_name && (d.name || innerName)) {
    invokeText = `${d.manifest_name}:${d.name || innerName}`;
    invokeCopyText = '/' + invokeText;
  }
  if (invokeText) {
    document.getElementById('invocation-cmd').textContent = invokeText;
    invBlock.hidden = false;
    const copyBtn = document.getElementById('invocation-copy');
    copyBtn.addEventListener('click', async () => {
      try {
        await navigator.clipboard.writeText(invokeCopyText);
        const orig = copyBtn.textContent;
        copyBtn.classList.add('copied');
        copyBtn.textContent = 'Copied';
        setTimeout(() => {
          copyBtn.textContent = orig;
          copyBtn.classList.remove('copied');
        }, 1500);
      } catch {
        /* clipboard blocked — leave the chip selectable for manual copy */
      }
    });
  }

  // Meta-row.
  const metaRow = document.getElementById('hero-meta-row');
  if (source === 'curated') {
    const author = (d.parent_author_name && d.parent_author_name !== 'owner_todo')
      ? `<strong>${esc(d.parent_author_name)}</strong>`
      : `<strong style="font-style:italic; color:var(--warn-color);">owner_todo</strong>`;
    const updated = d.parent_updated_at ? `Updated ${esc(fmtRelative(d.parent_updated_at))}` : '';
    metaRow.innerHTML =
      `<span>part of <a href="/marketplace/curated/${esc(marketplaceId)}/${esc(pluginName)}"><strong>${esc(pluginName)}</strong></a></span>
       <span class="dot">·</span>
       <span>by ${author}</span>
       ${updated ? `<span class="dot">·</span><span>${updated}</span>` : ''}`;
  } else {
    const author = d.owner_display || d.author_name || '';
    metaRow.innerHTML =
      `<span>by <strong>${esc(author)}</strong></span>
       <span class="dot">·</span>
       <span>${d.install_count || 0} installed</span>
       ${d.bundle_size != null ? `<span class="dot">·</span><span>${esc(fmtBytes(d.bundle_size))}</span>` : ''}
       ${d.updated_at ? `<span class="dot">·</span><span>Updated ${esc(fmtRelative(d.updated_at))}</span>` : ''}`;
  }

  // Hero action — Curated nested redirects to parent plugin (no install at
  // this level — Q5: install via the plugin). Flea standalone is directly
  // installable through the existing /api/store/.../install endpoint.
  const actions = document.getElementById('hero-actions');
  const HINT_DISMISS_KEY = 'mp.stack-hint.dismissed.v1';
  const hintEl = document.getElementById('stack-hint');
  function showHint() {
    if (localStorage.getItem(HINT_DISMISS_KEY) === '1') return;
    hintEl.hidden = false;
  }
  function setHintTitle(kind) {
    const t = document.getElementById('stack-hint-title');
    if (!t) return;
    t.textContent = kind === 'removed'
      ? '✓ Removed from your stack'
      : '✓ Added to your stack';
  }
  function renderInstallActionsHtml(installed) {
    if (installed) {
      return `
        <span class="status-pill" id="status-pill">✓ In your stack</span>
        <button class="btn-remove" id="remove-btn" type="button">✕ Remove from stack</button>
      `;
    }
    return `<button class="btn primary" id="install-btn" type="button">+ Add to my stack</button>`;
  }
  // Wires whichever of {install-btn, remove-btn} is currently in the
  // actions area. Both handlers re-render + re-bind so a returning
  // user can flip back and forth without a page reload. The hint
  // recipe is identical on add and remove (Claude Code needs the same
  // /update-agnes-plugins refresh either way) — only the title swaps.
  function bindInstallActions() {
    const installURL = `/api/store/entities/${encodeURIComponent(entityId)}/install`;
    const installBtnEl = document.getElementById('install-btn');
    if (installBtnEl) {
      installBtnEl.addEventListener('click', async () => {
        const r = await fetch(installURL, { method: 'POST' });
        if (!r.ok) { alert('Add failed (' + r.status + ')'); return; }
        actions.innerHTML = renderInstallActionsHtml(true);
        bindInstallActions();
        setHintTitle('added');
        showHint();
      });
    }
    const removeBtnEl = document.getElementById('remove-btn');
    if (removeBtnEl) {
      removeBtnEl.addEventListener('click', async () => {
        const r = await fetch(installURL, { method: 'DELETE' });
        if (!r.ok) { alert('Remove failed (' + r.status + ')'); return; }
        actions.innerHTML = renderInstallActionsHtml(false);
        bindInstallActions();
        setHintTitle('removed');
        showHint();
      });
    }
  }

  if (source === 'curated') {
    // Button alone in the actions-row (the row is flex-direction: row;
    // putting the helper inside it would lay them side-by-side). The
    // helper sits in #hero-helper, a sibling under the actions-row, so
    // the outer .actions flex-column stacks them: button → helper.
    actions.innerHTML = `<a class="btn primary" href="/marketplace/curated/${esc(marketplaceId)}/${esc(pluginName)}">Open parent plugin →</a>`;
    const helperEl = document.getElementById('hero-helper');
    helperEl.innerHTML = `
      This ${esc(kind === 'skill' ? 'skill' : 'agent')} is part of <strong>${esc(pluginName)}</strong>.<br>
      Add the bundle to your stack to use it.`;
    helperEl.hidden = false;
  } else {
    // v32+ quarantine: when the entity is non-approved (only owner +
    // admin land here — server-side gate 404s anyone else), render
    // the install button gray + disabled with explanatory tooltip.
    // The API also refuses POST /install with 409 entity_not_approved
    // so a clever user toggling `disabled` in devtools still hits the
    // gate. Skip listener wiring below for inert buttons.
    const isQuarantined = d.visibility_status && d.visibility_status !== 'approved';
    if (isQuarantined) {
      const stateLabel = d.visibility_status === 'archived' ? 'archived' : 'under review';
      actions.innerHTML = `<button class="btn primary" type="button" disabled
        title="This submission is not approved yet — install is disabled until checks pass."
        style="background:#e5e7eb;color:#6b7280;cursor:not-allowed;border:1px solid #d1d5db;">
          + Add to my stack (unavailable while ${stateLabel})
        </button>`;
    } else {
      // Two-element installed state — inline status label BEFORE the
      // Remove button on the same row. Default state is the primary
      // install CTA. Re-rendered after every click since the DOM
      // elements differ between states; bindInstallActions() re-wires
      // listeners on whichever id is now present.
      actions.innerHTML = renderInstallActionsHtml(!!d.installed);
    }
    const dismissBtn = document.getElementById('stack-hint-dismiss');
    if (dismissBtn) {
      dismissBtn.addEventListener('click', () => {
        localStorage.setItem(HINT_DISMISS_KEY, '1');
        hintEl.hidden = true;
      });
    }
    const copyBtn = document.getElementById('stack-hint-copy');
    if (copyBtn) {
      copyBtn.addEventListener('click', async (ev) => {
        const b = ev.currentTarget;
        try {
          await navigator.clipboard.writeText('/update-agnes-plugins');
          const orig = b.textContent;
          b.classList.add('copied');
          b.textContent = 'Copied';
          setTimeout(() => { b.textContent = orig; b.classList.remove('copied'); }, 1500);
        } catch { /* clipboard blocked — chip text remains selectable */ }
      });
    }
    // install-btn / remove-btn ids only exist in the non-quarantined
    // branch above. bindInstallActions wires whichever is currently
    // rendered; the click handlers re-render the actions area + re-bind
    // so the next click hits the freshly-attached listener.
    bindInstallActions();
  }

  // ── Hero tagline (marketplace-metadata.json :: tagline) ────────────
  // Optional 1-line value prop sitting under the h1. Stays hidden when
  // the curator hasn't filled it; the meta-row below still shows the
  // "by author · updated date" line so the hero never collapses.
  const heroTaglineEl = document.getElementById('hero-tagline');
  if (heroTaglineEl && d.tagline) {
    heroTaglineEl.textContent = d.tagline;
    heroTaglineEl.hidden = false;
  }

  // ── Description ────────────────────────────────────────────────────
  // When the curator authored a rich markdown body in marketplace-metadata.json,
  // the API renders + sanitizes it server-side and ships as
  // `description_long_html`. We inject as HTML. Falling back to the
  // 1-line frontmatter `description` preserves the historical render path
  // for skills/agents whose curator hasn't filled the new field yet.
  const descEl = document.getElementById('description-body');
  if (d.description_long_html && d.description_long_html.trim()) {
    descEl.innerHTML = d.description_long_html;
    descEl.classList.add('lead-rendered');
  } else {
    descEl.textContent = d.description || '';
  }

  // ── Use cases (marketplace-metadata.json :: use_cases[]) ───────────
  // One card per entry. Hidden until populated.
  const useCasesPanel = document.getElementById('panel-use-cases');
  const useCasesGrid = document.getElementById('panel-use-cases-grid');
  if (useCasesPanel && useCasesGrid && Array.isArray(d.use_cases) && d.use_cases.length) {
    useCasesGrid.innerHTML = d.use_cases.map(uc => `
      <div class="use-case-card">
        <h3>${esc(uc.title)}</h3>
        <p>${esc(uc.description)}</p>
        <pre class="use-case-prompt"><code>${esc(uc.prompt)}</code></pre>
      </div>
    `).join('');
    useCasesPanel.hidden = false;
  }

  // ── Sample interaction (marketplace-metadata.json :: sample_interaction) ─
  // Claude Code-style dark transcript. Assistant body was rendered +
  // sanitized server-side; we inject as HTML.
  const samplePanel = document.getElementById('panel-sample');
  if (samplePanel && d.sample_interaction
      && d.sample_interaction.user && d.sample_interaction.assistant_html) {
    document.getElementById('sample-user').textContent = d.sample_interaction.user;
    document.getElementById('sample-assistant').innerHTML = d.sample_interaction.assistant_html;
    samplePanel.hidden = false;
  }

  // ── When to use this (marketplace-metadata.json :: when_to_use) ────
  // Markdown disambiguation against alternative skills/agents
  // ("Use this for X; for Y, see /other-skill"). Rendered server-side.
  const whenPanel = document.getElementById('panel-when-to-use');
  const whenBody = document.getElementById('when-to-use-body');
  if (whenPanel && whenBody && d.when_to_use_html && d.when_to_use_html.trim()) {
    whenBody.innerHTML = d.when_to_use_html;
    whenBody.classList.add('lead-rendered');
    whenPanel.hidden = false;
  }

  // ── Details sidebar — skip rows whose value is missing. The
  // `owner_todo` placeholder for the Curator row stays as a deliberate
  // reminder to wire up curator metadata.
  const dl = document.getElementById('details-list');
  if (source === 'curated') {
    const author = (d.parent_author_name && d.parent_author_name !== 'owner_todo')
      ? esc(d.parent_author_name)
      : '<span class="todo">owner_todo</span>';
    dl.innerHTML = `
      <div class="row"><dt>Type</dt><dd>${esc(kind === 'skill' ? 'Skill' : 'Agent')}</dd></div>
      <div class="row"><dt>Parent plugin</dt><dd class="mono">${esc(pluginName)}</dd></div>
      <div class="row"><dt>Marketplace</dt><dd>${esc(d.marketplace_name || marketplaceId)}</dd></div>
      ${d.relpath ? `<div class="row"><dt>Path</dt><dd class="mono">${esc(d.relpath)}</dd></div>` : ''}
      ${d.bundle_size != null ? `<div class="row"><dt>Bundle size</dt><dd>${esc(fmtBytes(d.bundle_size))}</dd></div>` : ''}
      ${d.parent_updated_at ? `<div class="row"><dt>Updated</dt><dd>${esc(fmtRelative(d.parent_updated_at))}</dd></div>` : ''}
      <div class="row"><dt>Curator</dt><dd>${author}</dd></div>`;
  } else {
    const ownerLabel = d.owner_display || d.author_name || '';
    dl.innerHTML = `
      <div class="row"><dt>Type</dt><dd>${esc(kind === 'skill' ? 'Skill' : 'Agent')}</dd></div>
      ${ownerLabel ? `<div class="row"><dt>Owner</dt><dd>${esc(ownerLabel)}</dd></div>` : ''}
      ${d.version ? `<div class="row"><dt>Version</dt><dd class="mono">v${esc(d.version)}</dd></div>` : ''}
      ${d.category ? `<div class="row"><dt>Category</dt><dd>${esc(d.category)}</dd></div>` : ''}
      ${d.bundle_size != null ? `<div class="row"><dt>Bundle size</dt><dd>${esc(fmtBytes(d.bundle_size))}</dd></div>` : ''}
      <div class="row"><dt>Installs</dt><dd>${d.install_count || 0}</dd></div>
      ${d.released_at ? `<div class="row"><dt>Released</dt><dd>${esc(fmtRelative(d.released_at))}</dd></div>` : ''}
      ${d.updated_at ? `<div class="row"><dt>Updated</dt><dd>${esc(fmtRelative(d.updated_at))}</dd></div>` : ''}`;
  }

  // ── Docs section ────────────────────────────────────────────────
  // Flea has always populated `d.docs`. v32 added the same field for curated
  // skill/agent inner detail, sourced from marketplace-metadata.json's per-skill
  // sub-tree. Both surface here through a single render path — file rows
  // for internal-cached docs, link rows for external ones.
  if (Array.isArray(d.docs) && d.docs.length) {
    // Plain-link rendering — same shape as the plugin detail page. The
    // server already filtered out anything that isn't a real downloadable
    // PDF / Markdown / plain text, so every entry here is clickable +
    // downloads on click (Content-Disposition: attachment from the API).
    document.getElementById('docs-section').hidden = false;
    document.getElementById('docs-count').textContent = String(d.docs.length);
    document.getElementById('docs-list').innerHTML = d.docs.map(doc => `
      <li style="padding:10px 12px;border-radius:8px;background:var(--surface-alt,#f9fafb);border:1px solid var(--border);">
        <a href="${esc(doc.url || '#')}" download
           style="color:var(--primary);text-decoration:none;font-weight:500;display:block;"
           onmouseover="this.style.textDecoration='underline'"
           onmouseout="this.style.textDecoration='none'">${esc(doc.name)}</a>
      </li>`).join('');
  }

  // ── v32: demo video for skill / agent (from marketplace-metadata sub-tree) ──
  // Same auto-embed logic as the parent plugin detail page: YouTube /
  // Vimeo are embedded; raw .mp4/.webm/.ogg are inlined as <video>; anything
  // else falls back to a "Watch on external site" link.
  if (source === 'curated' && d.video_url) {
    let videoSection = document.getElementById('video-section');
    if (!videoSection) {
      videoSection = document.createElement('div');
      videoSection.id = 'video-section';
      videoSection.className = 'section';
      // .video-embed wrapper lives INSIDE .section-body so it inherits
      // the same 12px / 24px / 22px padding as the docs / files sections —
      // without that, the iframe bleeds to the panel edge.
      videoSection.innerHTML = `
        <div class="section-head">
          <h2>Demo video</h2>
        </div>
        <div class="section-body">
          <div class="video-embed" id="video-wrap"></div>
        </div>`;
      // Insert above the docs section so video shows first when both exist.
      const docs = document.getElementById('docs-section');
      docs.parentElement.insertBefore(videoSection, docs);
    }
    document.getElementById('video-wrap').innerHTML =
      buildVideoEmbed(d.video_url, esc);
  }

  // ── Files section — both sources, when non-empty ────────────────────
  if (Array.isArray(d.files) && d.files.length) {
    document.getElementById('files-section').hidden = false;
    document.getElementById('files-count').textContent = String(d.files.length);
    document.getElementById('files-list').innerHTML =
      d.files.map(f => fileRow(f.path, f.size, null)).join('');
  }
})();
</script>
{% endblock %}
