@veolab/discoverylab 1.4.2 → 1.4.4

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 (37) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/{chunk-MDKX7CCB.js → chunk-2UUMLAVR.js} +71 -8695
  4. package/dist/{chunk-34KRJWZL.js → chunk-34GGYFXX.js} +1 -1
  5. package/dist/{chunk-DGXAP477.js → chunk-3ERJNXYM.js} +1 -1
  6. package/dist/{chunk-VYYAP5G5.js → chunk-6GK5K6CS.js} +2 -2
  7. package/dist/{chunk-DKAX5RCX.js → chunk-7R5YNOXE.js} +1 -1
  8. package/dist/{chunk-RCY26WEK.js → chunk-CUBQRT5L.js} +2 -2
  9. package/dist/{chunk-EU63HPKT.js → chunk-GRU332L4.js} +1 -1
  10. package/dist/{chunk-YNLUOZSZ.js → chunk-HB3YPWF3.js} +6 -6
  11. package/dist/{chunk-SWZIBO2R.js → chunk-ILDZMFOR.js} +1 -1
  12. package/dist/{chunk-QMUEC6B5.js → chunk-PMCXEA7J.js} +2 -2
  13. package/dist/chunk-R5U7XKVJ.js +16 -0
  14. package/dist/{chunk-XAMA3JJG.js → chunk-YYOK2RF7.js} +1 -1
  15. package/dist/cli.js +29 -29
  16. package/dist/{db-745LC5YC.js → db-5ECN3O7F.js} +2 -2
  17. package/dist/{document-AE4XI2CP.js → document-64X4JTLM.js} +1 -1
  18. package/dist/{esvp-4LIAU76K.js → esvp-DKZXSABS.js} +3 -3
  19. package/dist/{esvp-mobile-FKFHDS5Q.js → esvp-mobile-TMNCSWHF.js} +4 -4
  20. package/dist/{frames-RCNLSDD6.js → frames-2NFCSKXQ.js} +3 -3
  21. package/dist/{gridCompositor-VUWBZXYL.js → gridCompositor-WC4KSGSQ.js} +1 -1
  22. package/dist/index.js +12 -12
  23. package/dist/{notion-api-OXSWOJPZ.js → notion-api-ZHGIYCQU.js} +1 -1
  24. package/dist/{ocr-FXRLEP66.js → ocr-YUYGFUKU.js} +1 -1
  25. package/dist/{playwright-GYKUH34L.js → playwright-JXLD4CM5.js} +3 -3
  26. package/dist/{renderer-D22GCMMD.js → renderer-KHPDJF5E.js} +4 -4
  27. package/dist/{server-NTT2XGCC.js → server-OVOACIOJ.js} +1 -1
  28. package/dist/server-QFNKZCOJ.js +24 -0
  29. package/dist/{setup-O6WQQAGP.js → setup-6JJYKKBS.js} +3 -3
  30. package/dist/{tools-FVVWKEGC.js → tools-Q7OZO732.js} +9 -9
  31. package/dist/visualizations/app-flow-map.html +177 -0
  32. package/dist/visualizations/device-showcase.html +150 -0
  33. package/dist/visualizations/flow-diagram.html +181 -0
  34. package/dist/visualizations/metrics-dashboard.html +263 -0
  35. package/package.json +3 -3
  36. package/dist/chunk-4VNS5WPM.js +0 -42
  37. package/dist/server-3DEELYXB.js +0 -24
@@ -4,11 +4,11 @@ import {
4
4
  setupInstallTool,
5
5
  setupStatusTool,
6
6
  setupTools
7
- } from "./chunk-RCY26WEK.js";
7
+ } from "./chunk-CUBQRT5L.js";
8
8
  import "./chunk-XKX6NBHF.js";
9
9
  import "./chunk-6EGBXRDK.js";
10
- import "./chunk-XAMA3JJG.js";
11
- import "./chunk-4VNS5WPM.js";
10
+ import "./chunk-YYOK2RF7.js";
11
+ import "./chunk-R5U7XKVJ.js";
12
12
  export {
13
13
  setupCheckTool,
14
14
  setupInitTool,
@@ -107,25 +107,25 @@ import {
107
107
  uiStatusTool,
108
108
  uiTools,
109
109
  videoInfoTool
110
- } from "./chunk-YNLUOZSZ.js";
111
- import "./chunk-QMUEC6B5.js";
110
+ } from "./chunk-HB3YPWF3.js";
111
+ import "./chunk-PMCXEA7J.js";
112
112
  import {
113
113
  setupCheckTool,
114
114
  setupInitTool,
115
115
  setupStatusTool,
116
116
  setupTools
117
- } from "./chunk-RCY26WEK.js";
117
+ } from "./chunk-CUBQRT5L.js";
118
118
  import "./chunk-XKX6NBHF.js";
