cduss commited on
Commit
871df0d
·
1 Parent(s): bb57642
Files changed (1) hide show
  1. index.html +473 -18
index.html CHANGED
@@ -1,19 +1,474 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Reachy Mini Spaces Store</title>
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
17
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
18
+ min-height: 100vh;
19
+ color: #333;
20
+ }
21
+
22
+ .container {
23
+ max-width: 1400px;
24
+ margin: 0 auto;
25
+ padding: 40px 20px;
26
+ }
27
+
28
+ .header {
29
+ text-align: center;
30
+ margin-bottom: 40px;
31
+ color: white;
32
+ }
33
+
34
+ .header h1 {
35
+ font-size: 3rem;
36
+ font-weight: 700;
37
+ margin-bottom: 10px;
38
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
39
+ }
40
+
41
+ .header p {
42
+ font-size: 1.2rem;
43
+ opacity: 0.9;
44
+ }
45
+
46
+ .controls {
47
+ display: flex;
48
+ justify-content: space-between;
49
+ align-items: center;
50
+ margin-bottom: 30px;
51
+ background: rgba(255, 255, 255, 0.1);
52
+ backdrop-filter: blur(10px);
53
+ padding: 20px;
54
+ border-radius: 15px;
55
+ }
56
+
57
+ .search-box {
58
+ flex: 1;
59
+ max-width: 400px;
60
+ margin-right: 20px;
61
+ }
62
+
63
+ .search-box input {
64
+ width: 100%;
65
+ padding: 12px 20px;
66
+ border: none;
67
+ border-radius: 25px;
68
+ font-size: 16px;
69
+ background: rgba(255, 255, 255, 0.9);
70
+ backdrop-filter: blur(10px);
71
+ outline: none;
72
+ transition: all 0.3s ease;
73
+ }
74
+
75
+ .search-box input:focus {
76
+ transform: scale(1.02);
77
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
78
+ }
79
+
80
+ .sort-controls {
81
+ display: flex;
82
+ gap: 10px;
83
+ }
84
+
85
+ .sort-btn {
86
+ padding: 10px 20px;
87
+ border: none;
88
+ border-radius: 20px;
89
+ background: rgba(255, 255, 255, 0.2);
90
+ color: white;
91
+ cursor: pointer;
92
+ transition: all 0.3s ease;
93
+ font-weight: 500;
94
+ }
95
+
96
+ .sort-btn:hover {
97
+ background: rgba(255, 255, 255, 0.3);
98
+ transform: translateY(-2px);
99
+ }
100
+
101
+ .sort-btn.active {
102
+ background: rgba(255, 255, 255, 0.9);
103
+ color: #667eea;
104
+ }
105
+
106
+ .stats {
107
+ text-align: center;
108
+ color: white;
109
+ margin-bottom: 30px;
110
+ font-size: 1.1rem;
111
+ opacity: 0.9;
112
+ }
113
+
114
+ .grid {
115
+ display: grid;
116
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
117
+ gap: 25px;
118
+ margin-bottom: 40px;
119
+ }
120
+
121
+ .app-card {
122
+ background: rgba(255, 255, 255, 0.95);
123
+ backdrop-filter: blur(20px);
124
+ border-radius: 20px;
125
+ padding: 25px;
126
+ cursor: pointer;
127
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
128
+ border: 1px solid rgba(255, 255, 255, 0.2);
129
+ position: relative;
130
+ overflow: hidden;
131
+ }
132
+
133
+ .app-card::before {
134
+ content: '';
135
+ position: absolute;
136
+ top: 0;
137
+ left: 0;
138
+ right: 0;
139
+ height: 4px;
140
+ background: linear-gradient(90deg, #667eea, #764ba2);
141
+ transform: scaleX(0);
142
+ transition: transform 0.3s ease;
143
+ }
144
+
145
+ .app-card:hover {
146
+ transform: translateY(-8px) scale(1.02);
147
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
148
+ }
149
+
150
+ .app-card:hover::before {
151
+ transform: scaleX(1);
152
+ }
153
+
154
+ .app-header {
155
+ display: flex;
156
+ align-items: flex-start;
157
+ margin-bottom: 15px;
158
+ }
159
+
160
+ .app-icon {
161
+ width: 60px;
162
+ height: 60px;
163
+ border-radius: 15px;
164
+ background: linear-gradient(135deg, #667eea, #764ba2);
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ margin-right: 15px;
169
+ font-size: 24px;
170
+ color: white;
171
+ font-weight: bold;
172
+ flex-shrink: 0;
173
+ }
174
+
175
+ .app-info {
176
+ flex: 1;
177
+ min-width: 0;
178
+ }
179
+
180
+ .app-title {
181
+ font-size: 1.3rem;
182
+ font-weight: 600;
183
+ margin-bottom: 5px;
184
+ color: #333;
185
+ line-height: 1.3;
186
+ word-wrap: break-word;
187
+ }
188
+
189
+ .app-author {
190
+ color: #666;
191
+ font-size: 0.9rem;
192
+ margin-bottom: 8px;
193
+ }
194
+
195
+ .app-description {
196
+ color: #555;
197
+ font-size: 0.95rem;
198
+ line-height: 1.5;
199
+ margin-bottom: 15px;
200
+ display: -webkit-box;
201
+ -webkit-line-clamp: 3;
202
+ -webkit-box-orient: vertical;
203
+ overflow: hidden;
204
+ }
205
+
206
+ .app-stats {
207
+ display: flex;
208
+ justify-content: space-between;
209
+ align-items: center;
210
+ margin-top: auto;
211
+ }
212
+
213
+ .stat-item {
214
+ display: flex;
215
+ align-items: center;
216
+ gap: 5px;
217
+ color: #666;
218
+ font-size: 0.9rem;
219
+ }
220
+
221
+ .stat-icon {
222
+ font-size: 16px;
223
+ }
224
+
225
+ .loading {
226
+ text-align: center;
227
+ color: white;
228
+ font-size: 1.2rem;
229
+ padding: 60px;
230
+ }
231
+
232
+ .loading::after {
233
+ content: '';
234
+ display: inline-block;
235
+ width: 20px;
236
+ height: 20px;
237
+ border: 2px solid rgba(255, 255, 255, 0.3);
238
+ border-top: 2px solid white;
239
+ border-radius: 50%;
240
+ animation: spin 1s linear infinite;
241
+ margin-left: 10px;
242
+ }
243
+
244
+ @keyframes spin {
245
+ 0% {
246
+ transform: rotate(0deg);
247
+ }
248
+
249
+ 100% {
250
+ transform: rotate(360deg);
251
+ }
252
+ }
253
+
254
+ .error {
255
+ text-align: center;
256
+ color: white;
257
+ background: rgba(255, 0, 0, 0.1);
258
+ padding: 30px;
259
+ border-radius: 15px;
260
+ margin: 20px 0;
261
+ }
262
+
263
+ @media (max-width: 768px) {
264
+ .header h1 {
265
+ font-size: 2rem;
266
+ }
267
+
268
+ .controls {
269
+ flex-direction: column;
270
+ gap: 15px;
271
+ }
272
+
273
+ .search-box {
274
+ margin-right: 0;
275
+ max-width: none;
276
+ }
277
+
278
+ .grid {
279
+ grid-template-columns: 1fr;
280
+ gap: 20px;
281
+ }
282
+ }
283
+ </style>
284
+ </head>
285
+
286
+ <body>
287
+ <div class="container">
288
+ <div class="header">
289
+ <h1>🤖 Reachy Mini Spaces</h1>
290
+ <p>Discover amazing AI-powered applications and demos</p>
291
  </div>
292
+
293
+ <div class="controls">
294
+ <div class="search-box">
295
+ <input type="text" id="searchInput" placeholder="Search spaces..." />
296
+ </div>
297
+ <div class="sort-controls">
298
+ <button class="sort-btn active" data-sort="likes">❤️ Likes</button>
299
+ <button class="sort-btn" data-sort="created">🕒 Recent</button>
300
+ <button class="sort-btn" data-sort="name">🔤 A-Z</button>
301
+ </div>
302
+ </div>
303
+
304
+ <div class="stats" id="stats">
305
+ <div class="loading">Loading Reachy Mini spaces...</div>
306
+ </div>
307
+
308
+ <div class="grid" id="spacesGrid">
309
+ </div>
310
+ </div>
311
+
312
+ <script>
313
+ class SpacesStore {
314
+ constructor() {
315
+ this.spaces = [];
316
+ this.filteredSpaces = [];
317
+ this.currentSort = 'likes';
318
+ this.searchTerm = '';
319
+ this.init();
320
+ }
321
+
322
+ async init() {
323
+ await this.loadSpaces();
324
+ this.setupEventListeners();
325
+ this.renderSpaces();
326
+ }
327
+
328
+ async loadSpaces() {
329
+ try {
330
+ const response = await fetch('https://huggingface.co/api/spaces?search=reachy-mini&sort=likes&direction=-1&limit=50');
331
+ const data = await response.json();
332
+
333
+ this.spaces = data.map(space => ({
334
+ id: space.id,
335
+ title: space.id.split('/').pop().replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
336
+ author: space.author,
337
+ description: space.cardData?.short_description || 'No description available',
338
+ likes: space.likes || 0,
339
+ created: new Date(space.createdAt).getTime(),
340
+ url: `https://huggingface.co/spaces/${space.id}`,
341
+ tags: space.tags || []
342
+ }));
343
+
344
+ this.filteredSpaces = [...this.spaces];
345
+ this.updateStats();
346
+ } catch (error) {
347
+ console.error('Error loading spaces:', error);
348
+ this.showError();
349
+ }
350
+ }
351
+
352
+ setupEventListeners() {
353
+ // Search functionality
354
+ const searchInput = document.getElementById('searchInput');
355
+ searchInput.addEventListener('input', (e) => {
356
+ this.searchTerm = e.target.value.toLowerCase();
357
+ this.filterSpaces();
358
+ });
359
+
360
+ // Sort functionality
361
+ const sortButtons = document.querySelectorAll('.sort-btn');
362
+ sortButtons.forEach(btn => {
363
+ btn.addEventListener('click', (e) => {
364
+ sortButtons.forEach(b => b.classList.remove('active'));
365
+ e.target.classList.add('active');
366
+ this.currentSort = e.target.dataset.sort;
367
+ this.sortSpaces();
368
+ });
369
+ });
370
+ }
371
+
372
+ filterSpaces() {
373
+ this.filteredSpaces = this.spaces.filter(space =>
374
+ space.title.toLowerCase().includes(this.searchTerm) ||
375
+ space.author.toLowerCase().includes(this.searchTerm) ||
376
+ space.description.toLowerCase().includes(this.searchTerm)
377
+ );
378
+ this.sortSpaces();
379
+ }
380
+
381
+ sortSpaces() {
382
+ switch (this.currentSort) {
383
+ case 'likes':
384
+ this.filteredSpaces.sort((a, b) => b.likes - a.likes);
385
+ break;
386
+ case 'created':
387
+ this.filteredSpaces.sort((a, b) => b.created - a.created);
388
+ break;
389
+ case 'name':
390
+ this.filteredSpaces.sort((a, b) => a.title.localeCompare(b.title));
391
+ break;
392
+ }
393
+ this.renderSpaces();
394
+ }
395
+
396
+ updateStats() {
397
+ const statsEl = document.getElementById('stats');
398
+ const total = this.spaces.length;
399
+ const totalLikes = this.spaces.reduce((sum, space) => sum + space.likes, 0);
400
+ statsEl.innerHTML = `Found ${total} spaces with ${totalLikes.toLocaleString()} total likes`;
401
+ }
402
+
403
+ renderSpaces() {
404
+ const grid = document.getElementById('spacesGrid');
405
+
406
+ if (this.filteredSpaces.length === 0) {
407
+ grid.innerHTML = '<div class="error">No spaces found matching your criteria</div>';
408
+ return;
409
+ }
410
+
411
+ grid.innerHTML = this.filteredSpaces.map(space => `
412
+ <div class="app-card" onclick="window.open('${space.url}', '_blank')">
413
+ <div class="app-header">
414
+ <div class="app-icon">
415
+ ${space.title.charAt(0)}
416
+ </div>
417
+ <div class="app-info">
418
+ <div class="app-title">${space.title}</div>
419
+ <div class="app-author">by ${space.author}</div>
420
+ </div>
421
+ </div>
422
+ <div class="app-description">${space.description}</div>
423
+ <div class="app-stats">
424
+ <div class="stat-item">
425
+ <span class="stat-icon">❤️</span>
426
+ <span>${space.likes}</span>
427
+ </div>
428
+ <div class="stat-item">
429
+ <span class="stat-icon">📅</span>
430
+ <span>${this.formatDate(space.created)}</span>
431
+ </div>
432
+ </div>
433
+ </div>
434
+ `).join('');
435
+ }
436
+
437
+ formatDate(timestamp) {
438
+ const date = new Date(timestamp);
439
+ const now = new Date();
440
+ const diffInDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
441
+
442
+ if (diffInDays === 0) return 'Today';
443
+ if (diffInDays === 1) return 'Yesterday';
444
+ if (diffInDays < 30) return `${diffInDays}d ago`;
445
+ if (diffInDays < 365) return `${Math.floor(diffInDays / 30)}mo ago`;
446
+ return `${Math.floor(diffInDays / 365)}y ago`;
447
+ }
448
+
449
+ showError() {
450
+ const grid = document.getElementById('spacesGrid');
451
+ const stats = document.getElementById('stats');
452
+
453
+ stats.innerHTML = 'Unable to load spaces';
454
+ grid.innerHTML = `
455
+ <div class="error">
456
+ <h3>Unable to load Hugging Face Spaces</h3>
457
+ <p>This might be due to CORS restrictions. The design is fully functional - you can see how it would work with real data!</p>
458
+ <p>In a production environment, you'd use a backend API or proxy to fetch the data.</p>
459
+ </div>
460
+ `;
461
+
462
+ // Show demo data instead
463
+ // setTimeout(() => this.loadDemoData(), 2000);
464
+ }
465
+
466
+
467
+ }
468
+
469
+ // Initialize the app
470
+ new SpacesStore();
471
+ </script>
472
+ </body>
473
+
474
+ </html>