hyperframes 0.2.2 → 0.2.3-alpha.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/cli.js +8651 -7892
- package/dist/docker/Dockerfile.render +33 -0
- package/dist/hyperframe-runtime.js +5 -5
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +5 -5
- package/dist/skills/gsap/SKILL.md +222 -0
- package/dist/skills/gsap/references/effects.md +304 -0
- package/dist/skills/gsap/references/frameworks.md +56 -0
- package/dist/skills/gsap/references/plugins.md +194 -0
- package/dist/skills/gsap/references/react.md +80 -0
- package/dist/skills/gsap/references/scrolltrigger.md +147 -0
- package/dist/skills/gsap/references/utils.md +91 -0
- package/dist/skills/gsap/scripts/extract-audio-data.py +188 -0
- package/dist/skills/hyperframes/SKILL.md +175 -0
- package/dist/skills/hyperframes/references/audio-reactive.md +76 -0
- package/dist/skills/hyperframes/references/captions.md +132 -0
- package/dist/skills/hyperframes/references/css-patterns.md +371 -0
- package/dist/skills/hyperframes/references/examples.md +146 -0
- package/dist/skills/hyperframes/references/marker-highlight.md +158 -0
- package/dist/skills/hyperframes/references/transitions/catalog.md +132 -0
- package/dist/skills/hyperframes/references/transitions/css-3d.md +12 -0
- package/dist/skills/hyperframes/references/transitions/css-blur.md +51 -0
- package/dist/skills/hyperframes/references/transitions/css-cover.md +43 -0
- package/dist/skills/hyperframes/references/transitions/css-destruction.md +95 -0
- package/dist/skills/hyperframes/references/transitions/css-dissolve.md +66 -0
- package/dist/skills/hyperframes/references/transitions/css-distortion.md +45 -0
- package/dist/skills/hyperframes/references/transitions/css-grid.md +10 -0
- package/dist/skills/hyperframes/references/transitions/css-light.md +49 -0
- package/dist/skills/hyperframes/references/transitions/css-mechanical.md +30 -0
- package/dist/skills/hyperframes/references/transitions/css-other.md +36 -0
- package/dist/skills/hyperframes/references/transitions/css-push.md +41 -0
- package/dist/skills/hyperframes/references/transitions/css-radial.md +37 -0
- package/dist/skills/hyperframes/references/transitions/css-scale.md +24 -0
- package/dist/skills/hyperframes/references/transitions/shader-setup.md +463 -0
- package/dist/skills/hyperframes/references/transitions/shader-transitions.md +329 -0
- package/dist/skills/hyperframes/references/transitions.md +96 -0
- package/dist/skills/hyperframes/references/tts.md +56 -0
- package/dist/skills/hyperframes-cli/SKILL.md +114 -0
- package/dist/studio/assets/{index-DA_l-VKo.js → index-B0VCLOXQ.js} +15 -15
- package/dist/studio/index.html +1 -1
- package/dist/templates/_shared/CLAUDE.md +5 -7
- package/dist/templates/blank/index.html +8 -10
- package/package.json +2 -4
- package/dist/skills/hyperframes-captions/SKILL.md +0 -212
- package/dist/skills/hyperframes-compose/SKILL.md +0 -155
- package/dist/skills/hyperframes-tts/SKILL.md +0 -79
- package/dist/templates/blank/compositions/captions.html +0 -95
- /package/dist/skills/{hyperframes-compose → hyperframes}/data-in-motion.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/house-style.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/bold-energetic.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/clean-corporate.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/dark-premium.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/jewel-rich.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/monochrome.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/nature-earth.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/neon-electric.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/pastel-soft.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/warm-editorial.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/patterns.md +0 -0
- /package/dist/skills/{hyperframes-captions → hyperframes/references}/dynamic-techniques.md +0 -0
- /package/dist/skills/{hyperframes-captions → hyperframes/references}/transcript-guide.md +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
## Scale / Zoom
|
|
2
|
+
|
|
3
|
+
### Zoom Through
|
|
4
|
+
|
|
5
|
+
Old zooms past camera + blurs, new zooms in from behind.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
tl.to(old, { scale: 2.5, opacity: 0, filter: "blur(8px)", duration: 0.4, ease: "power3.in" }, T);
|
|
9
|
+
tl.fromTo(new,
|
|
10
|
+
{ scale: 0.5, opacity: 0, filter: "blur(8px)" },
|
|
11
|
+
{ scale: 1, opacity: 1, filter: "blur(0px)", duration: 0.4, ease: "power3.out" }, T + 0.15);
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Zoom Out
|
|
15
|
+
|
|
16
|
+
Old shrinks away, new was behind it. Needs z-index management.
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
tl.set(new, { opacity: 1, zIndex: 1 }, T);
|
|
20
|
+
tl.set(old, { zIndex: 10, transformOrigin: "50% 50%" }, T);
|
|
21
|
+
tl.to(old, { scale: 0.3, opacity: 0, duration: 0.4, ease: "power3.in" }, T);
|
|
22
|
+
tl.set(old, { zIndex: "auto" }, T + 0.4);
|
|
23
|
+
tl.set(new, { zIndex: "auto" }, T + 0.4);
|
|
24
|
+
```
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# Shader Transition Setup
|
|
2
|
+
|
|
3
|
+
Complete boilerplate for WebGL shader transitions in HyperFrames. Read this when implementing a shader transition — copy the setup code, then plug in the fragment shader from the catalog.
|
|
4
|
+
|
|
5
|
+
## HTML
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<canvas
|
|
9
|
+
id="gl-canvas"
|
|
10
|
+
width="1920"
|
|
11
|
+
height="1080"
|
|
12
|
+
style="position:absolute;top:0;left:0;width:1920px;height:1080px;z-index:100;pointer-events:none;display:none;"
|
|
13
|
+
>
|
|
14
|
+
</canvas>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## WebGL Init + Scene Capture
|
|
18
|
+
|
|
19
|
+
Handles images, video, shapes, and text. Supports `object-fit: cover` on images and live video re-upload during transitions.
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
var sceneTextures = {};
|
|
23
|
+
var sceneHasVideo = {}; // tracks which scenes have live video
|
|
24
|
+
var glCanvas = document.getElementById("gl-canvas");
|
|
25
|
+
var gl = glCanvas.getContext("webgl", { preserveDrawingBuffer: true });
|
|
26
|
+
gl.viewport(0, 0, 1920, 1080);
|
|
27
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
28
|
+
|
|
29
|
+
// Wait for all media to load before capturing
|
|
30
|
+
function waitForMedia() {
|
|
31
|
+
return new Promise(function (resolve) {
|
|
32
|
+
var promises = [];
|
|
33
|
+
document.querySelectorAll("img").forEach(function (img) {
|
|
34
|
+
if (!img.complete)
|
|
35
|
+
promises.push(
|
|
36
|
+
new Promise(function (r) {
|
|
37
|
+
img.onload = r;
|
|
38
|
+
img.onerror = r;
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
document.querySelectorAll("video").forEach(function (vid) {
|
|
43
|
+
if (vid.readyState < 2)
|
|
44
|
+
promises.push(
|
|
45
|
+
new Promise(function (r) {
|
|
46
|
+
vid.addEventListener("loadeddata", r, { once: true });
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
Promise.all(promises).then(resolve);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function captureScene(sceneId) {
|
|
55
|
+
return new Promise(function (resolve) {
|
|
56
|
+
var scene = document.getElementById(sceneId);
|
|
57
|
+
var origOpacity = scene.style.opacity;
|
|
58
|
+
var origZ = scene.style.zIndex;
|
|
59
|
+
scene.style.opacity = "1";
|
|
60
|
+
scene.style.zIndex = "999";
|
|
61
|
+
|
|
62
|
+
if (scene.querySelector("video")) sceneHasVideo[sceneId] = scene.querySelector("video");
|
|
63
|
+
|
|
64
|
+
requestAnimationFrame(function () {
|
|
65
|
+
requestAnimationFrame(function () {
|
|
66
|
+
var c = document.createElement("canvas");
|
|
67
|
+
c.width = 1920;
|
|
68
|
+
c.height = 1080;
|
|
69
|
+
var ctx = c.getContext("2d");
|
|
70
|
+
|
|
71
|
+
ctx.fillStyle = window.getComputedStyle(scene).backgroundColor;
|
|
72
|
+
ctx.fillRect(0, 0, 1920, 1080);
|
|
73
|
+
|
|
74
|
+
var sr = scene.getBoundingClientRect();
|
|
75
|
+
var els = scene.querySelectorAll("*");
|
|
76
|
+
for (var i = 0; i < els.length; i++) {
|
|
77
|
+
var el = els[i];
|
|
78
|
+
var cs = window.getComputedStyle(el);
|
|
79
|
+
if (cs.display === "none" || cs.visibility === "hidden") continue;
|
|
80
|
+
var r = el.getBoundingClientRect();
|
|
81
|
+
if (r.width < 1 || r.height < 1) continue;
|
|
82
|
+
var x = r.left - sr.left,
|
|
83
|
+
y = r.top - sr.top,
|
|
84
|
+
w = r.width,
|
|
85
|
+
h = r.height;
|
|
86
|
+
|
|
87
|
+
ctx.save();
|
|
88
|
+
ctx.globalAlpha = parseFloat(cs.opacity) || 1;
|
|
89
|
+
|
|
90
|
+
// <img> elements (with object-fit: cover support)
|
|
91
|
+
if (el.tagName === "IMG" && el.complete && el.naturalWidth > 0) {
|
|
92
|
+
try {
|
|
93
|
+
if (cs.objectFit === "cover") {
|
|
94
|
+
var iR = el.naturalWidth / el.naturalHeight,
|
|
95
|
+
bR = w / h;
|
|
96
|
+
var sx = 0,
|
|
97
|
+
sy = 0,
|
|
98
|
+
sw = el.naturalWidth,
|
|
99
|
+
sh = el.naturalHeight;
|
|
100
|
+
if (iR > bR) {
|
|
101
|
+
sw = sh * bR;
|
|
102
|
+
sx = (el.naturalWidth - sw) / 2;
|
|
103
|
+
} else {
|
|
104
|
+
sh = sw / bR;
|
|
105
|
+
sy = (el.naturalHeight - sh) / 2;
|
|
106
|
+
}
|
|
107
|
+
ctx.drawImage(el, sx, sy, sw, sh, x, y, w, h);
|
|
108
|
+
} else {
|
|
109
|
+
ctx.drawImage(el, x, y, w, h);
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {}
|
|
112
|
+
ctx.restore();
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// <video> elements (grabs current frame)
|
|
117
|
+
if (el.tagName === "VIDEO" && el.readyState >= 2) {
|
|
118
|
+
try {
|
|
119
|
+
var vR = el.videoWidth / el.videoHeight,
|
|
120
|
+
bR2 = w / h;
|
|
121
|
+
var vx = 0,
|
|
122
|
+
vy = 0,
|
|
123
|
+
vw = el.videoWidth,
|
|
124
|
+
vh = el.videoHeight;
|
|
125
|
+
if (vR > bR2) {
|
|
126
|
+
vw = vh * bR2;
|
|
127
|
+
vx = (el.videoWidth - vw) / 2;
|
|
128
|
+
} else {
|
|
129
|
+
vh = vw / bR2;
|
|
130
|
+
vy = (el.videoHeight - vh) / 2;
|
|
131
|
+
}
|
|
132
|
+
ctx.drawImage(el, vx, vy, vw, vh, x, y, w, h);
|
|
133
|
+
} catch (e) {}
|
|
134
|
+
ctx.restore();
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Background color
|
|
139
|
+
var bg = cs.backgroundColor;
|
|
140
|
+
if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
|
141
|
+
ctx.fillStyle = bg;
|
|
142
|
+
var br = parseInt(cs.borderRadius) || 0;
|
|
143
|
+
if (br >= Math.min(w, h) / 2 - 1 && Math.abs(w - h) < 4) {
|
|
144
|
+
ctx.beginPath();
|
|
145
|
+
ctx.arc(x + w / 2, y + h / 2, Math.min(w, h) / 2, 0, Math.PI * 2);
|
|
146
|
+
ctx.fill();
|
|
147
|
+
} else if (br > 0) {
|
|
148
|
+
ctx.beginPath();
|
|
149
|
+
ctx.moveTo(x + br, y);
|
|
150
|
+
ctx.arcTo(x + w, y, x + w, y + h, br);
|
|
151
|
+
ctx.arcTo(x + w, y + h, x, y + h, br);
|
|
152
|
+
ctx.arcTo(x, y + h, x, y, br);
|
|
153
|
+
ctx.arcTo(x, y, x + w, y, br);
|
|
154
|
+
ctx.closePath();
|
|
155
|
+
ctx.fill();
|
|
156
|
+
} else {
|
|
157
|
+
ctx.fillRect(x, y, w, h);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Text (leaf nodes only, with text-shadow)
|
|
162
|
+
var hasChildEls = el.querySelector("div, span, img, video");
|
|
163
|
+
var text = "";
|
|
164
|
+
for (var j = 0; j < el.childNodes.length; j++)
|
|
165
|
+
if (el.childNodes[j].nodeType === 3) text += el.childNodes[j].textContent;
|
|
166
|
+
text = text.trim();
|
|
167
|
+
if (text && !hasChildEls) {
|
|
168
|
+
ctx.font = cs.fontWeight + " " + cs.fontSize + " " + cs.fontFamily;
|
|
169
|
+
ctx.fillStyle = cs.color;
|
|
170
|
+
if (cs.letterSpacing && cs.letterSpacing !== "normal")
|
|
171
|
+
ctx.letterSpacing = cs.letterSpacing;
|
|
172
|
+
var shadow = cs.textShadow;
|
|
173
|
+
if (shadow && shadow !== "none") {
|
|
174
|
+
var sp = shadow.match(/rgba?\([^)]+\)\s+(-?\d+)px\s+(-?\d+)px\s+(-?\d+)px/);
|
|
175
|
+
if (sp) {
|
|
176
|
+
ctx.shadowColor = shadow.match(/rgba?\([^)]+\)/)[0];
|
|
177
|
+
ctx.shadowOffsetX = parseFloat(sp[1]);
|
|
178
|
+
ctx.shadowOffsetY = parseFloat(sp[2]);
|
|
179
|
+
ctx.shadowBlur = parseFloat(sp[3]);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (cs.textAlign === "center" || w > 1800) {
|
|
183
|
+
ctx.textAlign = "center";
|
|
184
|
+
ctx.textBaseline = "middle";
|
|
185
|
+
ctx.fillText(text, x + w / 2, y + h / 2);
|
|
186
|
+
} else {
|
|
187
|
+
ctx.textAlign = "left";
|
|
188
|
+
ctx.textBaseline = "middle";
|
|
189
|
+
ctx.fillText(text, x, y + h / 2);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
ctx.restore();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
scene.style.opacity = origOpacity;
|
|
196
|
+
scene.style.zIndex = origZ;
|
|
197
|
+
|
|
198
|
+
var tex = gl.createTexture();
|
|
199
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
200
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
201
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
202
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
203
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
204
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
|
205
|
+
sceneTextures[sceneId] = tex;
|
|
206
|
+
resolve();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Re-capture video scenes every frame (call in updateTrans and during holds)
|
|
213
|
+
function recaptureVideoScene(sceneId) {
|
|
214
|
+
var video = sceneHasVideo[sceneId];
|
|
215
|
+
if (!video || video.readyState < 2) return;
|
|
216
|
+
var scene = document.getElementById(sceneId);
|
|
217
|
+
var c = document.createElement("canvas");
|
|
218
|
+
c.width = 1920;
|
|
219
|
+
c.height = 1080;
|
|
220
|
+
var ctx = c.getContext("2d");
|
|
221
|
+
ctx.fillStyle = window.getComputedStyle(scene).backgroundColor;
|
|
222
|
+
ctx.fillRect(0, 0, 1920, 1080);
|
|
223
|
+
var sr = scene.getBoundingClientRect();
|
|
224
|
+
var els = scene.querySelectorAll("*");
|
|
225
|
+
for (var i = 0; i < els.length; i++) {
|
|
226
|
+
var el = els[i],
|
|
227
|
+
cs = window.getComputedStyle(el);
|
|
228
|
+
if (cs.display === "none") continue;
|
|
229
|
+
var r = el.getBoundingClientRect();
|
|
230
|
+
if (r.width < 1) continue;
|
|
231
|
+
var x = r.left - sr.left,
|
|
232
|
+
y = r.top - sr.top,
|
|
233
|
+
w = r.width,
|
|
234
|
+
h = r.height;
|
|
235
|
+
ctx.save();
|
|
236
|
+
ctx.globalAlpha = parseFloat(cs.opacity) || 1;
|
|
237
|
+
if (el.tagName === "VIDEO" && el.readyState >= 2) {
|
|
238
|
+
try {
|
|
239
|
+
var vR = el.videoWidth / el.videoHeight,
|
|
240
|
+
bR = w / h;
|
|
241
|
+
var sx = 0,
|
|
242
|
+
sy = 0,
|
|
243
|
+
sw = el.videoWidth,
|
|
244
|
+
sh = el.videoHeight;
|
|
245
|
+
if (vR > bR) {
|
|
246
|
+
sw = sh * bR;
|
|
247
|
+
sx = (el.videoWidth - sw) / 2;
|
|
248
|
+
} else {
|
|
249
|
+
sh = sw / bR;
|
|
250
|
+
sy = (el.videoHeight - sh) / 2;
|
|
251
|
+
}
|
|
252
|
+
ctx.drawImage(el, sx, sy, sw, sh, x, y, w, h);
|
|
253
|
+
} catch (e) {}
|
|
254
|
+
} else if (el.tagName === "IMG" && el.complete) {
|
|
255
|
+
try {
|
|
256
|
+
ctx.drawImage(el, x, y, w, h);
|
|
257
|
+
} catch (e) {}
|
|
258
|
+
} else {
|
|
259
|
+
var bg = cs.backgroundColor;
|
|
260
|
+
if (bg && bg !== "rgba(0, 0, 0, 0)") {
|
|
261
|
+
ctx.fillStyle = bg;
|
|
262
|
+
ctx.fillRect(x, y, w, h);
|
|
263
|
+
}
|
|
264
|
+
var txt = "";
|
|
265
|
+
for (var j = 0; j < el.childNodes.length; j++)
|
|
266
|
+
if (el.childNodes[j].nodeType === 3) txt += el.childNodes[j].textContent;
|
|
267
|
+
txt = txt.trim();
|
|
268
|
+
if (txt && !el.querySelector("div,span,img,video")) {
|
|
269
|
+
ctx.font = cs.fontWeight + " " + cs.fontSize + " " + cs.fontFamily;
|
|
270
|
+
ctx.fillStyle = cs.color;
|
|
271
|
+
ctx.textAlign = "left";
|
|
272
|
+
ctx.textBaseline = "middle";
|
|
273
|
+
ctx.fillText(txt, x, y + h / 2);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
ctx.restore();
|
|
277
|
+
}
|
|
278
|
+
gl.bindTexture(gl.TEXTURE_2D, sceneTextures[sceneId]);
|
|
279
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Shader Compilation + Shared Constants
|
|
284
|
+
|
|
285
|
+
```js
|
|
286
|
+
var vertSrc =
|
|
287
|
+
"attribute vec2 a_pos; varying vec2 v_uv; void main(){" +
|
|
288
|
+
"v_uv=a_pos*0.5+0.5; v_uv.y=1.0-v_uv.y; gl_Position=vec4(a_pos,0,1);}";
|
|
289
|
+
|
|
290
|
+
var quadBuf = gl.createBuffer();
|
|
291
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
|
292
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
|
|
293
|
+
|
|
294
|
+
function compileShader(src, type) {
|
|
295
|
+
var s = gl.createShader(type);
|
|
296
|
+
gl.shaderSource(s, src);
|
|
297
|
+
gl.compileShader(s);
|
|
298
|
+
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS))
|
|
299
|
+
console.error("Shader:", gl.getShaderInfoLog(s));
|
|
300
|
+
return s;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function mkProg(fragSrc) {
|
|
304
|
+
var p = gl.createProgram();
|
|
305
|
+
gl.attachShader(p, compileShader(vertSrc, gl.VERTEX_SHADER));
|
|
306
|
+
gl.attachShader(p, compileShader(fragSrc, gl.FRAGMENT_SHADER));
|
|
307
|
+
gl.linkProgram(p);
|
|
308
|
+
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) console.error("Link:", gl.getProgramInfoLog(p));
|
|
309
|
+
return p;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Shared uniform header — every fragment shader starts with this
|
|
313
|
+
var H =
|
|
314
|
+
"precision mediump float;" +
|
|
315
|
+
"varying vec2 v_uv;" +
|
|
316
|
+
"uniform sampler2D u_from, u_to;" +
|
|
317
|
+
"uniform float u_progress;" +
|
|
318
|
+
"uniform vec2 u_resolution;\n";
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Noise Libraries
|
|
322
|
+
|
|
323
|
+
Include only what each shader needs. Do NOT include multiple libraries that redefine `hash()` in the same shader.
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
// Quintic C2 noise + inter-octave rotation FBM
|
|
327
|
+
var NQ =
|
|
328
|
+
"float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}" +
|
|
329
|
+
"float vnoise(vec2 p){vec2 i=floor(p),f=fract(p);" +
|
|
330
|
+
"f=f*f*f*(f*(f*6.-15.)+10.);" + // quintic interpolation — C2 continuous
|
|
331
|
+
"return mix(mix(hash(i),hash(i+vec2(1,0)),f.x)," +
|
|
332
|
+
"mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),f.x),f.y);}" +
|
|
333
|
+
"float fbm(vec2 p){float v=0.,a=.5;" +
|
|
334
|
+
"mat2 R=mat2(.8,.6,-.6,.8);" + // inter-octave rotation (~37deg)
|
|
335
|
+
"for(int i=0;i<5;i++){v+=a*vnoise(p);p=R*p*2.02;a*=.5;}return v;}";
|
|
336
|
+
|
|
337
|
+
// Noise with analytical derivatives (quintic) + erosion FBM
|
|
338
|
+
// Use for transitions that need gradient-based edge lighting
|
|
339
|
+
var ND =
|
|
340
|
+
"float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}" +
|
|
341
|
+
"vec3 noised(vec2 p){vec2 i=floor(p),f=fract(p);" +
|
|
342
|
+
"vec2 u=f*f*f*(f*(f*6.-15.)+10.),du=30.*f*f*(f*(f-2.)+1.);" +
|
|
343
|
+
"float a=hash(i),b=hash(i+vec2(1,0)),c=hash(i+vec2(0,1)),d=hash(i+vec2(1,1));" +
|
|
344
|
+
"return vec3(a+(b-a)*u.x+(c-a)*u.y+(a-b-c+d)*u.x*u.y," +
|
|
345
|
+
"du*vec2(b-a+(a-b-c+d)*u.y,c-a+(a-b-c+d)*u.x));}" +
|
|
346
|
+
"float erosionFBM(vec2 p){float v=0.,a=.5;vec2 d=vec2(0);mat2 R=mat2(.8,.6,-.6,.8);" +
|
|
347
|
+
"for(int i=0;i<6;i++){vec3 n=noised(p);d+=n.yz;v+=a*n.x/(1.+dot(d,d));p=R*p*2.02;a*=.5;}return v;}";
|
|
348
|
+
|
|
349
|
+
// Cosine palette: a + b*cos(2pi(c*t + d))
|
|
350
|
+
var CP = "vec3 palette(float t,vec3 a,vec3 b,vec3 c,vec3 d){" + "return a+b*cos(6.2832*(c*t+d));}";
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Render + State Machine
|
|
354
|
+
|
|
355
|
+
```js
|
|
356
|
+
function renderShader(prog, texFrom, texTo, progress) {
|
|
357
|
+
gl.useProgram(prog);
|
|
358
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
359
|
+
gl.bindTexture(gl.TEXTURE_2D, texFrom);
|
|
360
|
+
gl.uniform1i(gl.getUniformLocation(prog, "u_from"), 0);
|
|
361
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
362
|
+
gl.bindTexture(gl.TEXTURE_2D, texTo);
|
|
363
|
+
gl.uniform1i(gl.getUniformLocation(prog, "u_to"), 1);
|
|
364
|
+
gl.uniform1f(gl.getUniformLocation(prog, "u_progress"), progress);
|
|
365
|
+
gl.uniform2f(gl.getUniformLocation(prog, "u_resolution"), 1920, 1080);
|
|
366
|
+
var pos = gl.getAttribLocation(prog, "a_pos");
|
|
367
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
|
368
|
+
gl.enableVertexAttribArray(pos);
|
|
369
|
+
gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);
|
|
370
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
var progPass = mkProg(H + "void main(){gl_FragColor=texture2D(u_from,v_uv);}");
|
|
374
|
+
|
|
375
|
+
var trans = {
|
|
376
|
+
active: false,
|
|
377
|
+
prog: null,
|
|
378
|
+
fromId: null,
|
|
379
|
+
toId: null,
|
|
380
|
+
progress: 0,
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
function beginTrans(prog, fromId, toId) {
|
|
384
|
+
trans.prog = prog;
|
|
385
|
+
trans.fromId = fromId;
|
|
386
|
+
trans.toId = toId;
|
|
387
|
+
trans.progress = 0;
|
|
388
|
+
trans.active = true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function updateTrans() {
|
|
392
|
+
if (!trans.active) return;
|
|
393
|
+
// Re-capture video scenes every frame during transition
|
|
394
|
+
if (sceneHasVideo[trans.fromId]) recaptureVideoScene(trans.fromId);
|
|
395
|
+
if (sceneHasVideo[trans.toId]) recaptureVideoScene(trans.toId);
|
|
396
|
+
renderShader(trans.prog, sceneTextures[trans.fromId], sceneTextures[trans.toId], trans.progress);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function endTrans(showId) {
|
|
400
|
+
trans.active = false;
|
|
401
|
+
renderShader(progPass, sceneTextures[showId], sceneTextures[showId], 0);
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## GSAP Timeline Integration
|
|
406
|
+
|
|
407
|
+
```js
|
|
408
|
+
// Wait for media, start videos, capture all scenes, then build timeline
|
|
409
|
+
var sceneIds = ["scene1", "scene2" /* ... */];
|
|
410
|
+
waitForMedia()
|
|
411
|
+
.then(function () {
|
|
412
|
+
// Start any background videos (muted)
|
|
413
|
+
document.querySelectorAll("video").forEach(function (v) {
|
|
414
|
+
v.play();
|
|
415
|
+
});
|
|
416
|
+
return Promise.all(sceneIds.map(captureScene));
|
|
417
|
+
})
|
|
418
|
+
.then(function () {
|
|
419
|
+
glCanvas.style.display = "block";
|
|
420
|
+
renderShader(progPass, sceneTextures["scene1"], sceneTextures["scene1"], 0);
|
|
421
|
+
document.querySelectorAll(".scene").forEach(function (s) {
|
|
422
|
+
s.style.opacity = "0";
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
var tl = gsap.timeline({
|
|
426
|
+
paused: true,
|
|
427
|
+
onUpdate: function () {
|
|
428
|
+
updateTrans();
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// For each transition:
|
|
433
|
+
tl.call(
|
|
434
|
+
function () {
|
|
435
|
+
beginTrans(myShaderProg, "scene1", "scene2");
|
|
436
|
+
},
|
|
437
|
+
null,
|
|
438
|
+
T,
|
|
439
|
+
);
|
|
440
|
+
var tw = { p: 0 };
|
|
441
|
+
tl.to(
|
|
442
|
+
tw,
|
|
443
|
+
{
|
|
444
|
+
p: 1,
|
|
445
|
+
duration: DUR,
|
|
446
|
+
ease: "power2.inOut",
|
|
447
|
+
onUpdate: function () {
|
|
448
|
+
trans.progress = tw.p;
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
T,
|
|
452
|
+
);
|
|
453
|
+
tl.call(
|
|
454
|
+
function () {
|
|
455
|
+
endTrans("scene2");
|
|
456
|
+
},
|
|
457
|
+
null,
|
|
458
|
+
T + DUR,
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
window.__timelines["main"] = tl;
|
|
462
|
+
});
|
|
463
|
+
```
|