@real-music-packages/web-core 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/video.js CHANGED
@@ -43,35 +43,6 @@ function pickMimeType() {
43
43
  }
44
44
  return "video/webm";
45
45
  }
46
- async function waitForFirstVideoFrame(stream, timeoutMs = 2500) {
47
- const [track] = stream.getVideoTracks();
48
- if (!track || typeof document === "undefined") return;
49
- const video = document.createElement("video");
50
- video.muted = true;
51
- video.playsInline = true;
52
- video.srcObject = stream;
53
- const rvfc = video.requestVideoFrameCallback?.bind(video);
54
- try {
55
- await video.play();
56
- } catch {
57
- }
58
- await new Promise((resolve) => {
59
- let settled = false;
60
- const done = () => {
61
- if (!settled) {
62
- settled = true;
63
- resolve();
64
- }
65
- };
66
- if (rvfc) rvfc(done);
67
- setTimeout(done, timeoutMs);
68
- });
69
- try {
70
- video.pause();
71
- video.srcObject = null;
72
- } catch {
73
- }
74
- }
75
46
  async function recordScenes(scenes, opts) {
76
47
  const { audioStream, width, height, fps = 30, background = "#000000", onProgress } = opts;
77
48
  const canvas = document.createElement("canvas");
@@ -81,10 +52,6 @@ async function recordScenes(scenes, opts) {
81
52
  if (!maybeCtx) throw new Error("Failed to get 2D context");
82
53
  const ctx = maybeCtx;
83
54
  const videoStream = canvas.captureStream(fps);
84
- ctx.fillStyle = background;
85
- ctx.fillRect(0, 0, width, height);
86
- scenes[0].draw(ctx, 0);
87
- await waitForFirstVideoFrame(videoStream);
88
55
  const combined = new MediaStream([
89
56
  ...videoStream.getVideoTracks(),
90
57
  ...audioStream.getAudioTracks()
@@ -109,6 +76,9 @@ async function recordScenes(scenes, opts) {
109
76
  let sceneStart = 0;
110
77
  let sceneIdx = 0;
111
78
  let enteredScene = -1;
79
+ ctx.fillStyle = background;
80
+ ctx.fillRect(0, 0, width, height);
81
+ scenes[0].draw(ctx, 0);
112
82
  await new Promise((resolve) => {
113
83
  function tick() {
114
84
  const now = performance.now() - t0;
package/dist/video.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/video.ts"],"sourcesContent":["/**\n * Framework-agnostic promo video module.\n *\n * Provides:\n * - PromoTheme — colour/font/brand tokens\n * - Scene — {durationMs, draw, onEnter?}\n * - recordScenes — rAF-based canvas capture → Blob (browser only)\n * - hookScene / revealScene / ctaScene — scene builders (pure, unit-testable)\n * - runPromoCapture — orchestrator with dependency-injected recorder\n *\n * Visual style ported from whozart/src/lib/promo.ts + video.ts.\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface PromoTheme {\n paper: string;\n ink: string;\n accent: string;\n sepia: string;\n gold: string;\n fontDisplay: string;\n fontBody: string;\n brand: string;\n}\n\nexport interface Scene {\n durationMs: number;\n draw: (ctx: CanvasRenderingContext2D, t01: number) => void;\n onEnter?: () => void;\n}\n\nexport interface RecordOpts {\n audioStream: MediaStream;\n width: number;\n height: number;\n fps?: number;\n background?: string;\n onProgress?: (p: { phase: string; progress01: number }) => void;\n}\n\nexport interface PromoMeta {\n id?: string;\n title?: string;\n composer?: string;\n [k: string]: unknown;\n}\n\nexport interface PromoCaptureOpts {\n buildScenes: () => Scene[];\n audioStream: MediaStream;\n meta: PromoMeta;\n width?: number;\n height?: number;\n fps?: number;\n background?: string;\n download?: boolean;\n}\n\n// ─── Safe zone (phone short-form UI) ─────────────────────────────────────────\n\n/** Insets (fractions of frame) kept clear of TikTok/Reels/Shorts UI overlays. */\nexport const SAFE_ZONE = { top: 0.10, bottom: 0.20, left: 0.06, right: 0.12 } as const;\n\nexport interface SafeBox {\n left: number; right: number; top: number; bottom: number;\n w: number; h: number; cx: number; cy: number;\n /** Widest block centered at frame W/2 that still fits inside the box. */\n centeredW: number;\n}\n\n/** The usable content rectangle for a W×H frame, in pixels. */\nexport function safeBox(W: number, H: number): SafeBox {\n const left = W * SAFE_ZONE.left;\n const right = W * (1 - SAFE_ZONE.right);\n const top = H * SAFE_ZONE.top;\n const bottom = H * (1 - SAFE_ZONE.bottom);\n return {\n left, right, top, bottom,\n w: right - left, h: bottom - top,\n cx: (left + right) / 2, cy: (top + bottom) / 2,\n centeredW: 2 * Math.min(W / 2 - left, right - W / 2),\n };\n}\n\n/** Debug overlay: dim the four unsafe margins + outline the safe box. */\nexport function drawSafeGuides(ctx: CanvasRenderingContext2D): void {\n const W = ctx.canvas.width, H = ctx.canvas.height;\n const b = safeBox(W, H);\n ctx.save();\n ctx.globalAlpha = 1;\n ctx.fillStyle = 'rgba(220,40,40,0.35)';\n ctx.fillRect(0, 0, W, b.top); // top\n ctx.fillRect(0, b.bottom, W, H - b.bottom); // bottom\n ctx.fillRect(0, b.top, b.left, b.h); // left\n ctx.fillRect(b.right, b.top, W - b.right, b.h); // right (action rail)\n ctx.strokeStyle = 'rgba(40,200,90,1)';\n ctx.lineWidth = 6;\n ctx.strokeRect(b.left, b.top, b.w, b.h);\n ctx.restore();\n}\n\n// ─── MIME type picker ─────────────────────────────────────────────────────────\n\nexport function pickMimeType(): string {\n const candidates = [\n 'video/webm;codecs=vp9,opus',\n 'video/webm;codecs=vp8,opus',\n 'video/webm',\n ];\n for (const t of candidates) {\n if (typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported(t)) return t;\n }\n return 'video/webm';\n}\n\n// ─── recordScenes ─────────────────────────────────────────────────────────────\n\n// Resolve once the capture stream has produced its first real video frame, so a\n// recorder started right after will see audio and video begin together. Uses\n// requestVideoFrameCallback when available; always resolves within `timeoutMs`\n// (and immediately when not running in a browser) so a render can never hang.\nasync function waitForFirstVideoFrame(stream: MediaStream, timeoutMs = 2500): Promise<void> {\n const [track] = stream.getVideoTracks();\n if (!track || typeof document === 'undefined') return;\n const video = document.createElement('video');\n video.muted = true;\n (video as unknown as { playsInline?: boolean }).playsInline = true;\n video.srcObject = stream;\n const rvfc = (\n video as unknown as { requestVideoFrameCallback?: (cb: () => void) => number }\n ).requestVideoFrameCallback?.bind(video);\n try { await video.play(); } catch { /* muted autoplay is allowed; ignore */ }\n await new Promise<void>((resolve) => {\n let settled = false;\n const done = () => { if (!settled) { settled = true; resolve(); } };\n if (rvfc) rvfc(done);\n setTimeout(done, timeoutMs);\n });\n try { video.pause(); video.srcObject = null; } catch { /* ignore cleanup */ }\n}\n\nexport async function recordScenes(scenes: Scene[], opts: RecordOpts): Promise<Blob> {\n const { audioStream, width, height, fps = 30, background = '#000000', onProgress } = opts;\n\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n const maybeCtx = canvas.getContext('2d');\n if (!maybeCtx) throw new Error('Failed to get 2D context');\n const ctx: CanvasRenderingContext2D = maybeCtx;\n\n const videoStream = canvas.captureStream(fps);\n\n // Draw the first frame BEFORE recording so the capture pipeline has content.\n ctx.fillStyle = background;\n ctx.fillRect(0, 0, width, height);\n scenes[0].draw(ctx, 0);\n\n // A/V SYNC: don't start the recorder until the capture pipeline is actually\n // emitting video frames. On a headless GPU (swiftshader) the first frame can\n // lag the live audio stream by 1–2s; starting the recorder then would write\n // audio that LEADS the late-arriving video, so every note is heard before the\n // playhead reaches it. Gating on the first real frame makes both tracks begin\n // together, regardless of warmup latency. (No-op outside a browser.)\n await waitForFirstVideoFrame(videoStream);\n\n const combined = new MediaStream([\n ...videoStream.getVideoTracks(),\n ...audioStream.getAudioTracks(),\n ]);\n\n const mimeType = pickMimeType();\n const recorder = new MediaRecorder(combined, {\n mimeType,\n videoBitsPerSecond: 4_000_000,\n audioBitsPerSecond: 128_000,\n });\n\n const chunks: Blob[] = [];\n recorder.ondataavailable = (e) => {\n if (e.data.size > 0) chunks.push(e.data);\n };\n const stopped = new Promise<void>((res) => {\n recorder.onstop = () => res();\n });\n\n recorder.start();\n\n const totalMs = scenes.reduce((s, sc) => s + sc.durationMs, 0);\n const t0 = performance.now();\n\n onProgress?.({ phase: 'capturing', progress01: 0 });\n\n let sceneStart = 0;\n let sceneIdx = 0;\n let enteredScene = -1;\n\n await new Promise<void>((resolve) => {\n function tick() {\n const now = performance.now() - t0;\n if (now >= totalMs) {\n setTimeout(resolve, 100);\n return;\n }\n\n // Advance scene index\n while (\n sceneIdx < scenes.length - 1 &&\n now - sceneStart >= scenes[sceneIdx].durationMs\n ) {\n sceneStart += scenes[sceneIdx].durationMs;\n sceneIdx += 1;\n }\n\n const scene = scenes[sceneIdx];\n\n // Fire onEnter once per scene\n if (enteredScene !== sceneIdx) {\n enteredScene = sceneIdx;\n scene.onEnter?.();\n }\n\n const tInScene = (now - sceneStart) / scene.durationMs;\n\n // Clear to background\n ctx.fillStyle = background;\n ctx.fillRect(0, 0, width, height);\n\n scene.draw(ctx, Math.min(1, Math.max(0, tInScene)));\n\n onProgress?.({\n phase: 'capturing',\n progress01: Math.min(0.95, now / totalMs),\n });\n\n requestAnimationFrame(tick);\n }\n\n requestAnimationFrame(tick);\n });\n\n onProgress?.({ phase: 'finalizing', progress01: 0.96 });\n recorder.stop();\n await stopped;\n\n return new Blob(chunks, { type: mimeType });\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nexport function truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max - 1).trimEnd() + '…' : s;\n}\n\nexport function initials(name: string): string {\n return name\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 3)\n .map((w) => w[0])\n .join('')\n .toUpperCase();\n}\n\n// ─── Scene builders ───────────────────────────────────────────────────────────\n\nexport interface HookSceneOpts {\n lines: string[];\n brand?: string;\n}\n\nexport function hookScene(theme: PromoTheme, opts: HookSceneOpts): Scene {\n return {\n durationMs: 2000,\n draw(ctx, _t01) {\n const W = ctx.canvas.width;\n const H = ctx.canvas.height;\n ctx.textAlign = 'center';\n ctx.fillStyle = theme.ink;\n ctx.font = `bold 88px ${theme.fontDisplay}`;\n opts.lines.forEach((line, i) => {\n ctx.fillText(line, W / 2, H * 0.42 + i * 104);\n });\n const brand = opts.brand ?? theme.brand;\n ctx.font = `italic 38px ${theme.fontBody}`;\n ctx.fillStyle = theme.sepia;\n ctx.fillText(brand, W / 2, H * 0.42 + opts.lines.length * 104 + 12);\n },\n };\n}\n\nexport interface RevealSceneOpts {\n title: string;\n subtitle: string;\n initials?: string;\n}\n\nexport function revealScene(theme: PromoTheme, opts: RevealSceneOpts): Scene {\n return {\n durationMs: 2200,\n draw(ctx, t01) {\n const W = ctx.canvas.width;\n const H = ctx.canvas.height;\n const b = safeBox(W, H);\n const cx = W / 2;\n // Anchor through the safe box; ~0.4286 of box height ≈ original H*0.40 (no visible shift at 1080×1920).\n const cy = b.top + b.h * 0.4286;\n const r = 92;\n const k = Math.min(1, t01 * 1.4);\n ctx.globalAlpha = k;\n ctx.textAlign = 'center';\n\n // Medallion circle (faint surface)\n ctx.beginPath();\n ctx.arc(cx, cy, r, 0, Math.PI * 2);\n ctx.fillStyle = theme.paper;\n ctx.fill();\n ctx.lineWidth = 5;\n ctx.strokeStyle = theme.gold;\n ctx.stroke();\n\n // Initials inside medallion\n ctx.fillStyle = theme.ink;\n ctx.font = `bold 76px ${theme.fontDisplay}`;\n const badge = opts.initials ?? initials(opts.title);\n ctx.fillText(badge, cx, cy + 27);\n\n // Title (with font-size fallback if too wide)\n const truncated = truncate(opts.title, 24);\n let fontSize = 80;\n ctx.font = `bold ${fontSize}px ${theme.fontDisplay}`;\n if (ctx.measureText(truncated).width > W * 0.86) {\n fontSize = 68;\n ctx.font = `bold ${fontSize}px ${theme.fontDisplay}`;\n }\n ctx.fillStyle = theme.ink;\n ctx.fillText(truncated, cx, cy + r + 110);\n\n // Subtitle\n ctx.font = `italic 50px ${theme.fontBody}`;\n ctx.fillStyle = theme.sepia;\n ctx.fillText(opts.subtitle, cx, cy + r + 176);\n\n ctx.globalAlpha = 1;\n },\n };\n}\n\nexport interface CtaSceneOpts {\n lines: string[];\n}\n\nexport function ctaScene(theme: PromoTheme, opts: CtaSceneOpts): Scene {\n return {\n durationMs: 2500,\n draw(ctx, _t01) {\n const W = ctx.canvas.width;\n const H = ctx.canvas.height;\n const b = safeBox(W, H);\n // Safe-box-relative base ≈ original H*0.42 (no visible shift at 1080×1920).\n const baseY = b.cy - 40;\n ctx.textAlign = 'center';\n if (opts.lines.length > 0) {\n ctx.fillStyle = theme.ink;\n ctx.font = `bold 66px ${theme.fontDisplay}`;\n ctx.fillText(opts.lines[0], W / 2, baseY);\n }\n opts.lines.slice(1).forEach((line, i) => {\n ctx.fillStyle = theme.accent;\n ctx.font = `bold 72px ${theme.fontDisplay}`;\n ctx.fillText(line, W / 2, baseY + 100 * (i + 1));\n });\n },\n };\n}\n\n// ─── runPromoCapture ──────────────────────────────────────────────────────────\n\nexport async function runPromoCapture(\n opts: PromoCaptureOpts,\n record: (scenes: Scene[], o: RecordOpts) => Promise<Blob> = recordScenes,\n): Promise<void> {\n const w = window as unknown as Record<string, unknown>;\n w.__promoMeta = opts.meta;\n try {\n const blob = await record(opts.buildScenes(), {\n audioStream: opts.audioStream,\n width: opts.width ?? 1080,\n height: opts.height ?? 1920,\n fps: opts.fps,\n background: opts.background,\n });\n const url = URL.createObjectURL(blob);\n w.__promoBlobUrl = url;\n if (opts.download !== false) {\n const a = document.createElement('a');\n a.href = url;\n a.download = `promo-${opts.meta.id ?? 'clip'}.webm`;\n document.body.appendChild(a);\n a.click();\n }\n w.__promoReady = true;\n } catch (e) {\n w.__promoError = String(e);\n throw e;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/video.ts"],"sourcesContent":["/**\n * Framework-agnostic promo video module.\n *\n * Provides:\n * - PromoTheme — colour/font/brand tokens\n * - Scene — {durationMs, draw, onEnter?}\n * - recordScenes — rAF-based canvas capture → Blob (browser only)\n * - hookScene / revealScene / ctaScene — scene builders (pure, unit-testable)\n * - runPromoCapture — orchestrator with dependency-injected recorder\n *\n * Visual style ported from whozart/src/lib/promo.ts + video.ts.\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface PromoTheme {\n paper: string;\n ink: string;\n accent: string;\n sepia: string;\n gold: string;\n fontDisplay: string;\n fontBody: string;\n brand: string;\n}\n\nexport interface Scene {\n durationMs: number;\n draw: (ctx: CanvasRenderingContext2D, t01: number) => void;\n onEnter?: () => void;\n}\n\nexport interface RecordOpts {\n audioStream: MediaStream;\n width: number;\n height: number;\n fps?: number;\n background?: string;\n onProgress?: (p: { phase: string; progress01: number }) => void;\n}\n\nexport interface PromoMeta {\n id?: string;\n title?: string;\n composer?: string;\n [k: string]: unknown;\n}\n\nexport interface PromoCaptureOpts {\n buildScenes: () => Scene[];\n audioStream: MediaStream;\n meta: PromoMeta;\n width?: number;\n height?: number;\n fps?: number;\n background?: string;\n download?: boolean;\n}\n\n// ─── Safe zone (phone short-form UI) ─────────────────────────────────────────\n\n/** Insets (fractions of frame) kept clear of TikTok/Reels/Shorts UI overlays. */\nexport const SAFE_ZONE = { top: 0.10, bottom: 0.20, left: 0.06, right: 0.12 } as const;\n\nexport interface SafeBox {\n left: number; right: number; top: number; bottom: number;\n w: number; h: number; cx: number; cy: number;\n /** Widest block centered at frame W/2 that still fits inside the box. */\n centeredW: number;\n}\n\n/** The usable content rectangle for a W×H frame, in pixels. */\nexport function safeBox(W: number, H: number): SafeBox {\n const left = W * SAFE_ZONE.left;\n const right = W * (1 - SAFE_ZONE.right);\n const top = H * SAFE_ZONE.top;\n const bottom = H * (1 - SAFE_ZONE.bottom);\n return {\n left, right, top, bottom,\n w: right - left, h: bottom - top,\n cx: (left + right) / 2, cy: (top + bottom) / 2,\n centeredW: 2 * Math.min(W / 2 - left, right - W / 2),\n };\n}\n\n/** Debug overlay: dim the four unsafe margins + outline the safe box. */\nexport function drawSafeGuides(ctx: CanvasRenderingContext2D): void {\n const W = ctx.canvas.width, H = ctx.canvas.height;\n const b = safeBox(W, H);\n ctx.save();\n ctx.globalAlpha = 1;\n ctx.fillStyle = 'rgba(220,40,40,0.35)';\n ctx.fillRect(0, 0, W, b.top); // top\n ctx.fillRect(0, b.bottom, W, H - b.bottom); // bottom\n ctx.fillRect(0, b.top, b.left, b.h); // left\n ctx.fillRect(b.right, b.top, W - b.right, b.h); // right (action rail)\n ctx.strokeStyle = 'rgba(40,200,90,1)';\n ctx.lineWidth = 6;\n ctx.strokeRect(b.left, b.top, b.w, b.h);\n ctx.restore();\n}\n\n// ─── MIME type picker ─────────────────────────────────────────────────────────\n\nexport function pickMimeType(): string {\n const candidates = [\n 'video/webm;codecs=vp9,opus',\n 'video/webm;codecs=vp8,opus',\n 'video/webm',\n ];\n for (const t of candidates) {\n if (typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported(t)) return t;\n }\n return 'video/webm';\n}\n\n// ─── recordScenes ─────────────────────────────────────────────────────────────\n\nexport async function recordScenes(scenes: Scene[], opts: RecordOpts): Promise<Blob> {\n const { audioStream, width, height, fps = 30, background = '#000000', onProgress } = opts;\n\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n const maybeCtx = canvas.getContext('2d');\n if (!maybeCtx) throw new Error('Failed to get 2D context');\n const ctx: CanvasRenderingContext2D = maybeCtx;\n\n const videoStream = canvas.captureStream(fps);\n const combined = new MediaStream([\n ...videoStream.getVideoTracks(),\n ...audioStream.getAudioTracks(),\n ]);\n\n const mimeType = pickMimeType();\n const recorder = new MediaRecorder(combined, {\n mimeType,\n videoBitsPerSecond: 4_000_000,\n audioBitsPerSecond: 128_000,\n });\n\n const chunks: Blob[] = [];\n recorder.ondataavailable = (e) => {\n if (e.data.size > 0) chunks.push(e.data);\n };\n const stopped = new Promise<void>((res) => {\n recorder.onstop = () => res();\n });\n\n recorder.start();\n\n const totalMs = scenes.reduce((s, sc) => s + sc.durationMs, 0);\n const t0 = performance.now();\n\n onProgress?.({ phase: 'capturing', progress01: 0 });\n\n let sceneStart = 0;\n let sceneIdx = 0;\n let enteredScene = -1;\n\n // Draw first frame immediately\n ctx.fillStyle = background;\n ctx.fillRect(0, 0, width, height);\n scenes[0].draw(ctx, 0);\n\n await new Promise<void>((resolve) => {\n function tick() {\n const now = performance.now() - t0;\n if (now >= totalMs) {\n setTimeout(resolve, 100);\n return;\n }\n\n // Advance scene index\n while (\n sceneIdx < scenes.length - 1 &&\n now - sceneStart >= scenes[sceneIdx].durationMs\n ) {\n sceneStart += scenes[sceneIdx].durationMs;\n sceneIdx += 1;\n }\n\n const scene = scenes[sceneIdx];\n\n // Fire onEnter once per scene\n if (enteredScene !== sceneIdx) {\n enteredScene = sceneIdx;\n scene.onEnter?.();\n }\n\n const tInScene = (now - sceneStart) / scene.durationMs;\n\n // Clear to background\n ctx.fillStyle = background;\n ctx.fillRect(0, 0, width, height);\n\n scene.draw(ctx, Math.min(1, Math.max(0, tInScene)));\n\n onProgress?.({\n phase: 'capturing',\n progress01: Math.min(0.95, now / totalMs),\n });\n\n requestAnimationFrame(tick);\n }\n\n requestAnimationFrame(tick);\n });\n\n onProgress?.({ phase: 'finalizing', progress01: 0.96 });\n recorder.stop();\n await stopped;\n\n return new Blob(chunks, { type: mimeType });\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nexport function truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max - 1).trimEnd() + '…' : s;\n}\n\nexport function initials(name: string): string {\n return name\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 3)\n .map((w) => w[0])\n .join('')\n .toUpperCase();\n}\n\n// ─── Scene builders ───────────────────────────────────────────────────────────\n\nexport interface HookSceneOpts {\n lines: string[];\n brand?: string;\n}\n\nexport function hookScene(theme: PromoTheme, opts: HookSceneOpts): Scene {\n return {\n durationMs: 2000,\n draw(ctx, _t01) {\n const W = ctx.canvas.width;\n const H = ctx.canvas.height;\n ctx.textAlign = 'center';\n ctx.fillStyle = theme.ink;\n ctx.font = `bold 88px ${theme.fontDisplay}`;\n opts.lines.forEach((line, i) => {\n ctx.fillText(line, W / 2, H * 0.42 + i * 104);\n });\n const brand = opts.brand ?? theme.brand;\n ctx.font = `italic 38px ${theme.fontBody}`;\n ctx.fillStyle = theme.sepia;\n ctx.fillText(brand, W / 2, H * 0.42 + opts.lines.length * 104 + 12);\n },\n };\n}\n\nexport interface RevealSceneOpts {\n title: string;\n subtitle: string;\n initials?: string;\n}\n\nexport function revealScene(theme: PromoTheme, opts: RevealSceneOpts): Scene {\n return {\n durationMs: 2200,\n draw(ctx, t01) {\n const W = ctx.canvas.width;\n const H = ctx.canvas.height;\n const b = safeBox(W, H);\n const cx = W / 2;\n // Anchor through the safe box; ~0.4286 of box height ≈ original H*0.40 (no visible shift at 1080×1920).\n const cy = b.top + b.h * 0.4286;\n const r = 92;\n const k = Math.min(1, t01 * 1.4);\n ctx.globalAlpha = k;\n ctx.textAlign = 'center';\n\n // Medallion circle (faint surface)\n ctx.beginPath();\n ctx.arc(cx, cy, r, 0, Math.PI * 2);\n ctx.fillStyle = theme.paper;\n ctx.fill();\n ctx.lineWidth = 5;\n ctx.strokeStyle = theme.gold;\n ctx.stroke();\n\n // Initials inside medallion\n ctx.fillStyle = theme.ink;\n ctx.font = `bold 76px ${theme.fontDisplay}`;\n const badge = opts.initials ?? initials(opts.title);\n ctx.fillText(badge, cx, cy + 27);\n\n // Title (with font-size fallback if too wide)\n const truncated = truncate(opts.title, 24);\n let fontSize = 80;\n ctx.font = `bold ${fontSize}px ${theme.fontDisplay}`;\n if (ctx.measureText(truncated).width > W * 0.86) {\n fontSize = 68;\n ctx.font = `bold ${fontSize}px ${theme.fontDisplay}`;\n }\n ctx.fillStyle = theme.ink;\n ctx.fillText(truncated, cx, cy + r + 110);\n\n // Subtitle\n ctx.font = `italic 50px ${theme.fontBody}`;\n ctx.fillStyle = theme.sepia;\n ctx.fillText(opts.subtitle, cx, cy + r + 176);\n\n ctx.globalAlpha = 1;\n },\n };\n}\n\nexport interface CtaSceneOpts {\n lines: string[];\n}\n\nexport function ctaScene(theme: PromoTheme, opts: CtaSceneOpts): Scene {\n return {\n durationMs: 2500,\n draw(ctx, _t01) {\n const W = ctx.canvas.width;\n const H = ctx.canvas.height;\n const b = safeBox(W, H);\n // Safe-box-relative base ≈ original H*0.42 (no visible shift at 1080×1920).\n const baseY = b.cy - 40;\n ctx.textAlign = 'center';\n if (opts.lines.length > 0) {\n ctx.fillStyle = theme.ink;\n ctx.font = `bold 66px ${theme.fontDisplay}`;\n ctx.fillText(opts.lines[0], W / 2, baseY);\n }\n opts.lines.slice(1).forEach((line, i) => {\n ctx.fillStyle = theme.accent;\n ctx.font = `bold 72px ${theme.fontDisplay}`;\n ctx.fillText(line, W / 2, baseY + 100 * (i + 1));\n });\n },\n };\n}\n\n// ─── runPromoCapture ──────────────────────────────────────────────────────────\n\nexport async function runPromoCapture(\n opts: PromoCaptureOpts,\n record: (scenes: Scene[], o: RecordOpts) => Promise<Blob> = recordScenes,\n): Promise<void> {\n const w = window as unknown as Record<string, unknown>;\n w.__promoMeta = opts.meta;\n try {\n const blob = await record(opts.buildScenes(), {\n audioStream: opts.audioStream,\n width: opts.width ?? 1080,\n height: opts.height ?? 1920,\n fps: opts.fps,\n background: opts.background,\n });\n const url = URL.createObjectURL(blob);\n w.__promoBlobUrl = url;\n if (opts.download !== false) {\n const a = document.createElement('a');\n a.href = url;\n a.download = `promo-${opts.meta.id ?? 'clip'}.webm`;\n document.body.appendChild(a);\n a.click();\n }\n w.__promoReady = true;\n } catch (e) {\n w.__promoError = String(e);\n throw e;\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-music-packages/web-core",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Shared music-theory + audio primitives for the music-suite web apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",