RemiFabre commited on
Commit
506cf3d
·
1 Parent(s): 36e0611

Better landing page

Browse files
Files changed (2) hide show
  1. index.html +108 -247
  2. style.css +182 -386
index.html CHANGED
@@ -1,255 +1,116 @@
1
- <!doctype html>
2
- <html>
 
3
 
4
  <head>
5
- <meta charset="utf-8" />
6
- <meta name="viewport" content="width=device-width" />
7
- <title> Simpledances </title>
8
- <link rel="stylesheet" href="style.css" />
 
 
 
9
  </head>
10
 
11
  <body>
12
- <div class="hero">
13
- <div class="hero-content">
14
- <div class="app-icon">🕺🤖</div>
15
- <h1>Simple Dances Studio</h1>
16
- <p class="tagline">Play Reachy Mini dances! Tune BPM, sculpt parameters, and keep the groove looping.</p>
17
- </div>
18
- </div>
19
-
20
- <div class="container">
21
- <div class="main-card">
22
- <div class="app-preview">
23
- <div class="preview-image">
24
- <div class="chip-row">
25
- <span class="preview-chip">Playing</span>
26
- <span class="preview-chip">Saved just now</span>
27
- </div>
28
- <div class="preview-title">Groovy Sway &amp; Roll</div>
29
- <div class="preview-params">
30
- <div class="param-card">
31
- <p class="param-label">BPM</p>
32
- <p class="param-value">110</p>
33
- </div>
34
- <div class="param-card">
35
- <p class="param-label">Amplitude</p>
36
- <p class="param-value">0.70×</p>
37
- </div>
38
- <div class="param-card">
39
- <p class="param-label">Head roll</p>
40
- <p class="param-value">+12°</p>
41
- </div>
42
- <div class="param-card">
43
- <p class="param-label">Pitch sway</p>
44
- <p class="param-value">–8°</p>
45
- </div>
46
- </div>
47
- <p class="preview-note">This mockup mirrors the live UI: choose a dance, tweak sliders, smash “Apply”, then “Save” once you love the move.</p>
48
- </div>
49
- </div>
50
- </div>
51
- </div>
52
-
53
- <div class="download-section">
54
- <div class="download-card">
55
- <h2>Get the app running on your Reachy Mini</h2>
56
-
57
- <div class="dashboard-config">
58
- <label for="dashboardUrl">Reachy Dashboard URL</label>
59
- <input type="url" id="dashboardUrl" value="http://localhost:8000"
60
- placeholder="http://your-reachy-ip:8000" />
61
- </div>
62
-
63
- <button id="installBtn" class="install-btn primary">
64
- <span class="btn-icon">📥</span>
65
- Install Simple Dances Studio
66
- </button>
67
-
68
- <div id="installStatus" class="install-status"></div>
69
-
70
- </div>
71
- </div>
72
-
73
- <div class="footer">
74
- <p>
75
- 🕺 Simple Dances Studio • Crafted by <a href="https://github.com/pollen-robotics" target="_blank">Pollen Robotics</a> • <a href="https://huggingface.co/spaces/pollen-robotics/Reachy_Mini_Apps" target="_blank">See more Reachy Mini apps</a>
76
- </p>
77
- </div>
78
- </div>
79
-
80
- <script>
81
- // Get the current Hugging Face Space URL as the repository URL
82
- function getCurrentSpaceUrl() {
83
- // Get current page URL and convert to repository format
84
- const currentUrl = window.location.href;
85
-
86
- // Remove any trailing slashes and query parameters
87
- const cleanUrl = currentUrl.split('?')[0].replace(/\/$/, '');
88
-
89
- return cleanUrl;
90
- }
91
-
92
- // Parse TOML content to extract project name
93
- function parseTomlProjectName(tomlContent) {
94
- try {
95
- const lines = tomlContent.split('\n');
96
- let inProjectSection = false;
97
-
98
- for (const line of lines) {
99
- const trimmedLine = line.trim();
100
-
101
- // Check if we're entering the [project] section
102
- if (trimmedLine === '[project]') {
103
- inProjectSection = true;
104
- continue;
105
- }
106
-
107
- // Check if we're entering a different section
108
- if (trimmedLine.startsWith('[') && trimmedLine !== '[project]') {
109
- inProjectSection = false;
110
- continue;
111
- }
112
-
113
- // If we're in the project section, look for the name field
114
- if (inProjectSection && trimmedLine.startsWith('name')) {
115
- const match = trimmedLine.match(/name\s*=\s*["']([^"']+)["']/);
116
- if (match) {
117
- // Convert to lowercase and replace invalid characters for app naming
118
- return match[1].toLowerCase().replace(/[^a-z0-9-_]/g, '-');
119
- }
120
- }
121
- }
122
-
123
- throw new Error('Project name not found in pyproject.toml');
124
- } catch (error) {
125
- console.error('Error parsing pyproject.toml:', error);
126
- return 'unknown-app';
127
- }
128
- }
129
-
130
- // Fetch and parse pyproject.toml from the current space
131
- async function getAppNameFromCurrentSpace() {
132
- try {
133
- // Fetch pyproject.toml from the current space
134
- const response = await fetch('./pyproject.toml');
135
- if (!response.ok) {
136
- throw new Error(`Failed to fetch pyproject.toml: ${response.status}`);
137
- }
138
-
139
- const tomlContent = await response.text();
140
- return parseTomlProjectName(tomlContent);
141
- } catch (error) {
142
- console.error('Error fetching app name from current space:', error);
143
- // Fallback to extracting from URL if pyproject.toml is not accessible
144
- const url = getCurrentSpaceUrl();
145
- const parts = url.split('/');
146
- const spaceName = parts[parts.length - 1];
147
- return spaceName.toLowerCase().replace(/[^a-z0-9-_]/g, '-');
148
- }
149
- }
150
-
151
- async function installToReachy() {
152
- const dashboardUrl = document.getElementById('dashboardUrl').value.trim();
153
- const statusDiv = document.getElementById('installStatus');
154
- const installBtn = document.getElementById('installBtn');
155
-
156
- if (!dashboardUrl) {
157
- showStatus('error', 'Please enter your Reachy dashboard URL');
158
- return;
159
- }
160
-
161
- try {
162
- installBtn.disabled = true;
163
- installBtn.innerHTML = '<span class="btn-icon">⏳</span>Installing...';
164
- showStatus('loading', 'Connecting to your Reachy dashboard...');
165
-
166
- // Test connection
167
- const testResponse = await fetch(`${dashboardUrl}/api/status`, {
168
- method: 'GET',
169
- mode: 'cors',
170
- });
171
-
172
- if (!testResponse.ok) {
173
- throw new Error('Cannot connect to dashboard. Make sure the URL is correct and the dashboard is running.');
174
- }
175
-
176
- showStatus('loading', 'Reading app configuration...');
177
-
178
- // Get app name from pyproject.toml in current space
179
- const appName = await getAppNameFromCurrentSpace();
180
-
181
- // Get current space URL as repository URL
182
- const repoUrl = getCurrentSpaceUrl();
183
-
184
- showStatus('loading', `Starting installation of "${appName}"...`);
185
-
186
- // Start installation
187
- const installResponse = await fetch(`${dashboardUrl}/api/install`, {
188
- method: 'POST',
189
- mode: 'cors',
190
- headers: {
191
- 'Content-Type': 'application/json',
192
- },
193
- body: JSON.stringify({
194
- url: repoUrl,
195
- name: appName
196
- })
197
- });
198
-
199
- const result = await installResponse.json();
200
-
201
- if (installResponse.ok) {
202
- showStatus('success', `✅ Installation started for "${appName}"! Check your dashboard for progress.`);
203
- setTimeout(() => {
204
- showStatus('info', `Open your dashboard at ${dashboardUrl} to see the installed app.`);
205
- }, 3000);
206
- } else {
207
- throw new Error(result.detail || 'Installation failed');
208
- }
209
-
210
- } catch (error) {
211
- console.error('Installation error:', error);
212
- showStatus('error', `❌ ${error.message}`);
213
- } finally {
214
- installBtn.disabled = false;
215
- installBtn.innerHTML = '<span class="btn-icon">📥</span>Install App to Reachy';
216
- }
217
- }
218
-
219
- function showStatus(type, message) {
220
- const statusDiv = document.getElementById('installStatus');
221
- statusDiv.className = `install-status ${type}`;
222
- statusDiv.textContent = message;
223
- statusDiv.style.display = 'block';
224
- }
225
-
226
- function copyToClipboard() {
227
- const repoUrl = document.getElementById('repoUrl').textContent;
228
- navigator.clipboard.writeText(repoUrl).then(() => {
229
- showStatus('success', '📋 Repository URL copied to clipboard!');
230
- }).catch(() => {
231
- showStatus('error', 'Failed to copy URL. Please copy manually.');
232
- });
233
- }
234
-
235
- // Update the displayed repository URL on page load
236
- document.addEventListener('DOMContentLoaded', () => {
237
- // Auto-detect local dashboard
238
- const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
239
- if (isLocalhost) {
240
- document.getElementById('dashboardUrl').value = 'http://localhost:8000';
241
- }
242
-
243
- // Update the repository URL display if element exists
244
- const repoUrlElement = document.getElementById('repoUrl');
245
- if (repoUrlElement) {
246
- repoUrlElement.textContent = getCurrentSpaceUrl();
247
- }
248
- });
249
-
250
- // Event listeners
251
- document.getElementById('installBtn').addEventListener('click', installToReachy);
252
- </script>
253
  </body>
