EtonMu commited on
Commit
a9a71e9
·
verified ·
1 Parent(s): 8f376e0

Deploy Deep Analog web app

Browse files
Files changed (1) hide show
  1. static/index.html +256 -0
static/index.html CHANGED
@@ -221,6 +221,27 @@
221
  .dl-btn:hover { border-color: var(--accent); }
222
  .dl-btn .kind { color: var(--accent); font-size: 11px; letter-spacing: .08em; }
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  /* ---------- privacy modal ---------- */
225
  #privacyModal {
226
  position: fixed; inset: 0; z-index: 950;
@@ -548,6 +569,10 @@
548
  initCompare(cmp);
549
  }
550
 
 
 
 
 
551
  const dls = document.createElement('div');
552
  dls.className = 'downloads';
553
  dls.appendChild(dlButton('Download LUT', '.CUBE 33³',
@@ -590,6 +615,237 @@
590
  cmp.addEventListener('pointerup', () => { dragging = false; });
591
  }
592
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593
  // ------------------------------------------------ privacy modal
594
  ['privacyOpen', 'privacyOpen2'].forEach(id => {
595
  const el = $(id);
 
221
  .dl-btn:hover { border-color: var(--accent); }
222
  .dl-btn .kind { color: var(--accent); font-size: 11px; letter-spacing: .08em; }
223
 
224
+ /* ---------- 3D LUT visualization ---------- */
225
+ .viz-wrap { margin-top: 20px; }
226
+ .viz-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10px; }
227
+ .viz-head .t { font-size: 11.5px; color: var(--text-dim); letter-spacing: .12em; text-transform: uppercase; }
228
+ .viz-head .hint { font-size: 11px; color: var(--text-dim); }
229
+ .viz-canvas {
230
+ width: 100%; height: 420px; display: block;
231
+ border-radius: 10px; border: 1px solid var(--line);
232
+ background: radial-gradient(600px 380px at 50% 45%, #17171c, #0a0a0c 80%);
233
+ cursor: grab; touch-action: none;
234
+ }
235
+ .viz-canvas:active { cursor: grabbing; }
236
+ .viz-controls { display: flex; align-items: center; gap: 14px; margin-top: 12px; }
237
+ .viz-controls .vlbl { font-size: 11px; letter-spacing: .1em; color: var(--text-dim); text-transform: uppercase; white-space: nowrap; }
238
+ .viz-controls input[type=range] { flex: 1; }
239
+ .viz-play {
240
+ background: var(--bg-3); border: 1px solid var(--line); border-radius: 8px;
241
+ color: var(--accent); font-size: 12px; padding: 6px 12px; cursor: pointer;
242
+ }
243
+ .viz-play:hover { border-color: var(--accent); }
244
+
245
  /* ---------- privacy modal ---------- */
246
  #privacyModal {
247
  position: fixed; inset: 0; z-index: 950;
 
569
  initCompare(cmp);
570
  }
571
 
572
+ // 3D visualization of the predicted LUT (both modes)
573
+ try { body.appendChild(makeVizCard(data.cube_lut)); }
574
+ catch (e) { /* WebGL unavailable — skip viz silently */ }
575
+
576
  const dls = document.createElement('div');
577
  dls.className = 'downloads';
578
  dls.appendChild(dlButton('Download LUT', '.CUBE 33³',
 
615
  cmp.addEventListener('pointerup', () => { dragging = false; });
616
  }
617
 
618
+ // ------------------------------------------------ 3D LUT visualization
619
+ // Self-contained WebGL point cloud — no libraries, no external requests.
620
+ // Shows the LUT lattice morphing from identity (perfect cube) to the
621
+ // predicted film transform. Drag to rotate, scroll to zoom.
622
+
623
+ function parseCube(text) {
624
+ let N = 0; const vals = [];
625
+ const lines = text.split('\n');
626
+ for (let i = 0; i < lines.length; i++) {
627
+ const t = lines[i].trim();
628
+ if (!t || t[0] === 'T' || t[0] === 'D') continue; // TITLE / DOMAIN_*
629
+ if (t[0] === 'L') { N = parseInt(t.split(/\s+/)[1], 10); continue; }
630
+ const p = t.split(/\s+/);
631
+ if (p.length === 3) vals.push(+p[0], +p[1], +p[2]);
632
+ }
633
+ return { N, out: new Float32Array(vals) };
634
+ }
635
+
636
+ function makeVizCard(cubeText) {
637
+ const wrap = document.createElement('div');
638
+ wrap.className = 'viz-wrap';
639
+ wrap.innerHTML =
640
+ '<div class="viz-head"><span class="t">Color space — 3D LUT</span>' +
641
+ '<span class="hint">drag to rotate · scroll to zoom</span></div>' +
642
+ '<canvas class="viz-canvas"></canvas>' +
643
+ '<div class="viz-controls">' +
644
+ '<button class="viz-play">❚❚</button>' +
645
+ '<span class="vlbl">Original</span>' +
646
+ '<input type="range" min="0" max="1" step="0.001" value="0">' +
647
+ '<span class="vlbl" style="color:var(--accent)">Film</span></div>';
648
+
649
+ const canvas = wrap.querySelector('canvas');
650
+ const slider = wrap.querySelector('input[type=range]');
651
+ const playBtn = wrap.querySelector('.viz-play');
652
+
653
+ const { N, out } = parseCube(cubeText);
654
+ if (!N || out.length !== N * N * N * 3) throw new Error('bad cube');
655
+
656
+ // identity lattice: red varies fastest (.cube convention)
657
+ const n3 = N * N * N;
658
+ const idn = new Float32Array(n3 * 3);
659
+ for (let i = 0; i < n3; i++) {
660
+ idn[i * 3] = (i % N) / (N - 1);
661
+ idn[i * 3 + 1] = (Math.floor(i / N) % N) / (N - 1);
662
+ idn[i * 3 + 2] = Math.floor(i / (N * N)) / (N - 1);
663
+ }
664
+
665
+ const gl = canvas.getContext('webgl', { antialias: true, alpha: true });
666
+ if (!gl) throw new Error('no webgl');
667
+
668
+ function compile(vs, fs) {
669
+ function sh(type, src) {
670
+ const s = gl.createShader(type);
671
+ gl.shaderSource(s, src); gl.compileShader(s);
672
+ if (!gl.getShaderParameter(s, gl.COMPILE_STATUS))
673
+ throw new Error(gl.getShaderInfoLog(s));
674
+ return s;
675
+ }
676
+ const p = gl.createProgram();
677
+ gl.attachShader(p, sh(gl.VERTEX_SHADER, vs));
678
+ gl.attachShader(p, sh(gl.FRAGMENT_SHADER, fs));
679
+ gl.linkProgram(p);
680
+ if (!gl.getProgramParameter(p, gl.LINK_STATUS))
681
+ throw new Error(gl.getProgramInfoLog(p));
682
+ return p;
683
+ }
684
+
685
+ const ptProg = compile(
686
+ 'attribute vec3 aId; attribute vec3 aOut;' +
687
+ 'uniform mat4 uMVP; uniform float uMorph; uniform float uSize;' +
688
+ 'varying vec3 vC;' +
689
+ 'void main(){' +
690
+ ' vec3 p = mix(aId, aOut, uMorph);' +
691
+ ' vC = p;' +
692
+ ' gl_Position = uMVP * vec4(p - 0.5, 1.0);' +
693
+ ' gl_PointSize = uSize / gl_Position.w;' +
694
+ '}',
695
+ 'precision mediump float; varying vec3 vC;' +
696
+ 'void main(){' +
697
+ ' vec2 d = gl_PointCoord - 0.5;' +
698
+ ' float r = length(d);' +
699
+ ' if (r > 0.5) discard;' +
700
+ ' float a = smoothstep(0.5, 0.1, r);' +
701
+ ' gl_FragColor = vec4(vC * (0.35 + 0.65 * a), a * 0.9);' +
702
+ '}');
703
+
704
+ const lnProg = compile(
705
+ 'attribute vec3 aPos; uniform mat4 uMVP; varying vec3 vC;' +
706
+ 'void main(){ vC = aPos; gl_Position = uMVP * vec4(aPos - 0.5, 1.0); }',
707
+ 'precision mediump float; varying vec3 vC;' +
708
+ 'void main(){ gl_FragColor = vec4(vC * 0.85 + 0.15, 0.22); }');
709
+
710
+ // buffers
711
+ function buf(data) {
712
+ const b = gl.createBuffer();
713
+ gl.bindBuffer(gl.ARRAY_BUFFER, b);
714
+ gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
715
+ return b;
716
+ }
717
+ const bId = buf(idn), bOut = buf(out);
718
+
719
+ // reference cube wireframe (12 edges)
720
+ const E = [];
721
+ const c = [[0,0,0],[1,0,0],[0,1,0],[0,0,1],[1,1,0],[1,0,1],[0,1,1],[1,1,1]];
722
+ [[0,1],[0,2],[0,3],[1,4],[1,5],[2,4],[2,6],[3,5],[3,6],[4,7],[5,7],[6,7]]
723
+ .forEach(([a,b2]) => { E.push(...c[a], ...c[b2]); });
724
+ const bEdge = buf(new Float32Array(E));
725
+
726
+ // mat4 helpers (column-major)
727
+ function mul(a, b) {
728
+ const o = new Float32Array(16);
729
+ for (let i = 0; i < 4; i++) for (let j = 0; j < 4; j++) {
730
+ let s = 0;
731
+ for (let k = 0; k < 4; k++) s += a[k * 4 + j] * b[i * 4 + k];
732
+ o[i * 4 + j] = s;
733
+ }
734
+ return o;
735
+ }
736
+ function persp(fov, asp, n2, f) {
737
+ const t = 1 / Math.tan(fov / 2);
738
+ return new Float32Array([t / asp,0,0,0, 0,t,0,0, 0,0,(f+n2)/(n2-f),-1, 0,0,2*f*n2/(n2-f),0]);
739
+ }
740
+ function rotY(a) { const s=Math.sin(a),co=Math.cos(a);
741
+ return new Float32Array([co,0,-s,0, 0,1,0,0, s,0,co,0, 0,0,0,1]); }
742
+ function rotX(a) { const s=Math.sin(a),co=Math.cos(a);
743
+ return new Float32Array([1,0,0,0, 0,co,s,0, 0,-s,co,0, 0,0,0,1]); }
744
+ function trans(z) {
745
+ return new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,z,1]); }
746
+
747
+ // state
748
+ let yaw = 0.7, pitch = 0.45, dist = 2.6;
749
+ let dragging = false, lastX = 0, lastY = 0, lastInteract = 0;
750
+ let morph = 0, playing = true, phase = 0;
751
+
752
+ canvas.addEventListener('pointerdown', e => {
753
+ dragging = true; lastX = e.clientX; lastY = e.clientY;
754
+ lastInteract = performance.now();
755
+ canvas.setPointerCapture(e.pointerId);
756
+ });
757
+ canvas.addEventListener('pointermove', e => {
758
+ if (!dragging) return;
759
+ yaw += (e.clientX - lastX) * 0.008;
760
+ pitch = Math.max(-1.45, Math.min(1.45, pitch + (e.clientY - lastY) * 0.008));
761
+ lastX = e.clientX; lastY = e.clientY;
762
+ lastInteract = performance.now();
763
+ });
764
+ canvas.addEventListener('pointerup', () => { dragging = false; });
765
+ canvas.addEventListener('wheel', e => {
766
+ e.preventDefault();
767
+ dist = Math.max(1.5, Math.min(4.5, dist + e.deltaY * 0.002));
768
+ lastInteract = performance.now();
769
+ }, { passive: false });
770
+
771
+ slider.addEventListener('input', () => {
772
+ playing = false; playBtn.textContent = '▶';
773
+ morph = +slider.value;
774
+ });
775
+ playBtn.addEventListener('click', () => {
776
+ playing = !playing;
777
+ playBtn.textContent = playing ? '❚❚' : '▶';
778
+ });
779
+
780
+ const aId = gl.getAttribLocation(ptProg, 'aId');
781
+ const aOut = gl.getAttribLocation(ptProg, 'aOut');
782
+ const aPos = gl.getAttribLocation(lnProg, 'aPos');
783
+
784
+ let alive = true;
785
+ const obs = new MutationObserver(() => {
786
+ if (!document.body.contains(canvas)) { alive = false; obs.disconnect(); }
787
+ });
788
+ obs.observe(document.body, { childList: true, subtree: true });
789
+
790
+ function frame(t) {
791
+ if (!alive) return;
792
+ // resize
793
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
794
+ const w = canvas.clientWidth, h = canvas.clientHeight;
795
+ if (canvas.width !== w * dpr || canvas.height !== h * dpr) {
796
+ canvas.width = w * dpr; canvas.height = h * dpr;
797
+ }
798
+ gl.viewport(0, 0, canvas.width, canvas.height);
799
+
800
+ // auto-rotate when idle
801
+ if (!dragging && t - lastInteract > 2500) yaw += 0.0035;
802
+
803
+ // morph ping-pong with holds at each end
804
+ if (playing) {
805
+ phase = (phase + 1 / 360) % 2; // 6 s full cycle
806
+ const p = phase < 1 ? phase : 2 - phase; // 0→1→0
807
+ const q = Math.max(0, Math.min(1, (p - 0.15) / 0.7));
808
+ morph = q * q * (3 - 2 * q); // smoothstep + end holds
809
+ slider.value = morph;
810
+ }
811
+
812
+ const mvp = mul(persp(0.72, w / h, 0.1, 20),
813
+ mul(trans(-dist), mul(rotX(pitch), rotY(yaw))));
814
+
815
+ gl.clearColor(0, 0, 0, 0);
816
+ gl.clear(gl.COLOR_BUFFER_BIT);
817
+ gl.disable(gl.DEPTH_TEST);
818
+ gl.enable(gl.BLEND);
819
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE); // additive glow
820
+
821
+ // wireframe
822
+ gl.useProgram(lnProg);
823
+ gl.uniformMatrix4fv(gl.getUniformLocation(lnProg, 'uMVP'), false, mvp);
824
+ gl.bindBuffer(gl.ARRAY_BUFFER, bEdge);
825
+ gl.enableVertexAttribArray(aPos);
826
+ gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 0, 0);
827
+ gl.drawArrays(gl.LINES, 0, 24);
828
+
829
+ // points
830
+ gl.useProgram(ptProg);
831
+ gl.uniformMatrix4fv(gl.getUniformLocation(ptProg, 'uMVP'), false, mvp);
832
+ gl.uniform1f(gl.getUniformLocation(ptProg, 'uMorph'), morph);
833
+ gl.uniform1f(gl.getUniformLocation(ptProg, 'uSize'), 9.0 * dpr);
834
+ gl.bindBuffer(gl.ARRAY_BUFFER, bId);
835
+ gl.enableVertexAttribArray(aId);
836
+ gl.vertexAttribPointer(aId, 3, gl.FLOAT, false, 0, 0);
837
+ gl.bindBuffer(gl.ARRAY_BUFFER, bOut);
838
+ gl.enableVertexAttribArray(aOut);
839
+ gl.vertexAttribPointer(aOut, 3, gl.FLOAT, false, 0, 0);
840
+ gl.drawArrays(gl.POINTS, 0, n3);
841
+
842
+ requestAnimationFrame(frame);
843
+ }
844
+ requestAnimationFrame(frame);
845
+
846
+ return wrap;
847
+ }
848
+
849
  // ------------------------------------------------ privacy modal
850
  ['privacyOpen', 'privacyOpen2'].forEach(id => {
851
  const el = $(id);