apirrone commited on
Commit
267ddb2
·
1 Parent(s): e0c845d

update landing page

Browse files
Files changed (2) hide show
  1. index.html +54 -215
  2. style.css +176 -319
index.html CHANGED
@@ -4,232 +4,71 @@
4
  <head>
5
  <meta charset="utf-8" />
6
  <meta name="viewport" content="width=device-width" />
7
- <title> Reachy Mini Clock </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> Reachy Mini Clock </h1>
16
- <p class="tagline">Enter your tagline here</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="camera-feed">🛠️</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  </div>
 
26
  </div>
27
- </div>
28
- </div>
29
-
30
- <div class="download-section">
31
- <div class="download-card">
32
- <h2>Install This App</h2>
33
-
34
- <div class="dashboard-config">
35
- <label for="dashboardUrl">Your Reachy Dashboard URL:</label>
36
- <input type="url" id="dashboardUrl" value="http://localhost:8000"
37
- placeholder="http://your-reachy-ip:8000" />
38
- </div>
39
 
40
- <button id="installBtn" class="install-btn primary">
41
- <span class="btn-icon">📥</span>
42
- Install Reachy Mini Clock to Reachy Mini
43
- </button>
44
-
45
- <div id="installStatus" class="install-status"></div>
46
-
47
- </div>
48
- </div>
49
-
50
- <div class="footer">
51
  <p>
52
  🤖 Reachy Mini Clock •
53
  <a href="https://github.com/pollen-robotics" target="_blank">Pollen Robotics</a> •
54
- <a href="https://huggingface.co/spaces/pollen-robotics/Reachy_Mini_Apps" target="_blank">Browse More
55
- Apps</a>
56
  </p>
