@vib3code/sdk 2.0.1 → 2.0.3-canary.0a63e71

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.
Files changed (192) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +245 -0
  3. package/DOCS/ANDROID_DEPLOYMENT.md +59 -0
  4. package/DOCS/ARCHITECTURE.md +1 -0
  5. package/DOCS/CI_TESTING.md +2 -0
  6. package/DOCS/CLI_ONBOARDING.md +3 -1
  7. package/DOCS/CONTROL_REFERENCE.md +2 -0
  8. package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +119 -0
  9. package/DOCS/ENV_SETUP.md +2 -0
  10. package/DOCS/EPIC_SCROLL_EVENTS.md +775 -0
  11. package/DOCS/EXPANSION_DESIGN.md +979 -0
  12. package/DOCS/EXPANSION_DESIGN_ULTRA.md +389 -0
  13. package/DOCS/EXPORT_FORMATS.md +2 -0
  14. package/DOCS/GPU_DISPOSAL_GUIDE.md +2 -0
  15. package/DOCS/HANDOFF_LANDING_PAGE.md +156 -0
  16. package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +495 -0
  17. package/DOCS/LICENSING_TIERS.md +2 -0
  18. package/DOCS/MASTER_PLAN_2026-01-31.md +4 -2
  19. package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +939 -0
  20. package/DOCS/OBS_SETUP_GUIDE.md +2 -0
  21. package/DOCS/OPTIMIZATION_PLAN_MATH.md +119 -0
  22. package/DOCS/PRODUCT_STRATEGY.md +65 -0
  23. package/DOCS/PROJECT_SETUP.md +2 -0
  24. package/DOCS/README.md +105 -0
  25. package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +99 -0
  26. package/DOCS/RENDERER_LIFECYCLE.md +2 -0
  27. package/DOCS/REPO_MANIFEST.md +2 -0
  28. package/DOCS/ROADMAP.md +113 -0
  29. package/DOCS/SCROLL_TIMELINE_v3.md +271 -0
  30. package/DOCS/SITE_REFACTOR_PLAN.md +102 -0
  31. package/DOCS/STATUS.md +26 -0
  32. package/DOCS/SYSTEM_INVENTORY.md +37 -32
  33. package/DOCS/TELEMETRY_EXPORTS.md +2 -0
  34. package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +87 -0
  35. package/DOCS/VISUAL_ANALYSIS_FACETAD.md +135 -0
  36. package/DOCS/VISUAL_ANALYSIS_SIMONE.md +97 -0
  37. package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +88 -0
  38. package/DOCS/WEBGPU_STATUS.md +121 -38
  39. package/DOCS/XR_BENCHMARKS.md +2 -0
  40. package/DOCS/archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +1 -0
  41. package/DOCS/archive/DEV_TRACK_ANALYSIS.md +1 -0
  42. package/DOCS/archive/DEV_TRACK_PLAN_2026-01-07.md +1 -0
  43. package/DOCS/archive/SESSION_014_PLAN.md +1 -0
  44. package/DOCS/archive/SESSION_LOG_2026-01-07.md +1 -0
  45. package/DOCS/archive/STRATEGIC_BLUEPRINT_2026-01-07.md +1 -0
  46. package/DOCS/archive/SYSTEM_AUDIT_2026-01-30.md +1 -0
  47. package/DOCS/archive/WEBGPU_STATUS_2026-02-15_STALE.md +1 -0
  48. package/DOCS/{DEV_TRACK_SESSION_2026-01-31.md → dev-tracks/DEV_TRACK_SESSION_2026-01-31.md} +3 -1
  49. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +233 -0
  50. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +129 -0
  51. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +144 -0
  52. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-16.md +110 -0
  53. package/DOCS/dev-tracks/PERF_UPGRADE_2026-02-16.md +310 -0
  54. package/DOCS/dev-tracks/README.md +12 -0
  55. package/README.md +26 -13
  56. package/cpp/CMakeLists.txt +236 -0
  57. package/cpp/bindings/embind.cpp +269 -0
  58. package/cpp/build.sh +129 -0
  59. package/cpp/geometry/Crystal.cpp +103 -0
  60. package/cpp/geometry/Fractal.cpp +136 -0
  61. package/cpp/geometry/GeometryGenerator.cpp +262 -0
  62. package/cpp/geometry/KleinBottle.cpp +71 -0
  63. package/cpp/geometry/Sphere.cpp +134 -0
  64. package/cpp/geometry/Tesseract.cpp +94 -0
  65. package/cpp/geometry/Tetrahedron.cpp +83 -0
  66. package/cpp/geometry/Torus.cpp +65 -0
  67. package/cpp/geometry/WarpFunctions.cpp +238 -0
  68. package/cpp/geometry/Wave.cpp +85 -0
  69. package/cpp/include/vib3_ffi.h +238 -0
  70. package/cpp/math/Mat4x4.cpp +409 -0
  71. package/cpp/math/Mat4x4.hpp +209 -0
  72. package/cpp/math/Projection.cpp +142 -0
  73. package/cpp/math/Projection.hpp +148 -0
  74. package/cpp/math/Rotor4D.cpp +322 -0
  75. package/cpp/math/Rotor4D.hpp +204 -0
  76. package/cpp/math/Vec4.cpp +303 -0
  77. package/cpp/math/Vec4.hpp +225 -0
  78. package/cpp/src/vib3_ffi.cpp +607 -0
  79. package/cpp/tests/Geometry_test.cpp +213 -0
  80. package/cpp/tests/Mat4x4_test.cpp +494 -0
  81. package/cpp/tests/Projection_test.cpp +298 -0
  82. package/cpp/tests/Rotor4D_test.cpp +423 -0
  83. package/cpp/tests/Vec4_test.cpp +489 -0
  84. package/docs/webgpu-live.html +1 -1
  85. package/package.json +41 -30
  86. package/src/agent/index.js +1 -3
  87. package/src/agent/mcp/MCPServer.js +1220 -144
  88. package/src/agent/mcp/index.js +1 -1
  89. package/src/agent/mcp/stdio-server.js +264 -0
  90. package/src/agent/mcp/tools.js +498 -31
  91. package/src/cli/index.js +431 -47
  92. package/src/core/CanvasManager.js +97 -204
  93. package/src/core/ErrorReporter.js +1 -1
  94. package/src/core/Parameters.js +1 -1
  95. package/src/core/VIB3Engine.js +93 -4
  96. package/src/core/VitalitySystem.js +53 -0
  97. package/src/core/index.js +18 -0
  98. package/src/core/renderers/FacetedRendererAdapter.js +10 -9
  99. package/src/core/renderers/HolographicRendererAdapter.js +13 -9
  100. package/src/core/renderers/QuantumRendererAdapter.js +11 -7
  101. package/src/creative/AestheticMapper.js +628 -0
  102. package/src/creative/ChoreographyPlayer.js +481 -0
  103. package/src/creative/index.js +11 -0
  104. package/src/experimental/GameLoop.js +72 -0
  105. package/src/experimental/LatticePhysics.js +100 -0
  106. package/src/experimental/LiveDirector.js +143 -0
  107. package/src/experimental/PlayerController4D.js +154 -0
  108. package/src/experimental/VIB3Actor.js +138 -0
  109. package/src/experimental/VIB3Compositor.js +117 -0
  110. package/src/experimental/VIB3Link.js +122 -0
  111. package/src/experimental/VIB3Orchestrator.js +146 -0
  112. package/src/experimental/VIB3Universe.js +109 -0
  113. package/src/experimental/demos/CrystalLabyrinth.js +202 -0
  114. package/src/export/TradingCardManager.js +3 -4
  115. package/src/export/index.js +11 -1
  116. package/src/faceted/FacetedSystem.js +260 -394
  117. package/src/games/glyph-war/GlyphWarVisualizer.js +641 -0
  118. package/src/geometry/generators/Crystal.js +2 -2
  119. package/src/geometry/warp/HypersphereCore.js +53 -24
  120. package/src/holograms/HolographicVisualizer.js +84 -98
  121. package/src/holograms/RealHolographicSystem.js +194 -43
  122. package/src/math/Mat4x4.js +308 -105
  123. package/src/math/Rotor4D.js +124 -40
  124. package/src/math/Vec4.js +200 -103
  125. package/src/math/index.js +7 -7
  126. package/src/polychora/PolychoraSystem.js +77 -0
  127. package/src/quantum/QuantumEngine.js +103 -66
  128. package/src/quantum/QuantumVisualizer.js +31 -22
  129. package/src/reactivity/index.js +3 -5
  130. package/src/render/LayerPresetManager.js +372 -0
  131. package/src/render/LayerReactivityBridge.js +344 -0
  132. package/src/render/LayerRelationshipGraph.js +610 -0
  133. package/src/render/MultiCanvasBridge.js +148 -25
  134. package/src/render/ShaderLoader.js +38 -0
  135. package/src/render/ShaderProgram.js +4 -4
  136. package/src/render/UnifiedRenderBridge.js +4 -1
  137. package/src/render/backends/WebGPUBackend.js +8 -4
  138. package/src/render/index.js +27 -2
  139. package/src/scene/Node4D.js +74 -24
  140. package/src/scene/index.js +4 -4
  141. package/src/shaders/common/geometry24.glsl +65 -0
  142. package/src/shaders/common/geometry24.wgsl +54 -0
  143. package/src/shaders/common/rotation4d.glsl +4 -4
  144. package/src/shaders/common/rotation4d.wgsl +2 -2
  145. package/src/shaders/common/uniforms.wgsl +15 -8
  146. package/src/shaders/faceted/faceted.frag.glsl +220 -80
  147. package/src/shaders/faceted/faceted.frag.wgsl +144 -90
  148. package/src/shaders/holographic/holographic.frag.glsl +28 -9
  149. package/src/shaders/holographic/holographic.frag.wgsl +112 -41
  150. package/src/shaders/quantum/quantum.frag.glsl +1 -0
  151. package/src/shaders/quantum/quantum.frag.wgsl +6 -4
  152. package/src/testing/ParallelTestFramework.js +2 -2
  153. package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +2 -2
  154. package/src/viewer/GalleryUI.js +17 -0
  155. package/src/viewer/ViewerPortal.js +2 -2
  156. package/src/viewer/index.js +1 -1
  157. package/tools/headless-renderer.js +258 -0
  158. package/tools/shader-sync-verify.js +14 -8
  159. package/tools/site-analysis/all-reports.json +32 -0
  160. package/tools/site-analysis/combined-analysis.md +50 -0
  161. package/tools/site-analyzer.mjs +779 -0
  162. package/tools/visual-catalog/capture.js +276 -0
  163. package/tools/visual-catalog/composite.js +138 -0
  164. package/types/adaptive-sdk.d.ts +204 -5
  165. package/types/agent/cli.d.ts +78 -0
  166. package/types/agent/index.d.ts +18 -0
  167. package/types/agent/mcp.d.ts +87 -0
  168. package/types/agent/telemetry.d.ts +190 -0
  169. package/types/core/VIB3Engine.d.ts +26 -0
  170. package/types/core/index.d.ts +261 -0
  171. package/types/creative/AestheticMapper.d.ts +72 -0
  172. package/types/creative/ChoreographyPlayer.d.ts +96 -0
  173. package/types/creative/index.d.ts +17 -0
  174. package/types/export/index.d.ts +243 -0
  175. package/types/geometry/index.d.ts +164 -0
  176. package/types/math/index.d.ts +214 -0
  177. package/types/render/LayerPresetManager.d.ts +78 -0
  178. package/types/render/LayerReactivityBridge.d.ts +85 -0
  179. package/types/render/LayerRelationshipGraph.d.ts +174 -0
  180. package/types/render/index.d.ts +3 -0
  181. package/types/scene/index.d.ts +204 -0
  182. package/types/systems/index.d.ts +244 -0
  183. package/types/variations/index.d.ts +62 -0
  184. package/types/viewer/index.d.ts +225 -0
  185. package/DOCS/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +0 -34
  186. package/DOCS/DEV_TRACK_ANALYSIS.md +0 -77
  187. package/DOCS/DEV_TRACK_PLAN_2026-01-07.md +0 -42
  188. package/DOCS/SESSION_014_PLAN.md +0 -195
  189. package/DOCS/SESSION_LOG_2026-01-07.md +0 -56
  190. package/DOCS/STRATEGIC_BLUEPRINT_2026-01-07.md +0 -72
  191. package/DOCS/SYSTEM_AUDIT_2026-01-30.md +0 -738
  192. /package/src/viewer/{ReactivityManager.js → ViewerInputHandler.js} +0 -0
