scrolltube 2.1.2 → 2.1.4

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/AGENTS.md CHANGED
@@ -1,46 +1,61 @@
1
- <!-- BEGIN:scrolltube-agent-rules -->
2
- # ScrollTube Implementation Protocol
3
-
4
- This document is for AI agents tasked with implementing ScrollTube in a host application.
5
-
6
- ## šŸ¤– Agent Persona
7
- You are a ScrollTube Implementation Expert. Your goal is to transform static media into high-performance, interactive scroll experiences.
8
-
9
- ## šŸ› ļø Implementation Workflow
10
-
11
- ### Step 1: Asset Preparation (The CLI)
12
- Before writing any UI code, you MUST process the raw assets (video or images) into a ScrollTube project.
13
- ```bash
14
- npx stube create <input_path> --cloud --depth --prompt "main subject" [-s 2]
15
- ```
16
- - **Why?** This generates optimized multi-resolution frames, optional AI-tracked subject coordinates, and optional depth maps.
17
- - **Output**: A directory containing `scrolltube.json` and variant folders (`mobile/`, `desktop/`).
18
-
19
- ### Step 2: Project Architecture (React)
20
- Import the generated `scrolltube.json` and wrap your scene in the `ScrollTubeProvider`.
21
- ```tsx
22
- import project from './path/to/scrolltube.json';
23
- import { ScrollTubeProvider, ScrollTubeCanvas, SubjectLayer } from 'scrolltube/react';
24
-
25
- // Goal: 1 ScrollTubeProvider per interactive section.
26
- ```
27
-
28
- ### Step 3: Immersive Layering
29
- Use high-level components to build the scene. Avoid manual coordinate math.
30
- - **`<ScrollTubeCanvas />`**: Renders the image sequence (WebGL).
31
- - **`<SubjectLayer />`**: Pins HTML content to the moving product automatically.
32
- - **`useScrollTube()`**: Hook for custom triggers based on `progress` (0-1) or `frame`.
33
-
34
- ## šŸ“š Docs
35
- - [**Core Architecture**](docs/architecture.md): Understand the State-Snapshot Engine.
36
- - [**Asset Pipeline**](docs/asset-pipeline.md): Detailed CLI options (Smart-Crop, Variants, Step).
37
- - [**React Integration**](docs/react-integration.md): Component API reference.
38
- - [**AI Protocol**](docs/ai-integration.md): How to prompt other agents to build creative scenes for you.
39
-
40
- ## āš ļø Critical Constraints
41
- 1. **Coordination System**: ALWAYS use percentages (0-100) for Layer offsets relative to the Subject Focal Point.
42
- 2. **Performance**: Recommend `--step 2` or `--step 3` for mobile-first projects to reduce payload.
43
- 3. **Responsive**: The engine handles folder swapping (Mobile/Desktop) automatically based on viewport.
44
- 4. **Interactive**: Enable `depthEnabled` on the Canvas for 3D parallax effects if depth maps exist.
45
-
46
- <!-- END:scrolltube-agent-rules -->
1
+ <!-- BEGIN:scrolltube-agent-rules -->
2
+
3
+ ## ScrollTube Orchestration Protocol (for AI Agents)
4
+
5
+ This document defines the technical persona and workflow for AI agents tasked with implementing ScrollTube experiences.
6
+
7
+ ## šŸ¤– Agent Persona
8
+ You are a **ScrollTube Orchestration Specialist**. You move away from imperative animation math and towards declarative scene design. Your goal is to transform raw media into immersive experiences using the ScrollTube Universal SDK.
9
+
10
+ ## šŸ› ļø The Two-Phase Workflow
11
+
12
+ ### Phase A: Asset Intelligence (CLI)
13
+ Before writing UI code, you must initialize the project by processing raw media.
14
+ ```bash
15
+ npx scrolltube create <input_path> --name "my-project" --track "main subject" --cloud --depth
16
+ ```
17
+ - **Inputs**: Path to video or directory of images.
18
+ - **Why?** This generates optimized multi-resolution frames, AI-tracked coordinates, and 3D depth maps.
19
+ - **Output**: A directory with `scrolltube.json`, variants, and a backup of the source media.
20
+
21
+ ### Phase B: Immersive Implementation (React)
22
+ Import the generated JSON and build the UI. Use the provider to sync the engine with React state.
23
+
24
+ ```tsx
25
+ import project from './my-project/scrolltube.json';
26
+ import { ScrollTubeProvider, ScrollTubeCanvas, SubjectLayer } from 'scrolltube/react';
27
+
28
+ export const MyExperience = () => (
29
+ // 1. Set global scroll duration and smoothing
30
+ <ScrollTubeProvider containerHeight="300vh" scrub={0.1}>
31
+
32
+ {/* 2. Render the WebGL layer (Supports JSON Object or URL) */}
33
+ <ScrollTubeCanvas project={project} depthtilt={0.3} />
34
+
35
+ {/* 3. Pin UI to the tracked subject automatically */}
36
+ <SubjectLayer id="main" offset={{ x: 10, y: -5 }}>
37
+ <div className="info-box">
38
+ Hello From ScrollTube
39
+ </div>
40
+ </SubjectLayer>
41
+
42
+ </ScrollTubeProvider>
43
+ );
44
+ ```
45
+
46
+ ## šŸ“š Technical Protocol for Child Agents
47
+ If you are prompting another agent to build a scene, include this snippet:
48
+
49
+ > You are a ScrollTube specialist. Follow these principles:
50
+ > 1. **Engine-Aware**: Use 'ScrollTubeProvider' for state synchronization.
51
+ > 2. **Subject-Focal**: Use 'SubjectLayer' to pin UI to tracked coordinates.
52
+ > 3. **Declarative**: Use 'useScrollTube()' for progress (0-1) and frame hooks.
53
+ > 4. **Responsive**: For Mobile, place text layers ABOVE or BELOW the subject focal point. For Desktop, place them to the SIDES.
54
+
55
+ ## āš ļø Critical Constraints
56
+ 1. **Coordinates**: Use Subject-Relative offsets in 'SubjectLayer' for perfect pinning.
57
+ 2. **Sync**: Never use manual scroll listeners; the engine drives all child animations.
58
+ 3. **Performance**: Recommend `--step 10` for fast previews and `--step 1` or `2` for high-end production.
59
+ 4. **Hardware**: Enable `depthtilt` on the Canvas for 3D parallax effects if depth maps exist.
60
+
61
+ <!-- END:scrolltube-agent-rules -->
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ScrollTube is a modern animation SDK built for the era of high-performance, agent-driven development. It allows you to transform standard video or images into web assets that precisely track subjects and depth.
6
6
 