57
- </div>
58
- </div>
59
-
60
- <script>
61
- // Get the current Hugging Face Space URL as the repository URL
62
- function getCurrentSpaceUrl() {
63
- // Get current page URL and convert to repository format
64
- const currentUrl = window.location.href;
65
-
66
- // Remove any trailing slashes and query parameters
67
- const cleanUrl = currentUrl.split('?')[0].replace(/\/$/, '');
68
-
69
- return cleanUrl;
70
- }
71
-
72
- // Parse TOML content to extract project name
73
- function parseTomlProjectName(tomlContent) {
74
- try {
75
- const lines = tomlContent.split('\n');
76
- let inProjectSection = false;
77
-
78
- for (const line of lines) {
79
- const trimmedLine = line.trim();
80
-
81
- // Check if we're entering the [project] section
82
- if (trimmedLine === '[project]') {
83
- inProjectSection = true;
84
- continue;
85
- }
86
-
87
- // Check if we're entering a different section
88
- if (trimmedLine.startsWith('[') && trimmedLine !== '[project]') {
89
- inProjectSection = false;
90
- continue;
91
- }
92
-
93
- // If we're in the project section, look for the name field
94
- if (inProjectSection && trimmedLine.startsWith('name')) {
95
- const match = trimmedLine.match(/name\s*=\s*["']([^"']+)["']/);
96
- if (match) {
97
- // Convert to lowercase and replace invalid characters for app naming
98
- return match[1].toLowerCase().replace(/[^a-z0-9-_]/g, '-');
99
- }
100
- }
101
- }
102
-
103
- throw new Error('Project name not found in pyproject.toml');
104
- } catch (error) {
105
- console.error('Error parsing pyproject.toml:', error);
106
- return 'unknown-app';
107
- }
108
- }
109
-
110
- // Fetch and parse pyproject.toml from the current space
111
- async function getAppNameFromCurrentSpace() {
112
- try {
113
- // Fetch pyproject.toml from the current space
114
- const response = await fetch('./pyproject.toml');
115
- if (!response.ok) {
116
- throw new Error(`Failed to fetch pyproject.toml: ${response.status}`);
117
- }
118
-
119
- const tomlContent = await response.text();
120
- return parseTomlProjectName(tomlContent);
121
- } catch (error) {
122
- console.error('Error fetching app name from current space:', error);
123
- // Fallback to extracting from URL if pyproject.toml is not accessible
124
- const url = getCurrentSpaceUrl();
125
- const parts = url.split('/');
126
- const spaceName = parts[parts.length - 1];
127
- return spaceName.toLowerCase().replace(/[^a-z0-9-_]/g, '-');
128
- }
129
- }
130
-
131
- async function installToReachy() {
132
- const dashboardUrl = document.getElementById('dashboardUrl').value.trim();
133
- const statusDiv = document.getElementById('installStatus');
134
- const installBtn = document.getElementById('installBtn');
135
-
136
- if (!dashboardUrl) {
137
- showStatus('error', 'Please enter your Reachy dashboard URL');
138
- return;
139
- }
140
-
141
- try {
142
- installBtn.disabled = true;
143
- installBtn.innerHTML = '<span class="btn-icon">⏳</span>Installing...';
144
- showStatus('loading', 'Connecting to your Reachy dashboard...');
145
-
146
- // Test connection
147
- const testResponse = await fetch(`${dashboardUrl}/api/status`, {
148
- method: 'GET',
149
- mode: 'cors',
150
- });
151
-
152
- if (!testResponse.ok) {
153
- throw new Error('Cannot connect to dashboard. Make sure the URL is correct and the dashboard is running.');
154
- }
155
-
156
- showStatus('loading', 'Reading app configuration...');
157
-
158
- // Get app name from pyproject.toml in current space
159
- const appName = await getAppNameFromCurrentSpace();
160
-
161
- // Get current space URL as repository URL
162
- const repoUrl = getCurrentSpaceUrl();
163
-
164
- showStatus('loading', `Starting installation of "${appName}"...`);
165
-
166
- // Start installation
167
- const installResponse = await fetch(`${dashboardUrl}/api/install`, {
168
- method: 'POST',
169
- mode: 'cors',
170
- headers: {
171
- 'Content-Type': 'application/json',
172
- },
173
- body: JSON.stringify({
174
- url: repoUrl,
175
- name: appName
176
- })
177
- });
178
-
179
- const result = await installResponse.json();
180
-
181
- if (installResponse.ok) {
182
- showStatus('success', `✅ Installation started for "${appName}"! Check your dashboard for progress.`);
183
- setTimeout(() => {
184
- showStatus('info', `Open your dashboard at ${dashboardUrl} to see the installed app.`);
185
- }, 3000);
186
- } else {
187
- throw new Error(result.detail || 'Installation failed');
188
- }
189
-
190
- } catch (error) {
191
- console.error('Installation error:', error);
192
- showStatus('error', `❌ ${error.message}`);
193
- } finally {
194
- installBtn.disabled = false;
195
- installBtn.innerHTML = '<span class="btn-icon">📥</span>Install App to Reachy';
196
- }
197
- }
198
-
199
- function showStatus(type, message) {
200
- const statusDiv = document.getElementById('installStatus');
201
- statusDiv.className = `install-status ${type}`;
202
- statusDiv.textContent = message;
203
- statusDiv.style.display = 'block';
204
- }
205
-
206
- function copyToClipboard() {
207
- const repoUrl = document.getElementById('repoUrl').textContent;
208
- navigator.clipboard.writeText(repoUrl).then(() => {
209
- showStatus('success', '📋 Repository URL copied to clipboard!');
210
- }).catch(() => {
211
- showStatus('error', 'Failed to copy URL. Please copy manually.');
212
- });
213
- }
214
-
215
- // Update the displayed repository URL on page load
216
- document.addEventListener('DOMContentLoaded', () => {
217
- // Auto-detect local dashboard
218
- const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
219
- if (isLocalhost) {
220
- document.getElementById('dashboardUrl').value = 'http://localhost:8000';
221
- }
222
-
223
- // Update the repository URL display if element exists
224
- const repoUrlElement = document.getElementById('repoUrl');
225
- if (repoUrlElement) {
226
- repoUrlElement.textContent = getCurrentSpaceUrl();
227
- }
228
- });
229
-
230
- // Event listeners
231
- document.getElementById('installBtn').addEventListener('click', installToReachy);
232
- </script>
233
  </body>