254
 
255
  </html>
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
 
5
  <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <title>Simple Dances Studio</title>
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="style.css" />
13
  </head>
14
 
15
  <body>
16
+ <div class="landing-bg"></div>
17
+ <main class="landing-shell">
18
+ <header class="landing-hero">
19
+ <div>
20
+ <p class="eyebrow">Reachy Mini Playground</p>
21
+ <h1>Simple Dances Studio</h1>
22
+ <p class="subtitle">Play Reachy Mini dances! Tune BPM, sculpt parameters, and keep the groove looping.</p>
23
+ </div>
24
+ <div class="hero-meta">
25
+ <span class="hero-chip">🕺 Always ready to groove</span>
26
+ <span class="hero-chip">✨ No install wizard (yet)</span>
27
+ </div>
28
+ </header>
29
+
30
+ <section class="landing-grid">
31
+ <article class="landing-card">
32
+ <div class="card-header">
33
+ <div>
34
+ <h2>Dance Library</h2>
35
+ <p>Tap a move and instantly preview its description.</p>
36
+ </div>
37
+ </div>
38
+ <div class="landing-select">
39
+ <div class="select-label">Choose a dance</div>
40
+ <div class="select-fake">
41
+ <span>Groovy Sway &amp; Roll</span>
42
+ <span class="chevron">⌄</span>
43
+ </div>
44
+ </div>
45
+ <p class="description">A graceful sway with head spirals. Perfect intro groove.</p>
46
+ </article>
47
+
48
+ <article class="landing-card">
49
+ <div class="card-header">
50
+ <div>
51
+ <h2>Playback &amp; Status</h2>
52
+ <p>Start, stop, and check sync.</p>
53
+ </div>
54
+ </div>
55
+ <button class="primary ghosted">Start Dancing</button>
56
+ <div class="status-line">Syncing…</div>
57
+ </article>
58
+ </section>
59
+
60
+ <section class="landing-card params">
61
+ <div class="card-header">
62
+ <div>
63
+ <h2>Dance Parameters</h2>
64
+ <p>Fine-tune waveforms, amplitudes, and antenna flair.</p>
65
+ </div>
66
+ <div class="param-actions">
67
+ <button class="primary" disabled>Apply</button>
68
+ <button class="secondary" disabled>Save</button>
69
+ <button class="ghost" disabled>Reset</button>
70
+ </div>
71
+ </div>
72
+ <div class="bpm-block">
73
+ <div class="field-heading">
74
+ <span>BPM</span>
75
+ <strong>110</strong>
76
+ </div>
77
+ <div class="slider"></div>
78
+ </div>
79
+ <div class="params-grid">
80
+ <div class="param-chip">
81
+ <span class="param-label">Amplitude</span>
82
+ <span class="param-value">0.70×</span>
83
+ </div>
84
+ <div class="param-chip">
85
+ <span class="param-label">Head roll</span>
86
+ <span class="param-value">+12°</span>
87
+ </div>
88
+ <div class="param-chip">
89
+ <span class="param-label">Pitch sway</span>
90
+ <span class="param-value">−8°</span>
91
+ </div>
92
+ <div class="param-chip">
93
+ <span class="param-label">Antenna sync</span>
94
+ <span class="param-value">Wave</span>
95
+ </div>
96
+ </div>
97
+ </section>
98
+
99
+ <section class="landing-card viz">
100
+ <div class="card-header">
101
+ <div>
102
+ <h2>Kinesthetic Visualizer</h2>
103
+ <p>The kinetic drawing is temporarily removed. Ping the author if this feature matters to you!</p>
104
+ </div>
105
+ <span class="chip">¯\_(ツ)_/¯</span>
106
+ </div>
107
+ <div class="viz-placeholder">Visualizer on pause. Catch the disco ball soon ✨</div>
108
+ </section>
109
+ </main>
110
+
111
+ <footer class="landing-footer">
112
+ <p>🕺 Simple Dances Studio • Crafted by <a href="https://github.com/pollen-robotics" target="_blank">Pollen Robotics</a> • <a href="https://huggingface.co/spaces/pollen-robotics/Reachy_Mini_Apps" target="_blank">Browse more apps</a></p>
113
+ </footer>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  </body>
115
 