7
- [scroll.tube](https://www.scroll.tube)
7
+ [www.scroll.tube](https://www.scroll.tube)
8
8
 
9
9
  ---
10
10
 
@@ -28,7 +28,8 @@ Transform your video into a ScrollTube project from your terminal:
28
28
 
29
29
  ```bash
30
30
  # This will extract frames, track the subject, and generate optimized variants and depth maps.
31
- npx stube create "your-video.mp4" --name "my-project" --track "apple" --cloud --depth
31
+ npx scrolltube create "your-video.mp4" --name "my-project" --track "apple" --cloud --depth
32
+
32
33
  ```
33
34
 
34
35
  #### Programmatic Usage (Browser/Node)
@@ -58,7 +59,6 @@ const project = await pipeline.create({
58
59
  All you have to do now, is to drop the scrolltube.json project into your ScrollTubeProvider.
59
60
 
60
61
  #### Vanilla JS Integration
61
- For full implementation, please refer to the [Vanilla JS Example](https://github.com/aleskozelsky/scrolltube/blob/main/demos/html/index.html).
62
62
 
63
63
  [Live Demo](https://demo-html.scroll.tube)
64
64
 
@@ -73,9 +73,9 @@ For full implementation, please refer to the [Vanilla JS Example](https://github
73
73
  </script>
74
74
  ```
75
75
 
76
+ For full implementation, please refer to the [Vanilla JS Example](https://github.com/aleskozelsky/scrolltube/blob/main/demos/html/index.html).
76
77
 
77
78
  #### React Integration
78
- For full implementation, please refer to the [React Integration Example](https://github.com/aleskozelsky/scrolltube/blob/main/demos/create-next-app/src/app/page.tsx).
79
79
 
80
80
  [Live Demo](https://demo-nextjs.scroll.tube)
81
81
 
@@ -121,6 +121,8 @@ const AppleInfo = () => {
121
121
 
122
122
  ```
123
123
 
124
+ For full implementation, please refer to the [React Integration Example](https://github.com/aleskozelsky/scrolltube/blob/main/demos/create-next-app/src/app/page.tsx).
125
+
124
126
  ---
125
127
 
126
128
  ## Documentation & Guides
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const commander_1 = require("commander");
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const fs = __importStar(require("fs-extra"));
43
+ const path = __importStar(require("path"));
44
+ const child_process_1 = require("child_process");
45
+ const ffmpeg_static_1 = __importDefault(require("ffmpeg-static"));
46
+ const pipeline_1 = require("../pipeline");
47
+ const readline = __importStar(require("readline"));
48
+ require("dotenv/config");
49
+ const pkg = require('../../package.json');
50
+ /**
51
+ * MEDIA QUERY BUILDER
52
+ */
53
+ function buildVariantsFromIds(input) {
54
+ const result = [];
55
+ const orientations = ['portrait', 'landscape'];
56
+ // 1. Process each "Target" resolution
57
+ input.forEach(item => {
58
+ let res = 0;
59
+ if (typeof item === 'number')
60
+ res = item;
61
+ else if (typeof item === 'string')
62
+ res = parseInt(item);
63
+ else if (item.height)
64
+ res = item.height; // Assume height is the defining metric
65
+ if (!res || isNaN(res))
66
+ return;
67
+ orientations.forEach(orient => {
68
+ const isPortrait = orient === 'portrait';
69
+ const width = isPortrait ? res : Math.round(res * (16 / 9));
70
+ const height = isPortrait ? Math.round(res * (16 / 9)) : res;
71
+ result.push({
72
+ id: `${res}p_${orient.substring(0, 1)}`,
73
+ width,
74
+ height,
75
+ orientation: orient,
76
+ aspectRatio: isPortrait ? '9:16' : '16:9',
77
+ media: `(orientation: ${orient})` // Minimal fallback
78
+ });
79
+ });
80
+ });
81
+ // 2. Sort by height (ascending) so the engine finds the first one that fits
82
+ return result.sort((a, b) => a.height - b.height);
83
+ }
84
+ /**
85
+ * CONFIG LOADER
86
+ * Looks for scrolltube.config.js/ts in the current working directory.
87
+ */
88
+ async function loadProjectConfig() {
89
+ const possiblePaths = [
90
+ path.join(process.cwd(), 'scrolltube.cli.config.js'),
91
+ path.join(process.cwd(), 'scrolltube.cli.config.cjs'),
92
+ path.join(process.cwd(), 'scrolltube.cli.config.ts')
93
+ ];
94
+ for (const p of possiblePaths) {
95
+ if (fs.existsSync(p)) {
96
+ try {
97
+ // For simplicity in CLI we handle commonjs/esm basics
98
+ // If it's TS, it might need jiti or ts-node, but let's assume JS for now
99
+ // or use a simple dynamic import if supported.
100
+ return require(p);
101
+ }
102
+ catch (e) {
103
+ console.warn(chalk_1.default.yellow(`āš ļø Found config at ${p} but failed to load it. Skipping...`));
104
+ }
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+ /**
110
+ * Robust FFmpeg Detection
111
+ * Prioritizes bundled static binary, then system PATH.
112
+ */
113
+ function getFFmpegPath() {
114
+ // 1. Try bundled ffmpeg-static
115
+ if (ffmpeg_static_1.default)
116
+ return ffmpeg_static_1.default;
117
+ // 2. Try system PATH
118
+ try {
119
+ (0, child_process_1.execSync)('ffmpeg -version', { stdio: 'ignore' });
120
+ return 'ffmpeg';
121
+ }
122
+ catch (e) {
123
+ return null;
124
+ }
125
+ }
126
+ const program = new commander_1.Command();
127
+ program
128
+ .name('scrolltube')
129
+ .description('ScrollTube CLI - Immersive Web SDK')
130
+ .version(pkg.version);
131
+ /**
132
+ * Interactive Helper
133
+ */
134
+ async function prompt(question, defaultValue) {
135
+ const rl = readline.createInterface({
136
+ input: process.stdin,
137
+ output: process.stdout
138
+ });
139
+ return new Promise(resolve => {
140
+ rl.question(`${chalk_1.default.cyan('?')} ${question}${defaultValue ? ` (${defaultValue})` : ''}: `, (answer) => {
141
+ rl.close();
142
+ resolve(answer.trim() || defaultValue || '');
143
+ });
144
+ });
145
+ }
146
+ program
147
+ .command('create')
148
+ .description('ONE-STEP: Transform video/images into a responsive ScrollTube')
149
+ .argument('[input]', 'Path to input video or directory of images')
150
+ .option('-o, --output <dir>', 'Output directory (deprecated, use --name)')
151
+ .option('-p, --track <text>', 'Text prompt for subject tracking')
152
+ .option('-n, --name <string>', 'Name of the project')
153
+ .option('-v, --variants <string>', 'Comma-separated target resolutions (e.g. 720,1080)')
154
+ .option('-s, --step <number>', 'Process every Nth frame (default: 1)', '1')
155
+ .option('--cloud', 'Use Fal.ai for tracking and refinement')
156
+ .option('--depth', 'Generate a 3D depth map for the displacement effect (Requires --cloud)')
157
+ .action(async (inputArg, opts) => {
158
+ console.log(chalk_1.default.bold.blue('\nšŸŽžļø ScrollTube Asset Pipeline\n'));
159
+ // 0. PRE-FLIGHT CHECK
160
+ const ffmpegPath = getFFmpegPath();
161
+ if (!ffmpegPath) {
162
+ console.error(chalk_1.default.red('\nāŒ FFmpeg not found!'));
163
+ console.log(chalk_1.default.yellow('This CLI requires FFmpeg to process videos.'));
164
+ console.log('Please install it manually or ensure regular npm install was successful.');
165
+ process.exit(1);
166
+ }
167
+ const projectConfig = await loadProjectConfig();
168
+ let input = inputArg;
169
+ let track = opts.track;
170
+ let projectName = opts.name;
171
+ let useTracking = opts.cloud; // Default to cloud if flag set
172
+ let useDepth = opts.depth;
173
+ let customVariants = projectConfig?.variants || (opts.variants ? buildVariantsFromIds(opts.variants.split(',')) : null);
174
+ // 1. INPUT VALIDATION (Immediate)
175
+ while (!input || !fs.existsSync(input)) {
176
+ if (input && !fs.existsSync(input)) {
177
+ console.error(chalk_1.default.red(`\nāŒ Error: Input path "${input}" does not exist.`));
178
+ }
179
+ input = await prompt('Path to input video or directory of images');
180
+ }
181
+ // 2. PROJECT NAME & SETTINGS
182
+ if (!projectName) {
183
+ projectName = await prompt('Project name', 'scrolltube-project');
184
+ }
185
+ let step = parseInt(opts.step) || 1;
186
+ if (!inputArg) {
187
+ // 2. PROJECT SETTINGS
188
+ const stepInput = await prompt('Process every Nth frame (Step size)', '1');
189
+ step = parseInt(stepInput) || 1;
190
+ // 3. AI TRACKING & DEPTH
191
+ const trackPrompt = await prompt('AI Subject Tracking? (e.g. "red car", leave empty to skip)');
192
+ if (trackPrompt) {
193
+ useTracking = true;
194
+ track = trackPrompt;
195
+ }
196
+ const depthPrompt = await prompt('Generate 3D depth map? (y/n)', 'n');
197
+ useDepth = depthPrompt.toLowerCase().startsWith('y');
198
+ }
199
+ // AI Tracking logic preserved in CLI wrapper...
200
+ // ...
201
+ const pipeline = new pipeline_1.AssetPipeline({
202
+ apiKey: process.env.FAL_KEY,
203
+ onProgress: (p) => {
204
+ // You could add a progress bar here
205
+ }
206
+ });
207
+ try {
208
+ await pipeline.create({
209
+ input: input,
210
+ name: projectName,
211
+ track: useTracking ? track : undefined,
212
+ depth: useDepth,
213
+ variants: customVariants || [720, 1080],
214
+ step: step
215
+ });
216
+ console.log(chalk_1.default.bold.green(`\nāœ… Project Created Successfully!`));
217
+ console.log(chalk_1.default.white(`šŸ“ Output: ${projectName}`));
218
+ console.log(chalk_1.default.white(`šŸ“œ Config: scrolltube.json`));
219
+ console.log(chalk_1.default.cyan(`\nNext: Import the .json into your <ScrollTubeCanvas project={...} />\n`));
220
+ }
221
+ catch (err) {
222
+ console.error(chalk_1.default.red(`\nāŒ Error during pipeline: ${err.message}`));
223
+ process.exit(1);
224
+ }
225
+ });
226
+ // NEW UPDATE COMMAND
227
+ program
228
+ .command('update')
229
+ .description('Rerun extraction and tracking on an existing project')
230
+ .argument('<dir>', 'Project directory')
231
+ .option('-p, --track <text>', 'Additional subject to track')
232
+ .action(async (dir, opts) => {
233
+ console.log(chalk_1.default.bold.yellow('\nā™»ļø ScrollTube Update Pipeline\n'));
234
+ const projectPath = path.resolve(dir);
235
+ const configPath = path.join(projectPath, 'scrolltube.json');
236
+ if (!fs.existsSync(configPath)) {
237
+ console.error(chalk_1.default.red('āŒ Not a valid ScrollTube project directory (missing scrolltube.json).'));
238
+ process.exit(1);
239
+ }
240
+ const config = await fs.readJson(configPath);
241
+ if (config.version !== pkg.version) {
242
+ console.warn(chalk_1.default.yellow(`āš ļø Version Mismatch: Project is ${config.version}, CLI is ${pkg.version}`));
243
+ }
244
+ console.log(chalk_1.default.red('āš ļø UNDER CONSTRUCTION'));
245
+ console.log(chalk_1.default.yellow('The "update" command is currently being refactored for the Universal Pipeline.'));
246
+ console.log(chalk_1.default.dim('Please use "scft create" to regenerate your project for now.\n'));
247
+ process.exit(0);
248
+ });
249
+ program.parse(process.argv);
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';