234
 
235
- </html>
 
4
  <head>
5
  <meta charset="utf-8" />
6
  <meta name="viewport" content="width=device-width" />
7
+ <title>Reachy Mini Clock</title>
8
  <link rel="stylesheet" href="style.css" />
9
  </head>
10
 
11
  <body>
12
+ <div class="background-accent"></div>
13
+ <header class="hero container">
14
+ <div class="badge">Reachy Mini</div>
15
+ <h1>Reachy Mini Clock</h1>
16
+ <p class="lead">Use Reachy Mini as a clock! The right antenna shows hours and the left antenna minutes.</p>
17
+ <div class="hero-pills">
18
+ <span class="pill">Right antenna = hours</span>
19
+ <span class="pill">Left antenna = minutes</span>
20
+ <span class="pill">Hands-free, room-friendly timekeeping</span>
21
  </div>
22
+ </header>
23
+
24
+ <main class="container grid">
25
+ <section class="panel info">
26
+ <h2>How it works</h2>
27
+ <ul class="steps">
28
+ <li>
29
+ <span class="step-number">1</span>
30
+ <div>
31
+ <h3>Launch the app</h3>
32
+ <p>Wake up Reachy Mini and start the clock app from your dashboard.</p>
33
+ </div>
34
+ </li>
35
+ <li>
36
+ <span class="step-number">2</span>
37
+ <div>
38
+ <h3>Pick your timezone</h3>
39
+ <p>Use the app's GUI to select your timezone and save it!</p>
40
+ </div>
41
+ </li>
42
+ <li>
43
+ <span class="step-number">3</span>
44
+ <div>
45
+ <h3>Read the antennas</h3>
46
+ <p>Glance at Reachy: the right antenna points to the hour while the left sweeps the minutes.</p>
47
+ </div>
48
+ </li>
49
+ </ul>
50
+ </section>
51
+
52
+ <section class="panel visual">
53
+ <div class="placeholder">
54
+ <div class="placeholder-tag">Preview</div>
55
+ <div class="placeholder-graphic">
56
+ <div class="dial"></div>
57
+ <div class="antenna antenna-right"></div>
58
+ <div class="antenna antenna-left"></div>
59
  </div>
60
+ <p class="placeholder-caption">Placeholder image for the future Reachy Mini clock view.</p>
61
  </div>
62
+ </section>
63
+ </main>
 
 
 
 
 
 
 
 
 
 
64
 
65
+ <footer class="footer">
 
 
 
 
 
 
 
 
 
 
66
  <p>
67
  🤖 Reachy Mini Clock •
68
  <a href="https://github.com/pollen-robotics" target="_blank">Pollen Robotics</a> •
69
+ <a href="https://huggingface.co/spaces/pollen-robotics/Reachy_Mini_Apps" target="_blank">Browse more apps</a>
 
70
  </p>
71
+ </footer>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  </body>
73
 
74
+ </html>
style.css CHANGED
@@ -4,408 +4,265 @@
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
- .camera-feed {
86
- font-size: 4rem;
87
- margin-bottom: 1rem;
88
- opacity: 0.7;
89
- }
90
-
91
- .detection-overlay {
92
- position: absolute;
93
- top: 50%;
94
- left: 50%;
95
- transform: translate(-50%, -50%);
96
- width: 100%;
97
- }
98
-
99
- .bbox {
100
- background: rgba(34, 197, 94, 0.9);
101
- color: white;
102
- padding: 0.5rem 1rem;
103
- border-radius: 8px;
104
- font-size: 0.9rem;
105
- font-weight: 600;
106
- margin: 0.5rem;
107
  display: inline-block;
108
- border: 2px solid #22c55e;
 
 
 
 
 
 
 
 
109
  }
110
 
111
- .app-details {
112
- padding: 3rem;
 
 
113
  }
114
 
115
- .app-details h2 {
116
- font-size: 2rem;
117
- color: #1e293b;
118
- margin-bottom: 2rem;
119
- text-align: center;
120
  }
