@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.
- package/CHANGELOG.md +36 -0
- package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +245 -0
- package/DOCS/ANDROID_DEPLOYMENT.md +59 -0
- package/DOCS/ARCHITECTURE.md +1 -0
- package/DOCS/CI_TESTING.md +2 -0
- package/DOCS/CLI_ONBOARDING.md +3 -1
- package/DOCS/CONTROL_REFERENCE.md +2 -0
- package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +119 -0
- package/DOCS/ENV_SETUP.md +2 -0
- package/DOCS/EPIC_SCROLL_EVENTS.md +775 -0
- package/DOCS/EXPANSION_DESIGN.md +979 -0
- package/DOCS/EXPANSION_DESIGN_ULTRA.md +389 -0
- package/DOCS/EXPORT_FORMATS.md +2 -0
- package/DOCS/GPU_DISPOSAL_GUIDE.md +2 -0
- package/DOCS/HANDOFF_LANDING_PAGE.md +156 -0
- package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +495 -0
- package/DOCS/LICENSING_TIERS.md +2 -0
- package/DOCS/MASTER_PLAN_2026-01-31.md +4 -2
- package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +939 -0
- package/DOCS/OBS_SETUP_GUIDE.md +2 -0
- package/DOCS/OPTIMIZATION_PLAN_MATH.md +119 -0
- package/DOCS/PRODUCT_STRATEGY.md +65 -0
- package/DOCS/PROJECT_SETUP.md +2 -0
- package/DOCS/README.md +105 -0
- package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +99 -0
- package/DOCS/RENDERER_LIFECYCLE.md +2 -0
- package/DOCS/REPO_MANIFEST.md +2 -0
- package/DOCS/ROADMAP.md +113 -0
- package/DOCS/SCROLL_TIMELINE_v3.md +271 -0
- package/DOCS/SITE_REFACTOR_PLAN.md +102 -0
- package/DOCS/STATUS.md +26 -0
- package/DOCS/SYSTEM_INVENTORY.md +37 -32
- package/DOCS/TELEMETRY_EXPORTS.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +87 -0
- package/DOCS/VISUAL_ANALYSIS_FACETAD.md +135 -0
- package/DOCS/VISUAL_ANALYSIS_SIMONE.md +97 -0
- package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +88 -0
- package/DOCS/WEBGPU_STATUS.md +121 -38
- package/DOCS/XR_BENCHMARKS.md +2 -0
- package/DOCS/archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +1 -0
- package/DOCS/archive/DEV_TRACK_ANALYSIS.md +1 -0
- package/DOCS/archive/DEV_TRACK_PLAN_2026-01-07.md +1 -0
- package/DOCS/archive/SESSION_014_PLAN.md +1 -0
- package/DOCS/archive/SESSION_LOG_2026-01-07.md +1 -0
- package/DOCS/archive/STRATEGIC_BLUEPRINT_2026-01-07.md +1 -0
- package/DOCS/archive/SYSTEM_AUDIT_2026-01-30.md +1 -0
- package/DOCS/archive/WEBGPU_STATUS_2026-02-15_STALE.md +1 -0
- package/DOCS/{DEV_TRACK_SESSION_2026-01-31.md → dev-tracks/DEV_TRACK_SESSION_2026-01-31.md} +3 -1
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +233 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +129 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +144 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-16.md +110 -0
- package/DOCS/dev-tracks/PERF_UPGRADE_2026-02-16.md +310 -0
- package/DOCS/dev-tracks/README.md +12 -0
- package/README.md +26 -13
- package/cpp/CMakeLists.txt +236 -0
- package/cpp/bindings/embind.cpp +269 -0
- package/cpp/build.sh +129 -0
- package/cpp/geometry/Crystal.cpp +103 -0
- package/cpp/geometry/Fractal.cpp +136 -0
- package/cpp/geometry/GeometryGenerator.cpp +262 -0
- package/cpp/geometry/KleinBottle.cpp +71 -0
- package/cpp/geometry/Sphere.cpp +134 -0
- package/cpp/geometry/Tesseract.cpp +94 -0
- package/cpp/geometry/Tetrahedron.cpp +83 -0
- package/cpp/geometry/Torus.cpp +65 -0
- package/cpp/geometry/WarpFunctions.cpp +238 -0
- package/cpp/geometry/Wave.cpp +85 -0
- package/cpp/include/vib3_ffi.h +238 -0
- package/cpp/math/Mat4x4.cpp +409 -0
- package/cpp/math/Mat4x4.hpp +209 -0
- package/cpp/math/Projection.cpp +142 -0
- package/cpp/math/Projection.hpp +148 -0
- package/cpp/math/Rotor4D.cpp +322 -0
- package/cpp/math/Rotor4D.hpp +204 -0
- package/cpp/math/Vec4.cpp +303 -0
- package/cpp/math/Vec4.hpp +225 -0
- package/cpp/src/vib3_ffi.cpp +607 -0
- package/cpp/tests/Geometry_test.cpp +213 -0
- package/cpp/tests/Mat4x4_test.cpp +494 -0
- package/cpp/tests/Projection_test.cpp +298 -0
- package/cpp/tests/Rotor4D_test.cpp +423 -0
- package/cpp/tests/Vec4_test.cpp +489 -0
- package/docs/webgpu-live.html +1 -1
- package/package.json +41 -30
- package/src/agent/index.js +1 -3
- package/src/agent/mcp/MCPServer.js +1220 -144
- package/src/agent/mcp/index.js +1 -1
- package/src/agent/mcp/stdio-server.js +264 -0
- package/src/agent/mcp/tools.js +498 -31
- package/src/cli/index.js +431 -47
- package/src/core/CanvasManager.js +97 -204
- package/src/core/ErrorReporter.js +1 -1
- package/src/core/Parameters.js +1 -1
- package/src/core/VIB3Engine.js +93 -4
- package/src/core/VitalitySystem.js +53 -0
- package/src/core/index.js +18 -0
- package/src/core/renderers/FacetedRendererAdapter.js +10 -9
- package/src/core/renderers/HolographicRendererAdapter.js +13 -9
- package/src/core/renderers/QuantumRendererAdapter.js +11 -7
- package/src/creative/AestheticMapper.js +628 -0
- package/src/creative/ChoreographyPlayer.js +481 -0
- package/src/creative/index.js +11 -0
- package/src/experimental/GameLoop.js +72 -0
- package/src/experimental/LatticePhysics.js +100 -0
- package/src/experimental/LiveDirector.js +143 -0
- package/src/experimental/PlayerController4D.js +154 -0
- package/src/experimental/VIB3Actor.js +138 -0
- package/src/experimental/VIB3Compositor.js +117 -0
- package/src/experimental/VIB3Link.js +122 -0
- package/src/experimental/VIB3Orchestrator.js +146 -0
- package/src/experimental/VIB3Universe.js +109 -0
- package/src/experimental/demos/CrystalLabyrinth.js +202 -0
- package/src/export/TradingCardManager.js +3 -4
- package/src/export/index.js +11 -1
- package/src/faceted/FacetedSystem.js +260 -394
- package/src/games/glyph-war/GlyphWarVisualizer.js +641 -0
- package/src/geometry/generators/Crystal.js +2 -2
- package/src/geometry/warp/HypersphereCore.js +53 -24
- package/src/holograms/HolographicVisualizer.js +84 -98
- package/src/holograms/RealHolographicSystem.js +194 -43
- package/src/math/Mat4x4.js +308 -105
- package/src/math/Rotor4D.js +124 -40
- package/src/math/Vec4.js +200 -103
- package/src/math/index.js +7 -7
- package/src/polychora/PolychoraSystem.js +77 -0
- package/src/quantum/QuantumEngine.js +103 -66
- package/src/quantum/QuantumVisualizer.js +31 -22
- package/src/reactivity/index.js +3 -5
- package/src/render/LayerPresetManager.js +372 -0
- package/src/render/LayerReactivityBridge.js +344 -0
- package/src/render/LayerRelationshipGraph.js +610 -0
- package/src/render/MultiCanvasBridge.js +148 -25
- package/src/render/ShaderLoader.js +38 -0
- package/src/render/ShaderProgram.js +4 -4
- package/src/render/UnifiedRenderBridge.js +4 -1
- package/src/render/backends/WebGPUBackend.js +8 -4
- package/src/render/index.js +27 -2
- package/src/scene/Node4D.js +74 -24
- package/src/scene/index.js +4 -4
- package/src/shaders/common/geometry24.glsl +65 -0
- package/src/shaders/common/geometry24.wgsl +54 -0
- package/src/shaders/common/rotation4d.glsl +4 -4
- package/src/shaders/common/rotation4d.wgsl +2 -2
- package/src/shaders/common/uniforms.wgsl +15 -8
- package/src/shaders/faceted/faceted.frag.glsl +220 -80
- package/src/shaders/faceted/faceted.frag.wgsl +144 -90
- package/src/shaders/holographic/holographic.frag.glsl +28 -9
- package/src/shaders/holographic/holographic.frag.wgsl +112 -41
- package/src/shaders/quantum/quantum.frag.glsl +1 -0
- package/src/shaders/quantum/quantum.frag.wgsl +6 -4
- package/src/testing/ParallelTestFramework.js +2 -2
- package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +2 -2
- package/src/viewer/GalleryUI.js +17 -0
- package/src/viewer/ViewerPortal.js +2 -2
- package/src/viewer/index.js +1 -1
- package/tools/headless-renderer.js +258 -0
- package/tools/shader-sync-verify.js +14 -8
- package/tools/site-analysis/all-reports.json +32 -0
- package/tools/site-analysis/combined-analysis.md +50 -0
- package/tools/site-analyzer.mjs +779 -0
- package/tools/visual-catalog/capture.js +276 -0
- package/tools/visual-catalog/composite.js +138 -0
- package/types/adaptive-sdk.d.ts +204 -5
- package/types/agent/cli.d.ts +78 -0
- package/types/agent/index.d.ts +18 -0
- package/types/agent/mcp.d.ts +87 -0
- package/types/agent/telemetry.d.ts +190 -0
- package/types/core/VIB3Engine.d.ts +26 -0
- package/types/core/index.d.ts +261 -0
- package/types/creative/AestheticMapper.d.ts +72 -0
- package/types/creative/ChoreographyPlayer.d.ts +96 -0
- package/types/creative/index.d.ts +17 -0
- package/types/export/index.d.ts +243 -0
- package/types/geometry/index.d.ts +164 -0
- package/types/math/index.d.ts +214 -0
- package/types/render/LayerPresetManager.d.ts +78 -0
- package/types/render/LayerReactivityBridge.d.ts +85 -0
- package/types/render/LayerRelationshipGraph.d.ts +174 -0
- package/types/render/index.d.ts +3 -0
- package/types/scene/index.d.ts +204 -0
- package/types/systems/index.d.ts +244 -0
- package/types/variations/index.d.ts +62 -0
- package/types/viewer/index.d.ts +225 -0
- package/DOCS/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +0 -34
- package/DOCS/DEV_TRACK_ANALYSIS.md +0 -77
- package/DOCS/DEV_TRACK_PLAN_2026-01-07.md +0 -42
- package/DOCS/SESSION_014_PLAN.md +0 -195
- package/DOCS/SESSION_LOG_2026-01-07.md +0 -56
- package/DOCS/STRATEGIC_BLUEPRINT_2026-01-07.md +0 -72
- package/DOCS/SYSTEM_AUDIT_2026-01-30.md +0 -738
- /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);
|