()
| 35 | } |
| 36 | |
| 37 | export function MediaLibrary() { |
| 38 | const open = useStory((s) => s.mediaLibraryOpen) |
| 39 | const toggle = useStory((s) => s.toggleMediaLibrary) |
| 40 | const renderJobs = useStory((s) => s.renderJobs) |
| 41 | const openStoryPlayer = useStory((s) => s.openStoryPlayer) |
| 42 | const removeRenderJob = useStory((s) => s.removeRenderJob) |
| 43 | |
| 44 | if (!open) return null |
| 45 | |
| 46 | const completed = renderJobs.filter((j) => j.status === 'done' && j.url) |
| 47 | const byBucket: Record<Bucket, RenderJob[]> = { reel: [], intro: [], audiobook: [] } |
| 48 | for (const j of completed) byBucket[bucketOf(j)].push(j) |
| 49 | // Newest first within each bucket — most recent render at the top. |
| 50 | for (const k of ['reel', 'intro', 'audiobook'] as Bucket[]) { |
| 51 | byBucket[k].sort((a, b) => b.startedAt - a.startedAt) |
| 52 | } |
| 53 | |
| 54 | return ( |
| 55 | <div |
| 56 | className="fixed right-6 bottom-[72px] z-40 flex max-h-[80vh] w-[420px] flex-col border backdrop-blur" |
| 57 | style={{ |
| 58 | background: 'rgba(10, 13, 18, 0.97)', |
| 59 | borderColor: 'var(--line)', |
| 60 | boxShadow: '0 24px 64px rgba(0,0,0,0.6)', |
| 61 | }} |
| 62 | > |
| 63 | <div |
| 64 | className="sticky top-0 flex shrink-0 items-center justify-between gap-3 border-b border-[var(--line)] px-4 py-2.5" |
| 65 | style={{ background: 'rgba(10, 13, 18, 0.98)' }} |
| 66 | > |
| 67 | <div className="flex items-center gap-2"> |
| 68 | <Film size={11} strokeWidth={1.8} style={{ color: '#4fc3f7' }} /> |
| 69 | <span className="font-display text-[12px] uppercase tracking-[0.32em]" style={{ color: '#4fc3f7' }}> |
| 70 | Library |
| 71 | </span> |
| 72 | </div> |
| 73 | <button |
| 74 | onClick={toggle} |
| 75 | aria-label="Close media library" |
| 76 | className="transition-colors" |
| 77 | style={{ color: INK_SOFT }} |
| 78 | onMouseEnter={(e) => { e.currentTarget.style.color = INK_BRIGHT }} |
| 79 | onMouseLeave={(e) => { e.currentTarget.style.color = INK_SOFT }} |
| 80 | > |
| 81 | <X size={14} strokeWidth={1.5} /> |
| 82 | </button> |
| 83 | </div> |
| 84 | |
| 85 | {completed.length === 0 ? ( |
| 86 | <div className="flex-1 px-5 py-10 text-center"> |
| 87 | <Film size={20} strokeWidth={1.5} className="mx-auto mb-3" style={{ color: INK_SOFT }} /> |
| 88 | <div className="font-mono text-[10.5px] uppercase tracking-[0.24em]" style={{ color: INK_MED }}> |
| 89 | no media yet |
| 90 | </div> |
| 91 | <div className="mt-2 text-[11px] leading-[1.55]" style={{ color: INK_SOFT }}> |
| 92 | Click the Storybook button on any story beat — when the render |
| 93 | finishes it lands here automatically. |
| 94 | </div> |
nothing calls this directly
no test coverage detected