121
 
122
- .template-info {
123
- display: grid;
124
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
125
- gap: 2rem;
126
- margin-bottom: 3rem;
127
  }
128
 
129
- .info-box {
130
- background: #f0f9ff;
131
- border: 2px solid #e0f2fe;
 
 
132
  border-radius: 12px;
133
- padding: 2rem;
134
- }
135
-
136
- .info-box h3 {
137
- color: #0c4a6e;
138
- margin-bottom: 1rem;
139
- font-size: 1.2rem;
140
  }
141
 
142
- .info-box p {
143
- color: #0369a1;
144
- line-height: 1.6;
 
145
  }
146
 
147
- .how-to-use {
148
- background: #fefce8;
149
- border: 2px solid #fde047;
150
- border-radius: 12px;
151
- padding: 2rem;
152
- margin-top: 3rem;
153
  }
154
 
155
- .how-to-use h3 {
156
- color: #a16207;
157
- margin-bottom: 1.5rem;
158
- font-size: 1.3rem;
159
- text-align: center;
160
  }
161
 
162
  .steps {
 
163
  display: flex;
164
  flex-direction: column;
165
- gap: 1.5rem;
166
- }
167
-
168
- .step {
169
- display: flex;
170
- align-items: flex-start;
171
- gap: 1rem;
172
  }
173
 
174
- .step-number {
175
- background: #eab308;
176
- color: white;
177
- width: 2rem;
178
- height: 2rem;
179
- border-radius: 50%;
180
- display: flex;
181
- align-items: center;
182
- justify-content: center;
183
- font-weight: bold;
184
- flex-shrink: 0;
185
- }
186
-
187
- .step h4 {
188
- color: #a16207;
189
- margin-bottom: 0.5rem;
190
- font-size: 1.1rem;
191
- }
192
-
193
- .step p {
194
- color: #ca8a04;
195
- }
196
-
197
- .download-card {
198
- background: white;
199
- border-radius: 20px;
200
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
201
- padding: 3rem;
202
- text-align: center;
203
  }
204
 
205
- .download-card h2 {
206
- font-size: 2rem;
207
- color: #1e293b;
208
- margin-bottom: 1rem;
209
  }
210
 
211
- .download-card>p {
212
- color: #64748b;
213
- font-size: 1.1rem;
214
- margin-bottom: 2rem;
215
  }
216
 
217
- .dashboard-config {
218
- margin-bottom: 2rem;
219
- text-align: left;
220
- max-width: 400px;
221
- margin-left: auto;
222
- margin-right: auto;
 
 
 
 
223
  }
224
 
225
- .dashboard-config label {
226
- display: block;
227
- color: #374151;
228
- font-weight: 600;
229
- margin-bottom: 0.5rem;
230
  }
231
 
232
- .dashboard-config input {
233
  width: 100%;
234
- padding: 0.75rem 1rem;
235
- border: 2px solid #e5e7eb;
236
- border-radius: 8px;
237
- font-size: 0.95rem;
238
- transition: border-color 0.2s;
239
- }
240
-
241
- .dashboard-config input:focus {
242
- outline: none;
243
- border-color: #667eea;
244
  }
245
 
