feat(studio): full Blocks panel — browse, search, add, drag-and-drop registry items

Adds a Blocks tab to the Studio left sidebar with the full 78-item registry
catalog (58 blocks + 20 components). Users can browse by category, search by
title/description, preview CDN-hosted poster thumbnails with video-on-hover,
and install items on-demand with one click or drag-to-timeline.

Core changes:
- BlockCategory type + resolveBlockCategory() for 7 categories (Captions, VFX,
  Transitions, Effects, Social, Data, Scenes)
- Registry API routes: GET /api/registry/blocks (catalog) + POST install
- StudioApiAdapter extended with listRegistryCatalog + installRegistryBlock
- Vite adapter reads from disk; CLI adapter fetches from GitHub (24h cache)
- BlockParam interface + params on 6 blocks for future parameter controls

Studio UI:
- 4th sidebar tab "Blocks" with responsive grid, category pills, search bar
- BlockCard: CDN poster thumbnail, video autoplay on hover, duration + WebGL badges
- On-demand install: blocks append as sub-compositions on timeline; components
  overlay at start=0 spanning full duration with transparent background patching
- TIMELINE_BLOCK_MIME drag-and-drop to timeline
- BlockParamsPanel (Phase 3 scaffold) auto-opens for parameterized blocks

Registry manifests:
- All 58 blocks backfilled with preview: { video, poster } CDN URLs
- All 20 components normalized to object format + poster URLs added
- 6 blocks annotated with params (Liquid Glass/Background, Portal, Chart,
  Logo Outro, Magnetic)
- flowchart-vertical preview generated and uploaded to CDN
This commit is contained in:
Miguel Ángel
2026-05-18 21:15:15 -04:00
parent d9183ba27d
commit ffbc18ad31
111 changed files with 1800 additions and 106 deletions
+5
View File
@@ -10,12 +10,17 @@ export type {
ComponentItem,
RegistryManifestEntry,
RegistryManifest,
BlockCategory,
BlockCategoryMeta,
BlockParam,
} from "./types.js";
export {
ITEM_TYPES,
FILE_TYPES,
ITEM_TYPE_DIRS,
BLOCK_CATEGORIES,
resolveBlockCategory,
isExampleItem,
isBlockItem,
isComponentItem,
+52
View File
@@ -77,6 +77,17 @@ export interface ExampleItem extends RegistryItemBase {
duration: number;
}
export interface BlockParam {
key: string;
label: string;
type: "color" | "text" | "number" | "select";
default: string;
options?: { label: string; value: string }[];
min?: number;
max?: number;
step?: number;
}
/** Sub-composition block — installed by `hyperframes add <name>`. */
export interface BlockItem extends RegistryItemBase {
type: "hyperframes:block";
@@ -84,6 +95,8 @@ export interface BlockItem extends RegistryItemBase {
dimensions: RegistryItemDimensions;
/** Duration in seconds (required for blocks). */
duration: number;
/** Customizable parameters with CSS variable mapping. */
params?: BlockParam[];
}
/** Effect / snippet — merged into an existing composition. */
@@ -159,6 +172,45 @@ const _fileTypesExhaustive: _AssertFileTypesExhaustive = true;
void _itemTypesExhaustive;
void _fileTypesExhaustive;
// ── Block categories ───────────────────────────────────────────────────────
export type BlockCategory =
| "vfx"
| "transitions"
| "social"
| "data"
| "scenes"
| "captions"
| "effects";
export interface BlockCategoryMeta {
id: BlockCategory;
label: string;
color: string;
}
export const BLOCK_CATEGORIES: BlockCategoryMeta[] = [
{ id: "captions", label: "Captions", color: "cyan" },
{ id: "vfx", label: "VFX", color: "purple" },
{ id: "transitions", label: "Transitions", color: "blue" },
{ id: "effects", label: "Effects", color: "rose" },
{ id: "social", label: "Social", color: "pink" },
{ id: "data", label: "Data", color: "green" },
{ id: "scenes", label: "Scenes", color: "amber" },
];
export function resolveBlockCategory(tags: string[] | undefined): BlockCategory {
if (!tags || tags.length === 0) return "scenes";
const set = new Set(tags);
if (set.has("captions") || set.has("caption-style")) return "captions";
if (set.has("transition")) return "transitions";
if (set.has("social") || set.has("overlay")) return "social";
if (set.has("data") || set.has("chart") || set.has("map")) return "data";
if (set.has("html-in-canvas") || set.has("webgl") || set.has("shader")) return "vfx";
if (set.has("effect") || set.has("grain") || set.has("vignette")) return "effects";
return "scenes";
}
// ── Type guards ─────────────────────────────────────────────────────────────
export function isExampleItem(item: RegistryItem): item is ExampleItem {