@sansavision/create-vidra-app 0.1.7-alpha.0
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/index.js +79 -0
- package/package.json +26 -0
- package/template/README.md +47 -0
- package/template/index.html +262 -0
- package/template/package.json +20 -0
- package/template/src/video.js +111 -0
- package/template/video.vidra +81 -0
- package/template/web/chart.html +95 -0
package/index.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ─── create-vidra-app ───────────────────────────────────────────────
|
|
4
|
+
// Scaffolds a new Vidra project with SDK, Player, and Web Capture
|
|
5
|
+
// ready to go.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// npx @sansavision/create-vidra-app my-video
|
|
9
|
+
// cd my-video && npm install && npm run dev
|
|
10
|
+
|
|
11
|
+
import { mkdirSync, writeFileSync, readFileSync, readdirSync, statSync, copyFileSync } from "fs";
|
|
12
|
+
import { join, dirname, resolve, basename } from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
const projectName = args[0];
|
|
20
|
+
|
|
21
|
+
if (!projectName) {
|
|
22
|
+
console.log(`
|
|
23
|
+
\x1b[1m\x1b[35m🎬 create-vidra-app\x1b[0m
|
|
24
|
+
|
|
25
|
+
Scaffold a new Vidra video project.
|
|
26
|
+
|
|
27
|
+
\x1b[1mUsage:\x1b[0m
|
|
28
|
+
npx @sansavision/create-vidra-app \x1b[36m<project-name>\x1b[0m
|
|
29
|
+
|
|
30
|
+
\x1b[1mExample:\x1b[0m
|
|
31
|
+
npx @sansavision/create-vidra-app my-video
|
|
32
|
+
cd my-video
|
|
33
|
+
npm install
|
|
34
|
+
npm run dev
|
|
35
|
+
`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const targetDir = resolve(process.cwd(), projectName);
|
|
40
|
+
const templateDir = join(__dirname, "template");
|
|
41
|
+
const baseName = basename(targetDir);
|
|
42
|
+
|
|
43
|
+
console.log();
|
|
44
|
+
console.log(` \x1b[1m\x1b[35m🎬 Creating Vidra project:\x1b[0m ${projectName}`);
|
|
45
|
+
console.log();
|
|
46
|
+
|
|
47
|
+
// Recursively copy template directory
|
|
48
|
+
function copyDir(src, dest) {
|
|
49
|
+
mkdirSync(dest, { recursive: true });
|
|
50
|
+
for (const entry of readdirSync(src)) {
|
|
51
|
+
const srcPath = join(src, entry);
|
|
52
|
+
const destPath = join(dest, entry);
|
|
53
|
+
if (statSync(srcPath).isDirectory()) {
|
|
54
|
+
copyDir(srcPath, destPath);
|
|
55
|
+
} else {
|
|
56
|
+
let content = readFileSync(srcPath, "utf-8");
|
|
57
|
+
// Replace template variables
|
|
58
|
+
content = content.replace(/\{\{PROJECT_NAME\}\}/g, baseName);
|
|
59
|
+
writeFileSync(destPath, content);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
copyDir(templateDir, targetDir);
|
|
65
|
+
|
|
66
|
+
console.log(` \x1b[32m✓\x1b[0m Project scaffolded at \x1b[1m${targetDir}\x1b[0m`);
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(` \x1b[1mNext steps:\x1b[0m`);
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(` cd ${projectName}`);
|
|
71
|
+
console.log(` npm install`);
|
|
72
|
+
console.log(` npm run dev \x1b[2m# Live browser preview\x1b[0m`);
|
|
73
|
+
console.log(` npm run build:video \x1b[2m# Generate project IR JSON\x1b[0m`);
|
|
74
|
+
console.log();
|
|
75
|
+
console.log(` \x1b[2mTo render to MP4 (requires Vidra CLI):\x1b[0m`);
|
|
76
|
+
console.log(` vidra render video.vidra -o output.mp4`);
|
|
77
|
+
console.log();
|
|
78
|
+
console.log(` \x1b[35m📖 Docs:\x1b[0m https://github.com/Sansa-Organisation/vidra`);
|
|
79
|
+
console.log();
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sansavision/create-vidra-app",
|
|
3
|
+
"version": "0.1.7-alpha.0",
|
|
4
|
+
"description": "Scaffold a new Vidra video project — SDK, Player, Web Capture ready to go",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-vidra-app": "./index.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"template/**/*"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"vidra",
|
|
15
|
+
"video",
|
|
16
|
+
"create",
|
|
17
|
+
"scaffold",
|
|
18
|
+
"motion-graphics",
|
|
19
|
+
"programmatic-video"
|
|
20
|
+
],
|
|
21
|
+
"author": "Sansa Vision",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A video project powered by [Vidra](https://github.com/Sansa-Organisation/vidra).
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run dev # Open browser preview
|
|
10
|
+
npm run build:video # Generate project IR JSON
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Render to MP4
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Using the Vidra CLI:
|
|
17
|
+
vidra render video.vidra -o output.mp4
|
|
18
|
+
|
|
19
|
+
# Or use the visual editor:
|
|
20
|
+
vidra editor video.vidra
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Project Structure
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
├── index.html ← Browser preview (Vite dev server)
|
|
27
|
+
├── src/
|
|
28
|
+
│ └── video.js ← SDK-based video builder
|
|
29
|
+
├── web/
|
|
30
|
+
│ └── chart.html ← Web scene with capture bridge
|
|
31
|
+
├── video.vidra ← VidraScript DSL version
|
|
32
|
+
└── package.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Packages Used
|
|
36
|
+
|
|
37
|
+
| Package | Purpose |
|
|
38
|
+
|---------|---------|
|
|
39
|
+
| `@sansavision/vidra-sdk` | Build video projects programmatically |
|
|
40
|
+
| `@sansavision/vidra-player` | WASM-powered browser renderer |
|
|
41
|
+
| `@sansavision/vidra-web-capture` | Bridge for web scenes |
|
|
42
|
+
|
|
43
|
+
## Learn More
|
|
44
|
+
|
|
45
|
+
- [Vidra Documentation](https://github.com/Sansa-Organisation/vidra/tree/main/docs)
|
|
46
|
+
- [VidraScript Reference](https://github.com/Sansa-Organisation/vidra/blob/main/docs/vidrascript.md)
|
|
47
|
+
- [Web Scenes Guide](https://github.com/Sansa-Organisation/vidra/blob/main/docs/web-scenes.md)
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{PROJECT_NAME}} — Preview</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
body {
|
|
10
|
+
min-height: 100vh;
|
|
11
|
+
background: #0d1117;
|
|
12
|
+
color: #e6edf3;
|
|
13
|
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
align-items: center;
|
|
17
|
+
padding: 40px 20px;
|
|
18
|
+
}
|
|
19
|
+
h1 {
|
|
20
|
+
font-size: 28px;
|
|
21
|
+
font-weight: 700;
|
|
22
|
+
margin-bottom: 8px;
|
|
23
|
+
background: linear-gradient(135deg, #58a6ff, #d2a8ff);
|
|
24
|
+
-webkit-background-clip: text;
|
|
25
|
+
-webkit-text-fill-color: transparent;
|
|
26
|
+
}
|
|
27
|
+
.subtitle {
|
|
28
|
+
color: #8b949e;
|
|
29
|
+
font-size: 14px;
|
|
30
|
+
margin-bottom: 32px;
|
|
31
|
+
}
|
|
32
|
+
.canvas-wrapper {
|
|
33
|
+
position: relative;
|
|
34
|
+
border-radius: 12px;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
box-shadow: 0 0 0 1px rgba(240,246,252,0.1), 0 16px 48px rgba(0,0,0,0.4);
|
|
37
|
+
}
|
|
38
|
+
canvas {
|
|
39
|
+
display: block;
|
|
40
|
+
background: #000;
|
|
41
|
+
}
|
|
42
|
+
.controls {
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: 12px;
|
|
46
|
+
margin-top: 20px;
|
|
47
|
+
padding: 12px 20px;
|
|
48
|
+
background: #161b22;
|
|
49
|
+
border-radius: 10px;
|
|
50
|
+
border: 1px solid #30363d;
|
|
51
|
+
}
|
|
52
|
+
button {
|
|
53
|
+
background: #238636;
|
|
54
|
+
color: white;
|
|
55
|
+
border: none;
|
|
56
|
+
border-radius: 6px;
|
|
57
|
+
padding: 8px 20px;
|
|
58
|
+
font-size: 14px;
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
transition: background 0.15s;
|
|
62
|
+
}
|
|
63
|
+
button:hover { background: #2ea043; }
|
|
64
|
+
button.secondary {
|
|
65
|
+
background: #21262d;
|
|
66
|
+
border: 1px solid #30363d;
|
|
67
|
+
}
|
|
68
|
+
button.secondary:hover { background: #30363d; }
|
|
69
|
+
.frame-display {
|
|
70
|
+
font-family: 'SF Mono', 'JetBrains Mono', monospace;
|
|
71
|
+
font-size: 13px;
|
|
72
|
+
color: #8b949e;
|
|
73
|
+
min-width: 100px;
|
|
74
|
+
text-align: center;
|
|
75
|
+
}
|
|
76
|
+
.info-grid {
|
|
77
|
+
display: grid;
|
|
78
|
+
grid-template-columns: repeat(3, 1fr);
|
|
79
|
+
gap: 16px;
|
|
80
|
+
margin-top: 24px;
|
|
81
|
+
max-width: 800px;
|
|
82
|
+
width: 100%;
|
|
83
|
+
}
|
|
84
|
+
.info-card {
|
|
85
|
+
background: #161b22;
|
|
86
|
+
border: 1px solid #30363d;
|
|
87
|
+
border-radius: 10px;
|
|
88
|
+
padding: 20px;
|
|
89
|
+
}
|
|
90
|
+
.info-card h3 {
|
|
91
|
+
font-size: 13px;
|
|
92
|
+
color: #8b949e;
|
|
93
|
+
font-weight: 500;
|
|
94
|
+
margin-bottom: 8px;
|
|
95
|
+
text-transform: uppercase;
|
|
96
|
+
letter-spacing: 0.5px;
|
|
97
|
+
}
|
|
98
|
+
.info-card p {
|
|
99
|
+
font-size: 15px;
|
|
100
|
+
line-height: 1.5;
|
|
101
|
+
}
|
|
102
|
+
.info-card code {
|
|
103
|
+
background: #0d1117;
|
|
104
|
+
padding: 2px 6px;
|
|
105
|
+
border-radius: 4px;
|
|
106
|
+
font-size: 13px;
|
|
107
|
+
color: #58a6ff;
|
|
108
|
+
}
|
|
109
|
+
</style>
|
|
110
|
+
</head>
|
|
111
|
+
<body>
|
|
112
|
+
<h1>🎬 {{PROJECT_NAME}}</h1>
|
|
113
|
+
<p class="subtitle">Vidra Video Project — Browser Preview</p>
|
|
114
|
+
|
|
115
|
+
<div class="canvas-wrapper">
|
|
116
|
+
<canvas id="preview" width="960" height="540"></canvas>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div class="controls">
|
|
120
|
+
<button id="playBtn" onclick="togglePlay()">▶ Play</button>
|
|
121
|
+
<button class="secondary" onclick="seekStart()">⏮ Start</button>
|
|
122
|
+
<div class="frame-display" id="frameDisplay">Frame 0 / 0</div>
|
|
123
|
+
<button class="secondary" onclick="seekEnd()">⏭ End</button>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<div class="info-grid">
|
|
127
|
+
<div class="info-card">
|
|
128
|
+
<h3>SDK Video</h3>
|
|
129
|
+
<p>Edit <code>src/video.js</code> to build your video programmatically using the Vidra SDK.</p>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="info-card">
|
|
132
|
+
<h3>Web Scene</h3>
|
|
133
|
+
<p>Edit <code>web/chart.html</code> to create interactive web scenes using the capture bridge.</p>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="info-card">
|
|
136
|
+
<h3>Render</h3>
|
|
137
|
+
<p>Run <code>vidra render video.vidra</code> to export your project as an MP4 file.</p>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<script type="module">
|
|
142
|
+
// This preview page demonstrates how @sansavision/vidra-player
|
|
143
|
+
// would render a project in the browser. For now, it shows a
|
|
144
|
+
// placeholder with the project structure info.
|
|
145
|
+
|
|
146
|
+
const canvas = document.getElementById('preview');
|
|
147
|
+
const ctx = canvas.getContext('2d');
|
|
148
|
+
const frameDisplay = document.getElementById('frameDisplay');
|
|
149
|
+
const playBtn = document.getElementById('playBtn');
|
|
150
|
+
|
|
151
|
+
const FPS = 30;
|
|
152
|
+
const TOTAL_FRAMES = 330; // 11 seconds at 30fps
|
|
153
|
+
let currentFrame = 0;
|
|
154
|
+
let playing = false;
|
|
155
|
+
let lastTime = 0;
|
|
156
|
+
|
|
157
|
+
function drawFrame(frame) {
|
|
158
|
+
const time = frame / FPS;
|
|
159
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
160
|
+
|
|
161
|
+
// Background
|
|
162
|
+
ctx.fillStyle = '#0d1117';
|
|
163
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
164
|
+
|
|
165
|
+
if (time < 4) {
|
|
166
|
+
// Scene 1: Intro
|
|
167
|
+
const t = Math.min(time / 0.8, 1);
|
|
168
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
169
|
+
|
|
170
|
+
ctx.globalAlpha = eased;
|
|
171
|
+
ctx.fillStyle = '#e6edf3';
|
|
172
|
+
ctx.font = 'bold 36px Inter, system-ui';
|
|
173
|
+
ctx.textAlign = 'center';
|
|
174
|
+
ctx.fillText('Welcome to {{PROJECT_NAME}}', canvas.width / 2, canvas.height / 2 - 20);
|
|
175
|
+
|
|
176
|
+
const t2 = Math.max(0, Math.min((time - 0.3) / 0.8, 1));
|
|
177
|
+
ctx.globalAlpha = 1 - Math.pow(1 - t2, 3);
|
|
178
|
+
ctx.fillStyle = '#8b949e';
|
|
179
|
+
ctx.font = '16px Inter, system-ui';
|
|
180
|
+
ctx.fillText('Built with Vidra SDK', canvas.width / 2, canvas.height / 2 + 30);
|
|
181
|
+
|
|
182
|
+
// Accent line
|
|
183
|
+
const t3 = Math.max(0, Math.min((time - 0.6) / 0.6, 1));
|
|
184
|
+
ctx.globalAlpha = 1 - Math.pow(1 - t3, 3);
|
|
185
|
+
const lineWidth = t3 * 100;
|
|
186
|
+
ctx.fillStyle = '#58a6ff';
|
|
187
|
+
ctx.fillRect(canvas.width / 2 - lineWidth, canvas.height / 2 + 60, lineWidth * 2, 2);
|
|
188
|
+
|
|
189
|
+
} else if (time < 8) {
|
|
190
|
+
// Scene 2: Content
|
|
191
|
+
const st = time - 4;
|
|
192
|
+
ctx.fillStyle = '#161b22';
|
|
193
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
194
|
+
|
|
195
|
+
const t1 = Math.min(st / 0.6, 1);
|
|
196
|
+
ctx.globalAlpha = 1 - Math.pow(1 - t1, 3);
|
|
197
|
+
ctx.fillStyle = '#e6edf3';
|
|
198
|
+
ctx.font = 'bold 28px Inter, system-ui';
|
|
199
|
+
ctx.textAlign = 'center';
|
|
200
|
+
ctx.fillText('Programmatic Video', canvas.width / 2, 150);
|
|
201
|
+
|
|
202
|
+
const bullets = ['✦ TypeScript SDK', '✦ GPU-accelerated rendering', '✦ Web scenes (React, D3, Three.js)'];
|
|
203
|
+
bullets.forEach((txt, i) => {
|
|
204
|
+
const bt = Math.max(0, Math.min((st - 0.2 - i * 0.2) / 0.5, 1));
|
|
205
|
+
ctx.globalAlpha = 1 - Math.pow(1 - bt, 3);
|
|
206
|
+
ctx.fillStyle = '#58a6ff';
|
|
207
|
+
ctx.font = '18px Inter, system-ui';
|
|
208
|
+
ctx.fillText(txt, canvas.width / 2, 230 + i * 50);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
} else {
|
|
212
|
+
// Scene 3: Outro
|
|
213
|
+
const st = time - 8;
|
|
214
|
+
ctx.globalAlpha = Math.min(st / 0.8, 1);
|
|
215
|
+
ctx.fillStyle = '#ffffff';
|
|
216
|
+
ctx.font = 'bold 24px Inter, system-ui';
|
|
217
|
+
ctx.textAlign = 'center';
|
|
218
|
+
ctx.fillText('Start building with Vidra', canvas.width / 2, canvas.height / 2 - 10);
|
|
219
|
+
|
|
220
|
+
const t2 = Math.max(0, Math.min((st - 0.4) / 0.6, 1));
|
|
221
|
+
ctx.globalAlpha = t2;
|
|
222
|
+
ctx.fillStyle = '#8b949e';
|
|
223
|
+
ctx.font = '12px Inter, system-ui';
|
|
224
|
+
ctx.fillText('github.com/Sansa-Organisation/vidra', canvas.width / 2, canvas.height / 2 + 30);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
ctx.globalAlpha = 1;
|
|
228
|
+
frameDisplay.textContent = `Frame ${frame} / ${TOTAL_FRAMES}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function animate(timestamp) {
|
|
232
|
+
if (!playing) return;
|
|
233
|
+
if (!lastTime) lastTime = timestamp;
|
|
234
|
+
const delta = timestamp - lastTime;
|
|
235
|
+
if (delta >= 1000 / FPS) {
|
|
236
|
+
lastTime = timestamp;
|
|
237
|
+
currentFrame++;
|
|
238
|
+
if (currentFrame >= TOTAL_FRAMES) {
|
|
239
|
+
currentFrame = 0;
|
|
240
|
+
}
|
|
241
|
+
drawFrame(currentFrame);
|
|
242
|
+
}
|
|
243
|
+
requestAnimationFrame(animate);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
window.togglePlay = () => {
|
|
247
|
+
playing = !playing;
|
|
248
|
+
playBtn.textContent = playing ? '⏸ Pause' : '▶ Play';
|
|
249
|
+
if (playing) {
|
|
250
|
+
lastTime = 0;
|
|
251
|
+
requestAnimationFrame(animate);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
window.seekStart = () => { currentFrame = 0; drawFrame(0); };
|
|
256
|
+
window.seekEnd = () => { currentFrame = TOTAL_FRAMES - 1; drawFrame(currentFrame); };
|
|
257
|
+
|
|
258
|
+
// Initial render
|
|
259
|
+
drawFrame(0);
|
|
260
|
+
</script>
|
|
261
|
+
</body>
|
|
262
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "npx vite",
|
|
8
|
+
"build": "npx vite build",
|
|
9
|
+
"build:video": "node src/video.js",
|
|
10
|
+
"preview": "npx vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@sansavision/vidra-sdk": "^0.1.7-alpha.0",
|
|
14
|
+
"@sansavision/vidra-player": "^0.1.7-alpha.0",
|
|
15
|
+
"@sansavision/vidra-web-capture": "^0.1.7-alpha.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"vite": "^7.3.1"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// ─── {{PROJECT_NAME}} — Video Project (SDK) ────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// This file uses the Vidra SDK to build a video project programmatically.
|
|
4
|
+
// Run it with: npm run build:video
|
|
5
|
+
//
|
|
6
|
+
// The output is a JSON IR that can be rendered by the Vidra CLI:
|
|
7
|
+
// vidra render --ir output.json -o video.mp4
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
Project,
|
|
11
|
+
Scene,
|
|
12
|
+
Layer,
|
|
13
|
+
Easing,
|
|
14
|
+
hex,
|
|
15
|
+
} from "@sansavision/vidra-sdk";
|
|
16
|
+
|
|
17
|
+
// ── Build the project ───────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const project = new Project({ width: 1920, height: 1080, fps: 30 })
|
|
20
|
+
// Scene 1: Intro
|
|
21
|
+
.addScene(
|
|
22
|
+
new Scene("intro", 4)
|
|
23
|
+
// Dark gradient background
|
|
24
|
+
.addLayer(new Layer("bg").solid("#0d1117"))
|
|
25
|
+
|
|
26
|
+
// Main title — fades in and slides up
|
|
27
|
+
.addLayer(
|
|
28
|
+
new Layer("title")
|
|
29
|
+
.text("Welcome to {{PROJECT_NAME}}", "Inter", 72, "#e6edf3")
|
|
30
|
+
.position(960, 480)
|
|
31
|
+
.animate("opacity", 0, 1, 0.8, Easing.EaseOut)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// Subtitle
|
|
35
|
+
.addLayer(
|
|
36
|
+
new Layer("subtitle")
|
|
37
|
+
.text("Built with Vidra SDK", "Inter", 32, "#8b949e")
|
|
38
|
+
.position(960, 560)
|
|
39
|
+
.animate("opacity", 0, 1, 0.8, Easing.EaseOut, 0.3)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
// Decorative shape
|
|
43
|
+
.addLayer(
|
|
44
|
+
new Layer("accent")
|
|
45
|
+
.shape("rect", { width: 200, height: 4, radius: 2, fill: "#58a6ff" })
|
|
46
|
+
.position(960, 620)
|
|
47
|
+
.animate("opacity", 0, 1, 0.6, Easing.EaseOut, 0.6)
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
// Scene 2: Content
|
|
52
|
+
.addScene(
|
|
53
|
+
new Scene("content", 4)
|
|
54
|
+
.addLayer(new Layer("bg2").solid("#161b22"))
|
|
55
|
+
|
|
56
|
+
.addLayer(
|
|
57
|
+
new Layer("heading")
|
|
58
|
+
.text("Programmatic Video", "Inter", 56, "#e6edf3")
|
|
59
|
+
.position(960, 300)
|
|
60
|
+
.animate("opacity", 0, 1, 0.6, Easing.EaseOut)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
.addLayer(
|
|
64
|
+
new Layer("bullet1")
|
|
65
|
+
.text("✦ TypeScript SDK", "Inter", 36, "#58a6ff")
|
|
66
|
+
.position(960, 450)
|
|
67
|
+
.animate("opacity", 0, 1, 0.5, Easing.EaseOut, 0.2)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
.addLayer(
|
|
71
|
+
new Layer("bullet2")
|
|
72
|
+
.text("✦ GPU-accelerated rendering", "Inter", 36, "#58a6ff")
|
|
73
|
+
.position(960, 520)
|
|
74
|
+
.animate("opacity", 0, 1, 0.5, Easing.EaseOut, 0.4)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
.addLayer(
|
|
78
|
+
new Layer("bullet3")
|
|
79
|
+
.text("✦ Web scenes (React, D3, Three.js)", "Inter", 36, "#58a6ff")
|
|
80
|
+
.position(960, 590)
|
|
81
|
+
.animate("opacity", 0, 1, 0.5, Easing.EaseOut, 0.6)
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
// Scene 3: Outro
|
|
86
|
+
.addScene(
|
|
87
|
+
new Scene("outro", 3)
|
|
88
|
+
.addLayer(new Layer("bg3").solid("#0d1117"))
|
|
89
|
+
|
|
90
|
+
.addLayer(
|
|
91
|
+
new Layer("cta")
|
|
92
|
+
.text("Start building with Vidra", "Inter", 48, "#ffffff")
|
|
93
|
+
.position(960, 500)
|
|
94
|
+
.animate("opacity", 0, 1, 0.8, Easing.EaseOut)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
.addLayer(
|
|
98
|
+
new Layer("url")
|
|
99
|
+
.text("github.com/Sansa-Organisation/vidra", "Inter", 24, "#8b949e")
|
|
100
|
+
.position(960, 580)
|
|
101
|
+
.animate("opacity", 0, 1, 0.6, Easing.EaseOut, 0.4)
|
|
102
|
+
)
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// ── Output ──────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
const ir = project.build();
|
|
108
|
+
const json = JSON.stringify(ir, null, 2);
|
|
109
|
+
|
|
110
|
+
// Write to stdout (pipe to file: node src/video.js > project.json)
|
|
111
|
+
console.log(json);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// ─── {{PROJECT_NAME}} — VidraScript ─────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// This is the same project defined in src/video.js, but written in
|
|
4
|
+
// VidraScript DSL. Render it with the Vidra CLI:
|
|
5
|
+
//
|
|
6
|
+
// vidra render video.vidra -o output.mp4
|
|
7
|
+
// vidra dev video.vidra # live preview with hot reload
|
|
8
|
+
|
|
9
|
+
project(1920, 1080, 30) {
|
|
10
|
+
scene("intro", 4s) {
|
|
11
|
+
layer("bg") {
|
|
12
|
+
solid(#0d1117)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
layer("title") {
|
|
16
|
+
text("Welcome to {{PROJECT_NAME}}", font: "Inter", size: 72, color: #e6edf3)
|
|
17
|
+
position(960, 480)
|
|
18
|
+
animation(opacity, from: 0, to: 1, duration: 0.8s, easing: easeOut)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
layer("subtitle") {
|
|
22
|
+
text("Built with Vidra SDK", font: "Inter", size: 32, color: #8b949e)
|
|
23
|
+
position(960, 560)
|
|
24
|
+
animation(opacity, from: 0, to: 1, duration: 0.8s, easing: easeOut)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
layer("accent") {
|
|
28
|
+
shape(rect, width: 200, height: 4, radius: 2, fill: #58a6ff)
|
|
29
|
+
position(960, 620)
|
|
30
|
+
animation(opacity, from: 0, to: 1, duration: 0.6s, easing: easeOut)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
scene("content", 4s) {
|
|
35
|
+
layer("bg2") {
|
|
36
|
+
solid(#161b22)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
layer("heading") {
|
|
40
|
+
text("Programmatic Video", font: "Inter", size: 56, color: #e6edf3)
|
|
41
|
+
position(960, 300)
|
|
42
|
+
animation(opacity, from: 0, to: 1, duration: 0.6s, easing: easeOut)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
layer("bullet1") {
|
|
46
|
+
text("TypeScript SDK", font: "Inter", size: 36, color: #58a6ff)
|
|
47
|
+
position(960, 450)
|
|
48
|
+
animation(opacity, from: 0, to: 1, duration: 0.5s, easing: easeOut)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
layer("bullet2") {
|
|
52
|
+
text("GPU-accelerated rendering", font: "Inter", size: 36, color: #58a6ff)
|
|
53
|
+
position(960, 520)
|
|
54
|
+
animation(opacity, from: 0, to: 1, duration: 0.5s, easing: easeOut)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
layer("bullet3") {
|
|
58
|
+
text("Web scenes with React & D3", font: "Inter", size: 36, color: #58a6ff)
|
|
59
|
+
position(960, 590)
|
|
60
|
+
animation(opacity, from: 0, to: 1, duration: 0.5s, easing: easeOut)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
scene("outro", 3s) {
|
|
65
|
+
layer("bg3") {
|
|
66
|
+
solid(#0d1117)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
layer("cta") {
|
|
70
|
+
text("Start building with Vidra", font: "Inter", size: 48, color: #ffffff)
|
|
71
|
+
position(960, 500)
|
|
72
|
+
animation(opacity, from: 0, to: 1, duration: 0.8s, easing: easeOut)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
layer("url") {
|
|
76
|
+
text("github.com/Sansa-Organisation/vidra", font: "Inter", size: 24, color: #8b949e)
|
|
77
|
+
position(960, 580)
|
|
78
|
+
animation(opacity, from: 0, to: 1, duration: 0.6s, easing: easeOut)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>{{PROJECT_NAME}} — Web Scene</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
8
|
+
body {
|
|
9
|
+
width: 100vw; height: 100vh;
|
|
10
|
+
background: transparent;
|
|
11
|
+
display: flex; align-items: center; justify-content: center;
|
|
12
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
}
|
|
15
|
+
.container {
|
|
16
|
+
text-align: center;
|
|
17
|
+
color: white;
|
|
18
|
+
}
|
|
19
|
+
.bar-chart {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: flex-end;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
gap: 12px;
|
|
24
|
+
height: 300px;
|
|
25
|
+
margin-top: 40px;
|
|
26
|
+
}
|
|
27
|
+
.bar {
|
|
28
|
+
width: 60px;
|
|
29
|
+
border-radius: 8px 8px 0 0;
|
|
30
|
+
transition: height 0.3s ease;
|
|
31
|
+
}
|
|
32
|
+
.label {
|
|
33
|
+
color: #8b949e;
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
margin-top: 8px;
|
|
36
|
+
}
|
|
37
|
+
h2 {
|
|
38
|
+
font-size: 28px;
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
color: #e6edf3;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
</head>
|
|
44
|
+
<body>
|
|
45
|
+
<div class="container">
|
|
46
|
+
<h2>Revenue by Quarter</h2>
|
|
47
|
+
<div class="bar-chart" id="chart"></div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<script type="module">
|
|
51
|
+
import { VidraCapture } from '@sansavision/vidra-web-capture';
|
|
52
|
+
|
|
53
|
+
const capture = new VidraCapture();
|
|
54
|
+
const data = [
|
|
55
|
+
{ label: 'Q1', value: 45, color: '#58a6ff' },
|
|
56
|
+
{ label: 'Q2', value: 72, color: '#3fb950' },
|
|
57
|
+
{ label: 'Q3', value: 58, color: '#d2a8ff' },
|
|
58
|
+
{ label: 'Q4', value: 91, color: '#f0883e' },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const chart = document.getElementById('chart');
|
|
62
|
+
|
|
63
|
+
// Create bars
|
|
64
|
+
data.forEach(d => {
|
|
65
|
+
const col = document.createElement('div');
|
|
66
|
+
col.innerHTML = `
|
|
67
|
+
<div class="bar" style="background: ${d.color}; height: 0px;"></div>
|
|
68
|
+
<div class="label">${d.label}</div>
|
|
69
|
+
`;
|
|
70
|
+
chart.appendChild(col);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const bars = chart.querySelectorAll('.bar');
|
|
74
|
+
|
|
75
|
+
function render() {
|
|
76
|
+
const { time } = capture.getState();
|
|
77
|
+
|
|
78
|
+
// Animate bars growing over 2 seconds
|
|
79
|
+
const progress = Math.min(time / 2, 1);
|
|
80
|
+
const eased = 1 - Math.pow(1 - progress, 3); // ease-out cubic
|
|
81
|
+
|
|
82
|
+
data.forEach((d, i) => {
|
|
83
|
+
const delay = i * 0.15;
|
|
84
|
+
const t = Math.max(0, Math.min((time - delay) / 1.5, 1));
|
|
85
|
+
const barEased = 1 - Math.pow(1 - t, 3);
|
|
86
|
+
bars[i].style.height = `${barEased * d.value * 3}px`;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
requestAnimationFrame(render);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
render();
|
|
93
|
+
</script>
|
|
94
|
+
</body>
|
|
95
|
+
</html>
|