---
name: youtube-to-explainer
description: >
  Convert a long-form YouTube video (podcast interview, lecture, panel — AI,
  tech, marketing, physics, anything) into a source-anchored interactive
  Next.js explainer page on contextjamming.com, and register it in the
  interview correlation matrix at /youtube-explainers. Trigger when the user
  supplies one or more YouTube URLs and wants an explainer, a "compressed
  interview", a TL;DR page, or asks to "add this to the interview matrix" /
  "parse this podcast" / "run youtube-to-explainer".
---

# YouTube URL → Interview Explainer

Turns one YouTube URL into three artifacts:

1. A **compressed interview record** (TL;DR, ≤7 anchored key insights, one
   central move) extracted from the actual tape via Gemini.
2. An **explainer entry** on `/youtube-explainers` (and optionally a full
   standalone explainer page at `app/<slug>/`).
3. **Pairwise correlation records** against every interview already in the
   matrix.

**Division of labor:** Gemini is the intake — it is the only frontier model
that ingests a YouTube URL natively (frames + audio + timestamped speech +
metadata in one call). Claude (you) is the builder — extraction never writes
site code; the build never invents tape.

## Stage 0 — Recon

Read before writing anything:

- `app/youtube-explainers/interviewRelationships.ts` — the data model and
  every existing interview + pair record.
- `app/youtube-explainers/page.tsx` — gallery conventions.
- `app/explainers/explainers.module.css` + `hero.module.css` — the design
  system. **No new colors, no new fonts, no new dependencies.**

Derive a short kebab-case `id` for the new interview (e.g. `karpathy`), and
confirm it doesn't collide with an existing `InterviewId`.

## Stage 1 — Ingest via Gemini (native YouTube)

Preferred: the Gemini API with the URL as `fileData` (no scraping, no caption
export). Model: `gemini-2.5-pro` (or newer).

```bash
curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent?key=$GEMINI_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "contents": [{
      "parts": [
        { "fileData": { "fileUri": "<YOUTUBE_URL>" } },
        { "text": "<EXTRACTION_PROMPT>" }
      ]
    }]
  }'
```

Alternatives, in order: the `gemini` CLI if installed; otherwise ask the user
to paste a transcript (then mark the record `seeded`, not `gemini-verified`).

For videos over ~2 hours, chunk with clip offsets and merge the results:

```json
{ "fileData": { "fileUri": "<URL>" },
  "videoMetadata": { "startOffset": "3600s", "endOffset": "7200s" } }
```

### The extraction prompt (fixed contract)

Ask Gemini to return **JSON only**, with this schema:

```json
{
  "metadata": { "title": "", "channel": "", "published": "", "duration": "" },
  "tldr": "≤3 sentences",
  "centralMove": "the one load-bearing reversal the guest is making, 1 sentence",
  "keyInsights": [
    {
      "timestamp": "HH:MM:SS",
      "speaker": "guest|host|other:<name>",
      "kind": "claim|framing|speculation|anecdote",
      "insight": "paraphrase, ≤40 words, no direct quote"
    }
  ],
  "notableExchanges": [
    { "timestamp": "HH:MM:SS", "summary": "where host and guest actually disagreed or broke frame" }
  ],
  "concepts": ["5-10 correlation tags, lowercase"]
}
```

5–7 `keyInsights`. Every timestamp must come from the transcript Gemini
produced — spot-check two of them against the video before trusting the set.

## Stage 2 — Epistemic rules (non-negotiable)

- **Timestamps only from the tape.** If the record was seeded from public
  knowledge instead of a Gemini pass, it gets `status: "seeded"` and shows no
  timestamps. A Gemini-verified record gets `status: "gemini-verified"`.
- **Paraphrase, don't quote.** Interview audio is copyrighted; insights are
  ≤40-word paraphrases tagged by speaker.
- **Label the speaker and the kind.** A guest's claim, the host's framing,
  and speculation are different objects and stay visually distinct.
- **Affinity is editorial.** Correlation scores are synthesis aids, never
  presented as measurements.

## Stage 3 — Register in the matrix

Edit `app/youtube-explainers/interviewRelationships.ts`:

1. Add the id to `INTERVIEW_ORDER` and a full `Interview` record to
   `INTERVIEWS` (guest, role, headline, showLabel, published, durationLabel,
   watchUrl, accent — rotate among the three `--color-accent*` tokens —
   status, centralMove, tldr, keyInsights).
2. Add one `InterviewPairRelationship` against **every existing interview**
   (N existing interviews → N new pair records). Each needs: affinity (0–100,
   banded by `affinityBand`), label, synthesis, sharedConcepts (reuse
   existing tag vocabulary where honest), conversationalRhyme,
   corroborationStatus + note, both directions, productiveTension,
   compressionBoundary, evidenceNotes.
3. Corroboration status rules:
   - `on-record-tension` / `on-record-overlap` — only when both interviews
     state the conflicting/converging position on their own record.
   - `thematic-overlap` — same subject matter, no matching stated positions.
   - `editorial-synthesis` — the connection exists only in your framing.
   Never upgrade a status to make a pair look stronger.

The gallery and matrix on `/youtube-explainers` render from this file
automatically — no page edits needed for a new entry.

## Stage 4 — Optional standalone explainer page

If the user wants a full page (not just a matrix entry): create
`app/<slug>/page.tsx` following the house explainer grammar — reuse the
explainers CSS modules, add `metadata` with canonical URL, and build one
deterministic interactive instrument (insight-timeline scrubber, claim-kind
filter, or tension toggle — client component, local state, inline SVG, no new
deps). Link it from the interview's record (`watchUrl` stays the source; add
the internal link in the gallery tile if you extend the type). Add the route
to `app/sitemap.ts`.

## Stage 5 — Verify and ship

1. `npm run build` must pass.
2. Preview `/youtube-explainers`: new tile renders, matrix selectors include
   the new interview, every pair opens without error.
3. Update `app/sitemap.ts` if a new route was added.
4. Deploy only on request, via the `gemclaw-ship` skill
   (`npm run build:cf` → `npm run deploy:cf`; push alone does not deploy).

## Failure modes

- **Private/region-locked video** → Gemini can't fetch it; ask the user for a
  transcript, mark `seeded`.
- **No `GEMINI_API_KEY` and no `gemini` CLI** → same fallback.
- **Music/copyright-heavy content** → refuse lyric transcription; insights
  about the conversation only.
- **A pair you can't honestly score** → low affinity with
  `editorial-synthesis`, and say so in the evidence notes. An empty-feeling
  pair record is correct output, not a failure.
