## 1. Card component changes

- [x] 1.1 Add a `mode` prop (or equivalent) to `StandardCard.astro` to support rendering as a `<button>` (modal trigger) instead of an `<a>` link. When `mode="modal"`, the root element MUST be a `<button>` (or `role="button" tabindex="0"`) with the same visual styling as the current card.
- [x] 1.2 Add `data-*` attributes to modal-trigger cards in `ContentCard.astro` — encode `id`, `source`, `url`, `title`, `summary`, `publishedAt`, `thumbnailUrl`, and `metrics.views` so the modal script can read them on click.
- [x] 1.3 Update `ContentCard.astro` to use `mode="modal"` for `youtube` and `podcast` sources, and keep `mode="link"` (current behavior) for other sources.
- [x] 1.4 Change Umami event from `outbound_click` to `media_preview` on modal-trigger cards. Update `data-umami-event-*` attributes to include `target_id`, `placement`, `title`, `type`, and `source` (drop `domain`, `target_url` since it's no longer outbound).

## 2. Media modal component

- [x] 2.1 Create `MediaModal.astro` component containing a native `<dialog>` element with the modal layout: header (title + close button), embed area, description, date/views row, CTA row.
- [x] 2.2 Add CSS for the modal in `global.css`: backdrop styling, modal container, responsive layout (centered on desktop, near-full-width on mobile), embed aspect-ratio (16:9 for video), loading placeholder.
- [x] 2.3 Write the modal open/populate script: listen for clicks on modal-trigger cards, read `data-*` attributes, populate the modal fields (title, description, date, views), construct the embed URL, set the iframe `src`, and call `dialog.showModal()`.
- [x] 2.4 Implement embed URL construction: extract YouTube video ID from `item.url` → `https://www.youtube.com/embed/{id}?rel=0&modestbranding=1`. For podcast, embed Spotify when a Spotify episode ID is available (`https://open.spotify.com/embed/episode/{id}`). For non-Spotify podcast URLs, render an in-modal audio player when an `audioUrl` is available, otherwise show a "Listen on Spotify" link.
- [x] 2.5 Implement playback stop on close: on dialog `close` event, set iframe `src` to `about:blank` (or remove the iframe) to guarantee playback stops.
- [x] 2.6 Implement all three close methods: close button click, `Escape` key (native `<dialog>` handles this), and backdrop click (detect clicks on `<dialog>` element outside the inner content container).

## 3. Modal accessibility

- [x] 3.1 Set `aria-modal="true"` and `aria-labelledby` (referencing the title element) on the `<dialog>`.
- [x] 3.2 Add `aria-label="Close"` to the close button.
- [x] 3.3 Implement focus return: store a reference to the clicked card before opening, restore focus to it on close.
- [x] 3.4 Verify focus trapping works with the native `<dialog>` (Tab cycles through modal focusables only).
- [x] 3.5 Verify `prefers-reduced-motion` suppresses modal open/close animations (covered by the existing global CSS rule).

## 4. Modal CTAs

- [x] 4.1 Render "Subscribe on YouTube" / "Follow on Spotify" CTA inside the modal, linking to the channel/podcast profile URL (from `LINKS.youtubeChannel` / `LINKS.podcast`). Apply UTM parameters.
- [x] 4.2 Render "View on YouTube" / "Listen on Spotify" CTA inside the modal, linking to the specific content item URL. Apply UTM parameters.
- [x] 4.3 Add `data-umami-event="cta_click"` with `target_id=modal.cta.{action}.{platform}`, `placement=media_modal`, `platform`, and `target_url` on each modal CTA. Actions: `subscribe`/`view` for YouTube; `follow`/`listen` for Spotify.

## 5. Umami analytics updates

- [x] 5.1 Emit `media_preview_close` event on modal close with `target_id` (from the card that opened it) and `close_method` (`button`, `escape`, or `backdrop`). Use `umami.track()` JS API since `close_method` is determined at runtime.
- [x] 5.2 Verify `media_preview` event fires correctly from card `data-umami-event` attributes on listing surfaces (homepage newest, homepage high-performing, homepage podcast, videos list, podcast list).
- [x] 5.3 Update existing Umami attribute tests (`site/tests/umami-attributes.test.ts`) to expect `media_preview` instead of `outbound_click` for video/podcast cards.
- [x] 5.4 Add new test cases for modal CTA Umami attributes (`cta_click`, `target_id`, `placement=media_modal`).

## 6. Page integration

- [x] 6.1 Add the `MediaModal` component to `index.astro` (homepage).
- [x] 6.2 Add the `MediaModal` component to `videos.astro`.
- [x] 6.3 Add the `MediaModal` component to `podcast.astro`.

## 7. Verification

- [x] 7.1 Run `npm run build` in `site/` and verify no build errors.
- [x] 7.2 Run `npm test` in `site/` and verify all tests pass (including updated Umami attribute tests).
  Note: `site/tests/blog-nav.test.ts` is failing in the repo for reasons unrelated to this change.
- [x] 7.3 Manual smoke test: click a video card → modal opens with YouTube embed → close → playback stops. Repeat with Escape and backdrop click.
- [x] 7.4 Manual smoke test: click a podcast card → modal opens with Spotify embed (or fallback link) → close → playback stops.
- [x] 7.5 Verify modal accessibility: keyboard-only navigation, focus trap, focus return, screen reader announces dialog.
- [x] 7.6 Verify responsive behavior: modal layout on desktop and mobile viewports.
