'use client'
/**
* ProgramCard (pure presentation). Generic card for podcasts / webinars /
* events. Three densities — `default` (wide horizontal detail, archive
* pages), `sm` (compact horizontal for chat-inline), and `portrait`
* (vertical rail/strip card).
*
* `portrait` exists because mixed-content rails MUST share ONE card anatomy
* (2026 card-UI practice: a rail mixes content types, never card layouts —
* mixing orientations/aspects in one scroller is the anti-pattern). It
* renders the SAME three zones as the other portrait entity cards
* (CaseStudyCard et al.): wide media slot → fixed 72px title zone → fixed
* 60px person/meta footer, p-6 / gap-6. The program's identity lives in the
* Azeret Mono title and the date · duration meta line — not in a different
* layout.
*
* The card writes NO click logic — callers wrap with their own anchor
* and pass the resolved detail URL via `href`.
*/
import React, { useState } from 'react'
import Image from '../../../embed-shims/next-image'
import { format } from 'date-fns'
import { ExternalLink, Clock, Play, Video } from 'lucide-react'
import { Button } from '../../ui/button/button'
import { SquareAvatar } from '../../ui/square-avatar'
import { ImageGalleryModal } from '../../ui/image-gallery-modal'
import { cn } from '../../../utils/cn'
import {
formatDurationCompact,
formatTimeWithTimezone,
formatDurationFromRange,
} from '../../../utils/format'
import {
COMPACT_CARD_IMAGE_SLOT,
COMPACT_CARD_META_ROW_BOX,
COMPACT_CARD_OUTER,
COMPACT_CARD_ROW_FILLER,
COMPACT_CARD_SKELETON_IMAGE_SLOT,
COMPACT_CARD_SKELETON_OUTER,
COMPACT_CARD_SUMMARY,
COMPACT_CARD_TEXT_COL,
COMPACT_CARD_TITLE,
COMPACT_CARD_TITLE_ROW,
} from '../utils/compact-card-classes'
import {
programItemToStripProfile,
type BaseProgramItem,
type ProgramAuthorRef,
type ProgramConfig,
type ProgramMedia,
type ProgramHost,
} from '../types/entities/program-types'
import { EntityPortraitCard } from './entity-portrait-card'
import { useEntityCardLink } from './use-entity-card-link'
import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
type CardSize = 'default' | 'sm' | 'portrait'
/**
* Format a Date with date-fns pinned to UTC. `date-fns` `format()` reads the
* runtime's LOCAL wall-clock, so the same instant renders differently on the
* server (Vercel = UTC) and the client (visitor tz) → React #418 hydration
* mismatch. Shifting by the local offset before formatting emits the UTC
* wall-clock on every machine, so server and client agree. Mirrors the helper
* in the hub's `program-header.tsx` (kept local — the lib has no date-fns-tz
* dep) and the repo-wide "pin program dates to UTC" convention.
*/
function formatUtc(date: Date, fmt: string): string {
return format(new Date(date.getTime() + date.getTimezoneOffset() * 60_000), fmt)
}
export function ProgramCardSkeleton({ size = 'default' }: { size?: CardSize }) {
if (size === 'sm') {
return (
)
}
return (