@supersoniks/concorde 4.9.97-visual-stack.0 → 4.9.98-visual-stack.1
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/build-infos.json +1 -1
- package/concorde-3d.bundle.js +201 -152
- package/concorde-3d.es.js +14621 -13823
- package/concorde-core.bundle.js +1 -1
- package/concorde-core.es.js +1 -1
- package/concorde-hugging-face-infer.bundle.js +1 -1
- package/concorde-hugging-face-infer.es.js +1 -1
- package/concorde-shader.bundle.js +31 -20
- package/concorde-shader.es.js +3236 -2865
- package/concorde-webgpu.bundle.js +43 -32
- package/concorde-webgpu.es.js +2977 -2618
- package/dist/concorde-3d.bundle.js +201 -152
- package/dist/concorde-3d.es.js +14621 -13823
- package/dist/concorde-core.bundle.js +1 -1
- package/dist/concorde-core.es.js +1 -1
- package/dist/concorde-hugging-face-infer.bundle.js +1 -1
- package/dist/concorde-hugging-face-infer.es.js +1 -1
- package/dist/concorde-shader.bundle.js +31 -20
- package/dist/concorde-shader.es.js +3236 -2865
- package/dist/concorde-webgpu.bundle.js +43 -32
- package/dist/concorde-webgpu.es.js +2977 -2618
- package/dist/js/core/components/functional/3d/3d.js +100 -2
- package/dist/js/core/components/functional/3d/3d.js.map +1 -1
- package/dist/js/core/components/functional/3d/constants.js +3 -0
- package/dist/js/core/components/functional/3d/constants.js.map +1 -1
- package/dist/js/core/components/functional/3d/ensure.js +6 -3
- package/dist/js/core/components/functional/3d/ensure.js.map +1 -1
- package/dist/js/core/components/functional/3d/form-params.js +2 -0
- package/dist/js/core/components/functional/3d/form-params.js.map +1 -1
- package/dist/js/core/components/functional/3d/frame-source.js +22 -0
- package/dist/js/core/components/functional/3d/frame-source.js.map +1 -0
- package/dist/js/core/components/functional/3d/life.js +2 -1
- package/dist/js/core/components/functional/3d/life.js.map +1 -1
- package/dist/js/core/components/functional/3d/out-publish.js +3 -2
- package/dist/js/core/components/functional/3d/out-publish.js.map +1 -1
- package/dist/js/core/components/functional/3d/playback.js +6 -1
- package/dist/js/core/components/functional/3d/playback.js.map +1 -1
- package/dist/js/core/components/functional/3d/post-bind.js +207 -0
- package/dist/js/core/components/functional/3d/post-bind.js.map +1 -0
- package/dist/js/core/components/functional/3d/post-channels.js +39 -0
- package/dist/js/core/components/functional/3d/post-channels.js.map +1 -0
- package/dist/js/core/components/functional/3d/post-fx.js +495 -0
- package/dist/js/core/components/functional/3d/post-fx.js.map +1 -0
- package/dist/js/core/components/functional/3d/props.js +142 -0
- package/dist/js/core/components/functional/3d/props.js.map +1 -1
- package/dist/js/core/components/functional/3d/reload.js +5 -0
- package/dist/js/core/components/functional/3d/reload.js.map +1 -1
- package/dist/js/core/components/functional/3d/runtime.js +80 -3
- package/dist/js/core/components/functional/3d/runtime.js.map +1 -1
- package/dist/js/core/components/functional/3d/scene-runtime.js +1 -1
- package/dist/js/core/components/functional/3d/scene-runtime.js.map +1 -1
- package/dist/js/core/components/functional/3d/tune.js +21 -0
- package/dist/js/core/components/functional/3d/tune.js.map +1 -1
- package/dist/js/core/components/functional/3d/updated.js +13 -1
- package/dist/js/core/components/functional/3d/updated.js.map +1 -1
- package/dist/js/core/components/functional/shader/gl-runtime.js +280 -31
- package/dist/js/core/components/functional/shader/gl-runtime.js.map +1 -1
- package/dist/js/core/components/functional/shader/shader.js +207 -61
- package/dist/js/core/components/functional/shader/shader.js.map +1 -1
- package/dist/js/core/components/functional/shader/shadertoy-prelude.js +39 -5
- package/dist/js/core/components/functional/shader/shadertoy-prelude.js.map +1 -1
- package/dist/js/core/components/functional/shader/types.js +58 -7
- package/dist/js/core/components/functional/shader/types.js.map +1 -1
- package/dist/js/core/components/functional/webgpu/gl-fallback.js +27 -6
- package/dist/js/core/components/functional/webgpu/gl-fallback.js.map +1 -1
- package/dist/js/core/components/functional/webgpu/gpu-runtime.js +115 -13
- package/dist/js/core/components/functional/webgpu/gpu-runtime.js.map +1 -1
- package/dist/js/core/components/functional/webgpu/helpers.js +8 -2
- package/dist/js/core/components/functional/webgpu/helpers.js.map +1 -1
- package/dist/js/core/components/functional/webgpu/webgpu.js +43 -1
- package/dist/js/core/components/functional/webgpu/webgpu.js.map +1 -1
- package/dist/js/core/utils/mediaRef.js +86 -0
- package/dist/js/core/utils/mediaRef.js.map +1 -1
- package/dist/types/core/components/functional/3d/3d.d.ts +31 -3
- package/dist/types/core/components/functional/3d/3d.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/constants.d.ts +1 -1
- package/dist/types/core/components/functional/3d/constants.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/ensure.d.ts +3 -0
- package/dist/types/core/components/functional/3d/ensure.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/form-params.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/frame-source.d.ts +23 -0
- package/dist/types/core/components/functional/3d/frame-source.d.ts.map +1 -0
- package/dist/types/core/components/functional/3d/life.d.ts +2 -0
- package/dist/types/core/components/functional/3d/life.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/out-publish.d.ts +1 -0
- package/dist/types/core/components/functional/3d/out-publish.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/playback.d.ts +9 -0
- package/dist/types/core/components/functional/3d/playback.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/post-bind.d.ts +52 -0
- package/dist/types/core/components/functional/3d/post-bind.d.ts.map +1 -0
- package/dist/types/core/components/functional/3d/post-channels.d.ts +40 -0
- package/dist/types/core/components/functional/3d/post-channels.d.ts.map +1 -0
- package/dist/types/core/components/functional/3d/post-fx.d.ts +73 -0
- package/dist/types/core/components/functional/3d/post-fx.d.ts.map +1 -0
- package/dist/types/core/components/functional/3d/props.d.ts +42 -0
- package/dist/types/core/components/functional/3d/props.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/reload.d.ts +3 -0
- package/dist/types/core/components/functional/3d/reload.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/runtime.d.ts +16 -1
- package/dist/types/core/components/functional/3d/runtime.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/scene-runtime.d.ts +1 -1
- package/dist/types/core/components/functional/3d/scene-runtime.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/tune.d.ts +5 -0
- package/dist/types/core/components/functional/3d/tune.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/types.d.ts +16 -0
- package/dist/types/core/components/functional/3d/types.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/updated.d.ts.map +1 -1
- package/dist/types/core/components/functional/shader/gl-runtime.d.ts +53 -2
- package/dist/types/core/components/functional/shader/gl-runtime.d.ts.map +1 -1
- package/dist/types/core/components/functional/shader/shader.d.ts +41 -9
- package/dist/types/core/components/functional/shader/shader.d.ts.map +1 -1
- package/dist/types/core/components/functional/shader/shadertoy-prelude.d.ts +16 -2
- package/dist/types/core/components/functional/shader/shadertoy-prelude.d.ts.map +1 -1
- package/dist/types/core/components/functional/shader/types.d.ts +35 -5
- package/dist/types/core/components/functional/shader/types.d.ts.map +1 -1
- package/dist/types/core/components/functional/webgpu/gl-fallback.d.ts +4 -0
- package/dist/types/core/components/functional/webgpu/gl-fallback.d.ts.map +1 -1
- package/dist/types/core/components/functional/webgpu/gpu-runtime.d.ts +14 -0
- package/dist/types/core/components/functional/webgpu/gpu-runtime.d.ts.map +1 -1
- package/dist/types/core/components/functional/webgpu/helpers.d.ts +1 -0
- package/dist/types/core/components/functional/webgpu/helpers.d.ts.map +1 -1
- package/dist/types/core/components/functional/webgpu/webgpu.d.ts +11 -1
- package/dist/types/core/components/functional/webgpu/webgpu.d.ts.map +1 -1
- package/dist/types/core/utils/mediaRef.d.ts +34 -0
- package/dist/types/core/utils/mediaRef.d.ts.map +1 -1
- package/package.json +49 -1
- package/src/core/components/functional/3d/3d.md +46 -3
- package/src/core/components/functional/3d/3d.spec.ts +42 -0
- package/src/core/components/functional/3d/3d.ts +112 -3
- package/src/core/components/functional/3d/constants.ts +3 -0
- package/src/core/components/functional/3d/ensure.ts +11 -3
- package/src/core/components/functional/3d/form-params.ts +2 -0
- package/src/core/components/functional/3d/frame-source.ts +30 -0
- package/src/core/components/functional/3d/life.ts +4 -1
- package/src/core/components/functional/3d/out-publish.ts +4 -3
- package/src/core/components/functional/3d/playback.ts +13 -1
- package/src/core/components/functional/3d/post-bind.ts +262 -0
- package/src/core/components/functional/3d/post-channels.spec.ts +36 -0
- package/src/core/components/functional/3d/post-channels.ts +69 -0
- package/src/core/components/functional/3d/post-fx.ts +594 -0
- package/src/core/components/functional/3d/props.ts +45 -0
- package/src/core/components/functional/3d/reload.ts +8 -0
- package/src/core/components/functional/3d/runtime.ts +93 -4
- package/src/core/components/functional/3d/scene-runtime.ts +3 -0
- package/src/core/components/functional/3d/tune.ts +21 -0
- package/src/core/components/functional/3d/types.ts +14 -0
- package/src/core/components/functional/3d/updated.ts +17 -1
- package/src/core/components/functional/shader/gl-runtime.ts +374 -36
- package/src/core/components/functional/shader/shader.md +36 -11
- package/src/core/components/functional/shader/shader.spec.ts +350 -3
- package/src/core/components/functional/shader/shader.ts +236 -59
- package/src/core/components/functional/shader/shadertoy-prelude.ts +49 -5
- package/src/core/components/functional/shader/types.ts +92 -13
- package/src/core/components/functional/webgpu/gl-fallback.ts +28 -4
- package/src/core/components/functional/webgpu/gpu-runtime.ts +154 -18
- package/src/core/components/functional/webgpu/helpers.ts +8 -2
- package/src/core/components/functional/webgpu/webgpu.md +13 -2
- package/src/core/components/functional/webgpu/webgpu.spec.ts +37 -0
- package/src/core/components/functional/webgpu/webgpu.ts +48 -1
- package/src/core/utils/mediaRef.spec.ts +61 -0
- package/src/core/utils/mediaRef.ts +108 -0
- package/src/docs/_misc/visual-stack.md +37 -3
- package/src/docs/components/docs-demo-sources.ts +42 -0
- package/src/docs/docs.ts +3 -0
- package/src/docs/example/docs-3d-post-depth-demo.ts +68 -0
- package/src/docs/example/docs-3d-shader-live-demo.ts +98 -0
- package/src/docs/example/docs-3d-webgpu-live-demo.ts +83 -0
- package/src/docs/example/docs-harris-webgpu-demo.ts +53 -55
- package/src/docs/search/docs-search.json +2583 -863
- package/src/tsconfig.json +36 -0
- package/src/tsconfig.tsbuildinfo +1 -1
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
import { css, html, LitElement } from "lit";
|
|
4
4
|
import { customElement, property, state } from "lit/decorators.js";
|
|
5
5
|
import { PublisherManager } from "@supersoniks/concorde/utils";
|
|
6
|
+
import {
|
|
7
|
+
lookupLiveElement,
|
|
8
|
+
FrameConsumerRegistry,
|
|
9
|
+
} from "@supersoniks/concorde/core/utils/mediaRef";
|
|
6
10
|
import { Canvas2dFallbackRuntime } from "./canvas2d-fallback";
|
|
7
11
|
import { formBind } from "./form-bind";
|
|
8
12
|
import { WebGl2FallbackRuntime } from "./gl-fallback";
|
|
@@ -138,6 +142,8 @@ export class SonicWebGpu extends LitElement {
|
|
|
138
142
|
raf = 0;
|
|
139
143
|
startTime = 0;
|
|
140
144
|
frame = 0;
|
|
145
|
+
/** Contrat SonicFrameSource — incrémenté après chaque draw. */
|
|
146
|
+
frameSeq = 0;
|
|
141
147
|
mouseVec: [number, number, number, number] = [0, 0, 0, 0];
|
|
142
148
|
/** Cible normalisée (0–1, origine haut-gauche) depuis dataProvider — tracking / DP. */
|
|
143
149
|
remoteTarget: { x: number; y: number } | null = null;
|
|
@@ -148,6 +154,7 @@ export class SonicWebGpu extends LitElement {
|
|
|
148
154
|
private resizeObserver: ResizeObserver | null = null;
|
|
149
155
|
private intersectionObserver: IntersectionObserver | null = null;
|
|
150
156
|
private rebuildToken = 0;
|
|
157
|
+
private frameConsumers = new FrameConsumerRegistry();
|
|
151
158
|
ready = false;
|
|
152
159
|
/** Évite deux boots concurrents (IO + rAF) qui disposent le fallback. */
|
|
153
160
|
private boot: Promise<void> | null = null;
|
|
@@ -202,6 +209,7 @@ export class SonicWebGpu extends LitElement {
|
|
|
202
209
|
this.removeEventListener("pointerenter", this.onPointerEnter);
|
|
203
210
|
this.unbindFormProvider();
|
|
204
211
|
this.revokeFrameUrl();
|
|
212
|
+
this.frameConsumers.clear();
|
|
205
213
|
this.frameOutSeq++;
|
|
206
214
|
this.frameOutBusy = false;
|
|
207
215
|
this.resizeObserver?.disconnect();
|
|
@@ -402,6 +410,10 @@ export class SonicWebGpu extends LitElement {
|
|
|
402
410
|
}
|
|
403
411
|
|
|
404
412
|
this.runtime = runtime;
|
|
413
|
+
const maybeGl2 = this.runtime as unknown as {
|
|
414
|
+
setElementResolver?: (fn: (key: string) => Element | null) => void;
|
|
415
|
+
};
|
|
416
|
+
maybeGl2.setElementResolver?.((key) => this.lookupElement(key));
|
|
405
417
|
const backend = runtime.backend as SonicWebGpuBackend;
|
|
406
418
|
this.activeBackend = backend;
|
|
407
419
|
let notice = "";
|
|
@@ -473,6 +485,21 @@ export class SonicWebGpu extends LitElement {
|
|
|
473
485
|
return canvas;
|
|
474
486
|
}
|
|
475
487
|
|
|
488
|
+
/** Résout `#id` dans getRootNode() puis document ; clés live `__sonic_frame_`. */
|
|
489
|
+
private lookupElement(key: string): Element | null {
|
|
490
|
+
if (!key.startsWith("#")) return null;
|
|
491
|
+
const live = lookupLiveElement(key);
|
|
492
|
+
if (live) return live;
|
|
493
|
+
const id = key.slice(1);
|
|
494
|
+
if (!id || id.startsWith("__sonic_frame_")) return null;
|
|
495
|
+
const root = this.getRootNode();
|
|
496
|
+
if (root instanceof Document || root instanceof ShadowRoot) {
|
|
497
|
+
const el = root.getElementById(id);
|
|
498
|
+
if (el) return el;
|
|
499
|
+
}
|
|
500
|
+
return typeof document !== "undefined" ? document.getElementById(id) : null;
|
|
501
|
+
}
|
|
502
|
+
|
|
476
503
|
private releaseRuntime() {
|
|
477
504
|
this.stopLoop();
|
|
478
505
|
this.ready = false;
|
|
@@ -484,6 +511,14 @@ export class SonicWebGpu extends LitElement {
|
|
|
484
511
|
this.canvasEl = null;
|
|
485
512
|
}
|
|
486
513
|
|
|
514
|
+
/** Enregistre les producteurs live pour play-offscreen auto. */
|
|
515
|
+
private syncFrameConsumers() {
|
|
516
|
+
const keys = [this.channel0, this.channel1, this.channel2, this.channel3];
|
|
517
|
+
this.frameConsumers.sync(
|
|
518
|
+
keys.filter((k) => k.startsWith("#")).map((k) => this.lookupElement(k)),
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
487
522
|
private async rebuild() {
|
|
488
523
|
if (!this.runtime) return;
|
|
489
524
|
const token = ++this.rebuildToken;
|
|
@@ -507,6 +542,7 @@ export class SonicWebGpu extends LitElement {
|
|
|
507
542
|
]);
|
|
508
543
|
}
|
|
509
544
|
if (token !== this.rebuildToken) return;
|
|
545
|
+
this.syncFrameConsumers();
|
|
510
546
|
this.errorMessage = "";
|
|
511
547
|
this.ready = true;
|
|
512
548
|
this.syncSize();
|
|
@@ -587,6 +623,9 @@ export class SonicWebGpu extends LitElement {
|
|
|
587
623
|
if (!this.startTime) this.startTime = now;
|
|
588
624
|
const time = (now - this.startTime) / 1000;
|
|
589
625
|
this.applyRemoteMouse();
|
|
626
|
+
if (this.runtime instanceof WebGpuRuntime) {
|
|
627
|
+
this.runtime.updateLiveChannels((key) => this.lookupElement(key));
|
|
628
|
+
}
|
|
590
629
|
this.runtime.draw({
|
|
591
630
|
width: w,
|
|
592
631
|
height: h,
|
|
@@ -596,6 +635,7 @@ export class SonicWebGpu extends LitElement {
|
|
|
596
635
|
params: [this.param0, this.param1, this.param2, this.param3],
|
|
597
636
|
});
|
|
598
637
|
this.frame++;
|
|
638
|
+
this.frameSeq++;
|
|
599
639
|
this.maybePublishOut(now, time);
|
|
600
640
|
} catch (e) {
|
|
601
641
|
console.warn(`${LOG_PREFIX} frame skip:`, e);
|
|
@@ -620,6 +660,12 @@ export class SonicWebGpu extends LitElement {
|
|
|
620
660
|
outFrame.maybePublishOut(this as never, now, time);
|
|
621
661
|
}
|
|
622
662
|
|
|
663
|
+
/** Contrat SonicFrameSource — canvas WebGPU / fallback. */
|
|
664
|
+
getFrameCanvas(): HTMLCanvasElement | null {
|
|
665
|
+
if (!this.ready || this.errorMessage) return null;
|
|
666
|
+
return this.canvasEl;
|
|
667
|
+
}
|
|
668
|
+
|
|
623
669
|
private revokeFrameUrl() {
|
|
624
670
|
outFrame.revokeFrameUrl(this as never);
|
|
625
671
|
}
|
|
@@ -653,7 +699,8 @@ export type {
|
|
|
653
699
|
} from "./types";
|
|
654
700
|
export { parseBackendPrefer, parseKernel, WebGpuCompileError } from "./types";
|
|
655
701
|
export { buildWgslSource, DEFAULT_MAIN_IMAGE } from "./wgsl-prelude";
|
|
656
|
-
export { WebGpuRuntime } from "./gpu-runtime";
|
|
702
|
+
export { WebGpuRuntime, channelTextureNeedsRecreate } from "./gpu-runtime";
|
|
657
703
|
export { WebGl2FallbackRuntime } from "./gl-fallback";
|
|
658
704
|
export { Canvas2dFallbackRuntime } from "./canvas2d-fallback";
|
|
659
705
|
export { ParticlesRuntime } from "./particles-runtime";
|
|
706
|
+
export { channelFormValue } from "./helpers";
|
|
@@ -2,7 +2,10 @@ import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
|
2
2
|
import {
|
|
3
3
|
attachMediaUrls,
|
|
4
4
|
cloneMediaRef,
|
|
5
|
+
isFrameSource,
|
|
5
6
|
isMediaBlobObject,
|
|
7
|
+
mediaElementKey,
|
|
8
|
+
registerLiveElement,
|
|
6
9
|
revokeMediaUrls,
|
|
7
10
|
toMediaUrl,
|
|
8
11
|
} from "./mediaRef";
|
|
@@ -15,6 +18,64 @@ describe("toMediaUrl", () => {
|
|
|
15
18
|
expect(toMediaUrl(null)).toBeNull();
|
|
16
19
|
expect(toMediaUrl({ blob: new Blob() })).toBeNull();
|
|
17
20
|
});
|
|
21
|
+
|
|
22
|
+
it("ignore element (pas d’URL)", () => {
|
|
23
|
+
expect(toMediaUrl({ element: "#plateau" })).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("isFrameSource / mediaElementKey", () => {
|
|
28
|
+
it("détecte SonicFrameSource", () => {
|
|
29
|
+
expect(
|
|
30
|
+
isFrameSource({
|
|
31
|
+
getFrameCanvas: () => null,
|
|
32
|
+
frameSeq: 0,
|
|
33
|
+
}),
|
|
34
|
+
).toBe(true);
|
|
35
|
+
expect(isFrameSource({})).toBe(false);
|
|
36
|
+
expect(isFrameSource(null)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("FrameConsumerRegistry register / unregister", async () => {
|
|
40
|
+
const { FrameConsumerRegistry, isFrameConsumerHost } = await import(
|
|
41
|
+
"./mediaRef"
|
|
42
|
+
);
|
|
43
|
+
const tokens: object[] = [];
|
|
44
|
+
const host = {
|
|
45
|
+
registerFrameConsumer(t: object = {}) {
|
|
46
|
+
tokens.push(t);
|
|
47
|
+
},
|
|
48
|
+
unregisterFrameConsumer(t: object = {}) {
|
|
49
|
+
const i = tokens.indexOf(t);
|
|
50
|
+
if (i >= 0) tokens.splice(i, 1);
|
|
51
|
+
},
|
|
52
|
+
getFrameCanvas: () => null,
|
|
53
|
+
frameSeq: 0,
|
|
54
|
+
};
|
|
55
|
+
expect(isFrameConsumerHost(host)).toBe(true);
|
|
56
|
+
const reg = new FrameConsumerRegistry();
|
|
57
|
+
// FrameConsumerRegistry expects Element — cast for unit test
|
|
58
|
+
reg.sync([host as unknown as Element]);
|
|
59
|
+
expect(tokens.length).toBe(1);
|
|
60
|
+
reg.clear();
|
|
61
|
+
expect(tokens.length).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("normalise element string / Element avec id", () => {
|
|
65
|
+
expect(mediaElementKey("#plateau")).toBe("#plateau");
|
|
66
|
+
expect(mediaElementKey("plateau")).toBe("#plateau");
|
|
67
|
+
expect(mediaElementKey("")).toBeNull();
|
|
68
|
+
const el = document.createElement("div");
|
|
69
|
+
el.id = "scene";
|
|
70
|
+
expect(mediaElementKey(el)).toBe("#scene");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("enregistre un Element sans id", () => {
|
|
74
|
+
const el = document.createElement("div");
|
|
75
|
+
const key = registerLiveElement(el);
|
|
76
|
+
expect(key.startsWith("#__sonic_frame_")).toBe(true);
|
|
77
|
+
expect(mediaElementKey(el)).toBe(key);
|
|
78
|
+
});
|
|
18
79
|
});
|
|
19
80
|
|
|
20
81
|
describe("attachMediaUrls / revokeMediaUrls", () => {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* - `url` : http(s) | blob: | data: (préféré pour les canaux shader)
|
|
5
5
|
* - `blob` : binaire (HF, upload) — le producteur publie aussi `url` quand possible
|
|
6
6
|
* - `blobUrl` : alias historique (extract shader)
|
|
7
|
+
* - `element` : sélecteur `#id` ou Element (pont live → canaux shader, ignoré par toMediaUrl)
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
export type SonicMediaRef = {
|
|
@@ -13,8 +14,115 @@ export type SonicMediaRef = {
|
|
|
13
14
|
width?: number;
|
|
14
15
|
height?: number;
|
|
15
16
|
mime?: string;
|
|
17
|
+
/** Sélecteur `#id` ou référence Element (upload live, pas d’URL). */
|
|
18
|
+
element?: string | Element;
|
|
16
19
|
};
|
|
17
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Contrat minimal pour qu’un composant Concorde alimente un canal shader
|
|
23
|
+
* sans JPEG / blob (`channel0="#plateau"` ou `{ element }`).
|
|
24
|
+
*/
|
|
25
|
+
export type SonicFrameSource = {
|
|
26
|
+
/** Canvas lisible (preserveDrawingBuffer ou 2D). `null` tant que pas prêt. */
|
|
27
|
+
getFrameCanvas(): HTMLCanvasElement | null;
|
|
28
|
+
/** Incrémenté après chaque rendu effectif. */
|
|
29
|
+
readonly frameSeq: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Producteur de frames live qui peut rester actif hors viewport
|
|
34
|
+
* tant qu’un consommateur est enregistré (`sonic-3d` Lot 9).
|
|
35
|
+
*/
|
|
36
|
+
export type SonicFrameConsumerHost = {
|
|
37
|
+
registerFrameConsumer(token?: object): void;
|
|
38
|
+
unregisterFrameConsumer(token?: object): void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function isFrameSource(el: unknown): el is SonicFrameSource {
|
|
42
|
+
if (!el || typeof el !== "object") return false;
|
|
43
|
+
const o = el as SonicFrameSource;
|
|
44
|
+
return (
|
|
45
|
+
typeof o.getFrameCanvas === "function" && typeof o.frameSeq === "number"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isFrameConsumerHost(el: unknown): el is SonicFrameConsumerHost {
|
|
50
|
+
if (!el || typeof el !== "object") return false;
|
|
51
|
+
const o = el as SonicFrameConsumerHost;
|
|
52
|
+
return (
|
|
53
|
+
typeof o.registerFrameConsumer === "function" &&
|
|
54
|
+
typeof o.unregisterFrameConsumer === "function"
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Suit les `#id` résolus → register/unregister auprès du producteur. */
|
|
59
|
+
export class FrameConsumerRegistry {
|
|
60
|
+
private tokens = new Map<Element, object>();
|
|
61
|
+
|
|
62
|
+
sync(elements: Iterable<Element | null | undefined>) {
|
|
63
|
+
const next = new Set<Element>();
|
|
64
|
+
for (const el of elements) {
|
|
65
|
+
if (el && isFrameConsumerHost(el)) next.add(el);
|
|
66
|
+
}
|
|
67
|
+
for (const [el, token] of [...this.tokens]) {
|
|
68
|
+
if (next.has(el)) continue;
|
|
69
|
+
if (isFrameConsumerHost(el)) el.unregisterFrameConsumer(token);
|
|
70
|
+
this.tokens.delete(el);
|
|
71
|
+
}
|
|
72
|
+
for (const el of next) {
|
|
73
|
+
if (this.tokens.has(el)) continue;
|
|
74
|
+
const token = {};
|
|
75
|
+
(el as unknown as SonicFrameConsumerHost).registerFrameConsumer(token);
|
|
76
|
+
this.tokens.set(el, token);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
clear() {
|
|
81
|
+
this.sync([]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Clés synthétiques pour Elements DP sans `id` (`#__sonic_frame_N`). */
|
|
86
|
+
const LIVE_PREFIX = "#__sonic_frame_";
|
|
87
|
+
let liveSeq = 0;
|
|
88
|
+
const liveByKey = new Map<string, Element>();
|
|
89
|
+
const keyByLive = new WeakMap<Element, string>();
|
|
90
|
+
|
|
91
|
+
/** Enregistre un Element sans id → clé `#__sonic_frame_N` (réutilisée). */
|
|
92
|
+
export function registerLiveElement(el: Element): string {
|
|
93
|
+
const existing = keyByLive.get(el);
|
|
94
|
+
if (existing) return existing;
|
|
95
|
+
const key = `${LIVE_PREFIX}${++liveSeq}`;
|
|
96
|
+
liveByKey.set(key, el);
|
|
97
|
+
keyByLive.set(el, key);
|
|
98
|
+
return key;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function lookupLiveElement(key: string): Element | null {
|
|
102
|
+
if (!key.startsWith(LIVE_PREFIX)) return null;
|
|
103
|
+
const el = liveByKey.get(key) ?? null;
|
|
104
|
+
if (el && !el.isConnected) {
|
|
105
|
+
liveByKey.delete(key);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return el;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Normalise `element` DP → clé canal (`#id` ou live). */
|
|
112
|
+
export function mediaElementKey(value: unknown): string | null {
|
|
113
|
+
if (value == null) return null;
|
|
114
|
+
if (typeof value === "string") {
|
|
115
|
+
const s = value.trim();
|
|
116
|
+
if (!s) return null;
|
|
117
|
+
return s.startsWith("#") ? s : `#${s}`;
|
|
118
|
+
}
|
|
119
|
+
if (typeof Element !== "undefined" && value instanceof Element) {
|
|
120
|
+
if (value.id) return `#${value.id}`;
|
|
121
|
+
return registerLiveElement(value);
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
18
126
|
/** Objet image sérialisé (sortie HF / extract) : Blob + dimensions. */
|
|
19
127
|
export function isMediaBlobObject(
|
|
20
128
|
value: unknown,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Visual stack
|
|
2
2
|
|
|
3
|
-
Examples of chaining opt-in addons (HF, shader, WebGPU). Outside the main “functional” flow.
|
|
3
|
+
Examples of chaining opt-in addons (HF, shader, WebGPU, 3D). Outside the main “functional” flow.
|
|
4
4
|
|
|
5
5
|
## HF depth → parallax
|
|
6
6
|
|
|
@@ -29,9 +29,42 @@ photo (sample)
|
|
|
29
29
|
|
|
30
30
|
<docs-lit-demo for="docs-visual-stack-demo"></docs-lit-demo>
|
|
31
31
|
|
|
32
|
+
## 3D → shader live (GPU bridge)
|
|
33
|
+
|
|
34
|
+
`sonic-3d` and `sonic-shader` keep separate WebGL contexts. For realtime post, point a channel at the 3D host with `#id` — the shader copies the canvas each frame (`texImage2D`), no JPEG.
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<sonic-3d id="plateau" play auto-rotate …></sonic-3d>
|
|
38
|
+
<sonic-shader channel0="#plateau" image="…" play></sonic-shader>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Also accepted: `<img>`, `<video>`, `<canvas>`, or another Concorde host that implements `SonicFrameSource` (`getFrameCanvas` + `frameSeq`). DP form: `{ element: "#plateau" }` or `{ element: el }`.
|
|
42
|
+
|
|
43
|
+
`frame-out` (JPEG ObjectURL) remains for export / HF / freeze — not for live post.
|
|
44
|
+
|
|
45
|
+
<docs-lit-demo for="docs-3d-shader-live-demo"></docs-lit-demo>
|
|
46
|
+
|
|
47
|
+
## 3D → WebGPU live
|
|
48
|
+
|
|
49
|
+
Same `#id` bridge as the shader: `sonic-webgpu channel0="#plateau-gpu"` copies the 3D canvas each `frameSeq` via `copyExternalImageToTexture` (persistent texture — no recreate when size is stable).
|
|
50
|
+
|
|
51
|
+
<docs-lit-demo for="docs-3d-webgpu-live-demo"></docs-lit-demo>
|
|
52
|
+
|
|
53
|
+
## Post FX + depth (in sonic-3d)
|
|
54
|
+
|
|
55
|
+
Scene → offscreen color + depth → GLSL passes (`post-*`) in the same WebGL context. Use `post-image-ch1="depth"` for edge / DoF-style effects without a second component.
|
|
56
|
+
|
|
57
|
+
<docs-lit-demo for="docs-3d-post-depth-demo"></docs-lit-demo>
|
|
58
|
+
|
|
32
59
|
## Harris → patches → WebGPU
|
|
33
60
|
|
|
34
|
-
`sonic-shader` detects corners (Harris) and extracts patches → `channel0…3` on `sonic-webgpu` (`kernel="image"`): GPU mosaic.
|
|
61
|
+
`sonic-shader` detects corners (Harris, `buffer-format="rgba16f"` + NMS 5×5 / shape filter) and extracts patches → `channel0…3` on `sonic-webgpu` (`kernel="image"`): GPU mosaic.
|
|
62
|
+
|
|
63
|
+
Use `buffer-format="rgba16f"` so signed `IxIy` / non-clamped `Ix²` survive the buffer (RGBA8 zeros negatives and saturates). See [Shader — buffer precision](#core/components/functional/shader/shader.md/shader).
|
|
64
|
+
|
|
65
|
+
Channel rebinds and `release-offscreen="auto"` (keep FBO state when a pass uses `self`) : [Shader — état et visibilité](#core/components/functional/shader/shader.md/shader).
|
|
66
|
+
|
|
67
|
+
`sonic-3d` live sources keep playing off-screen when a `#id` consumer is registered (or `play-offscreen`) — see [3D — bridging](#core/components/functional/3d/3d.md/3d).
|
|
35
68
|
|
|
36
69
|
<docs-lit-demo for="docs-harris-webgpu-demo"></docs-lit-demo>
|
|
37
70
|
|
|
@@ -41,9 +74,10 @@ photo (sample)
|
|
|
41
74
|
|------|-----|
|
|
42
75
|
| Depth | [Hugging Face infer](#core/components/functional/infer/hugging-face/hugging-face-infer.md/hugging-face-infer) |
|
|
43
76
|
| FX + extract | [Shader](#core/components/functional/shader/shader.md/shader) |
|
|
77
|
+
| 3D viewer | [3D](#core/components/functional/3d/3d.md/3d) |
|
|
44
78
|
| Compute / mosaic | [WebGPU](#core/components/functional/webgpu/webgpu.md/webgpu) |
|
|
45
79
|
| Stats / thresholds | [Jsonata](#core/components/functional/jsonata/jsonata.md/jsonata) |
|
|
46
80
|
|
|
47
81
|
## Media contract
|
|
48
82
|
|
|
49
|
-
`SonicMediaRef` / `toMediaUrl` / `cloneMediaRef` — `@supersoniks/concorde/core/utils/mediaRef`.
|
|
83
|
+
`SonicMediaRef` / `SonicFrameSource` / `toMediaUrl` / `cloneMediaRef` — `@supersoniks/concorde/core/utils/mediaRef`.
|
|
@@ -215,6 +215,30 @@ export const DOCS_LIT_DEMO_REGISTRY: Record<string, DocsLitDemoConfig> = {
|
|
|
215
215
|
},
|
|
216
216
|
],
|
|
217
217
|
},
|
|
218
|
+
"docs-3d-shader-live-demo": {
|
|
219
|
+
sources: [
|
|
220
|
+
{
|
|
221
|
+
path: "src/docs/example/docs-3d-shader-live-demo.ts",
|
|
222
|
+
label: "3D → shader live",
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
"docs-3d-webgpu-live-demo": {
|
|
227
|
+
sources: [
|
|
228
|
+
{
|
|
229
|
+
path: "src/docs/example/docs-3d-webgpu-live-demo.ts",
|
|
230
|
+
label: "3D → webgpu live",
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
"docs-3d-post-depth-demo": {
|
|
235
|
+
sources: [
|
|
236
|
+
{
|
|
237
|
+
path: "src/docs/example/docs-3d-post-depth-demo.ts",
|
|
238
|
+
label: "3D post depth edges",
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
},
|
|
218
242
|
"docs-webgpu-demo": {
|
|
219
243
|
sources: [
|
|
220
244
|
{
|
|
@@ -492,6 +516,24 @@ export const DOCS_DEMO_SOURCE_REGISTRY: Record<string, DocsSource[]> = {
|
|
|
492
516
|
"docs-visual-stack-demo": [
|
|
493
517
|
{ path: "src/docs/example/docs-visual-stack-demo.ts", label: "visual stack" },
|
|
494
518
|
],
|
|
519
|
+
"docs-3d-shader-live-demo": [
|
|
520
|
+
{
|
|
521
|
+
path: "src/docs/example/docs-3d-shader-live-demo.ts",
|
|
522
|
+
label: "3d → shader live",
|
|
523
|
+
},
|
|
524
|
+
],
|
|
525
|
+
"docs-3d-webgpu-live-demo": [
|
|
526
|
+
{
|
|
527
|
+
path: "src/docs/example/docs-3d-webgpu-live-demo.ts",
|
|
528
|
+
label: "3d → webgpu live",
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
"docs-3d-post-depth-demo": [
|
|
532
|
+
{
|
|
533
|
+
path: "src/docs/example/docs-3d-post-depth-demo.ts",
|
|
534
|
+
label: "3d post depth",
|
|
535
|
+
},
|
|
536
|
+
],
|
|
495
537
|
"docs-webgpu-demo": [
|
|
496
538
|
{ path: "src/docs/example/docs-webgpu-demo.ts", label: "webgpu demo" },
|
|
497
539
|
],
|
package/src/docs/docs.ts
CHANGED
|
@@ -21,6 +21,9 @@ import "./example/docs-hugging-face-demos";
|
|
|
21
21
|
import "./example/docs-shader-demos";
|
|
22
22
|
import "./example/docs-webgpu-demo";
|
|
23
23
|
import "./example/docs-visual-stack-demo";
|
|
24
|
+
import "./example/docs-3d-shader-live-demo";
|
|
25
|
+
import "./example/docs-3d-webgpu-live-demo";
|
|
26
|
+
import "./example/docs-3d-post-depth-demo";
|
|
24
27
|
import "./example/docs-harris-webgpu-demo";
|
|
25
28
|
import "./example/docs-router-demos";
|
|
26
29
|
import "./example/docs-submit-demos";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Démo Lot 2 : post FX dans sonic-3d avec canal depth (contours).
|
|
3
|
+
*/
|
|
4
|
+
import { css, html, LitElement } from "lit";
|
|
5
|
+
import { customElement } from "lit/decorators.js";
|
|
6
|
+
import { tailwind } from "../tailwind";
|
|
7
|
+
import "../../core/components/functional/3d/3d";
|
|
8
|
+
import { DEMO_3D_STYLES, HELMET_GLB } from "./docs-3d-shared";
|
|
9
|
+
|
|
10
|
+
const DEPTH_EDGE = `
|
|
11
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
12
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
13
|
+
vec2 px = 1.0 / max(iResolution.xy, vec2(1.0));
|
|
14
|
+
vec3 col = texture(iChannel0, uv).rgb;
|
|
15
|
+
|
|
16
|
+
float dC = texture(iChannel1, uv).r;
|
|
17
|
+
float dL = texture(iChannel1, uv - px * vec2(1.0, 0.0)).r;
|
|
18
|
+
float dR = texture(iChannel1, uv + px * vec2(1.0, 0.0)).r;
|
|
19
|
+
float dU = texture(iChannel1, uv + px * vec2(0.0, 1.0)).r;
|
|
20
|
+
float dD = texture(iChannel1, uv - px * vec2(0.0, 1.0)).r;
|
|
21
|
+
float edge = length(vec2(dR - dL, dU - dD));
|
|
22
|
+
edge = smoothstep(0.002 + uParam0 * 0.02, 0.02 + uParam0 * 0.05, edge);
|
|
23
|
+
|
|
24
|
+
vec3 ink = vec3(0.05, 0.95, 1.0);
|
|
25
|
+
col = mix(col, ink, edge * 0.85);
|
|
26
|
+
col *= 0.92 + 0.08 * sin(iTime * 1.5);
|
|
27
|
+
fragColor = vec4(col, 1.0);
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
@customElement("docs-3d-post-depth-demo")
|
|
32
|
+
export class Docs3dPostDepthDemo extends LitElement {
|
|
33
|
+
static styles = [
|
|
34
|
+
tailwind,
|
|
35
|
+
DEMO_3D_STYLES,
|
|
36
|
+
css`
|
|
37
|
+
sonic-3d {
|
|
38
|
+
width: 100%;
|
|
39
|
+
}
|
|
40
|
+
`,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
render() {
|
|
44
|
+
return html`
|
|
45
|
+
<div class="flex flex-col gap-2">
|
|
46
|
+
<sonic-3d
|
|
47
|
+
src=${HELMET_GLB}
|
|
48
|
+
camera="orbit"
|
|
49
|
+
auto-rotate
|
|
50
|
+
play
|
|
51
|
+
pick
|
|
52
|
+
min-distance="1"
|
|
53
|
+
max-distance="10"
|
|
54
|
+
aspect-ratio="16/10"
|
|
55
|
+
post-image=${DEPTH_EDGE}
|
|
56
|
+
post-image-ch0="scene"
|
|
57
|
+
post-image-ch1="depth"
|
|
58
|
+
param0="0.55"
|
|
59
|
+
></sonic-3d>
|
|
60
|
+
<p class="text-sm text-neutral-500">
|
|
61
|
+
Post FX interne (<code>post-image</code>) : couleur
|
|
62
|
+
<code>scene</code> + discontinuités sur <code>depth</code> — pas de
|
|
63
|
+
copie vers un second composant.
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Démo pont live : sonic-3d → sonic-shader via channel0="#plateau"
|
|
3
|
+
* (copie canvas chaque frame, sans JPEG frame-out).
|
|
4
|
+
*/
|
|
5
|
+
import { css, html, LitElement } from "lit";
|
|
6
|
+
import { customElement } from "lit/decorators.js";
|
|
7
|
+
import { tailwind } from "../tailwind";
|
|
8
|
+
import "../../core/components/functional/3d/3d";
|
|
9
|
+
import "../../core/components/functional/shader/shader";
|
|
10
|
+
import { DEMO_3D_STYLES, HELMET_GLB } from "./docs-3d-shared";
|
|
11
|
+
|
|
12
|
+
/** Aberration chromatique + bords neon — bien visible vs le 3d brut. */
|
|
13
|
+
const LIVE_POST = `
|
|
14
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
15
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
16
|
+
vec2 c = uv - 0.5;
|
|
17
|
+
float aber = 0.004 + clamp(uParam0, 0.0, 1.0) * 0.028;
|
|
18
|
+
aber *= 1.0 + 0.35 * sin(iTime * 2.2);
|
|
19
|
+
|
|
20
|
+
float r = texture(iChannel0, uv + c * aber).r;
|
|
21
|
+
float g = texture(iChannel0, uv).g;
|
|
22
|
+
float b = texture(iChannel0, uv - c * aber).b;
|
|
23
|
+
vec3 col = vec3(r, g, b);
|
|
24
|
+
|
|
25
|
+
// Contours sobel légers (luma)
|
|
26
|
+
vec2 px = 1.0 / max(iResolution.xy, vec2(1.0));
|
|
27
|
+
float tl = dot(texture(iChannel0, uv + px * vec2(-1.0, 1.0)).rgb, vec3(0.299, 0.587, 0.114));
|
|
28
|
+
float tr = dot(texture(iChannel0, uv + px * vec2( 1.0, 1.0)).rgb, vec3(0.299, 0.587, 0.114));
|
|
29
|
+
float bl = dot(texture(iChannel0, uv + px * vec2(-1.0, -1.0)).rgb, vec3(0.299, 0.587, 0.114));
|
|
30
|
+
float br = dot(texture(iChannel0, uv + px * vec2( 1.0, -1.0)).rgb, vec3(0.299, 0.587, 0.114));
|
|
31
|
+
float edge = length(vec2(tr + br - tl - bl, tl + tr - bl - br));
|
|
32
|
+
col += vec3(0.15, 0.85, 1.0) * smoothstep(0.08, 0.45, edge);
|
|
33
|
+
|
|
34
|
+
// Scanlines + vignette
|
|
35
|
+
col *= 0.88 + 0.12 * sin(uv.y * iResolution.y * 1.2);
|
|
36
|
+
float vig = smoothstep(1.15, 0.35, length(c));
|
|
37
|
+
col *= vig;
|
|
38
|
+
|
|
39
|
+
fragColor = vec4(col, 1.0);
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
@customElement("docs-3d-shader-live-demo")
|
|
44
|
+
export class Docs3dShaderLiveDemo extends LitElement {
|
|
45
|
+
static styles = [
|
|
46
|
+
tailwind,
|
|
47
|
+
DEMO_3D_STYLES,
|
|
48
|
+
css`
|
|
49
|
+
.grid {
|
|
50
|
+
display: grid;
|
|
51
|
+
gap: 0.75rem;
|
|
52
|
+
grid-template-columns: 1fr;
|
|
53
|
+
}
|
|
54
|
+
@media (min-width: 720px) {
|
|
55
|
+
.grid {
|
|
56
|
+
grid-template-columns: 1fr 1fr;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
sonic-3d,
|
|
60
|
+
sonic-shader {
|
|
61
|
+
width: 100%;
|
|
62
|
+
border-radius: 0.5rem;
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
}
|
|
65
|
+
`,
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
render() {
|
|
69
|
+
return html`
|
|
70
|
+
<div class="flex flex-col gap-2">
|
|
71
|
+
<div class="grid">
|
|
72
|
+
<sonic-3d
|
|
73
|
+
id="plateau"
|
|
74
|
+
src=${HELMET_GLB}
|
|
75
|
+
camera="orbit"
|
|
76
|
+
auto-rotate
|
|
77
|
+
play
|
|
78
|
+
min-distance="1"
|
|
79
|
+
max-distance="10"
|
|
80
|
+
aspect-ratio="16/10"
|
|
81
|
+
></sonic-3d>
|
|
82
|
+
<sonic-shader
|
|
83
|
+
play
|
|
84
|
+
channel0="#plateau"
|
|
85
|
+
image=${LIVE_POST}
|
|
86
|
+
param0="0.7"
|
|
87
|
+
aspect-ratio="16/10"
|
|
88
|
+
></sonic-shader>
|
|
89
|
+
</div>
|
|
90
|
+
<p class="text-sm text-neutral-500">
|
|
91
|
+
Live bridge: <code>channel0="#plateau"</code> (no JPEG). Left = raw
|
|
92
|
+
<code>sonic-3d</code>, right = chromatic aberration + neon edges +
|
|
93
|
+
scanlines. Slider-ready via <code>param0</code> (aberration strength).
|
|
94
|
+
</p>
|
|
95
|
+
</div>
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
}
|