@classytic/vixel-studio
v0.1.1
Published
Templates, backgrounds, frames + card layouts for vixel — a Canva/screenshot-studio-style design catalog as pure VixelSpec data, plus a type-safe authoring API for hosts to build or bring their own templates. Renders on the existing vixel renderers (video
Downloads
368
Readme
@classytic/vixel-studio
A Canva / screenshot-studio-style design catalog for vixel — templates, gradient
backgrounds, browser/device frames, and card layouts as pure VixelSpec data, plus a
type-safe authoring API so a host can build or bring its own templates easily.
It reuses vixel-schema's template engine (registry, slots, themes, 12×12 grid) — it
doesn't reinvent it — and renders on the existing vixel renderers. Because a still is a
1-frame VixelSpec (and exportToImage already ships), the same templates produce
video frames and standalone images alike. Framework-free: depends only on
@classytic/vixel-schema. No React, no ffmpeg.
Use a built-in template
import { registerStudioTemplates } from '@classytic/vixel-studio';
import { buildScene, collectSlots } from '@classytic/vixel-schema';
import { exportToImage } from '@classytic/vixel-ui/export';
registerStudioTemplates(); // opt-in (pay only for what you register)
const clips = buildScene({
template: 'studio/browser-shot',
at: 0, duration: 5,
content: { url: 'classytic.com', gradient: 'sunset', browser: 'safari' },
});
const spec = { version: 1, output: { width: 1920, height: 1080, fps: 30 }, tracks: [{ type: 'visual', clips }] };
// fill the screenshot slot, then render a still:
collectSlots(spec); // → [{ slot: { id: 'shot', kind: 'media' }, ... }]
// set that clip's media.source = <your screenshot>, then:
const png = await exportToImage(spec, { format: 'png' });Build your OWN template (the DX)
defineTemplate<Input> gives you a typed build (your content flows in, the theme is
resolved) and you compose the element builders — no hand-written VisualClip JSON:
import { defineTemplate, gradientBackground, deviceMockup, textSlot } from '@classytic/vixel-studio';
export const launchCard = defineTemplate<{ device?: 'macbook' | 'iphone' | 'ipad'; tagline: string }>({
id: 'mine/launch-card', name: 'Launch Card', category: 'scene', aspect: 'landscape',
description: 'A device mockup with a tagline.',
build: (input, theme) => [
...gradientBackground({ preset: 'purple_haze' }),
// frameSrc is OPTIONAL — the bezel PNG is a HOST asset (this package ships none);
// omit it and the device is DRAWN from the preset geometry instead:
...deviceMockup({ device: input.device ?? 'macbook', frameSrc: '/assets/mockups/macbook.png', screenSlot: { id: 'screen', label: 'Screen' } }),
textSlot({ frame: { x: 0.1, y: 0.82, w: 0.8, h: 0.1 }, text: input.tagline, slot: { id: 'tagline', label: 'Tagline' } }),
],
});Bring your own pack (BYO)
import { definePack, STUDIO_TEMPLATES } from '@classytic/vixel-studio';
const pack = definePack([...STUDIO_TEMPLATES, launchCard]); // mix built-ins + yours
pack.register(); // now in `listTemplates()` / `authoringManifest()` / the agent tool surfaceWhat's in the box
| | |
|---|---|
| Authoring | defineTemplate, definePack, registerStudioTemplates |
| Slots / frames | mediaSlot · textSlot · bgImage · iconSlot · stickerSlot · browserFrame · deviceMockup · quoteCard |
| Backgrounds | gradientBackground (preset · seed-derived · explicit) · solidBackground |
| Smart gradients | deriveGradient(seed, {style}) · shiftColor — generate on-brand gradients from one color |
| Drawn decorations | underline · arrow · badge · blob |
| Presets (data) | GRADIENTS · SOLID_COLORS · BROWSER_CHROME · DEVICE_MOCKUPS |
| Built-in templates | studio/browser-shot · studio/device-shot · studio/tweet-card · studio/social-ad · studio/infographic · studio/quote-post |
| Re-exports | registerTemplate / listTemplates / buildScene / collectSlots / authoringManifest (the schema engine) |
The catalog here is curated, not exhaustive — a small hand-picked set of the common looks. A host adds its own named presets as needed; for anything off-palette, don't hunt a list — derive it:
import { gradientBackground, deriveGradient } from '@classytic/vixel-studio';
// an on-brand background from ONE seed color (a brand accent / theme bg) — no fixed palette:
gradientBackground({ seed: brand.accent, style: 'deepen' }); // same-hue depth (safe contrast)
gradientBackground({ seed: brand.accent, style: 'duotone' }); // a vivid hue-rotated two-tone
deriveGradient('#6366f1', { style: 'soft' }); // → { from, to, angle }deriveGradient is HSL color-math (pure, zero-dep), so every brand gets an infinite,
tasteful palette — smarter than shipping hundreds of static gradients. The built-in post
templates already background themselves this way from the resolved theme.
The boundary: draw vs. asset
vixel owns what can be drawn from primitives (shapes, gradients, text): browserFrame,
gradients, underline/arrow/badge/blob. It does not own asset catalogs —
icon packs, sticker/GIF libraries, device-bezel PNGs, photos. For those, vixel holds only
the slot + geometry (iconSlot, stickerSlot, bgImage, deviceMockup's frameSrc)
and the host drops the bytes into source (Iconify, prism overlays, media-kit,
generate_image). Same seam as image generation: host produces a URL → vixel slot.
Brand → Theme (host's job)
The social-ad / infographic / quote-post templates read the resolved Theme and
render on-brand. The host maps its own brand kit → a vixel Theme (palette + fonts) and
passes the theme id; vixel never defines a BrandKit. Prompt templates for image generation
are likewise the host's (vixel composes; the host generates).
Roadmap
- 3D perspective tilt — the one genuine renderer gap (the "tilted screenshot" look):
a
transform.perspective+rotateX/rotateYprimitive in vixel-schema + a Pixi/ffmpeg render. Templates here use 2D rotation until it lands. - Mesh / "magic" gradients — a Pixi shader effect pack (multi-spot radial fields).
- Multi-stop gradients — vixel shape fills are two-stop today; richer stops is a small schema enhancement.
Everything else is template/preset data — no renderer changes.