@@ -0,0 +1,779 @@
1
+ /**
2
+ * Playwright Site Analyzer — Scroll Effect Documentation
3
+ * Captures screenshots at scroll intervals and documents DOM structure,
4
+ * CSS animations, transforms, and scroll-driven effects.
5
+ */
6
+ import { createRequire } from 'module';
7
+ const require = createRequire(import.meta.url);
8
+ const { chromium } = require('playwright');
9
+ import { writeFileSync, mkdirSync } from 'fs';
10
+ import { join } from 'path';
11
+
12
+ const SITES = [
13
+ { name: 'clickerss', url: 'https://www.clickerss.com/' },
14
+ { name: 'facetad', url: 'https://www.facetad.com/' },
15
+ { name: 'wix-template-3630', url: 'https://www.wix.com/website-template/view/html/3630' },
16
+ { name: 'tableside', url: 'https://www.tableside.com.au/' },
17
+ { name: 'wix-studio-space', url: 'https://www.wix.com/studio/design/inspiration/space' },
18
+ { name: 'simone-webflow', url: 'https://weare-simone.webflow.io/' },
19
+ ];
20
+
21
+ const OUT_DIR = join(process.cwd(), 'tools', 'site-analysis');
22
+ mkdirSync(OUT_DIR, { recursive: true });
23
+
24
+ // Analysis script injected into each page
25
+ const ANALYSIS_SCRIPT = `
26
+ (() => {
27
+ const results = {
28
+ totalHeight: document.documentElement.scrollHeight,
29
+ viewportHeight: window.innerHeight,
30
+ viewportWidth: window.innerWidth,
31
+ scrollRatio: document.documentElement.scrollHeight / window.innerHeight,
32
+ sections: [],
33
+ animations: [],
34
+ stickyElements: [],
35
+ parallaxCandidates: [],
36
+ clipPathElements: [],
37
+ transformElements: [],
38
+ opacityTransitions: [],
39
+ scaleElements: [],
40
+ blendModes: [],
41
+ gradients: [],
42
+ videoElements: [],
43
+ canvasElements: [],
44
+ svgAnimations: [],
45
+ typographyEffects: [],
46
+ interactiveElements: [],
47
+ fixedElements: [],
48
+ };
49
+
50
+ const allElements = document.querySelectorAll('*');
51
+
52
+ allElements.forEach((el, i) => {
53
+ if (i > 3000) return; // Cap for performance
54
+ const cs = getComputedStyle(el);
55
+ const rect = el.getBoundingClientRect();
56
+
57
+ // Skip invisible/tiny elements
58
+ if (rect.width < 10 || rect.height < 10) return;
59
+
60
+ const tag = el.tagName.toLowerCase();
61
+ const classes = el.className && typeof el.className === 'string' ? el.className.substring(0, 120) : '';
62
+ const id = el.id ? el.id.substring(0, 60) : '';
63
+ const identifier = id || classes.split(' ')[0] || tag;
64
+
65
+ // Sticky/Fixed positioning
66
+ if (cs.position === 'sticky') {
67
+ results.stickyElements.push({
68
+ el: identifier, tag, top: cs.top,
69
+ y: Math.round(rect.top), h: Math.round(rect.height),
70
+ });
71
+ }
72
+ if (cs.position === 'fixed') {
73
+ results.fixedElements.push({
74
+ el: identifier, tag,
75
+ y: Math.round(rect.top), h: Math.round(rect.height),
76
+ zIndex: cs.zIndex,
77
+ });
78
+ }
79
+
80
+ // Transform detection
81
+ if (cs.transform && cs.transform !== 'none') {
82
+ results.transformElements.push({
83
+ el: identifier, tag,
84
+ transform: cs.transform.substring(0, 200),
85
+ willChange: cs.willChange,
86
+ y: Math.round(rect.top),
87
+ });
88
+ }
89
+
90
+ // Clip-path detection
91
+ if (cs.clipPath && cs.clipPath !== 'none') {
92
+ results.clipPathElements.push({
93
+ el: identifier, tag,
94
+ clipPath: cs.clipPath.substring(0, 200),
95
+ y: Math.round(rect.top),
96
+ });
97
+ }
98
+
99
+ // Opacity (not fully opaque)
100
+ if (cs.opacity !== '1' && cs.opacity !== '') {
101
+ results.opacityTransitions.push({
102
+ el: identifier, opacity: cs.opacity,
103
+ y: Math.round(rect.top),
104
+ });
105
+ }
106
+
107
+ // Scale transforms
108
+ if (cs.transform && (cs.transform.includes('scale') || cs.transform.includes('matrix'))) {
109
+ results.scaleElements.push({
110
+ el: identifier,
111
+ transform: cs.transform.substring(0, 150),
112
+ y: Math.round(rect.top),
113
+ });
114
+ }
115
+
116
+ // Blend modes
117
+ if (cs.mixBlendMode && cs.mixBlendMode !== 'normal') {
118
+ results.blendModes.push({
119
+ el: identifier, mode: cs.mixBlendMode,
120
+ y: Math.round(rect.top),
121
+ });
122
+ }
123
+
124
+ // Gradients in background
125
+ if (cs.backgroundImage && cs.backgroundImage.includes('gradient')) {
126
+ results.gradients.push({
127
+ el: identifier,
128
+ gradient: cs.backgroundImage.substring(0, 300),
129
+ y: Math.round(rect.top),
130
+ });
131
+ }
132
+
133
+ // CSS animations
134
+ if (cs.animationName && cs.animationName !== 'none') {
135
+ results.animations.push({
136
+ el: identifier,
137
+ name: cs.animationName,
138
+ duration: cs.animationDuration,
139
+ timing: cs.animationTimingFunction,
140
+ delay: cs.animationDelay,
141
+ iteration: cs.animationIterationCount,
142
+ y: Math.round(rect.top),
143
+ });
144
+ }
145
+
146
+ // CSS transitions
147
+ if (cs.transition && cs.transition !== 'all 0s ease 0s' && cs.transition !== 'none 0s ease 0s') {
148
+ results.interactiveElements.push({
149
+ el: identifier,
150
+ transition: cs.transition.substring(0, 200),
151
+ cursor: cs.cursor,
152
+ y: Math.round(rect.top),
153
+ });
154
+ }
155
+
156
+ // Video elements
157
+ if (tag === 'video') {
158
+ results.videoElements.push({
159
+ el: identifier,
160
+ src: (el.src || el.querySelector('source')?.src || '').substring(0, 200),
161
+ autoplay: el.autoplay, loop: el.loop, muted: el.muted,
162
+ w: Math.round(rect.width), h: Math.round(rect.height),
163
+ y: Math.round(rect.top),
164
+ });
165
+ }
166
+
167
+ // Canvas elements
168
+ if (tag === 'canvas') {
169
+ results.canvasElements.push({
170
+ el: identifier,
171
+ w: el.width, h: el.height,
172
+ cssW: Math.round(rect.width), cssH: Math.round(rect.height),
173
+ y: Math.round(rect.top),
174
+ });
175
+ }
176
+
177
+ // SVG elements
178
+ if (tag === 'svg' && rect.height > 30) {
179
+ results.svgAnimations.push({
180
+ el: identifier,
181
+ w: Math.round(rect.width), h: Math.round(rect.height),
182
+ y: Math.round(rect.top),
183
+ childCount: el.children.length,
184
+ });
185
+ }
186
+
187
+ // Parallax candidates (elements with translateY or will-change: transform in scrollable context)
188
+ if (cs.willChange === 'transform' || cs.willChange === 'scroll-position' ||
189
+ (cs.transform && cs.transform !== 'none' && cs.position !== 'fixed')) {
190
+ if (!results.parallaxCandidates.find(p => p.el === identifier)) {
191
+ results.parallaxCandidates.push({
192
+ el: identifier, tag,
193
+ transform: (cs.transform || '').substring(0, 150),
194
+ willChange: cs.willChange,
195
+ y: Math.round(rect.top),
196
+ h: Math.round(rect.height),
197
+ });
198
+ }
199
+ }
200
+
201
+ // Large sections
202
+ if (rect.height > window.innerHeight * 0.5 &&
203
+ ['section', 'div', 'main', 'article'].includes(tag)) {
204
+ if (classes || id) {
205
+ results.sections.push({
206
+ el: identifier, tag,
207
+ y: Math.round(rect.top),
208
+ h: Math.round(rect.height),
209
+ vh: (rect.height / window.innerHeight).toFixed(1),
210
+ bg: cs.backgroundColor !== 'rgba(0, 0, 0, 0)' ? cs.backgroundColor : null,
211
+ overflow: cs.overflow,
212
+ position: cs.position,
213
+ });
214
+ }
215
+ }
216
+ });
217
+
218
+ return results;
219
+ })()
220
+ `;
221
+
222
+ // Script to detect element changes during scroll
223
+ const SCROLL_DIFF_SCRIPT = `
224
+ ((scrollY) => {
225
+ const snapshot = [];
226
+ const allElements = document.querySelectorAll('*');
227
+ let count = 0;
228
+
229
+ allElements.forEach((el) => {
230
+ if (count > 1500) return;
231
+ const cs = getComputedStyle(el);
232
+ const rect = el.getBoundingClientRect();
233
+ if (rect.width < 10 || rect.height < 10) return;
234
+
235
+ const tag = el.tagName.toLowerCase();
236
+ const classes = el.className && typeof el.className === 'string' ? el.className.substring(0, 80) : '';
237
+ const id = el.id ? el.id.substring(0, 40) : '';
238
+ const identifier = id || classes.split(' ')[0] || tag;
239
+
240
+ // Only track elements with interesting properties
241
+ if (cs.transform !== 'none' || cs.opacity !== '1' ||
242
+ cs.position === 'sticky' || cs.position === 'fixed' ||
243
+ (cs.clipPath && cs.clipPath !== 'none')) {
244
+ snapshot.push({
245
+ id: identifier,
246
+ transform: cs.transform !== 'none' ? cs.transform.substring(0, 100) : null,
247
+ opacity: cs.opacity !== '1' ? cs.opacity : null,
248
+ clipPath: cs.clipPath !== 'none' ? cs.clipPath?.substring(0, 100) : null,
249
+ top: Math.round(rect.top),
250
+ left: Math.round(rect.left),
251
+ width: Math.round(rect.width),
252
+ height: Math.round(rect.height),
253
+ visible: rect.top < window.innerHeight && rect.bottom > 0,
254
+ });
255
+ count++;
256
+ }
257
+ });
258
+
259
+ return { scrollY, snapshot };
260
+ })(window.scrollY)
261
+ `;
262
+
263
+ async function analyzeSite(browser, site) {
264
+ console.log(`\\n${'='.repeat(60)}`);
265
+ console.log(`ANALYZING: ${site.name} (${site.url})`);
266
+ console.log('='.repeat(60));
267
+
268
+ const siteDir = join(OUT_DIR, site.name);
269
+ mkdirSync(siteDir, { recursive: true });
270
+
271
+ const context = await browser.newContext({
272
+ viewport: { width: 1440, height: 900 },
273
+ ignoreHTTPSErrors: true,
274
+ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
275
+ });
276
+
277
+ const page = await context.newPage();
278
+
279
+ try {
280
+ // Navigate with generous timeout
281
+ await page.goto(site.url, { waitUntil: 'networkidle', timeout: 30000 });
282
+ // Wait for JS animations to initialize
283
+ await page.waitForTimeout(3000);
284
+
285
+ // Take initial screenshot
286
+ await page.screenshot({ path: join(siteDir, '00-top.png'), fullPage: false });
287
+
288
+ // Run initial analysis at top of page
289
+ const initialAnalysis = await page.evaluate(ANALYSIS_SCRIPT);
290
+ console.log(` Page height: ${initialAnalysis.totalHeight}px (${initialAnalysis.scrollRatio.toFixed(1)}x viewport)`);
291
+ console.log(` Sections: ${initialAnalysis.sections.length}`);
292
+ console.log(` Animations: ${initialAnalysis.animations.length}`);
293
+ console.log(` Transform elements: ${initialAnalysis.transformElements.length}`);
294
+ console.log(` Parallax candidates: ${initialAnalysis.parallaxCandidates.length}`);
295
+ console.log(` Clip-path elements: ${initialAnalysis.clipPathElements.length}`);
296
+ console.log(` Sticky elements: ${initialAnalysis.stickyElements.length}`);
297
+ console.log(` Videos: ${initialAnalysis.videoElements.length}`);
298
+ console.log(` Canvas: ${initialAnalysis.canvasElements.length}`);
299
+ console.log(` SVGs: ${initialAnalysis.svgAnimations.length}`);
300
+ console.log(` Gradients: ${initialAnalysis.gradients.length}`);
301
+ console.log(` Blend modes: ${initialAnalysis.blendModes.length}`);
302
+
303
+ // Scroll through the page in increments, capturing snapshots
304
+ const totalHeight = initialAnalysis.totalHeight;
305
+ const viewportH = initialAnalysis.viewportHeight;
306
+ const scrollSteps = Math.min(25, Math.ceil(totalHeight / (viewportH * 0.5)));
307
+ const stepSize = totalHeight / scrollSteps;
308
+
309
+ const scrollSnapshots = [];
310
+
311
+ for (let i = 0; i <= scrollSteps; i++) {
312
+ const scrollTo = Math.min(i * stepSize, totalHeight - viewportH);
313
+
314
+ // Use wheel events to trigger Lenis/GSAP scroll (window.scrollTo won't trigger them)
315
+ const currentY = await page.evaluate(() => window.scrollY || window.pageYOffset);
316
+ const delta = scrollTo - currentY;
317
+ const wheelSteps = Math.max(1, Math.ceil(Math.abs(delta) / 400));
318
+ const perStep = delta / wheelSteps;
319
+ for (let w = 0; w < wheelSteps; w++) {
320
+ await page.mouse.wheel(0, perStep);
321
+ await page.waitForTimeout(40);
322
+ }
323
+ await page.waitForTimeout(800); // Let Lenis lerp + animations settle
324
+
325
+ // Capture screenshot
326
+ const screenshotName = `scroll-${String(i).padStart(2, '0')}-${Math.round(scrollTo)}px.png`;
327
+ await page.screenshot({ path: join(siteDir, screenshotName), fullPage: false });
328
+
329
+ // Capture element state at this scroll position
330
+ const snapshot = await page.evaluate(SCROLL_DIFF_SCRIPT);
331
+ scrollSnapshots.push(snapshot);
332
+ }
333
+
334
+ // Analyze scroll-driven changes
335
+ const scrollEffects = analyzeScrollChanges(scrollSnapshots);
336
+
337
+ // Combine all data
338
+ const report = {
339
+ site: site.name,
340
+ url: site.url,
341
+ pageMetrics: {
342
+ totalHeight: totalHeight,
343
+ viewportHeight: viewportH,
344
+ scrollRatio: (totalHeight / viewportH).toFixed(1),
345
+ scrollSteps: scrollSteps,
346
+ },
347
+ initialAnalysis,
348
+ scrollEffects,
349
+ screenshotCount: scrollSteps + 1,
350
+ };
351
+
352
+ // Write JSON report
353
+ writeFileSync(
354
+ join(siteDir, 'analysis.json'),
355
+ JSON.stringify(report, null, 2)
356
+ );
357
+
358
+ console.log(` Scroll effects detected: ${scrollEffects.length}`);
359
+ console.log(` Screenshots: ${scrollSteps + 1}`);
360
+ console.log(` Report saved: ${siteDir}/analysis.json`);
361
+
362
+ return report;
363
+
364
+ } catch (err) {
365
+ console.error(` ERROR analyzing ${site.name}: ${err.message}`);
366
+ return { site: site.name, url: site.url, error: err.message };
367
+ } finally {
368
+ await context.close();
369
+ }
370
+ }
371
+
372
+ function analyzeScrollChanges(snapshots) {
373
+ const effects = [];
374
+
375
+ if (snapshots.length < 2) return effects;
376
+
377
+ // Track elements across scroll positions
378
+ const elementHistory = new Map();
379
+
380
+ snapshots.forEach((snap, snapIdx) => {
381
+ snap.snapshot.forEach((el) => {
382
+ if (!elementHistory.has(el.id)) {
383
+ elementHistory.set(el.id, []);
384
+ }
385
+ elementHistory.get(el.id).push({
386
+ scrollY: snap.scrollY,
387
+ snapIdx,
388
+ ...el,
389
+ });
390
+ });
391
+ });
392
+
393
+ // Detect scroll-driven effects
394
+ elementHistory.forEach((history, elId) => {
395
+ if (history.length < 3) return;
396
+
397
+ // Detect parallax (position changes at different rate than scroll)
398
+ const topDeltas = [];
399
+ for (let i = 1; i < history.length; i++) {
400
+ const scrollDelta = history[i].scrollY - history[i-1].scrollY;
401
+ const topDelta = history[i].top - history[i-1].top;
402
+ if (scrollDelta > 0) {
403
+ topDeltas.push({
404
+ ratio: topDelta / -scrollDelta,
405
+ scrollY: history[i].scrollY,
406
+ });
407
+ }
408
+ }
409
+
410
+ if (topDeltas.length > 2) {
411
+ const avgRatio = topDeltas.reduce((a, b) => a + b.ratio, 0) / topDeltas.length;
412
+ if (Math.abs(avgRatio - 1) > 0.1 && Math.abs(avgRatio) < 5) {
413
+ effects.push({
414
+ type: 'parallax',
415
+ element: elId,
416
+ speedRatio: avgRatio.toFixed(2),
417
+ direction: avgRatio > 1 ? 'faster-than-scroll' : avgRatio > 0 ? 'slower-than-scroll' : 'reverse',
418
+ });
419
+ }
420
+ }
421
+
422
+ // Detect opacity transitions
423
+ const opacityChanges = history.filter(h => h.opacity !== null);
424
+ if (opacityChanges.length > 1) {
425
+ const opacities = opacityChanges.map(h => parseFloat(h.opacity));
426
+ const min = Math.min(...opacities);
427
+ const max = Math.max(...opacities);
428
+ if (max - min > 0.2) {
429
+ effects.push({
430
+ type: 'opacity-transition',
431
+ element: elId,
432
+ range: `${min.toFixed(2)} → ${max.toFixed(2)}`,
433
+ startScroll: opacityChanges[0].scrollY,
434
+ endScroll: opacityChanges[opacityChanges.length - 1].scrollY,
435
+ });
436
+ }
437
+ }
438
+
439
+ // Detect transform changes (scale, rotation)
440
+ const transformChanges = history.filter(h => h.transform !== null);
441
+ if (transformChanges.length > 1) {
442
+ const unique = new Set(transformChanges.map(h => h.transform));
443
+ if (unique.size > 2) {
444
+ effects.push({
445
+ type: 'transform-animation',
446
+ element: elId,
447
+ sampleCount: unique.size,
448
+ firstTransform: transformChanges[0].transform,
449
+ lastTransform: transformChanges[transformChanges.length - 1].transform,
450
+ });
451
+ }
452
+ }
453
+
454
+ // Detect clip-path changes
455
+ const clipChanges = history.filter(h => h.clipPath !== null);
456
+ if (clipChanges.length > 1) {
457
+ const unique = new Set(clipChanges.map(h => h.clipPath));
458
+ if (unique.size > 1) {
459
+ effects.push({
460
+ type: 'clip-path-morph',
461
+ element: elId,
462
+ first: clipChanges[0].clipPath,
463
+ last: clipChanges[clipChanges.length - 1].clipPath,
464
+ });
465
+ }
466
+ }
467
+
468
+ // Detect size changes (scale effects)
469
+ const sizeChanges = history.filter(h => h.visible);
470
+ if (sizeChanges.length > 3) {
471
+ const widths = sizeChanges.map(h => h.width);
472
+ const heights = sizeChanges.map(h => h.height);
473
+ const widthRange = Math.max(...widths) - Math.min(...widths);
474
+ const heightRange = Math.max(...heights) - Math.min(...heights);
475
+ if (widthRange > 50 || heightRange > 50) {
476
+ effects.push({
477
+ type: 'size-morph',
478
+ element: elId,
479
+ widthRange: `${Math.min(...widths)} → ${Math.max(...widths)}`,
480
+ heightRange: `${Math.min(...heights)} → ${Math.max(...heights)}`,
481
+ });
482
+ }
483
+ }
484
+ });
485
+
486
+ return effects;
487
+ }
488
+
489
+ // Summarize a single site report into markdown
490
+ function summarizeSite(report) {
491
+ if (report.error) {
492
+ return `### ${report.site} (${report.url})\n\n**ERROR**: ${report.error}\n\n`;
493
+ }
494
+
495
+ const a = report.initialAnalysis;
496
+ let md = '';
497
+
498
+ md += `### ${report.site} (${report.url})\n\n`;
499
+ md += `**Page Metrics**: ${a.totalHeight}px total (${report.pageMetrics.scrollRatio}x viewport), ${a.sections.length} sections\n\n`;
500
+
501
+ // Sections
502
+ if (a.sections.length > 0) {
503
+ md += `#### Layout Sections\n`;
504
+ a.sections.forEach(s => {
505
+ md += `- **${s.el}** (${s.tag}): ${s.h}px (${s.vh}vh) at y=${s.y}, pos=${s.position}`;
506
+ if (s.bg) md += `, bg=${s.bg}`;
507
+ md += `\n`;
508
+ });
509
+ md += '\n';
510
+ }
511
+
512
+ // Animations
513
+ if (a.animations.length > 0) {
514
+ md += `#### CSS Animations (${a.animations.length})\n`;
515
+ a.animations.forEach(an => {
516
+ md += `- **${an.el}**: \`${an.name}\` ${an.duration} ${an.timing} (iter: ${an.iteration})\n`;
517
+ });
518
+ md += '\n';
519
+ }
520
+
521
+ // Transforms
522
+ if (a.transformElements.length > 0) {
523
+ md += `#### Transform Elements (${a.transformElements.length})\n`;
524
+ a.transformElements.slice(0, 20).forEach(t => {
525
+ md += `- **${t.el}**: \`${t.transform}\``;
526
+ if (t.willChange !== 'auto') md += ` (will-change: ${t.willChange})`;
527
+ md += `\n`;
528
+ });
529
+ if (a.transformElements.length > 20) md += `- ... and ${a.transformElements.length - 20} more\n`;
530
+ md += '\n';
531
+ }
532
+
533
+ // Parallax candidates
534
+ if (a.parallaxCandidates.length > 0) {
535
+ md += `#### Parallax Candidates (${a.parallaxCandidates.length})\n`;
536
+ a.parallaxCandidates.slice(0, 15).forEach(p => {
537
+ md += `- **${p.el}**: ${p.transform || 'no transform'} (will-change: ${p.willChange}), h=${p.h}px\n`;
538
+ });
539
+ md += '\n';
540
+ }
541
+
542
+ // Clip-paths
543
+ if (a.clipPathElements.length > 0) {
544
+ md += `#### Clip-Path Elements (${a.clipPathElements.length})\n`;
545
+ a.clipPathElements.forEach(c => {
546
+ md += `- **${c.el}**: \`${c.clipPath}\`\n`;
547
+ });
548
+ md += '\n';
549
+ }
550
+
551
+ // Sticky/Fixed
552
+ if (a.stickyElements.length > 0) {
553
+ md += `#### Sticky Elements (${a.stickyElements.length})\n`;
554
+ a.stickyElements.forEach(s => {
555
+ md += `- **${s.el}** (${s.tag}): top=${s.top}, h=${s.h}px\n`;
556
+ });
557
+ md += '\n';
558
+ }
559
+
560
+ if (a.fixedElements.length > 0) {
561
+ md += `#### Fixed Elements (${a.fixedElements.length})\n`;
562
+ a.fixedElements.forEach(f => {
563
+ md += `- **${f.el}** (${f.tag}): z=${f.zIndex}, h=${f.h}px\n`;
564
+ });
565
+ md += '\n';
566
+ }
567
+
568
+ // Blend modes
569
+ if (a.blendModes.length > 0) {
570
+ md += `#### Blend Modes\n`;
571
+ a.blendModes.forEach(b => {
572
+ md += `- **${b.el}**: \`${b.mode}\`\n`;
573
+ });
574
+ md += '\n';
575
+ }
576
+
577
+ // Gradients
578
+ if (a.gradients.length > 0) {
579
+ md += `#### Gradients (${a.gradients.length})\n`;
580
+ a.gradients.slice(0, 10).forEach(g => {
581
+ md += `- **${g.el}**: \`${g.gradient.substring(0, 120)}...\`\n`;
582
+ });
583
+ md += '\n';
584
+ }
585
+
586
+ // Videos / Canvas / SVG
587
+ if (a.videoElements.length > 0) {
588
+ md += `#### Video Elements (${a.videoElements.length})\n`;
589
+ a.videoElements.forEach(v => {
590
+ md += `- **${v.el}**: ${v.w}x${v.h}, autoplay=${v.autoplay}, loop=${v.loop}\n`;
591
+ });
592
+ md += '\n';
593
+ }
594
+ if (a.canvasElements.length > 0) {
595
+ md += `#### Canvas Elements (${a.canvasElements.length})\n`;
596
+ a.canvasElements.forEach(c => {
597
+ md += `- **${c.el}**: ${c.w}x${c.h} (CSS: ${c.cssW}x${c.cssH})\n`;
598
+ });
599
+ md += '\n';
600
+ }
601
+
602
+ // Scroll-driven effects
603
+ if (report.scrollEffects.length > 0) {
604
+ md += `#### Scroll-Driven Effects (${report.scrollEffects.length})\n`;
605
+
606
+ const byType = {};
607
+ report.scrollEffects.forEach(e => {
608
+ if (!byType[e.type]) byType[e.type] = [];
609
+ byType[e.type].push(e);
610
+ });
611
+
612
+ Object.entries(byType).forEach(([type, effects]) => {
613
+ md += `\n**${type}** (${effects.length}):\n`;
614
+ effects.slice(0, 8).forEach(e => {
615
+ if (type === 'parallax') {
616
+ md += `- \`${e.element}\`: speed ratio ${e.speedRatio} (${e.direction})\n`;
617
+ } else if (type === 'opacity-transition') {
618
+ md += `- \`${e.element}\`: ${e.range} (scroll ${e.startScroll}→${e.endScroll})\n`;
619
+ } else if (type === 'transform-animation') {
620
+ md += `- \`${e.element}\`: ${e.sampleCount} unique transforms\n`;
621
+ } else if (type === 'clip-path-morph') {
622
+ md += `- \`${e.element}\`: \`${e.first}\` → \`${e.last}\`\n`;
623
+ } else if (type === 'size-morph') {
624
+ md += `- \`${e.element}\`: w=${e.widthRange}, h=${e.heightRange}\n`;
625
+ }
626
+ });
627
+ });
628
+ md += '\n';
629
+ }
630
+
631
+ // Interactive elements summary
632
+ if (a.interactiveElements.length > 0) {
633
+ md += `#### Interactive Elements (${a.interactiveElements.length})\n`;
634
+ const cursors = {};
635
+ a.interactiveElements.forEach(el => {
636
+ const c = el.cursor || 'default';
637
+ cursors[c] = (cursors[c] || 0) + 1;
638
+ });
639
+ Object.entries(cursors).forEach(([cursor, count]) => {
640
+ md += `- cursor: ${cursor} (${count} elements)\n`;
641
+ });
642
+ md += '\n';
643
+ }
644
+
645
+ md += '---\n\n';
646
+ return md;
647
+ }
648
+
649
+ // Local proxy forwarder that handles authenticated upstream proxy
650
+ function startLocalProxy() {
651
+ return new Promise((resolve) => {
652
+ const http = require('http');
653
+ const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY;
654
+ if (!proxyUrl) { resolve({ port: 0, close() {} }); return; }
655
+ const url = new URL(proxyUrl);
656
+ const AUTH = 'Basic ' + Buffer.from(
657
+ decodeURIComponent(url.username) + ':' + decodeURIComponent(url.password)
658
+ ).toString('base64');
659
+
660
+ const server = http.createServer((req, res) => {
661
+ const opts = {
662
+ host: url.hostname, port: parseInt(url.port),
663
+ method: req.method, path: req.url,
664
+ headers: { ...req.headers, 'Proxy-Authorization': AUTH },
665
+ };
666
+ const upstream = http.request(opts, (upRes) => {
667
+ res.writeHead(upRes.statusCode, upRes.headers);
668
+ upRes.pipe(res);
669
+ });
670
+ upstream.on('error', (e) => res.end('Error: ' + e.message));
671
+ req.pipe(upstream);
672
+ });
673
+
674
+ server.on('connect', (req, cltSocket, head) => {
675
+ const connectReq = http.request({
676
+ host: url.hostname, port: parseInt(url.port),
677
+ method: 'CONNECT', path: req.url,
678
+ headers: { 'Host': req.url, 'Proxy-Authorization': AUTH },
679
+ });
680
+ connectReq.on('connect', (res, srvSocket) => {
681
+ cltSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
682
+ srvSocket.write(head);
683
+ srvSocket.pipe(cltSocket);
684
+ cltSocket.pipe(srvSocket);
685
+ });
686
+ connectReq.on('error', () => {
687
+ cltSocket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n');
688
+ cltSocket.end();
689
+ });
690
+ connectReq.end();
691
+ });
692
+
693
+ server.listen(0, '127.0.0.1', () => {
694
+ resolve({ port: server.address().port, close() { server.close(); } });
695
+ });
696
+ });
697
+ }
698
+
699
+ async function main() {
700
+ console.log('VIB3+ Site Analyzer — Scroll Effect Documentation');
701
+ console.log('Launching Chromium...');
702
+
703
+ // Parse proxy from environment for Chromium
704
+ const proxyConfig = (() => {
705
+ const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY;
706
+ if (!proxyUrl) return {};
707
+ try {
708
+ const url = new URL(proxyUrl);
709
+ return {
710
+ proxy: {
711
+ server: `${url.protocol}//${url.hostname}:${url.port}`,
712
+ username: decodeURIComponent(url.username),
713
+ password: decodeURIComponent(url.password),
714
+ },
715
+ };
716
+ } catch {
717
+ return {};
718
+ }
719
+ })();
720
+
721
+ if (proxyConfig.proxy) {
722
+ console.log(`Using proxy: ${proxyConfig.proxy.server} (authenticated)`);
723
+ }
724
+
725
+ // Start a local proxy forwarder to handle auth (Chromium can't do long JWT auth)
726
+ const localProxy = await startLocalProxy();
727
+ console.log(`Local proxy forwarder on port ${localProxy.port}`);
728
+
729
+ const browser = await chromium.launch({
730
+ headless: true,
731
+ executablePath: '/root/.cache/ms-playwright/chromium-1194/chrome-linux/chrome',
732
+ args: [
733
+ '--no-sandbox', '--disable-setuid-sandbox', '--ignore-certificate-errors',
734
+ '--disable-gpu', '--disable-dev-shm-usage', '--single-process',
735
+ ],
736
+ proxy: { server: `http://127.0.0.1:${localProxy.port}` },
737
+ });
738
+
739
+ const reports = [];
740
+
741
+ for (const site of SITES) {
742
+ try {
743
+ const report = await analyzeSite(browser, site);
744
+ reports.push(report);
745
+ } catch (err) {
746
+ console.error(`Failed to analyze ${site.name}: ${err.message}`);
747
+ reports.push({ site: site.name, url: site.url, error: err.message });
748
+ }
749
+ }
750
+
751
+ await browser.close();
752
+ localProxy.close();
753
+
754
+ // Generate combined markdown report
755
+ let combinedMd = '# Reference Site Scroll Analysis\n\n';
756
+ combinedMd += `*Generated: ${new Date().toISOString()}*\n\n`;
757
+ combinedMd += `Analyzed ${reports.length} sites for scroll effects, parallax, morphing, depth illusions, and multi-element coordination.\n\n`;
758
+ combinedMd += '---\n\n';
759
+
760
+ reports.forEach(report => {
761
+ combinedMd += summarizeSite(report);
762
+ });
763
+
764
+ writeFileSync(join(OUT_DIR, 'combined-analysis.md'), combinedMd);
765
+
766
+ // Also write structured JSON for all reports
767
+ writeFileSync(
768
+ join(OUT_DIR, 'all-reports.json'),
769
+ JSON.stringify(reports, null, 2)
770
+ );
771
+
772
+ console.log(`\\n${'='.repeat(60)}`);
773
+ console.log('ANALYSIS COMPLETE');
774
+ console.log(`Reports: ${join(OUT_DIR, 'combined-analysis.md')}`);
775
+ console.log(`JSON: ${join(OUT_DIR, 'all-reports.json')}`);
776
+ console.log('='.repeat(60));
777
+ }
778
+
779
+ main().catch(console.error);