MySafeCode commited on
Commit
2eeac5f
·
verified ·
1 Parent(s): 01f8f90

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +1211 -0
index.html ADDED
@@ -0,0 +1,1211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>StableCog Dashboard</title>
7
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎨</text></svg>">
8
+
9
+ <style>
10
+ :root {
11
+ --primary-color: #667eea;
12
+ --secondary-color: #764ba2;
13
+ --success-color: #4CAF50;
14
+ --warning-color: #FF9800;
15
+ --danger-color: #F44336;
16
+ --light-color: #90EE90;
17
+ --dark-color: #333;
18
+ --card-bg: rgba(255, 255, 255, 0.1);
19
+ --text-light: #ffffff;
20
+ --text-dark: #333333;
21
+ --shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
22
+ --border-radius: 15px;
23
+ }
24
+
25
+ * {
26
+ margin: 0;
27
+ padding: 0;
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ body {
32
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
33
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
34
+ min-height: 100vh;
35
+ padding: 20px;
36
+ color: var(--text-light);
37
+ }
38
+
39
+ .container {
40
+ max-width: 1200px;
41
+ margin: 0 auto;
42
+ }
43
+
44
+ .header {
45
+ text-align: center;
46
+ margin-bottom: 30px;
47
+ padding: 20px;
48
+ }
49
+
50
+ .header h1 {
51
+ font-size: 2.8rem;
52
+ margin-bottom: 10px;
53
+ font-weight: 700;
54
+ }
55
+
56
+ .header p {
57
+ font-size: 1.1rem;
58
+ opacity: 0.9;
59
+ max-width: 600px;
60
+ margin: 0 auto;
61
+ }
62
+
63
+ .dashboard {
64
+ display: grid;
65
+ grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
66
+ gap: 25px;
67
+ margin-bottom: 30px;
68
+ }
69
+
70
+ .card {
71
+ background: var(--card-bg);
72
+ backdrop-filter: blur(10px);
73
+ border-radius: var(--border-radius);
74
+ padding: 25px;
75
+ border: 1px solid rgba(255, 255, 255, 0.2);
76
+ box-shadow: var(--shadow);
77
+ transition: transform 0.3s ease;
78
+ }
79
+
80
+ .card:hover {
81
+ transform: translateY(-5px);
82
+ }
83
+
84
+ .card-title {
85
+ font-size: 1.5rem;
86
+ margin-bottom: 20px;
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 10px;
90
+ }
91
+
92
+ .card-title i {
93
+ font-size: 1.8rem;
94
+ }
95
+
96
+ .stat-grid {
97
+ display: grid;
98
+ grid-template-columns: repeat(2, 1fr);
99
+ gap: 15px;
100
+ margin-bottom: 20px;
101
+ }
102
+
103
+ .stat-box {
104
+ background: rgba(255, 255, 255, 0.1);
105
+ border-radius: 10px;
106
+ padding: 15px;
107
+ text-align: center;
108
+ }
109
+
110
+ .stat-value {
111
+ font-size: 2.2rem;
112
+ font-weight: bold;
113
+ margin-bottom: 5px;
114
+ }
115
+
116
+ .stat-label {
117
+ font-size: 0.9rem;
118
+ opacity: 0.8;
119
+ }
120
+
121
+ .progress-container {
122
+ margin: 20px 0;
123
+ }
124
+
125
+ .progress-header {
126
+ display: flex;
127
+ justify-content: space-between;
128
+ margin-bottom: 8px;
129
+ }
130
+
131
+ .progress-bar {
132
+ height: 22px;
133
+ background: rgba(255, 255, 255, 0.15);
134
+ border-radius: 11px;
135
+ overflow: hidden;
136
+ position: relative;
137
+ }
138
+
139
+ .progress-fill {
140
+ height: 100%;
141
+ border-radius: 11px;
142
+ transition: width 0.5s ease-in-out;
143
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
144
+ }
145
+
146
+ .progress-text {
147
+ position: absolute;
148
+ right: 10px;
149
+ top: 50%;
150
+ transform: translateY(-50%);
151
+ font-size: 12px;
152
+ font-weight: bold;
153
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
154
+ }
155
+
156
+ .status-badge {
157
+ display: inline-flex;
158
+ align-items: center;
159
+ gap: 8px;
160
+ padding: 8px 16px;
161
+ background: rgba(255, 255, 255, 0.1);
162
+ border-radius: 20px;
163
+ font-weight: bold;
164
+ margin-top: 15px;
165
+ }
166
+
167
+ .models-grid {
168
+ display: grid;
169
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
170
+ gap: 15px;
171
+ max-height: 500px;
172
+ overflow-y: auto;
173
+ padding-right: 10px;
174
+ }
175
+
176
+ .model-card {
177
+ background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
178
+ border-radius: 10px;
179
+ padding: 15px;
180
+ border-left: 4px solid var(--primary-color);
181
+ }
182
+
183
+ .model-header {
184
+ display: flex;
185
+ justify-content: space-between;
186
+ align-items: center;
187
+ margin-bottom: 8px;
188
+ }
189
+
190
+ .model-name {
191
+ font-weight: bold;
192
+ font-size: 1rem;
193
+ display: flex;
194
+ align-items: center;
195
+ gap: 8px;
196
+ }
197
+
198
+ .model-badge {
199
+ padding: 2px 8px;
200
+ border-radius: 12px;
201
+ font-size: 0.7rem;
202
+ font-weight: bold;
203
+ }
204
+
205
+ .badge-default { background: var(--success-color); }
206
+ .badge-public { background: #2196F3; }
207
+ .badge-community { background: var(--danger-color); }
208
+ .badge-other { background: #9E9E9E; }
209
+
210
+ .model-id {
211
+ font-size: 0.8rem;
212
+ opacity: 0.7;
213
+ }
214
+
215
+ .model-description {
216
+ font-size: 0.9rem;
217
+ opacity: 0.9;
218
+ margin-bottom: 10px;
219
+ line-height: 1.4;
220
+ }
221
+
222
+ .model-meta {
223
+ display: flex;
224
+ gap: 10px;
225
+ font-size: 0.8rem;
226
+ opacity: 0.7;
227
+ }
228
+
229
+ .controls {
230
+ display: flex;
231
+ gap: 15px;
232
+ margin-top: 25px;
233
+ flex-wrap: wrap;
234
+ }
235
+
236
+ .btn {
237
+ padding: 12px 24px;
238
+ border: none;
239
+ border-radius: 10px;
240
+ font-size: 1rem;
241
+ font-weight: 600;
242
+ cursor: pointer;
243
+ display: flex;
244
+ align-items: center;
245
+ gap: 8px;
246
+ transition: all 0.3s ease;
247
+ }
248
+
249
+ .btn-primary {
250
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
251
+ color: white;
252
+ }
253
+
254
+ .btn-secondary {
255
+ background: rgba(255, 255, 255, 0.2);
256
+ color: white;
257
+ border: 1px solid rgba(255, 255, 255, 0.3);
258
+ }
259
+
260
+ .btn:hover {
261
+ transform: translateY(-2px);
262
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
263
+ }
264
+
265
+ .btn:active {
266
+ transform: translateY(0);
267
+ }
268
+
269
+ .api-key-input {
270
+ display: flex;
271
+ gap: 10px;
272
+ margin-bottom: 20px;
273
+ flex-wrap: wrap;
274
+ }
275
+
276
+ .api-key-input input {
277
+ flex: 1;
278
+ min-width: 300px;
279
+ padding: 12px 15px;
280
+ border: 1px solid rgba(255, 255, 255, 0.3);
281
+ border-radius: 10px;
282
+ background: rgba(255, 255, 255, 0.1);
283
+ color: white;
284
+ font-size: 1rem;
285
+ }
286
+
287
+ .api-key-input input::placeholder {
288
+ color: rgba(255, 255, 255, 0.6);
289
+ }
290
+
291
+ .api-key-input input:focus {
292
+ outline: none;
293
+ border-color: var(--primary-color);
294
+ box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.3);
295
+ }
296
+
297
+ .error-container {
298
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
299
+ border-radius: var(--border-radius);
300
+ padding: 25px;
301
+ margin: 20px 0;
302
+ text-align: center;
303
+ }
304
+
305
+ .error-title {
306
+ font-size: 1.5rem;
307
+ margin-bottom: 15px;
308
+ }
309
+
310
+ .error-content {
311
+ background: rgba(255, 255, 255, 0.15);
312
+ backdrop-filter: blur(10px);
313
+ padding: 20px;
314
+ border-radius: 10px;
315
+ margin-bottom: 20px;
316
+ border: 1px solid rgba(255, 255, 255, 0.2);
317
+ }
318
+
319
+ .loading {
320
+ display: inline-block;
321
+ width: 20px;
322
+ height: 20px;
323
+ border: 3px solid rgba(255, 255, 255, 0.3);
324
+ border-radius: 50%;
325
+ border-top-color: white;
326
+ animation: spin 1s ease-in-out infinite;
327
+ }
328
+
329
+ @keyframes spin {
330
+ to { transform: rotate(360deg); }
331
+ }
332
+
333
+ .credit-breakdown {
334
+ margin-top: 20px;
335
+ padding-top: 20px;
336
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
337
+ }
338
+
339
+ .credit-item {
340
+ background: rgba(255, 255, 255, 0.05);
341
+ border-radius: 8px;
342
+ padding: 12px;
343
+ margin-bottom: 10px;
344
+ }
345
+
346
+ .credit-header {
347
+ display: flex;
348
+ justify-content: space-between;
349
+ margin-bottom: 5px;
350
+ }
351
+
352
+ .credit-name {
353
+ font-weight: bold;
354
+ }
355
+
356
+ .credit-amount {
357
+ font-weight: bold;
358
+ }
359
+
360
+ .credit-description {
361
+ font-size: 0.8rem;
362
+ opacity: 0.8;
363
+ margin-bottom: 8px;
364
+ }
365
+
366
+ .credit-progress {
367
+ height: 6px;
368
+ background: rgba(255, 255, 255, 0.1);
369
+ border-radius: 3px;
370
+ overflow: hidden;
371
+ }
372
+
373
+ .credit-progress-fill {
374
+ height: 100%;
375
+ border-radius: 3px;
376
+ }
377
+
378
+ .last-updated {
379
+ text-align: center;
380
+ font-size: 0.9rem;
381
+ opacity: 0.7;
382
+ margin-top: 10px;
383
+ }
384
+
385
+ .tabs {
386
+ display: flex;
387
+ gap: 5px;
388
+ margin-bottom: 25px;
389
+ background: rgba(255, 255, 255, 0.1);
390
+ border-radius: 12px;
391
+ padding: 5px;
392
+ }
393
+
394
+ .tab {
395
+ flex: 1;
396
+ padding: 12px 20px;
397
+ text-align: center;
398
+ border-radius: 8px;
399
+ cursor: pointer;
400
+ font-weight: 600;
401
+ transition: all 0.3s ease;
402
+ }
403
+
404
+ .tab.active {
405
+ background: white;
406
+ color: var(--primary-color);
407
+ }
408
+
409
+ .tab-content {
410
+ display: none;
411
+ }
412
+
413
+ .tab-content.active {
414
+ display: block;
415
+ }
416
+
417
+ .footer {
418
+ text-align: center;
419
+ margin-top: 40px;
420
+ padding-top: 20px;
421
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
422
+ font-size: 0.9rem;
423
+ opacity: 0.7;
424
+ }
425
+
426
+ @media (max-width: 768px) {
427
+ .dashboard {
428
+ grid-template-columns: 1fr;
429
+ }
430
+
431
+ .stat-grid {
432
+ grid-template-columns: 1fr;
433
+ }
434
+
435
+ .models-grid {
436
+ grid-template-columns: 1fr;
437
+ }
438
+
439
+ .header h1 {
440
+ font-size: 2rem;
441
+ }
442
+
443
+ .api-key-input input {
444
+ min-width: 100%;
445
+ }
446
+
447
+ .controls {
448
+ flex-direction: column;
449
+ }
450
+
451
+ .btn {
452
+ width: 100%;
453
+ justify-content: center;
454
+ }
455
+ }
456
+ </style>
457
+ </head>
458
+ <body>
459
+ <div class="container">
460
+ <div class="header">
461
+ <h1>🎯 StableCog Dashboard</h1>
462
+ <p>Monitor your credits and explore available AI models for image generation</p>
463
+ </div>
464
+
465
+ <!-- API Key Input -->
466
+ <div class="api-key-input">
467
+ <input type="password"
468
+ id="apiKeyInput"
469
+ placeholder="Enter your StableCog API key (or leave empty for demo)"
470
+ autocomplete="off">
471
+ <button class="btn btn-primary" onclick="saveApiKey()">
472
+ <span>🔑</span> Save API Key
473
+ </button>
474
+ <button class="btn btn-secondary" onclick="clearApiKey()">
475
+ <span>🗑️</span> Clear
476
+ </button>
477
+ </div>
478
+
479
+ <!-- Tabs -->
480
+ <div class="tabs">
481
+ <div class="tab active" onclick="switchTab('credits')">💰 Credits</div>
482
+ <div class="tab" onclick="switchTab('models')">🤖 Models</div>
483
+ <div class="tab" onclick="switchTab('info')">📚 Info</div>
484
+ </div>
485
+
486
+ <!-- Credits Tab -->
487
+ <div id="creditsTab" class="tab-content active">
488
+ <div class="dashboard">
489
+ <!-- Credit Status Card -->
490
+ <div class="card">
491
+ <h2 class="card-title">🎨 Credit Status</h2>
492
+
493
+ <div class="stat-grid">
494
+ <div class="stat-box">
495
+ <div class="stat-value" id="totalCredits">0</div>
496
+ <div class="stat-label">Total Credits</div>
497
+ </div>
498
+ <div class="stat-box">
499
+ <div class="stat-value" id="remainingCredits">0</div>
500
+ <div class="stat-label">Remaining Credits</div>
501
+ </div>
502
+ <div class="stat-box">
503
+ <div class="stat-value" id="usedCredits">0</div>
504
+ <div class="stat-label">Used Credits</div>
505
+ </div>
506
+ <div class="stat-box">
507
+ <div class="stat-value" id="usagePercentage">0%</div>
508
+ <div class="stat-label">Usage</div>
509
+ </div>
510
+ </div>
511
+
512
+ <div class="progress-container">
513
+ <div class="progress-header">
514
+ <span>Overall Usage</span>
515
+ <span id="usageText">0%</span>
516
+ </div>
517
+ <div class="progress-bar">
518
+ <div class="progress-fill" id="progressFill"></div>
519
+ <div class="progress-text" id="progressText">0%</div>
520
+ </div>
521
+ </div>
522
+
523
+ <div class="status-badge" id="statusBadge">
524
+ <span id="statusEmoji">🔄</span>
525
+ <span id="statusText">Loading...</span>
526
+ </div>
527
+
528
+ <!-- Credit Breakdown -->
529
+ <div class="credit-breakdown" id="creditBreakdown">
530
+ <h3 style="margin-bottom: 15px; font-size: 18px;">📊 Credit Breakdown</h3>
531
+ <!-- Credit items will be inserted here -->
532
+ </div>
533
+
534
+ <div class="last-updated" id="creditsLastUpdated">
535
+ ⏰ Last checked: Never
536
+ </div>
537
+ </div>
538
+
539
+ <!-- Recommendation Card -->
540
+ <div class="card">
541
+ <h2 class="card-title">🎯 AI Recommendation</h2>
542
+ <div id="recommendation" style="font-size: 1.1rem; line-height: 1.6; opacity: 0.9;">
543
+ Enter your API key and check credits to see personalized recommendations.
544
+ </div>
545
+ </div>
546
+ </div>
547
+
548
+ <div class="controls">
549
+ <button class="btn btn-primary" onclick="checkCredits()" id="checkCreditsBtn">
550
+ <span id="creditsIcon">🔄</span>
551
+ <span id="creditsText">Check Credits</span>
552
+ </button>
553
+ <button class="btn btn-secondary" onclick="viewRawResponse('credits')">
554
+ <span>📋</span> View Raw Response
555
+ </button>
556
+ </div>
557
+
558
+ <!-- Raw Response Modal -->
559
+ <div id="creditsRawModal" style="display: none; margin-top: 20px;">
560
+ <div class="card">
561
+ <h2 class="card-title">📋 Raw API Response</h2>
562
+ <pre id="creditsRawResponse" style="
563
+ background: rgba(0, 0, 0, 0.2);
564
+ padding: 15px;
565
+ border-radius: 8px;
566
+ overflow: auto;
567
+ max-height: 300px;
568
+ font-family: 'Courier New', monospace;
569
+ font-size: 12px;
570
+ white-space: pre-wrap;
571
+ "></pre>
572
+ <button class="btn btn-secondary" onclick="hideRawResponse('credits')" style="margin-top: 15px;">
573
+ <span>✕</span> Close
574
+ </button>
575
+ </div>
576
+ </div>
577
+ </div>
578
+
579
+ <!-- Models Tab -->
580
+ <div id="modelsTab" class="tab-content">
581
+ <div class="dashboard">
582
+ <div class="card">
583
+ <h2 class="card-title">🤖 Available Models</h2>
584
+
585
+ <div class="stat-grid">
586
+ <div class="stat-box">
587
+ <div class="stat-value" id="totalModels">0</div>
588
+ <div class="stat-label">Total Models</div>
589
+ </div>
590
+ <div class="stat-box">
591
+ <div class="stat-value" id="publicModels">0</div>
592
+ <div class="stat-label">Public Models</div>
593
+ </div>
594
+ <div class="stat-box">
595
+ <div class="stat-value" id="defaultModels">0</div>
596
+ <div class="stat-label">Default Models</div>
597
+ </div>
598
+ <div class="stat-box">
599
+ <div class="stat-value" id="communityModels">0</div>
600
+ <div class="stat-label">Community Models</div>
601
+ </div>
602
+ </div>
603
+
604
+ <div class="models-grid" id="modelsList">
605
+ <!-- Models will be inserted here -->
606
+ <div style="text-align: center; padding: 40px; opacity: 0.7;">
607
+ Click "Load Models" to see available models
608
+ </div>
609
+ </div>
610
+
611
+ <div class="last-updated" id="modelsLastUpdated">
612
+ ⏰ Last updated: Never
613
+ </div>
614
+ </div>
615
+ </div>
616
+
617
+ <div class="controls">
618
+ <button class="btn btn-primary" onclick="loadModels()" id="loadModelsBtn">
619
+ <span id="modelsIcon">🔄</span>
620
+ <span id="modelsText">Load Models</span>
621
+ </button>
622
+ <button class="btn btn-secondary" onclick="viewRawResponse('models')">
623
+ <span>📋</span> View Raw Response
624
+ </button>
625
+ </div>
626
+
627
+ <!-- Raw Response Modal -->
628
+ <div id="modelsRawModal" style="display: none; margin-top: 20px;">
629
+ <div class="card">
630
+ <h2 class="card-title">📋 Raw API Response</h2>
631
+ <pre id="modelsRawResponse" style="
632
+ background: rgba(0, 0, 0, 0.2);
633
+ padding: 15px;
634
+ border-radius: 8px;
635
+ overflow: auto;
636
+ max-height: 300px;
637
+ font-family: 'Courier New', monospace;
638
+ font-size: 12px;
639
+ white-space: pre-wrap;
640
+ "></pre>
641
+ <button class="btn btn-secondary" onclick="hideRawResponse('models')" style="margin-top: 15px;">
642
+ <span>✕</span> Close
643
+ </button>
644
+ </div>
645
+ </div>
646
+ </div>
647
+
648
+ <!-- Info Tab -->
649
+ <div id="infoTab" class="tab-content">
650
+ <div class="card">
651
+ <h2 class="card-title">📚 How to Use & Setup</h2>
652
+
653
+ <div style="line-height: 1.6; opacity: 0.9;">
654
+ <h3 style="margin: 20px 0 10px 0;">Getting Started:</h3>
655
+ <ol style="margin-left: 20px; margin-bottom: 20px;">
656
+ <li>Enter your StableCog API key in the input field above</li>
657
+ <li>Click "Save API Key" to store it in your browser</li>
658
+ <li>Switch to "Credits" tab and click "Check Credits"</li>
659
+ <li>Switch to "Models" tab and click "Load Models"</li>
660
+ </ol>
661
+
662
+ <h3 style="margin: 20px 0 10px 0;">Understanding Credits:</h3>
663
+ <ul style="margin-left: 20px; margin-bottom: 20px;">
664
+ <li><strong>Total Credits:</strong> Sum of all initial credits received</li>
665
+ <li><strong>Remaining Credits:</strong> Currently available credits</li>
666
+ <li><strong>Used Credits:</strong> Credits spent on image generation</li>
667
+ <li><strong>Credit Types:</strong> Different sources (Free, Refund, etc.)</li>
668
+ </ul>
669
+
670
+ <h3 style="margin: 20px 0 10px 0;">Privacy & Security:</h3>
671
+ <ul style="margin-left: 20px; margin-bottom: 20px;">
672
+ <li>Your API key is stored only in your browser's localStorage</li>
673
+ <li>No data is sent to any server except StableCog API</li>
674
+ <li>You can clear the API key anytime using the "Clear" button</li>
675
+ <li>All API calls are made directly from your browser</li>
676
+ </ul>
677
+
678
+ <h3 style="margin: 20px 0 10px 0;">Troubleshooting:</h3>
679
+ <ul style="margin-left: 20px;">
680
+ <li>Ensure your API key is correct and has proper permissions</li>
681
+ <li>Check your internet connection</li>
682
+ <li>Verify StableCog API is available</li>
683
+ <li>Use "View Raw Response" to see detailed error messages</li>
684
+ </ul>
685
+ </div>
686
+ </div>
687
+ </div>
688
+
689
+ <!-- Error Container (hidden by default) -->
690
+ <div id="errorContainer" class="error-container" style="display: none;">
691
+ <h2 class="error-title" id="errorTitle">⚠️ API Error</h2>
692
+ <div class="error-content">
693
+ <p id="errorMessage"></p>
694
+ </div>
695
+ <button class="btn btn-secondary" onclick="hideError()">
696
+ <span>✕</span> Dismiss
697
+ </button>
698
+ </div>
699
+
700
+ <div class="footer">
701
+ Built with ❤️ for StableCog users |
702
+ <a href="https://stablecog.com/docs/api" target="_blank" style="color: var(--light-color); text-decoration: none;">
703
+ API Documentation
704
+ </a> |
705
+ <a href="https://github.com/stability-ai/stablecog/issues" target="_blank" style="color: var(--light-color); text-decoration: none;">
706
+ Report Issues
707
+ </a>
708
+ </div>
709
+ </div>
710
+
711
+ <script>
712
+ // Configuration
713
+ const API_HOST = 'https://api.stablecog.com';
714
+ const CREDITS_ENDPOINT = '/v1/credits';
715
+ const MODELS_ENDPOINT = '/v1/image/generation/models';
716
+
717
+ // State
718
+ let apiKey = '';
719
+ let creditsData = null;
720
+ let modelsData = null;
721
+
722
+ // Initialize on page load
723
+ document.addEventListener('DOMContentLoaded', function() {
724
+ loadApiKey();
725
+ // Check if API key exists, if so auto-check credits
726
+ if (apiKey) {
727
+ setTimeout(() => {
728
+ checkCredits();
729
+ loadModels();
730
+ }, 500);
731
+ }
732
+ });
733
+
734
+ // API Key Management
735
+ function loadApiKey() {
736
+ apiKey = localStorage.getItem('stablecog_api_key') || '';
737
+ document.getElementById('apiKeyInput').value = apiKey;
738
+ }
739
+
740
+ function saveApiKey() {
741
+ const input = document.getElementById('apiKeyInput');
742
+ apiKey = input.value.trim();
743
+ if (apiKey) {
744
+ localStorage.setItem('stablecog_api_key', apiKey);
745
+ showMessage('API key saved successfully!');
746
+ // Auto-check after saving
747
+ setTimeout(() => {
748
+ checkCredits();
749
+ loadModels();
750
+ }, 500);
751
+ } else {
752
+ localStorage.removeItem('stablecog_api_key');
753
+ showMessage('API key cleared. Using demo mode.');
754
+ }
755
+ }
756
+
757
+ function clearApiKey() {
758
+ document.getElementById('apiKeyInput').value = '';
759
+ saveApiKey();
760
+ }
761
+
762
+ // Tab Switching
763
+ function switchTab(tabName) {
764
+ // Update tabs
765
+ document.querySelectorAll('.tab').forEach(tab => {
766
+ tab.classList.remove('active');
767
+ });
768
+ document.querySelectorAll('.tab-content').forEach(content => {
769
+ content.classList.remove('active');
770
+ });
771
+
772
+ // Activate selected tab
773
+ document.querySelector(`.tab[onclick="switchTab('${tabName}')"]`).classList.add('active');
774
+ document.getElementById(`${tabName}Tab`).classList.add('active');
775
+ }
776
+
777
+ // API Calls
778
+ async function checkCredits() {
779
+ const btn = document.getElementById('checkCreditsBtn');
780
+ const icon = document.getElementById('creditsIcon');
781
+ const text = document.getElementById('creditsText');
782
+
783
+ // Show loading state
784
+ btn.disabled = true;
785
+ icon.textContent = '';
786
+ icon.innerHTML = '<div class="loading"></div>';
787
+ text.textContent = 'Checking...';
788
+
789
+ try {
790
+ const response = await fetch(`${API_HOST}${CREDITS_ENDPOINT}`, {
791
+ headers: {
792
+ 'Authorization': `Bearer ${apiKey || 'demo'}`,
793
+ 'Content-Type': 'application/json'
794
+ }
795
+ });
796
+
797
+ if (!response.ok) {
798
+ throw new Error(`API Error: ${response.status} - ${response.statusText}`);
799
+ }
800
+
801
+ const data = await response.json();
802
+ creditsData = data;
803
+ updateCreditsDisplay(data);
804
+ hideError();
805
+ } catch (error) {
806
+ showError('Failed to check credits', error.message);
807
+ // For demo purposes, show sample data
808
+ if (!apiKey) {
809
+ showSampleCredits();
810
+ }
811
+ } finally {
812
+ // Reset button state
813
+ btn.disabled = false;
814
+ icon.textContent = '🔄';
815
+ text.textContent = 'Check Credits';
816
+ }
817
+ }
818
+
819
+ async function loadModels() {
820
+ const btn = document.getElementById('loadModelsBtn');
821
+ const icon = document.getElementById('modelsIcon');
822
+ const text = document.getElementById('modelsText');
823
+
824
+ // Show loading state
825
+ btn.disabled = true;
826
+ icon.textContent = '';
827
+ icon.innerHTML = '<div class="loading"></div>';
828
+ text.textContent = 'Loading...';
829
+
830
+ try {
831
+ const response = await fetch(`${API_HOST}${MODELS_ENDPOINT}`, {
832
+ headers: {
833
+ 'Authorization': `Bearer ${apiKey || 'demo'}`,
834
+ 'Content-Type': 'application/json'
835
+ }
836
+ });
837
+
838
+ if (!response.ok) {
839
+ throw new Error(`API Error: ${response.status} - ${response.statusText}`);
840
+ }
841
+
842
+ const data = await response.json();
843
+ modelsData = data;
844
+ updateModelsDisplay(data);
845
+ hideError();
846
+ } catch (error) {
847
+ showError('Failed to load models', error.message);
848
+ // For demo purposes, show sample data
849
+ if (!apiKey) {
850
+ showSampleModels();
851
+ }
852
+ } finally {
853
+ // Reset button state
854
+ btn.disabled = false;
855
+ icon.textContent = '🔄';
856
+ text.textContent = 'Load Models';
857
+ }
858
+ }
859
+
860
+ // Display Functions
861
+ function updateCreditsDisplay(data) {
862
+ // Calculate totals
863
+ const totalRemaining = data.total_remaining_credits || 0;
864
+ const creditsList = data.credits || [];
865
+
866
+ let totalInitial = 0;
867
+ let totalUsed = 0;
868
+
869
+ creditsList.forEach(credit => {
870
+ const initial = credit.type?.amount || 0;
871
+ const remaining = credit.remaining_amount || 0;
872
+ totalInitial += initial;
873
+ totalUsed += (initial - remaining);
874
+ });
875
+
876
+ const totalCredits = Math.max(totalInitial, totalRemaining + totalUsed);
877
+ const usagePercentage = totalCredits > 0 ? (totalUsed / totalCredits * 100) : 0;
878
+
879
+ // Update UI
880
+ document.getElementById('totalCredits').textContent = totalCredits;
881
+ document.getElementById('remainingCredits').textContent = totalRemaining;
882
+ document.getElementById('usedCredits').textContent = totalUsed;
883
+ document.getElementById('usagePercentage').textContent = `${usagePercentage.toFixed(1)}%`;
884
+ document.getElementById('usageText').textContent = `${usagePercentage.toFixed(1)}%`;
885
+ document.getElementById('progressText').textContent = `${usagePercentage.toFixed(1)}%`;
886
+
887
+ // Update progress bar
888
+ const progressFill = document.getElementById('progressFill');
889
+ progressFill.style.width = `${Math.min(usagePercentage, 100)}%`;
890
+
891
+ // Set progress bar color based on usage
892
+ if (usagePercentage < 50) {
893
+ progressFill.style.background = '#4CAF50';
894
+ } else if (usagePercentage < 80) {
895
+ progressFill.style.background = '#FF9800';
896
+ } else {
897
+ progressFill.style.background = '#F44336';
898
+ }
899
+
900
+ // Update status
901
+ const statusBadge = document.getElementById('statusBadge');
902
+ const statusEmoji = document.getElementById('statusEmoji');
903
+ const statusText = document.getElementById('statusText');
904
+
905
+ if (totalRemaining === 0) {
906
+ statusEmoji.textContent = '💸';
907
+ statusText.textContent = 'No Credits';
908
+ statusBadge.style.color = '#F44336';
909
+ } else if (usagePercentage >= 90) {
910
+ statusEmoji.textContent = '🔴';
911
+ statusText.textContent = 'Critically Low';
912
+ statusBadge.style.color = '#F44336';
913
+ } else if (usagePercentage >= 75) {
914
+ statusEmoji.textContent = '🟡';
915
+ statusText.textContent = 'Running Low';
916
+ statusBadge.style.color = '#FF9800';
917
+ } else if (totalRemaining < 10) {
918
+ statusEmoji.textContent = '📝';
919
+ statusText.textContent = 'Limited';
920
+ statusBadge.style.color = '#FF9800';
921
+ } else if (totalRemaining < 50) {
922
+ statusEmoji.textContent = '✨';
923
+ statusText.textContent = 'Available';
924
+ statusBadge.style.color = '#4CAF50';
925
+ } else {
926
+ statusEmoji.textContent = '🚀';
927
+ statusText.textContent = 'Plenty';
928
+ statusBadge.style.color = '#4CAF50';
929
+ }
930
+
931
+ // Update recommendation
932
+ updateRecommendation(totalRemaining, usagePercentage);
933
+
934
+ // Update credit breakdown
935
+ updateCreditBreakdown(creditsList);
936
+
937
+ // Update timestamp
938
+ const now = new Date();
939
+ document.getElementById('creditsLastUpdated').textContent =
940
+ `⏰ Last checked: ${now.toLocaleString()}`;
941
+ }
942
+
943
+ function updateModelsDisplay(data) {
944
+ const models = data.models || [];
945
+
946
+ // Update stats
947
+ document.getElementById('totalModels').textContent = models.length;
948
+ document.getElementById('publicModels').textContent =
949
+ models.filter(m => m.is_public).length;
950
+ document.getElementById('defaultModels').textContent =
951
+ models.filter(m => m.is_default).length;
952
+ document.getElementById('communityModels').textContent =
953
+ models.filter(m => m.is_community).length;
954
+
955
+ // Update models list
956
+ const modelsList = document.getElementById('modelsList');
957
+ modelsList.innerHTML = '';
958
+
959
+ models.sort((a, b) => {
960
+ // Sort: default first, then public, then by name
961
+ if (a.is_default !== b.is_default) return b.is_default - a.is_default;
962
+ if (a.is_public !== b.is_public) return b.is_public - a.is_public;
963
+ return a.name.localeCompare(b.name);
964
+ });
965
+
966
+ models.forEach((model, index) => {
967
+ const modelCard = document.createElement('div');
968
+ modelCard.className = 'model-card';
969
+
970
+ // Determine badge
971
+ let badgeClass = 'badge-other';
972
+ let badgeText = model.type?.toUpperCase() || 'UNKNOWN';
973
+
974
+ if (model.is_community) {
975
+ badgeClass = 'badge-community';
976
+ badgeText = 'COMMUNITY';
977
+ } else if (model.is_default) {
978
+ badgeClass = 'badge-default';
979
+ badgeText = 'DEFAULT';
980
+ } else if (model.is_public) {
981
+ badgeClass = 'badge-public';
982
+ badgeText = 'PUBLIC';
983
+ }
984
+
985
+ // Shorten ID for display
986
+ const shortId = model.id.length > 8 ? model.id.substring(0, 8) + '...' : model.id;
987
+
988
+ modelCard.innerHTML = `
989
+ <div class="model-header">
990
+ <div class="model-name">
991
+ #${index + 1}. ${model.name}
992
+ <span class="model-badge ${badgeClass}">${badgeText}</span>
993
+ </div>
994
+ <div class="model-id">ID: ${shortId}</div>
995
+ </div>
996
+ <div class="model-description">${model.description || 'No description available'}</div>
997
+ <div class="model-meta">
998
+ <span>📅 Created: ${model.created_at ? model.created_at.substring(0, 10) : 'Unknown'}</span>
999
+ <span>🔄 Updated: ${model.updated_at ? model.updated_at.substring(0, 10) : 'Unknown'}</span>
1000
+ </div>
1001
+ `;
1002
+
1003
+ modelsList.appendChild(modelCard);
1004
+ });
1005
+
1006
+ // Update timestamp
1007
+ const now = new Date();
1008
+ document.getElementById('modelsLastUpdated').textContent =
1009
+ `⏰ Last updated: ${now.toLocaleString()}`;
1010
+ }
1011
+
1012
+ function updateRecommendation(remainingCredits, usagePercentage) {
1013
+ const recommendation = document.getElementById('recommendation');
1014
+
1015
+ if (remainingCredits === 0) {
1016
+ recommendation.innerHTML = "💸 <strong>No credits remaining.</strong> Please add credits to continue using StableCog services.";
1017
+ } else if (usagePercentage >= 90) {
1018
+ recommendation.innerHTML = "🛑 <strong>Critically low credits!</strong> Consider purchasing more credits before starting new projects.";
1019
+ } else if (usagePercentage >= 75) {
1020
+ recommendation.innerHTML = "⚠️ <strong>Credits are running low.</strong> You can still do some work, but plan ahead for larger projects.";
1021
+ } else if (remainingCredits < 10) {
1022
+ recommendation.innerHTML = "📝 <strong>Limited credits available.</strong> Good for small tasks, testing, or single images.";
1023
+ } else if (remainingCredits < 50) {
1024
+ recommendation.innerHTML = "✨ <strong>Credits available!</strong> Suitable for several medium-sized projects or batch processing.";
1025
+ } else {
1026
+ recommendation.innerHTML = "🚀 <strong>Plenty of credits!</strong> Ready for extensive image generation work and experimentation.";
1027
+ }
1028
+ }
1029
+
1030
+ function updateCreditBreakdown(creditsList) {
1031
+ const breakdownContainer = document.getElementById('creditBreakdown');
1032
+ const itemsContainer = breakdownContainer.querySelector('.credit-items') ||
1033
+ document.createElement('div');
1034
+ itemsContainer.className = 'credit-items';
1035
+
1036
+ itemsContainer.innerHTML = '';
1037
+
1038
+ creditsList.forEach(credit => {
1039
+ const initial = credit.type?.amount || 0;
1040
+ const remaining = credit.remaining_amount || 0;
1041
+ const used = initial - remaining;
1042
+ const percentage = initial > 0 ? (used / initial * 100) : 0;
1043
+
1044
+ const item = document.createElement('div');
1045
+ item.className = 'credit-item';
1046
+
1047
+ item.innerHTML = `
1048
+ <div class="credit-header">
1049
+ <span class="credit-name">${credit.type?.name || 'Unknown'}</span>
1050
+ <span class="credit-amount">${remaining} / ${initial} ⭐</span>
1051
+ </div>
1052
+ <div class="credit-description">${credit.type?.description || ''}</div>
1053
+ <div class="credit-progress">
1054
+ <div class="credit-progress-fill" style="width: ${Math.min(percentage, 100)}%; background: ${getProgressColor(percentage)};"></div>
1055
+ </div>
1056
+ `;
1057
+
1058
+ itemsContainer.appendChild(item);
1059
+ });
1060
+
1061
+ if (!breakdownContainer.querySelector('.credit-items')) {
1062
+ breakdownContainer.appendChild(itemsContainer);
1063
+ }
1064
+ }
1065
+
1066
+ function getProgressColor(percentage) {
1067
+ if (percentage < 50) return '#4CAF50';
1068
+ if (percentage < 80) return '#FF9800';
1069
+ return '#F44336';
1070
+ }
1071
+
1072
+ // Raw Response Viewer
1073
+ function viewRawResponse(type) {
1074
+ const modal = document.getElementById(`${type}RawModal`);
1075
+ const pre = document.getElementById(`${type}RawResponse`);
1076
+
1077
+ const data = type === 'credits' ? creditsData : modelsData;
1078
+ pre.textContent = JSON.stringify(data, null, 2);
1079
+ modal.style.display = 'block';
1080
+ }
1081
+
1082
+ function hideRawResponse(type) {
1083
+ document.getElementById(`${type}RawModal`).style.display = 'none';
1084
+ }
1085
+
1086
+ // Error Handling
1087
+ function showError(title, message) {
1088
+ document.getElementById('errorTitle').textContent = title;
1089
+ document.getElementById('errorMessage').textContent = message;
1090
+ document.getElementById('errorContainer').style.display = 'block';
1091
+ }
1092
+
1093
+ function hideError() {
1094
+ document.getElementById('errorContainer').style.display = 'none';
1095
+ }
1096
+
1097
+ function showMessage(message) {
1098
+ // Simple toast notification
1099
+ const toast = document.createElement('div');
1100
+ toast.style.cssText = `
1101
+ position: fixed;
1102
+ top: 20px;
1103
+ right: 20px;
1104
+ background: rgba(76, 175, 80, 0.9);
1105
+ color: white;
1106
+ padding: 12px 20px;
1107
+ border-radius: 8px;
1108
+ box-shadow: 0 5px 15px rgba(0,0,0,0.3);
1109
+ z-index: 1000;
1110
+ animation: slideIn 0.3s ease;
1111
+ `;
1112
+ toast.textContent = message;
1113
+ document.body.appendChild(toast);
1114
+
1115
+ setTimeout(() => {
1116
+ toast.style.animation = 'slideOut 0.3s ease';
1117
+ setTimeout(() => toast.remove(), 300);
1118
+ }, 3000);
1119
+ }
1120
+
1121
+ // Sample data for demo
1122
+ function showSampleCredits() {
1123
+ const sampleData = {
1124
+ "total_remaining_credits": 5,
1125
+ "credits": [
1126
+ {
1127
+ "id": "cd813ce3-b5fe-4a74-ae5c-e077ce49b984",
1128
+ "remaining_amount": 0,
1129
+ "expires_at": "2100-01-01T05:00:00Z",
1130
+ "type": {
1131
+ "id": "7ca94fd6-c201-4ca6-a9bf-4473c83e30b4",
1132
+ "name": "Refund",
1133
+ "amount": 0,
1134
+ "description": "For generate/upscale failure refunds"
1135
+ }
1136
+ },
1137
+ {
1138
+ "id": "b8a508a2-a5dc-4329-ad25-015b50fbc51f",
1139
+ "remaining_amount": 5,
1140
+ "expires_at": "2100-01-01T05:00:00Z",
1141
+ "type": {
1142
+ "id": "3b12b23e-478b-4c18-8e34-70b3f0af1ee6",
1143
+ "name": "Free",
1144
+ "amount": 50,
1145
+ "description": "Base free credits for user"
1146
+ }
1147
+ }
1148
+ ]
1149
+ };
1150
+ creditsData = sampleData;
1151
+ updateCreditsDisplay(sampleData);
1152
+ }
1153
+
1154
+ function showSampleModels() {
1155
+ const sampleData = {
1156
+ "models": [
1157
+ {
1158
+ "id": "sd-xl-1.0",
1159
+ "name": "Stable Diffusion XL",
1160
+ "description": "High-quality image generation model with excellent detail",
1161
+ "type": "image",
1162
+ "is_public": true,
1163
+ "is_default": true,
1164
+ "is_community": false,
1165
+ "created_at": "2024-01-01T00:00:00Z",
1166
+ "updated_at": "2024-06-01T00:00:00Z"
1167
+ },
1168
+ {
1169
+ "id": "dreamshaper-v7",
1170
+ "name": "DreamShaper v7",
1171
+ "description": "Artistic style model for creative image generation",
1172
+ "type": "image",
1173
+ "is_public": true,
1174
+ "is_default": false,
1175
+ "is_community": false,
1176
+ "created_at": "2024-02-01T00:00:00Z",
1177
+ "updated_at": "2024-05-01T00:00:00Z"
1178
+ },
1179
+ {
1180
+ "id": "anime-diffusion",
1181
+ "name": "Anime Diffusion",
1182
+ "description": "Specialized model for anime-style artwork",
1183
+ "type": "image",
1184
+ "is_public": true,
1185
+ "is_default": false,
1186
+ "is_community": true,
1187
+ "created_at": "2024-03-01T00:00:00Z",
1188
+ "updated_at": "2024-04-01T00:00:00Z"
1189
+ }
1190
+ ]
1191
+ };
1192
+ modelsData = sampleData;
1193
+ updateModelsDisplay(sampleData);
1194
+ }
1195
+
1196
+ // Add CSS animations
1197
+ const style = document.createElement('style');
1198
+ style.textContent = `
1199
+ @keyframes slideIn {
1200
+ from { transform: translateX(100%); opacity: 0; }
1201
+ to { transform: translateX(0); opacity: 1; }
1202
+ }
1203
+ @keyframes slideOut {
1204
+ from { transform: translateX(0); opacity: 1; }
1205
+ to { transform: translateX(100%); opacity: 0; }
1206
+ }
1207
+ `;
1208
+ document.head.appendChild(style);
1209
+ </script>
1210
+ </body>
1211
+ </html>