119
- import "./chunk-34KRJWZL.js";
120
- import "./chunk-DKAX5RCX.js";
121
- import "./chunk-DGXAP477.js";
119
+ import "./chunk-34GGYFXX.js";
120
+ import "./chunk-7R5YNOXE.js";
121
+ import "./chunk-3ERJNXYM.js";
122
122
  import "./chunk-LB3RNE3O.js";
123
- import "./chunk-EU63HPKT.js";
123
+ import "./chunk-GRU332L4.js";
124
124
  import "./chunk-6EGBXRDK.js";
125
125
  import "./chunk-SLNJEF32.js";
126
- import "./chunk-XAMA3JJG.js";
126
+ import "./chunk-YYOK2RF7.js";
127
127
  import "./chunk-XFVDP332.js";
128
- import "./chunk-4VNS5WPM.js";
128
+ import "./chunk-R5U7XKVJ.js";
129
129
  export {
130
130
  analyzeScreenshotTool,
131
131
  analyzeTools,
@@ -0,0 +1,177 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>App Flow Map</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ background: #1a1a2e;
11
+ color: #e0e0e0;
12
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
13
+ min-height: 100vh;
14
+ padding: 32px;
15
+ overflow-x: hidden;
16
+ }
17
+ .map { max-width: 900px; margin: 0 auto; }
18
+ .map-header {
19
+ text-align: center; margin-bottom: 36px;
20
+ opacity: 0; animation: fadeIn 0.6s 0.1s forwards;
21
+ }
22
+ .map-header h1 { font-size: 24px; font-weight: 700; color: #fff; }
23
+ .map-header p { font-size: 13px; color: rgba(255,255,255,0.45); margin-top: 6px; }
24
+ @keyframes fadeIn { to { opacity: 1; } }
25
+ @keyframes slideUp { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }
26
+
27
+ /* Phase blocks */
28
+ .phase {
29
+ border-radius: 16px;
30
+ padding: 20px;
31
+ margin-bottom: 0;
32
+ opacity: 0;
33
+ animation: slideUp 0.5s forwards;
34
+ position: relative;
35
+ }
36
+ .phase-title { font-size: 16px; font-weight: 700; color: #fff; margin-bottom: 4px; }
37
+ .phase-desc { font-size: 12px; opacity: 0.6; margin-bottom: 14px; }
38
+
39
+ /* Phase color themes */
40
+ .phase-0 { background: rgba(34, 197, 94, 0.12); border: 1px solid rgba(34, 197, 94, 0.25); }
41
+ .phase-0 .phase-title { color: #86efac; }
42
+ .phase-1 { background: rgba(99, 102, 241, 0.12); border: 1px solid rgba(99, 102, 241, 0.25); }
43
+ .phase-1 .phase-title { color: #a5b4fc; }
44
+ .phase-2 { background: rgba(234, 179, 8, 0.12); border: 1px solid rgba(234, 179, 8, 0.25); }
45
+ .phase-2 .phase-title { color: #fde047; }
46
+ .phase-3 { background: rgba(236, 72, 153, 0.12); border: 1px solid rgba(236, 72, 153, 0.25); }
47
+ .phase-3 .phase-title { color: #f9a8d4; }
48
+ .phase-4 { background: rgba(14, 165, 233, 0.12); border: 1px solid rgba(14, 165, 233, 0.25); }
49
+ .phase-4 .phase-title { color: #7dd3fc; }
50
+
51
+ /* Screen cards inside phases */
52
+ .phase-screens {
53
+ display: flex; gap: 10px; overflow-x: auto; padding: 4px 0;
54
+ }
55
+ .screen-card {
56
+ flex-shrink: 0;
57
+ background: rgba(255,255,255,0.06);
58
+ border: 1px solid rgba(255,255,255,0.1);
59
+ border-radius: 10px;
60
+ padding: 8px;
61
+ min-width: 130px;
62
+ max-width: 160px;
63
+ transition: transform 0.2s, box-shadow 0.2s;
64
+ }
65
+ .screen-card:hover {
66
+ transform: translateY(-2px);
67
+ box-shadow: 0 8px 24px rgba(0,0,0,0.3);
68
+ }
69
+ .screen-card-img {
70
+ width: 100%; height: 180px;
71
+ border-radius: 6px; overflow: hidden;
72
+ background: #000; margin-bottom: 6px;
73
+ }
74
+ .screen-card-img img { width: 100%; height: 100%; object-fit: contain; }
75
+ .screen-card-label { font-size: 11px; font-weight: 600; color: rgba(255,255,255,0.85); }
76
+ .screen-card-detail { font-size: 10px; color: rgba(255,255,255,0.4); margin-top: 2px; }
77
+
78
+ /* Arrow connector between phases */
79
+ .arrow-connector {
80
+ display: flex; align-items: center; justify-content: center;
81
+ height: 40px;
82
+ opacity: 0; animation: fadeIn 0.3s forwards;
83
+ }
84
+ .arrow-connector svg {
85
+ stroke: rgba(255,255,255,0.2); fill: none; stroke-width: 2;
86
+ }
87
+
88
+ /* Insight callout */
89
+ .insight {
90
+ margin-top: 6px; padding: 8px 12px;
91
+ background: rgba(255,255,255,0.04);
92
+ border-left: 3px solid rgba(255,255,255,0.15);
93
+ border-radius: 0 8px 8px 0;
94
+ font-size: 11px; color: rgba(255,255,255,0.55);
95
+ line-height: 1.5;
96
+ }
97
+
98
+ .footer {
99
+ text-align: center; margin-top: 32px;
100
+ font-size: 10px; color: rgba(255,255,255,0.2);
101
+ }
102
+ .model-label {
103
+ position: fixed; bottom: 10px; right: 14px;
104
+ font-size: 9px; color: rgba(255,255,255,0.2);
105
+ background: rgba(0,0,0,0.3);
106
+ padding: 2px 8px; border-radius: 4px;
107
+ }
108
+ </style>
109
+ </head>
110
+ <body>
111
+ <div class="map">
112
+ <div class="map-header">
113
+ <h1 id="mapTitle">App Flow Map</h1>
114
+ <p id="mapSubtitle"></p>
115
+ </div>
116
+ <div id="mapContent"></div>
117
+ <div class="footer" id="mapFooter">Generated by DiscoveryLab</div>
118
+ <div class="model-label" id="modelLabel"></div>
119
+ </div>
120
+ <script>
121
+ const DATA = window.__VISUALIZATION_DATA__ || {};
122
+ const phases = DATA.phases || [];
123
+
124
+ if (DATA.title) document.getElementById('mapTitle').textContent = DATA.title;
125
+ if (DATA.subtitle) document.getElementById('mapSubtitle').textContent = DATA.subtitle;
126
+ if (DATA.providerName) document.getElementById('modelLabel').textContent = DATA.providerName;
127
+
128
+ const container = document.getElementById('mapContent');
129
+ let animDelay = 0.3;
130
+
131
+ phases.forEach((phase, phaseIdx) => {
132
+ // Arrow between phases
133
+ if (phaseIdx > 0) {
134
+ const arrow = document.createElement('div');
135
+ arrow.className = 'arrow-connector';
136
+ arrow.style.animationDelay = animDelay + 's';
137
+ arrow.innerHTML = '<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 5v14M5 12l7 7 7-7" stroke-linecap="round" stroke-linejoin="round"/></svg>';
138
+ container.appendChild(arrow);
139
+ animDelay += 0.15;
140
+ }
141
+
142
+ const phaseEl = document.createElement('div');
143
+ phaseEl.className = `phase phase-${phaseIdx % 5}`;
144
+ phaseEl.style.animationDelay = animDelay + 's';
145
+
146
+ let html = `
147
+ <div class="phase-title">${phase.title || 'Phase ' + (phaseIdx + 1)}</div>
148
+ <div class="phase-desc">${phase.description || ''}</div>
149
+ `;
150
+
151
+ // Screen cards
152
+ if (phase.screens && phase.screens.length > 0) {
153
+ html += '<div class="phase-screens">';
154
+ phase.screens.forEach(screen => {
155
+ html += `
156
+ <div class="screen-card">
157
+ ${screen.imageUrl ? `<div class="screen-card-img"><img src="${screen.imageUrl}" alt="" loading="lazy"></div>` : ''}
158
+ <div class="screen-card-label">${screen.label || ''}</div>
159
+ ${screen.detail ? `<div class="screen-card-detail">${screen.detail}</div>` : ''}
160
+ </div>
161
+ `;
162
+ });
163
+ html += '</div>';
164
+ }
165
+
166
+ // Insight
167
+ if (phase.insight) {
168
+ html += `<div class="insight">${phase.insight}</div>`;
169
+ }
170
+
171
+ phaseEl.innerHTML = html;
172
+ container.appendChild(phaseEl);
173
+ animDelay += 0.3;
174
+ });
175
+ </script>
176
+ </body>
177
+ </html>
@@ -0,0 +1,150 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Device Showcase</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ background: linear-gradient(135deg, #0c0c1d, #1e1e3f, #0c0c1d);
11
+ color: #fff;
12
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
13
+ min-height: 100vh;
14
+ overflow: hidden;
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
18
+ justify-content: center;
19
+ }
20
+ .header { text-align: center; margin-bottom: 32px; z-index: 10; }
21
+ .header h1 { font-size: 28px; font-weight: 700; }
22
+ .header p { font-size: 13px; color: rgba(255,255,255,0.5); margin-top: 4px; }
23
+ .carousel-wrapper {
24
+ perspective: 1200px;
25
+ width: 100%;
26
+ max-width: 800px;
27
+ height: 500px;
28
+ position: relative;
29
+ }
30
+ .carousel {
31
+ width: 100%;
32
+ height: 100%;
33
+ position: relative;
34
+ transform-style: preserve-3d;
35
+ animation: spin 20s linear infinite;
36
+ animation-play-state: running;
37
+ }
38
+ .carousel:hover { animation-play-state: paused; }
39
+ @keyframes spin { to { transform: rotateY(360deg); } }
40
+ .device {
41
+ position: absolute;
42
+ width: 200px;
43
+ left: 50%;
44
+ top: 50%;
45
+ margin-left: -100px;
46
+ margin-top: -200px;
47
+ transform-style: preserve-3d;
48
+ transition: transform 0.5s, box-shadow 0.5s;
49
+ }
50
+ .device-frame {
51
+ background: #111;
52
+ border-radius: 24px;
53
+ padding: 8px;
54
+ box-shadow: 0 20px 60px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.1);
55
+ border: 1px solid rgba(255,255,255,0.08);
56
+ }
57
+ .device-screen {
58
+ border-radius: 16px;
59
+ overflow: hidden;
60
+ background: #000;
61
+ }
62
+ .device-screen img {
63
+ width: 100%;
64
+ height: auto;
65
+ display: block;
66
+ }
67
+ .device-label {
68
+ text-align: center;
69
+ font-size: 11px;
70
+ color: rgba(255,255,255,0.5);
71
+ margin-top: 12px;
72
+ }
73
+ .glow {
74
+ position: absolute;
75
+ width: 300px; height: 300px;
76
+ background: radial-gradient(circle, rgba(99,102,241,0.15) 0%, transparent 70%);
77
+ top: 50%; left: 50%;
78
+ transform: translate(-50%, -50%);
79
+ pointer-events: none;
80
+ }
81
+ .footer {
82
+ position: fixed; bottom: 16px;
83
+ font-size: 11px; color: rgba(255,255,255,0.25);
84
+ }
85
+ .nav-dots {
86
+ display: flex; gap: 8px; margin-top: 24px; z-index: 10;
87
+ }
88
+ .nav-dot {
89
+ width: 8px; height: 8px; border-radius: 50%;
90
+ background: rgba(255,255,255,0.2);
91
+ cursor: pointer;
92
+ transition: background 0.3s;
93
+ }
94
+ .nav-dot.active { background: #6366f1; }
95
+ </style>
96
+ </head>
97
+ <body>
98
+ <div class="header">
99
+ <h1 id="vizTitle">Device Showcase</h1>
100
+ <p id="vizSubtitle"></p>
101
+ </div>
102
+ <div class="carousel-wrapper">
103
+ <div class="glow"></div>
104
+ <div class="carousel" id="carousel"></div>
105
+ </div>
106
+ <div class="nav-dots" id="navDots"></div>
107
+ <div class="footer" id="vizFooter">Generated by DiscoveryLab</div>
108
+ <script>
109
+ const DATA = window.__VISUALIZATION_DATA__ || {};
110
+ const screens = DATA.screens || [];
111
+
112
+ if (DATA.title) document.getElementById('vizTitle').textContent = DATA.title;
113
+ if (DATA.subtitle) document.getElementById('vizSubtitle').textContent = DATA.subtitle;
114
+
115
+ const carousel = document.getElementById('carousel');
116
+ const dotsContainer = document.getElementById('navDots');
117
+ const n = screens.length || 1;
118
+ const angleStep = 360 / n;
119
+ const radius = Math.max(250, n * 60);
120
+
121
+ screens.forEach((screen, i) => {
122
+ const angle = angleStep * i;
123
+ const device = document.createElement('div');
124
+ device.className = 'device';
125
+ device.style.transform = `rotateY(${angle}deg) translateZ(${radius}px)`;
126
+ device.innerHTML = `
127
+ <div class="device-frame">
128
+ <div class="device-screen">
129
+ <img src="${screen.imageUrl}" alt="Screen ${i + 1}" loading="lazy">
130
+ </div>
131
+ </div>
132
+ <div class="device-label">${screen.label || `Screen ${i + 1}`}</div>
133
+ `;
134
+ carousel.appendChild(device);
135
+
136
+ const dot = document.createElement('div');
137
+ dot.className = `nav-dot ${i === 0 ? 'active' : ''}`;
138
+ dot.addEventListener('click', () => {
139
+ carousel.style.animation = 'none';
140
+ carousel.offsetHeight; // reflow
141
+ carousel.style.transform = `rotateY(${-angle}deg)`;
142
+ carousel.style.transition = 'transform 0.8s ease';
143
+ dotsContainer.querySelectorAll('.nav-dot').forEach(d => d.classList.remove('active'));
144
+ dot.classList.add('active');
145
+ });
146
+ dotsContainer.appendChild(dot);
147
+ });
148
+ </script>
149
+ </body>
150
+ </html>
@@ -0,0 +1,181 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Flow Diagram</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ background: linear-gradient(160deg, #0f0f23, #1a1a3e);
11
+ color: #fff;
12
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
13
+ min-height: 100vh;
14
+ display: flex;
15
+ flex-direction: column;
16
+ }
17
+ .container {
18
+ flex: 1;
19
+ display: flex;
20
+ flex-direction: column;
21
+ padding: 24px;
22
+ max-width: 100%;
23
+ overflow: hidden;
24
+ }
25
+ .header { text-align: center; margin-bottom: 20px; flex-shrink: 0; }
26
+ .header h1 { font-size: 22px; font-weight: 700; margin-bottom: 4px; }
27
+ .header p { font-size: 13px; color: rgba(255,255,255,0.5); }
28
+ .flow {
29
+ flex: 1;
30
+ display: flex;
31
+ flex-direction: row;
32
+ align-items: center;
33
+ justify-content: center;
34
+ gap: 0;
35
+ padding: 10px 0;
36
+ min-height: 0;
37
+ }
38
+ .flow.vertical {
39
+ flex-direction: column;
40
+ overflow-y: auto;
41
+ overflow-x: hidden;
42
+ }
43
+ .step {
44
+ display: flex;
45
+ flex-direction: column;
46
+ align-items: center;
47
+ flex-shrink: 1;
48
+ min-width: 0;
49
+ max-width: 220px;
50
+ opacity: 0;
51
+ transform: translateY(20px);
52
+ animation: fadeInUp 0.5s forwards;
53
+ /* delay set dynamically via JS for correct sequential order */
54
+ }
55
+ @keyframes fadeInUp {
56
+ to { opacity: 1; transform: translateY(0); }
57
+ }
58
+ .step-badge {
59
+ width: 28px; height: 28px; border-radius: 50%;
60
+ background: #6366f1; color: #fff;
61
+ display: flex; align-items: center; justify-content: center;
62
+ font-size: 12px; font-weight: 700;
63
+ margin-bottom: 8px;
64
+ box-shadow: 0 3px 10px rgba(99,102,241,0.4);
65
+ flex-shrink: 0;
66
+ }
67
+ .step-screenshot {
68
+ width: 100%;
69
+ max-height: 55vh;
70
+ border-radius: 10px;
71
+ overflow: hidden;
72
+ box-shadow: 0 6px 20px rgba(0,0,0,0.3);
73
+ border: 1px solid rgba(255,255,255,0.1);
74
+ transition: transform 0.3s, box-shadow 0.3s;
75
+ flex-shrink: 1;
76
+ }
77
+ .step-screenshot:hover {
78
+ transform: scale(1.03);
79
+ box-shadow: 0 10px 30px rgba(99,102,241,0.25);
80
+ }
81
+ .step-screenshot img {
82
+ width: 100%;
83
+ height: auto;
84
+ display: block;
85
+ max-height: 55vh;
86
+ object-fit: contain;
87
+ }
88
+ .step-label {
89
+ font-size: 11px;
90
+ color: rgba(255,255,255,0.7);
91
+ margin-top: 8px;
92
+ text-align: center;
93
+ max-width: 100%;
94
+ overflow: hidden;
95
+ text-overflow: ellipsis;
96
+ white-space: nowrap;
97
+ flex-shrink: 0;
98
+ }
99
+ .arrow {
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ flex-shrink: 0;
104
+ padding: 0 4px;
105
+ opacity: 0;
106
+ animation: fadeInArrow 0.3s forwards;
107
+ /* delay set dynamically via JS */
108
+ }
109
+ @keyframes fadeInArrow { to { opacity: 1; } }
110
+ .arrow svg {
111
+ width: 20px; height: 20px;
112
+ stroke: rgba(255,255,255,0.35);
113
+ fill: none;
114
+ stroke-width: 2;
115
+ }
116
+ .flow.vertical .arrow { transform: rotate(90deg); }
117
+ .footer {
118
+ text-align: center;
119
+ padding: 12px;
120
+ font-size: 10px;
121
+ color: rgba(255,255,255,0.25);
122
+ flex-shrink: 0;
123
+ }
124
+ </style>
125
+ </head>
126
+ <body>
127
+ <div class="container">
128
+ <div class="header">
129
+ <h1 id="vizTitle">App Flow</h1>
130
+ <p id="vizSubtitle"></p>
131
+ </div>
132
+ <div class="flow" id="flowContainer"></div>
133
+ <div class="footer" id="vizFooter">Generated by DiscoveryLab</div>
134
+ </div>
135
+ <script>
136
+ const DATA = window.__VISUALIZATION_DATA__ || {};
137
+ const steps = DATA.steps || [];
138
+ const direction = DATA.direction || 'horizontal';
139
+ const count = steps.length;
140
+
141
+ if (DATA.title) document.getElementById('vizTitle').textContent = DATA.title;
142
+ if (DATA.subtitle) document.getElementById('vizSubtitle').textContent = DATA.subtitle;
143
+
144
+ const container = document.getElementById('flowContainer');
145
+ if (direction === 'vertical') container.classList.add('vertical');
146
+
147
+ // Auto-size steps based on count and viewport
148
+ const maxStepWidth = Math.min(220, Math.floor((window.innerWidth - 80 - (count - 1) * 28) / count));
149
+
150
+ // Sequential animation: step1 → arrow1 → step2 → arrow2 → step3 ...
151
+ const baseDelay = 0.2; // seconds
152
+ const stepDuration = 0.3; // time between each element appearing
153
+
154
+ steps.forEach((step, i) => {
155
+ const elementIndex = i * 2; // step0=0, arrow0=1, step1=2, arrow1=3, step2=4...
156
+
157
+ if (i > 0) {
158
+ const arrowIndex = (i * 2) - 1;
159
+ const arrow = document.createElement('div');
160
+ arrow.className = 'arrow';
161
+ arrow.style.animationDelay = (baseDelay + arrowIndex * stepDuration) + 's';
162
+ arrow.innerHTML = '<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7" stroke-linecap="round" stroke-linejoin="round"/></svg>';
163
+ container.appendChild(arrow);
164
+ }
165
+
166
+ const el = document.createElement('div');
167
+ el.className = 'step';
168
+ el.style.maxWidth = maxStepWidth + 'px';
169
+ el.style.animationDelay = (baseDelay + elementIndex * stepDuration) + 's';
170
+ el.innerHTML = `
171
+ <div class="step-badge">${step.number || i + 1}</div>
172
+ <div class="step-screenshot">
173
+ <img src="${step.imageUrl}" alt="Step ${i + 1}">
174
+ </div>
175
+ <div class="step-label">${step.label || ''}</div>
176
+ `;
177
+ container.appendChild(el);
178
+ });
179
+ </script>
180
+ </body>
181
+ </html>