246
- .install-btn {
247
- background: linear-gradient(135deg, #667eea, #764ba2);
248
- color: white;
249
- border: none;
250
- padding: 1.25rem 3rem;
251
- border-radius: 16px;
252
- font-size: 1.2rem;
 
253
  font-weight: 700;
254
- cursor: pointer;
255
- transition: all 0.3s ease;
256
- display: inline-flex;
257
- align-items: center;
258
- gap: 0.75rem;
259
- margin-bottom: 2rem;
260
- box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
261
  }
262
 
263
- .install-btn:hover:not(:disabled) {
264
- transform: translateY(-3px);
265
- box-shadow: 0 15px 35px rgba(102, 126, 234, 0.4);
 
 
 
 
 
 
266
  }
267
 
268
- .install-btn:disabled {
269
- opacity: 0.7;
270
- cursor: not-allowed;
271
- transform: none;
 
 
 
272
  }
273
 
274
- .manual-option {
275
- background: #f8fafc;
276
- border-radius: 12px;
277
- padding: 2rem;
278
- margin-top: 2rem;
 
279
  }
280
 
281
- .manual-option h3 {
282
- color: #1e293b;
283
- margin-bottom: 1rem;
284
- font-size: 1.2rem;
 
 
 
 
285
  }
286
 
287
- .manual-option>p {
288
- color: #64748b;
289
- margin-bottom: 1rem;
290
  }
291
 
292
- .btn-icon {
293
- font-size: 1.1rem;
 
294
  }
295
 
296
- .install-status {
297
- padding: 1rem;
298
- border-radius: 8px;
299
- font-size: 0.9rem;
300
  text-align: center;
301
- display: none;
302
- margin-top: 1rem;
303
- }
304
-
305
- .install-status.success {
306
- background: #dcfce7;
307
- color: #166534;
308
- border: 1px solid #bbf7d0;
309
- }
310
-
311
- .install-status.error {
312
- background: #fef2f2;
313
- color: #dc2626;
314
- border: 1px solid #fecaca;
315
- }
316
-
317
- .install-status.loading {
318
- background: #dbeafe;
319
- color: #1d4ed8;
320
- border: 1px solid #bfdbfe;
321
- }
322
-
323
- .install-status.info {
324
- background: #e0f2fe;
325
- color: #0369a1;
326
- border: 1px solid #7dd3fc;
327
- }
328
-
329
- .manual-install {
330
- background: #1f2937;
331
- border-radius: 8px;
332
- padding: 1rem;
333
- margin-bottom: 1rem;
334
- display: flex;
335
- align-items: center;
336
- gap: 1rem;
337
- }
338
-
339
- .manual-install code {
340
- color: #10b981;
341
- font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
342
- font-size: 0.85rem;
343
- flex: 1;
344
- overflow-x: auto;
345
- }
346
-
347
- .copy-btn {
348
- background: #374151;
349
- color: white;
350
- border: none;
351
- padding: 0.5rem 1rem;
352
- border-radius: 6px;
353
- font-size: 0.8rem;
354
- cursor: pointer;
355
- transition: background-color 0.2s;
356
- }
357
-
358
- .copy-btn:hover {
359
- background: #4b5563;
360
- }
361
-
362
- .manual-steps {
363
- color: #6b7280;
364
- font-size: 0.9rem;
365
- line-height: 1.8;
366
  }
367
 
368
  .footer {
369
  text-align: center;
370
- padding: 2rem;
371
- color: white;
372
- opacity: 0.8;
 
 
373
  }
374
 
375
  .footer a {
376
- color: white;
377
  text-decoration: none;
378
  font-weight: 600;
379
  }
380
 
381
  .footer a:hover {
382
- text-decoration: underline;
383
  }
384
 
385
- /* Responsive Design */
386
- @media (max-width: 768px) {
387
- .hero {
388
- padding: 2rem 1rem;
389
- }
390
-
391
- .hero h1 {
392
- font-size: 2rem;
393
- }
394
-
395
- .container {
396
- padding: 0 1rem;
397
- }
398
-
399
- .app-details,
400
- .download-card {
401
- padding: 2rem;
402
  }
403
 
404
- .features-grid {
405
- grid-template-columns: 1fr;
406
  }
407
 
408
- .download-options {
409
- grid-template-columns: 1fr;
410
  }
411
- }
 
4
  box-sizing: border-box;
5
  }
6
 
7
+ :root {
8
+ --bg: #0f172a;
9
+ --card: #0b1221;
10
+ --accent: #7dd3fc;
11
+ --accent-2: #a855f7;
12
+ --text: #e2e8f0;
13
+ --muted: #94a3b8;
14
+ --border: rgba(148, 163, 184, 0.2);
15
+ }
16
+
17
  body {
18
+ font-family: "Space Grotesk", "Neue Haas Grotesk", "DM Sans", "Inter", system-ui, -apple-system, sans-serif;
19
  line-height: 1.6;
20
+ color: var(--text);
21
+ background: radial-gradient(circle at 20% 20%, rgba(125, 211, 252, 0.08), transparent 25%),
22
+ radial-gradient(circle at 80% 0%, rgba(168, 85, 247, 0.1), transparent 30%),
23
+ var(--bg);
24
  min-height: 100vh;
25
+ padding: 3rem 1.5rem 2rem;
26
+ position: relative;
27
+ overflow-x: hidden;
28
  }