116
  </html>
style.css CHANGED
@@ -1,465 +1,261 @@
1
- * {
2
- margin: 0;
3
- padding: 0;
4
- box-sizing: border-box;
5
- }
6
-
7
- body {
8
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
9
- line-height: 1.6;
10
- color: #333;
11
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
- min-height: 100vh;
13
- }
14
-
15
- .hero {
16
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
- color: white;
18
- padding: 4rem 2rem;
19
- text-align: center;
20
- }
21
-
22
- .hero-content {
23
- max-width: 800px;
24
- margin: 0 auto;
25
- }
26
-
27
- .app-icon {
28
- font-size: 4rem;
29
- margin-bottom: 1rem;
30
- display: inline-block;
31
- }
32
-
33
- .hero h1 {
34
- font-size: 3rem;
35
- font-weight: 700;
36
- margin-bottom: 1rem;
37
- background: linear-gradient(45deg, #fff, #f0f9ff);
38
- background-clip: text;
39
- -webkit-background-clip: text;
40
- -webkit-text-fill-color: transparent;
41
- }
42
-
43
- .tagline {
44
- font-size: 1.25rem;
45
- opacity: 0.9;
46
- max-width: 600px;
47
- margin: 0 auto;
48
- }
49
 
50
- .container {
51
- max-width: 1200px;
52
- margin: 0 auto;
53
- padding: 0 2rem;
54
- position: relative;
55
- z-index: 2;
56
  }
57
 
58
- .main-card {
59
- background: white;
60
- border-radius: 20px;
61
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
62
- margin-top: -2rem;
63
- overflow: hidden;
64
- margin-bottom: 3rem;
65
- }
66
-
67
- .app-preview {
68
- background: linear-gradient(135deg, #1e3a8a, #3b82f6);
69
- padding: 3rem;
70
- color: white;
71
- text-align: center;
72
- position: relative;
73
- }
74
-
75
- .preview-image {
76
- background: #000;
77
- border-radius: 15px;
78
- padding: 2rem;
79
- max-width: 500px;
80
- margin: 0 auto;
81
- position: relative;
82
- overflow: hidden;
83
- }
84
-
85
- .chip-row {
86
- display: flex;
87
- gap: 0.6rem;
88
- margin-bottom: 1rem;
89
- justify-content: center;
90
- }
91
-
92
- .preview-chip {
93
- padding: 0.35rem 0.9rem;
94
- border-radius: 999px;
95
- border: 1px solid rgba(255, 255, 255, 0.2);
96
- background: rgba(255, 255, 255, 0.08);
97
- font-size: 0.85rem;
98
- color: #fff;
99
- }
100
-
101
- .preview-title {
102
- font-size: 1.4rem;
103
- font-weight: 600;
104
- margin-bottom: 1rem;
105
- }
106
-
107
- .preview-params {
108
- display: grid;
109
- grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
110
- gap: 0.8rem;
111
- }
112
-
113
- .param-card {
114
- background: rgba(255, 255, 255, 0.08);
115
- border-radius: 10px;
116
- padding: 0.75rem;
117
- text-align: left;
118
- }
119
-
120
- .param-label {
121
- text-transform: uppercase;
122
- font-size: 0.75rem;
123
- letter-spacing: 0.08em;
124
- color: rgba(255, 255, 255, 0.6);
125
- margin-bottom: 0.2rem;
126
- }
127
-
128
- .param-value {
129
- font-size: 1.1rem;
130
- font-weight: 600;
131
- }
132
-
133
- .preview-note {
134
- margin-top: 1.2rem;
135
- font-size: 0.9rem;
136
- color: rgba(255, 255, 255, 0.8);
137
- }
138
-
139
- .camera-feed {
140
- font-size: 4rem;
141
- margin-bottom: 1rem;
142
- opacity: 0.7;
143
- }
144
-
145
- .detection-overlay {
146
- position: absolute;
147
- top: 50%;
148
- left: 50%;
149
- transform: translate(-50%, -50%);
150
- width: 100%;
151
- }
152
-
153
- .bbox {
154
- background: rgba(34, 197, 94, 0.9);
155
- color: white;
156
- padding: 0.5rem 1rem;
157
- border-radius: 8px;
158
- font-size: 0.9rem;
159
- font-weight: 600;
160
- margin: 0.5rem;
161
- display: inline-block;
162
- border: 2px solid #22c55e;
163
- }
164
-
165
- .app-details {
166
- padding: 3rem;
167
- }
168
-
169
- .app-details h2 {
170
- font-size: 2rem;
171
- color: #1e293b;
172
- margin-bottom: 2rem;
173
- text-align: center;
174
- }
175
-
176
- .template-info {
177
- display: grid;
178
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
179
- gap: 2rem;
180
- margin-bottom: 3rem;
181
- }
182
-
183
- .info-box {
184
- background: #f0f9ff;
185
- border: 2px solid #e0f2fe;
186
- border-radius: 12px;
187
- padding: 2rem;
188
  }
189
 
190
- .info-box h3 {
191
- color: #0c4a6e;
192
- margin-bottom: 1rem;
193
- font-size: 1.2rem;
 
 
 
194
  }
195
 
196
- .info-box p {
197
- color: #0369a1;
198
- line-height: 1.6;
 
 
 
 
 
199
  }
200
 
201
- .how-to-use {
202
- background: #fefce8;
203
- border: 2px solid #fde047;
204
- border-radius: 12px;
205
- padding: 2rem;
206
- margin-top: 3rem;
207
  }
208
 
209
- .how-to-use h3 {
210
- color: #a16207;
211
- margin-bottom: 1.5rem;
212
- font-size: 1.3rem;
213
- text-align: center;
 
214
  }
215
 
216
- .steps {
217
- display: flex;
218
- flex-direction: column;
219
- gap: 1.5rem;
 
 
220
  }
221
 
222
- .step {
223
- display: flex;
224
- align-items: flex-start;
225
- gap: 1rem;
226
  }
227
 
228
- .step-number {
229
- background: #eab308;
230
- color: white;
231
- width: 2rem;
232
- height: 2rem;
233
- border-radius: 50%;
234
- display: flex;
235
- align-items: center;
236
- justify-content: center;
237
- font-weight: bold;
238
- flex-shrink: 0;
239
  }
240
 
241
- .step h4 {
242
- color: #a16207;
243
- margin-bottom: 0.5rem;
244
- font-size: 1.1rem;
245
  }
246
 
247
- .step p {
248
- color: #ca8a04;
 
 
 
 
 
 
249
  }
250
 
251
- .download-card {
252
- background: white;
253
- border-radius: 20px;
254
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
255
- padding: 3rem;
256
- text-align: center;
257
  }
258
 
259
- .download-card h2 {
260
- font-size: 2rem;
261
- color: #1e293b;
262
- margin-bottom: 1rem;
 
 
 
263
  }
264
 
265
- .download-card>p {
266
- color: #64748b;
267
- font-size: 1.1rem;
268
- margin-bottom: 2rem;
 
269
  }
270
 
271
- .dashboard-config {
272
- margin-bottom: 2rem;
273
- text-align: left;
274
- max-width: 400px;
275
- margin-left: auto;
276
- margin-right: auto;
277
  }
278
 
279
- .dashboard-config label {
280
- display: block;
281
- color: #374151;
282
- font-weight: 600;
283
- margin-bottom: 0.5rem;
284
  }
285
 
286
- .dashboard-config input {
287
- width: 100%;
288
- padding: 0.75rem 1rem;
289
- border: 2px solid #e5e7eb;
290
- border-radius: 8px;
291
- font-size: 0.95rem;
292
- transition: border-color 0.2s;
 
 
 
293
  }
294
 
295
- .dashboard-config input:focus {
296
- outline: none;
297
- border-color: #667eea;
298
  }
299
 
300
- .install-btn {
301
- background: linear-gradient(135deg, #667eea, #764ba2);
302
- color: white;
303
- border: none;
304
- padding: 1.25rem 3rem;
305
- border-radius: 16px;
306
- font-size: 1.2rem;
307
- font-weight: 700;
308
- cursor: pointer;
309
- transition: all 0.3s ease;
310
- display: inline-flex;
311
- align-items: center;
312
- gap: 0.75rem;
313
- margin-bottom: 2rem;
314
- box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
315
  }
316
 
317
- .install-btn:hover:not(:disabled) {
318
- transform: translateY(-3px);
319
- box-shadow: 0 15px 35px rgba(102, 126, 234, 0.4);
320
  }
321
 
322
- .install-btn:disabled {
323
- opacity: 0.7;
324
- cursor: not-allowed;
325
- transform: none;
326
  }
327
 
328
- .manual-option {
329
- background: #f8fafc;
330
- border-radius: 12px;
331
- padding: 2rem;
332
- margin-top: 2rem;
 
 
 
333
  }
334
 
335
- .manual-option h3 {
336
- color: #1e293b;
337
- margin-bottom: 1rem;
338
- font-size: 1.2rem;
339
  }
340
 
341
- .manual-option>p {
342
- color: #64748b;
343
- margin-bottom: 1rem;
344
  }
345
 
346
- .btn-icon {
347
- font-size: 1.1rem;
 
348
  }
349
 
350
- .install-status {
351
- padding: 1rem;
352
- border-radius: 8px;
353
- font-size: 0.9rem;
354
- text-align: center;
355
- display: none;
356
- margin-top: 1rem;
357
  }
358
 
359
- .install-status.success {
360
- background: #dcfce7;
361
- color: #166534;
362
- border: 1px solid #bbf7d0;
 
 
 
363
  }
364
 
365
- .install-status.error {
366
- background: #fef2f2;
367
- color: #dc2626;
368
- border: 1px solid #fecaca;
369
  }
370
 
371
- .install-status.loading {
372
- background: #dbeafe;
373
- color: #1d4ed8;
374
- border: 1px solid #bfdbfe;
375
  }
376
 
377
- .install-status.info {
378
- background: #e0f2fe;
379
- color: #0369a1;
380
- border: 1px solid #7dd3fc;
 
 
381
  }
382
 
383
- .manual-install {
384
- background: #1f2937;
385
- border-radius: 8px;
386
- padding: 1rem;
387
- margin-bottom: 1rem;
388
- display: flex;
389
- align-items: center;
390
- gap: 1rem;
391
  }
392
 
393
- .manual-install code {
394
- color: #10b981;
395
- font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
396
- font-size: 0.85rem;
397
- flex: 1;
398
- overflow-x: auto;
399
  }
400
 
401
- .copy-btn {
402
- background: #374151;
403
- color: white;
404
- border: none;
405
- padding: 0.5rem 1rem;
406
- border-radius: 6px;
407
- font-size: 0.8rem;
408
- cursor: pointer;
409
- transition: background-color 0.2s;
410
  }
411
 
412
- .copy-btn:hover {
413
- background: #4b5563;
 
414
  }
415
 
416
- .manual-steps {
417
- color: #6b7280;
418
- font-size: 0.9rem;
419
- line-height: 1.8;
 
 
 
 
420
  }
421
 
422
- .footer {
423
- text-align: center;
424
- padding: 2rem;
425
- color: white;
426
- opacity: 0.8;
427
  }
428
 
429
- .footer a {
430
- color: white;
431
- text-decoration: none;
432
- font-weight: 600;
433
  }
434
 
435
- .footer a:hover {
436
- text-decoration: underline;
437
  }
438
 
439
- /* Responsive Design */
440
- @media (max-width: 768px) {
441
- .hero {
442
- padding: 2rem 1rem;
443
- }
444
-
445
- .hero h1 {
446
- font-size: 2rem;
447
- }
448
-
449
- .container {
450
- padding: 0 1rem;
451
- }
452
-
453
- .app-details,
454
- .download-card {
455
- padding: 2rem;
456
- }
457
-
458
- .features-grid {
459
- grid-template-columns: 1fr;
460
- }
461
 
462
- .download-options {
463
- grid-template-columns: 1fr;
464
- }
465
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
+ :root {
3
+ font-family: 'Space Grotesk', 'Segoe UI', system-ui, sans-serif;
4
+ color: #f7f7fb;
5
+ background-color: #05060a;
 
 
6
  }
7
 
8
+ * {
9
+ box-sizing: border-box;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  }
11
 
12
+ body {
13
+ margin: 0;
14
+ min-height: 100vh;
15
+ background: radial-gradient(circle at top, rgba(255, 95, 109, 0.25), transparent 50%),
16
+ radial-gradient(circle at 20% 20%, rgba(135, 241, 255, 0.18), transparent 55%),
17
+ #05060a;
18
+ color: #fff;
19
  }
20
 
21
+ .landing-bg {
22
+ position: fixed;
23
+ inset: 0;
24
+ background: radial-gradient(circle at 80% 10%, rgba(255, 206, 134, 0.4), transparent 60%),
25
+ radial-gradient(circle at 10% 80%, rgba(96, 88, 255, 0.35), transparent 70%);
26
+ filter: blur(90px);
27
+ opacity: 0.6;
28
+ pointer-events: none;
29
  }
30
 
31
+ .landing-shell {
32
+ position: relative;
33
+ max-width: 1100px;
34
+ margin: 0 auto;
35
+ padding: 48px 24px 120px;
 
36
  }
37
 
38
+ .landing-hero {
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ justify-content: space-between;
42
+ gap: 24px;
43
+ margin-bottom: 32px;
44
  }
45
 
46
+ .eyebrow {
47
+ text-transform: uppercase;
48
+ font-size: 0.75rem;
49
+ letter-spacing: 0.25rem;
50
+ color: rgba(247, 247, 251, 0.7);
51
+ margin: 0 0 8px;
52
  }
53
 
54
+ .landing-hero h1 {
55
+ margin: 0;
56
+ font-size: clamp(2.2rem, 4vw, 3.1rem);
 
57
  }
58
 
59
+ .subtitle {
60
+ margin-top: 8px;
61
+ color: rgba(247, 247, 251, 0.7);
62
+ max-width: 520px;
 
 
 
 
 
 
 
63
  }
64
 
65
+ .hero-meta {
66
+ display: flex;
67
+ gap: 12px;
68
+ align-items: flex-start;
69
  }
70
 
71
+ .hero-chip {
72
+ display: inline-flex;
73
+ align-items: center;
74
+ padding: 8px 14px;
75
+ border-radius: 999px;
76
+ border: 1px solid rgba(255, 255, 255, 0.2);
77
+ background: rgba(255, 255, 255, 0.08);
78
+ font-size: 0.85rem;
79
  }
80
 
81
+ .landing-grid {
82
+ display: grid;
83
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
84
+ gap: 20px;
85
+ margin-bottom: 20px;
 
86
  }
87
 
88
+ .landing-card {
89
+ background: rgba(14, 16, 26, 0.65);
90
+ border: 1px solid rgba(255, 255, 255, 0.08);
91
+ border-radius: 20px;
92
+ padding: 24px;
93
+ box-shadow: 0 25px 70px rgba(0, 0, 0, 0.35);
94
+ backdrop-filter: blur(16px);
95
  }
96
 
97
+ .card-header {
98
+ display: flex;
99
+ justify-content: space-between;
100
+ align-items: flex-start;
101
+ margin-bottom: 12px;
102
  }
103
 
104
+ .card-header h2 {
105
+ margin: 0 0 6px;
 
 
 
 
106
  }
107
 
108
+ .card-header p {
109
+ margin: 0;
110
+ color: rgba(247, 247, 251, 0.7);
 
 
111
  }
112
 
113
+ .primary {
114
+ width: 100%;
115
+ padding: 12px;
116
+ border-radius: 999px;
117
+ border: none;
118
+ font-size: 1rem;
119
+ font-weight: 600;
120
+ background: linear-gradient(120deg, #ff8ba7, #87f1ff);
121
+ color: #05060a;
122
+ opacity: 0.5;
123
  }
124
 
125
+ .primary.ghosted {
126
+ opacity: 0.6;
 
127
  }
128
 
129
+ .status-line {
130
+ text-align: center;
131
+ font-size: 0.95rem;
132
+ color: rgba(247, 247, 251, 0.7);
133
+ margin-top: 12px;
 
 
 
 
 
 
 
 
 
 
134
  }
135
 
136
+ .landing-select {
137
+ margin-top: 10px;
 
138
  }
139
 
140
+ .select-label {
141
+ font-size: 0.85rem;
142
+ opacity: 0.7;
143
+ margin-bottom: 6px;
144
  }
145
 
146
+ .select-fake {
147
+ display: flex;
148
+ justify-content: space-between;
149
+ align-items: center;
150
+ border-radius: 12px;
151
+ border: 1px solid rgba(255, 255, 255, 0.2);
152
+ padding: 12px 16px;
153
+ background: rgba(255, 255, 255, 0.03);
154
  }
155
 
156
+ .chevron {
157
+ opacity: 0.6;
 
 
158
  }
159
 
160
+ .description {
161
+ margin-top: 8px;
162
+ color: rgba(247, 247, 251, 0.7);
163
  }
164
 
165
+ .param-actions {
166
+ display: flex;
167
+ gap: 12px;
168
  }
169
 
170
+ .param-actions .primary {
171
+ flex: 0 0 auto;
 
 
 
 
 
172
  }
173
 
174
+ .param-actions .secondary,
175
+ .param-actions .ghost {
176
+ padding: 10px 20px;
177
+ border-radius: 999px;
178
+ border: 1px solid rgba(255, 255, 255, 0.2);
179
+ background: transparent;
180
+ color: rgba(247, 247, 251, 0.7);
181
  }
182
 
183
+ .bpm-block {
184
+ margin-top: 18px;
185
+ margin-bottom: 18px;
 
186
  }
187
 
188
+ .field-heading {
189
+ display: flex;
190
+ justify-content: space-between;
191
+ font-weight: 500;
192
  }
193
 
194
+ .slider {
195
+ margin-top: 12px;
196
+ height: 6px;
197
+ border-radius: 999px;
198
+ background: linear-gradient(90deg, #ff8ba7, #87f1ff);
199
+ opacity: 0.4;
200
  }
201
 
202
+ .params-grid {
203
+ display: grid;
204
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
205
+ gap: 12px;
 
 
 
 
206
  }
207
 
208
+ .param-chip {
209
+ padding: 12px 16px;
210
+ border-radius: 14px;
211
+ border: 1px solid rgba(255, 255, 255, 0.15);
212
+ background: rgba(255, 255, 255, 0.04);
 
213
  }
214
 
215
+ .param-label {
216
+ display: block;
217
+ font-size: 0.75rem;
218
+ opacity: 0.7;
219
+ text-transform: uppercase;
220
+ letter-spacing: 0.08em;
 
 
 
221
  }
222
 
223
+ .param-value {
224
+ font-size: 1.1rem;
225
+ font-weight: 600;
226
  }
227
 
228
+ .viz-placeholder {
229
+ margin-top: 12px;
230
+ padding: 24px;
231
+ border-radius: 16px;
232
+ border: 1px dashed rgba(255, 255, 255, 0.2);
233
+ background: rgba(255, 255, 255, 0.03);
234
+ color: rgba(247, 247, 251, 0.7);
235
+ text-align: center;
236
  }
237
 
238
+ .landing-footer {
239
+ text-align: center;
240
+ color: rgba(247, 247, 251, 0.7);
241
+ padding: 32px 16px 48px;
 
242
  }
243
 
244
+ .landing-footer a {
245
+ color: #fff;
246
+ text-decoration: none;
 
247
  }
248
 
249
+ .landing-footer a:hover {
250
+ text-decoration: underline;
251
  }
252
 
253
+ @media (max-width: 720px) {
254
+ .landing-grid {
255
+ grid-template-columns: 1fr;
256
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
 
258
+ .param-actions {
259
+ flex-direction: column;
260
+ }
261
  }