29
 
30
+ .background-accent {
31
+ position: absolute;
32
+ inset: 0;
33
+ pointer-events: none;
34
+ background: radial-gradient(circle at 70% 60%, rgba(125, 211, 252, 0.07), transparent 40%),
35
+ radial-gradient(circle at 15% 75%, rgba(168, 85, 247, 0.08), transparent 35%);
36
+ z-index: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
38
 
39
  .container {
40
+ max-width: 1100px;
41
  margin: 0 auto;
 
42
  position: relative;
43
+ z-index: 1;
44
  }
45
 
46
+ .hero {
 
 
 
 
 
 
 
 
 
 
 
 
47
  text-align: center;
48
+ margin-bottom: 2.5rem;
49
  }
50
 
51
+ .badge {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  display: inline-block;
53
+ padding: 0.4rem 0.9rem;
54
+ border-radius: 999px;
55
+ background: rgba(125, 211, 252, 0.15);
56
+ color: var(--accent);
57
+ border: 1px solid rgba(125, 211, 252, 0.25);
58
+ font-weight: 600;
59
+ text-transform: uppercase;
60
+ letter-spacing: 0.08em;
61
+ font-size: 0.85rem;
62
  }
63
 
64
+ h1 {
65
+ font-size: clamp(2.4rem, 4vw, 3.4rem);
66
+ margin: 0.6rem 0;
67
+ letter-spacing: -0.03em;
68
  }
69
 
70
+ .lead {
71
+ color: var(--muted);
72
+ font-size: 1.05rem;
73
+ max-width: 680px;
74
+ margin: 0 auto 1.2rem;
75
  }
76
 
77
+ .hero-pills {
78
+ display: flex;
79
+ flex-wrap: wrap;
80
+ justify-content: center;
81
+ gap: 0.6rem;
82
  }
83
 
84
+ .pill {
85
+ background: rgba(255, 255, 255, 0.05);
86
+ border: 1px solid var(--border);
87
+ color: var(--text);
88
+ padding: 0.55rem 0.9rem;
89
  border-radius: 12px;
90
+ font-weight: 600;
91
+ font-size: 0.95rem;
92
+ backdrop-filter: blur(6px);
 
 
 
 
93
  }
94
 
95
+ .grid {
96
+ display: grid;
97
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
98
+ gap: 1.5rem;
99
  }
100
 
101
+ .panel {
102
+ background: var(--card);
103
+ border: 1px solid var(--border);
104
+ border-radius: 18px;
105
+ padding: 1.6rem;
106
+ box-shadow: 0 20px 70px rgba(0, 0, 0, 0.35);
107
  }
108
 
109
+ .panel h2 {
110
+ font-size: 1.4rem;
111
+ margin-bottom: 1rem;
 
 
112
  }
113
 
114
  .steps {
115
+ list-style: none;
116
  display: flex;
117
  flex-direction: column;
118
+ gap: 1.1rem;
 
 
 
 
 
 
119
  }
120
 
121
+ .steps li {
122
+ display: grid;
123
+ grid-template-columns: 46px 1fr;
124
+ align-items: start;
125
+ gap: 0.8rem;
126
+ padding: 0.9rem 1rem;
127
+ border-radius: 14px;
128
+ border: 1px solid var(--border);
129
+ background: rgba(255, 255, 255, 0.02);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  }
131
 
132
+ .steps h3 {
133
+ margin: 0;
134
+ font-size: 1.05rem;
 
135
  }
136
 
137
+ .steps p {
138
+ color: var(--muted);
 
 
139
  }
140
 
141
+ .step-number {
142
+ width: 46px;
143
+ height: 46px;
144
+ border-radius: 12px;
145
+ background: linear-gradient(135deg, rgba(125, 211, 252, 0.18), rgba(168, 85, 247, 0.18));
146
+ border: 1px solid var(--border);
147
+ display: grid;
148
+ place-items: center;
149
+ font-weight: 700;
150
+ color: var(--text);
151
  }
152
 
153
+ .visual {
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: center;
 
157
  }
158
 
159
+ .placeholder {
160
  width: 100%;
161
+ background: linear-gradient(145deg, rgba(15, 23, 42, 0.7), rgba(15, 23, 42, 0.95));
162
+ border: 1px dashed rgba(125, 211, 252, 0.4);
163
+ border-radius: 20px;
164
+ padding: 1.4rem;
165
+ position: relative;
166
+ overflow: hidden;
 
 
 
 
167
  }
168
 
169
+ .placeholder-tag {
170
+ position: absolute;
171
+ top: 0.9rem;
172
+ right: 0.9rem;
173
+ padding: 0.35rem 0.7rem;
174
+ border-radius: 999px;
175
+ background: rgba(168, 85, 247, 0.15);
176
+ color: #e9d5ff;
177
  font-weight: 700;
178
+ letter-spacing: 0.05em;
179
+ font-size: 0.85rem;
 
 
 
 
 
180
  }
181
 
182
+ .placeholder-graphic {
183
+ position: relative;
184
+ aspect-ratio: 4 / 3;
185
+ background: radial-gradient(circle at 50% 40%, rgba(125, 211, 252, 0.1), transparent 60%),
186
+ radial-gradient(circle at 50% 70%, rgba(168, 85, 247, 0.12), transparent 55%),
187
+ #0b1021;
188
+ border-radius: 14px;
189
+ display: grid;
190
+ place-items: center;
191
  }
192
 
193
+ .dial {
194
+ width: 70%;
195
+ height: 70%;
196
+ border-radius: 50%;
197
+ border: 2px dashed rgba(255, 255, 255, 0.15);
198
+ box-shadow: inset 0 0 0 1px rgba(125, 211, 252, 0.15);
199
+ position: relative;
200
  }
201
 
202
+ .dial::after {
203
+ content: '';
204
+ position: absolute;
205
+ inset: 32%;
206
+ border-radius: 50%;
207
+ background: radial-gradient(circle, rgba(125, 211, 252, 0.25), transparent 70%);
208
  }
209
 
210
+ .antenna {
211
+ position: absolute;
212
+ height: 34%;
213
+ width: 4px;
214
+ background: linear-gradient(180deg, rgba(125, 211, 252, 0.9), rgba(168, 85, 247, 0.9));
215
+ border-radius: 999px;
216
+ transform-origin: bottom center;
217
+ bottom: 15%;
218
  }
219
 
220
+ .antenna-right {
221
+ transform: rotate(30deg);
222
+ right: 50%;
223
  }
224
 
225
+ .antenna-left {
226
+ transform: rotate(-60deg);
227
+ left: 50%;
228
  }
229
 
230
+ .placeholder-caption {
231
+ color: var(--muted);
232
+ margin-top: 0.9rem;
 
233
  text-align: center;
234
+ font-size: 0.95rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
236
 
237
  .footer {
238
  text-align: center;
239
+ margin-top: 2.5rem;
240
+ color: var(--muted);
241
+ font-size: 0.95rem;
242
+ position: relative;
243
+ z-index: 1;
244
  }
245
 
246
  .footer a {
247
+ color: var(--text);
248
  text-decoration: none;
249
  font-weight: 600;
250
  }
251
 
252
  .footer a:hover {
253
+ color: var(--accent);
254
  }
255
 
256
+ @media (max-width: 640px) {
257
+ body {
258
+ padding: 2.5rem 1rem 1.5rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  }
260
 
261
+ .steps li {
262
+ grid-template-columns: 42px 1fr;
263
  }
264
 
265
+ .antenna {
266
+ height: 40%;
267
  }
268
+ }