qpqpqpqpqpqp commited on
Commit
ec8b244
·
verified ·
1 Parent(s): f7d04c1

I recommend to rename them, I couldn't get it at a glance

Browse files
Files changed (1) hide show
  1. index.html +1088 -1088
index.html CHANGED
@@ -1,1088 +1,1088 @@
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>Mal's Models</title>
7
- <script type="text/javascript" src="data-civitai.js"></script>
8
- <script type="text/javascript" src="data-huggingface.js"></script>
9
- <script type="text/javascript" src="comparator.js"></script>
10
- <link rel="preconnect" href="https://fonts.googleapis.com">
11
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
13
- <style>
14
- * {
15
- margin: 0;
16
- padding: 0;
17
- box-sizing: border-box;
18
- }
19
-
20
- body {
21
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
22
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
23
- color: #f1f5f9;
24
- min-height: 100vh;
25
- padding: 20px;
26
- }
27
-
28
- a {
29
- text-decoration: none;
30
- color: #60a5fa;
31
- transition: color 0.2s;
32
- }
33
-
34
- a:hover {
35
- color: #93c5fd;
36
- }
37
-
38
- .header {
39
- max-width: 1400px;
40
- margin: 0 auto 30px;
41
- background: rgba(30, 41, 59, 0.7);
42
- backdrop-filter: blur(10px);
43
- border-radius: 16px;
44
- padding: 24px;
45
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
46
- border: 1px solid rgba(148, 163, 184, 0.1);
47
- }
48
-
49
- .header h1 {
50
- font-size: 28px;
51
- font-weight: 700;
52
- margin-bottom: 12px;
53
- background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 100%);
54
- -webkit-background-clip: text;
55
- -webkit-text-fill-color: transparent;
56
- background-clip: text;
57
- text-align: center;
58
- }
59
-
60
- .profile-links {
61
- display: flex;
62
- flex-wrap: wrap;
63
- gap: 16px;
64
- justify-content: center;
65
- align-items: center;
66
- margin-bottom: 12px;
67
- padding: 12px;
68
- background: rgba(15, 23, 42, 0.4);
69
- border-radius: 8px;
70
- }
71
-
72
- .profile-links a {
73
- display: inline-flex;
74
- align-items: center;
75
- gap: 6px;
76
- padding: 6px 12px;
77
- background: rgba(96, 165, 250, 0.1);
78
- border-radius: 6px;
79
- font-size: 14px;
80
- transition: all 0.2s;
81
- }
82
-
83
- .profile-links a:hover {
84
- background: rgba(96, 165, 250, 0.2);
85
- transform: translateY(-1px);
86
- }
87
-
88
- .last-update {
89
- text-align: center;
90
- font-size: 13px;
91
- color: #94a3b8;
92
- margin-bottom: 16px;
93
- }
94
-
95
- .controls {
96
- display: flex;
97
- flex-wrap: wrap;
98
- gap: 12px;
99
- align-items: center;
100
- justify-content: center;
101
- }
102
-
103
- .search-container {
104
- flex: 1 1 auto;
105
- min-width: 300px;
106
- max-width: 600px;
107
- position: relative;
108
- }
109
-
110
- .search-container input[type="text"] {
111
- width: 100%;
112
- padding: 12px 16px;
113
- padding-right: 100px;
114
- background: rgba(15, 23, 42, 0.6);
115
- border: 2px solid rgba(148, 163, 184, 0.2);
116
- border-radius: 12px;
117
- color: #f1f5f9;
118
- font-size: 15px;
119
- font-family: inherit;
120
- transition: all 0.3s;
121
- }
122
-
123
- .search-container input[type="text"]:focus {
124
- outline: none;
125
- border-color: #60a5fa;
126
- background: rgba(15, 23, 42, 0.8);
127
- box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.1);
128
- }
129
-
130
- .search-container input[type="text"]::placeholder {
131
- color: #64748b;
132
- }
133
-
134
- .clear-btn {
135
- position: absolute;
136
- right: 8px;
137
- top: 50%;
138
- transform: translateY(-50%);
139
- padding: 6px 14px;
140
- background: rgba(239, 68, 68, 0.2);
141
- border: 1px solid rgba(239, 68, 68, 0.3);
142
- border-radius: 8px;
143
- color: #fca5a5;
144
- font-size: 13px;
145
- font-weight: 500;
146
- cursor: pointer;
147
- transition: all 0.2s;
148
- }
149
-
150
- .clear-btn:hover {
151
- background: rgba(239, 68, 68, 0.3);
152
- color: #fecaca;
153
- }
154
-
155
- .mode-select {
156
- padding: 12px 40px 12px 16px;
157
- background: rgba(15, 23, 42, 0.6);
158
- border: 2px solid rgba(148, 163, 184, 0.2);
159
- border-radius: 12px;
160
- color: #f1f5f9;
161
- font-size: 15px;
162
- font-family: inherit;
163
- cursor: pointer;
164
- transition: all 0.3s;
165
- font-weight: 500;
166
- height: 48px;
167
- line-height: 20px;
168
- min-width: 150px;
169
- flex-shrink: 0;
170
- appearance: none;
171
- -webkit-appearance: none;
172
- -moz-appearance: none;
173
- background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
174
- background-repeat: no-repeat;
175
- background-position: right 12px center;
176
- background-size: 20px;
177
- box-shadow: none;
178
- outline: none;
179
- }
180
-
181
- .mode-select::-ms-expand {
182
- display: none;
183
- }
184
-
185
- .mode-select:focus {
186
- outline: none;
187
- border-color: #60a5fa;
188
- box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.1);
189
- }
190
-
191
- .mode-select option {
192
- background: #0f172a;
193
- color: #e2e8f0;
194
- padding: 16px 20px;
195
- font-size: 15px;
196
- font-weight: 500;
197
- border: none;
198
- outline: none;
199
- }
200
-
201
- .mode-select option:hover {
202
- background: linear-gradient(90deg, rgba(96, 165, 250, 0.15) 0%, rgba(96, 165, 250, 0.05) 100%);
203
- color: #60a5fa;
204
- font-weight: 600;
205
- }
206
-
207
- .mode-select option:checked,
208
- .mode-select option:focus {
209
- background: linear-gradient(90deg, rgba(96, 165, 250, 0.25) 0%, rgba(96, 165, 250, 0.15) 100%);
210
- color: #93c5fd;
211
- font-weight: 600;
212
- box-shadow: none;
213
- outline: none;
214
- }
215
-
216
- .checkbox-group {
217
- display: flex;
218
- flex-wrap: wrap;
219
- gap: 8px;
220
- align-items: center;
221
- }
222
-
223
- .checkbox-label {
224
- display: flex;
225
- align-items: center;
226
- gap: 8px;
227
- padding: 10px 16px;
228
- background: rgba(15, 23, 42, 0.4);
229
- border: 2px solid rgba(148, 163, 184, 0.2);
230
- border-radius: 10px;
231
- cursor: pointer;
232
- transition: all 0.2s;
233
- font-size: 14px;
234
- font-weight: 500;
235
- user-select: none;
236
- }
237
-
238
- .checkbox-label:hover {
239
- background: rgba(15, 23, 42, 0.6);
240
- border-color: rgba(148, 163, 184, 0.3);
241
- }
242
-
243
- .checkbox-label input[type="checkbox"] {
244
- width: 18px;
245
- height: 18px;
246
- cursor: pointer;
247
- accent-color: #60a5fa;
248
- }
249
-
250
- .checkbox-label input[type="checkbox"]:checked + span {
251
- color: #60a5fa;
252
- }
253
-
254
- .stats-bar {
255
- display: flex;
256
- align-items: center;
257
- gap: 12px;
258
- padding: 12px 20px;
259
- background: rgba(15, 23, 42, 0.6);
260
- border-radius: 10px;
261
- font-size: 14px;
262
- font-weight: 600;
263
- flex-shrink: 0;
264
- }
265
-
266
- .stats-bar label {
267
- color: #94a3b8;
268
- }
269
-
270
- .stats-bar input {
271
- width: 70px;
272
- padding: 6px 10px;
273
- background: rgba(30, 41, 59, 0.8);
274
- border: 1px solid rgba(148, 163, 184, 0.2);
275
- border-radius: 6px;
276
- color: #60a5fa;
277
- text-align: center;
278
- font-weight: 700;
279
- font-size: 16px;
280
- }
281
-
282
- .mainContent {
283
- max-width: 1400px;
284
- margin: 0 auto;
285
- display: grid;
286
- grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
287
- gap: 20px;
288
- padding: 0 4px;
289
- }
290
-
291
- .element {
292
- background: rgba(30, 41, 59, 0.6);
293
- backdrop-filter: blur(10px);
294
- border-radius: 16px;
295
- overflow: hidden;
296
- border: 1px solid rgba(148, 163, 184, 0.1);
297
- transition: all 0.3s;
298
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
299
- display: flex;
300
- flex-direction: column;
301
- cursor: pointer;
302
- }
303
-
304
- .element:hover {
305
- transform: translateY(-4px);
306
- box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
307
- border-color: rgba(96, 165, 250, 0.3);
308
- }
309
-
310
- .element .modelName {
311
- padding: 14px 16px;
312
- background: rgba(15, 23, 42, 0.8);
313
- font-weight: 600;
314
- font-size: 14px;
315
- text-overflow: ellipsis;
316
- overflow: hidden;
317
- white-space: nowrap;
318
- border-bottom: 1px solid rgba(148, 163, 184, 0.1);
319
- color: #e2e8f0;
320
- }
321
-
322
- .element .imageContainer {
323
- position: relative;
324
- width: 100%;
325
- padding-top: 137.5%; /* 192/264 aspect ratio */
326
- overflow: hidden;
327
- background: rgba(15, 23, 42, 0.6);
328
- }
329
-
330
- .element .imageContainer img {
331
- position: absolute;
332
- top: 0;
333
- left: 0;
334
- width: 100%;
335
- height: 100%;
336
- object-fit: cover;
337
- transition: transform 0.3s;
338
- }
339
-
340
- .element:hover .imageContainer img {
341
- transform: scale(1.05);
342
- }
343
-
344
- .statsBox {
345
- padding: 16px;
346
- background: rgba(15, 23, 42, 0.8);
347
- font-size: 13px;
348
- line-height: 1.8;
349
- flex: 1;
350
- display: none; /* Hidden in main view */
351
- }
352
-
353
- .statsBox .stat-row {
354
- display: flex;
355
- align-items: center;
356
- gap: 8px;
357
- margin-bottom: 6px;
358
- }
359
-
360
- .statsBox .stat-row:last-child {
361
- margin-bottom: 0;
362
- }
363
-
364
- .statsBox .stat-label {
365
- font-weight: 600;
366
- color: #94a3b8;
367
- min-width: 75px;
368
- }
369
-
370
- .statsBox .stat-value {
371
- display: flex;
372
- align-items: center;
373
- gap: 6px;
374
- }
375
-
376
- /* Modal Styles */
377
- .modal-overlay {
378
- display: none;
379
- position: fixed;
380
- top: 0;
381
- left: 0;
382
- right: 0;
383
- bottom: 0;
384
- background: rgba(0, 0, 0, 0.8);
385
- backdrop-filter: blur(8px);
386
- z-index: 1000;
387
- align-items: center;
388
- justify-content: center;
389
- padding: 20px;
390
- animation: fadeIn 0.2s ease-out;
391
- }
392
-
393
- .modal-overlay.active {
394
- display: flex;
395
- }
396
-
397
- .modal-content {
398
- background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
399
- border-radius: 20px;
400
- max-width: 1100px;
401
- width: 100%;
402
- max-height: 90vh;
403
- overflow: hidden;
404
- position: relative;
405
- border: 2px solid rgba(148, 163, 184, 0.2);
406
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
407
- animation: slideUp 0.3s ease-out;
408
- display: flex;
409
- flex-direction: column;
410
- }
411
-
412
- @keyframes slideUp {
413
- from {
414
- opacity: 0;
415
- transform: translateY(30px);
416
- }
417
- to {
418
- opacity: 1;
419
- transform: translateY(0);
420
- }
421
- }
422
-
423
- .modal-header {
424
- padding: 24px;
425
- border-bottom: 1px solid rgba(148, 163, 184, 0.1);
426
- display: flex;
427
- align-items: center;
428
- justify-content: space-between;
429
- position: sticky;
430
- top: 0;
431
- background: rgba(30, 41, 59, 0.95);
432
- backdrop-filter: blur(10px);
433
- z-index: 10;
434
- }
435
-
436
- .modal-title {
437
- font-size: 24px;
438
- font-weight: 700;
439
- color: #f1f5f9;
440
- margin: 0;
441
- }
442
-
443
- .modal-close {
444
- width: 36px;
445
- height: 36px;
446
- border-radius: 10px;
447
- background: rgba(239, 68, 68, 0.2);
448
- border: 1px solid rgba(239, 68, 68, 0.3);
449
- color: #fca5a5;
450
- font-size: 20px;
451
- cursor: pointer;
452
- display: flex;
453
- align-items: center;
454
- justify-content: center;
455
- transition: all 0.2s;
456
- font-weight: 700;
457
- }
458
-
459
- .modal-close:hover {
460
- background: rgba(239, 68, 68, 0.3);
461
- color: #fecaca;
462
- transform: rotate(90deg);
463
- }
464
-
465
- .modal-body {
466
- display: flex;
467
- flex: 1;
468
- overflow: hidden;
469
- gap: 0;
470
- }
471
-
472
- .modal-left {
473
- flex: 0 0 45%;
474
- display: flex;
475
- align-items: center;
476
- justify-content: center;
477
- background: rgba(15, 23, 42, 0.6);
478
- padding: 24px;
479
- }
480
-
481
- .modal-image-container {
482
- width: 100%;
483
- border-radius: 12px;
484
- overflow: hidden;
485
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
486
- max-height: 100%;
487
- }
488
-
489
- .modal-image-container img {
490
- width: 100%;
491
- height: auto;
492
- display: block;
493
- }
494
-
495
- .modal-right {
496
- flex: 1;
497
- padding: 24px;
498
- overflow-y: auto;
499
- display: flex;
500
- flex-direction: column;
501
- gap: 16px;
502
- }
503
-
504
- .modal-stats {
505
- display: grid;
506
- gap: 12px;
507
- }
508
-
509
- .modal-stat-row {
510
- display: flex;
511
- align-items: center;
512
- justify-content: space-between;
513
- padding: 16px 20px;
514
- background: rgba(15, 23, 42, 0.6);
515
- border-radius: 12px;
516
- border: 1px solid rgba(148, 163, 184, 0.1);
517
- transition: all 0.2s;
518
- }
519
-
520
- .modal-stat-row:hover {
521
- background: rgba(15, 23, 42, 0.8);
522
- border-color: rgba(148, 163, 184, 0.2);
523
- }
524
-
525
- .modal-stat-label {
526
- font-weight: 600;
527
- font-size: 15px;
528
- color: #cbd5e1;
529
- }
530
-
531
- .modal-stat-value {
532
- display: flex;
533
- align-items: center;
534
- gap: 12px;
535
- }
536
-
537
- .modal-hf-link {
538
- padding: 6px 12px;
539
- background: rgba(255, 107, 0, 0.15);
540
- border: 1px solid rgba(255, 107, 0, 0.3);
541
- border-radius: 8px;
542
- font-size: 12px;
543
- font-weight: 600;
544
- color: #ff9d5c;
545
- transition: all 0.2s;
546
- text-decoration: none;
547
- display: inline-flex;
548
- align-items: center;
549
- gap: 6px;
550
- }
551
-
552
- .modal-hf-link:hover {
553
- background: rgba(255, 107, 0, 0.25);
554
- border-color: rgba(255, 107, 0, 0.5);
555
- transform: translateY(-1px);
556
- color: #ffb380;
557
- }
558
-
559
- .modal-hf-placeholder {
560
- padding: 6px 12px;
561
- background: rgba(100, 116, 139, 0.15);
562
- border: 1px solid rgba(100, 116, 139, 0.3);
563
- border-radius: 8px;
564
- font-size: 12px;
565
- font-weight: 600;
566
- color: #94a3b8;
567
- display: inline-flex;
568
- align-items: center;
569
- gap: 6px;
570
- opacity: 0.5;
571
- }
572
-
573
- .hf-logo {
574
- width: 16px;
575
- height: 16px;
576
- display: inline-block;
577
- }
578
-
579
- @media (max-width: 900px) {
580
- .modal-body {
581
- flex-direction: column;
582
- }
583
-
584
- .modal-left {
585
- flex: 0 0 auto;
586
- max-height: 400px;
587
- }
588
-
589
- .modal-right {
590
- flex: 1;
591
- }
592
- }
593
-
594
- .status-icon {
595
- font-size: 16px;
596
- display: inline-block;
597
- }
598
-
599
- .status-available {
600
- color: #4ade80;
601
- }
602
-
603
- .status-unavailable {
604
- color: #f87171;
605
- }
606
-
607
- .status-unknown {
608
- color: #fbbf24;
609
- }
610
-
611
- .hf-link {
612
- padding: 2px 8px;
613
- background: rgba(96, 165, 250, 0.1);
614
- border: 1px solid rgba(96, 165, 250, 0.2);
615
- border-radius: 6px;
616
- font-size: 11px;
617
- font-weight: 600;
618
- color: #60a5fa;
619
- transition: all 0.2s;
620
- display: inline-block;
621
- }
622
-
623
- .hf-link:hover {
624
- background: rgba(96, 165, 250, 0.2);
625
- border-color: rgba(96, 165, 250, 0.4);
626
- }
627
-
628
- @media (max-width: 768px) {
629
- .controls {
630
- flex-direction: column;
631
- align-items: stretch;
632
- }
633
-
634
- .checkbox-group {
635
- justify-content: center;
636
- }
637
-
638
- .mainContent {
639
- grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
640
- gap: 16px;
641
- }
642
- }
643
-
644
- /* Loading animation */
645
- @keyframes fadeIn {
646
- from {
647
- opacity: 0;
648
- transform: translateY(20px);
649
- }
650
- to {
651
- opacity: 1;
652
- transform: translateY(0);
653
- }
654
- }
655
-
656
- .element {
657
- animation: fadeIn 0.4s ease-out;
658
- }
659
-
660
- /* Scrollbar styling */
661
- ::-webkit-scrollbar {
662
- width: 12px;
663
- }
664
-
665
- ::-webkit-scrollbar-track {
666
- background: rgba(15, 23, 42, 0.5);
667
- }
668
-
669
- ::-webkit-scrollbar-thumb {
670
- background: rgba(148, 163, 184, 0.3);
671
- border-radius: 6px;
672
- }
673
-
674
- ::-webkit-scrollbar-thumb:hover {
675
- background: rgba(148, 163, 184, 0.5);
676
- }
677
- </style>
678
- </head>
679
- <body>
680
- <div class="header">
681
- <h1>🎨 Mal's Models</h1>
682
-
683
- <div class="profile-links">
684
- <a href="https://civitai.com/user/malcolmrey" target="_blank">🎨 CivitAI</a>
685
- <a href="https://huggingface.com/malcolmrey" target="_blank">🤗 HuggingFace</a>
686
- <a href="https://buymeacoffee.com/malcolmrey" target="_blank">☕ BuyMeACoffee</a>
687
- <a href="https://reddit.com/r/malcolmrey" target="_blank">💬 Reddit</a>
688
- </div>
689
-
690
- <div class="last-update">Last updated: 2025.11.02</div>
691
-
692
- <div class="controls">
693
- <div class="search-container">
694
- <input
695
- id="search"
696
- type="text"
697
- onkeyup="javascript:searchModels(this.value);"
698
- placeholder="Search models..."
699
- />
700
- <button
701
- class="clear-btn"
702
- onclick="javascript:clearCurrentSearchValue(); javascript:searchModels(getCurrentSearchValue())"
703
- >
704
- Clear
705
- </button>
706
- </div>
707
-
708
- <select
709
- class="mode-select"
710
- id="searchMode"
711
- onchange="javascript:searchModels(getCurrentSearchValue());"
712
- >
713
- <option value="available">Available</option>
714
- <option value="missing">Missing</option>
715
- </select>
716
-
717
- <div class="stats-bar">
718
- <label>Found:</label>
719
- <input id="found" type="text" readonly />
720
- </div>
721
- </div>
722
-
723
- <div class="controls" style="margin-top: 16px;">
724
- <div class="checkbox-group">
725
- <label class="checkbox-label">
726
- <input
727
- id="selectedLora"
728
- type="checkbox"
729
- onclick="javascript:searchModels(getCurrentSearchValue());"
730
- />
731
- <span>SD LoRA</span>
732
- </label>
733
-
734
- <label class="checkbox-label">
735
- <input
736
- id="selectedLocon"
737
- type="checkbox"
738
- onclick="javascript:searchModels(getCurrentSearchValue());"
739
- />
740
- <span>SD LoCon</span>
741
- </label>
742
-
743
- <label class="checkbox-label">
744
- <input
745
- id="selectedEmbedding"
746
- type="checkbox"
747
- onclick="javascript:searchModels(getCurrentSearchValue());"
748
- />
749
- <span>SD Embedding</span>
750
- </label>
751
-
752
- <label class="checkbox-label">
753
- <input
754
- id="selectedFlux"
755
- type="checkbox"
756
- onclick="javascript:searchModels(getCurrentSearchValue());"
757
- />
758
- <span>Flux</span>
759
- </label>
760
-
761
- <label class="checkbox-label">
762
- <input
763
- id="selectedWan"
764
- type="checkbox"
765
- onclick="javascript:searchModels(getCurrentSearchValue());"
766
- />
767
- <span>WAN</span>
768
- </label>
769
-
770
- <label class="checkbox-label">
771
- <input
772
- id="selectedSdxl"
773
- type="checkbox"
774
- onclick="javascript:searchModels(getCurrentSearchValue());"
775
- />
776
- <span>SDXL</span>
777
- </label>
778
-
779
- <label class="checkbox-label">
780
- <input
781
- id="selectedQwen"
782
- type="checkbox"
783
- onclick="javascript:searchModels(getCurrentSearchValue());"
784
- />
785
- <span>Qwen</span>
786
- </label>
787
- </div>
788
- </div>
789
- </div>
790
-
791
- <div id="mainContent" class="mainContent"></div>
792
-
793
- <!-- Modal -->
794
- <div id="modalOverlay" class="modal-overlay" onclick="closeModalOnOverlay(event)">
795
- <div class="modal-content" onclick="event.stopPropagation()">
796
- <div class="modal-header">
797
- <h2 id="modalTitle" class="modal-title"></h2>
798
- <button class="modal-close" onclick="closeModal()" aria-label="Close">×</button>
799
- </div>
800
- <div class="modal-body">
801
- <div class="modal-left">
802
- <div class="modal-image-container">
803
- <img id="modalImage" src="" alt="" />
804
- </div>
805
- </div>
806
- <div class="modal-right">
807
- <div id="modalStats" class="modal-stats"></div>
808
- </div>
809
- </div>
810
- </div>
811
- </div>
812
-
813
- <script type="text/javascript">
814
- function yesNo(value) {
815
- if (value === undefined) {
816
- return '<span class="status-icon status-unknown">❓</span>';
817
- }
818
- return value
819
- ? '<span class="status-icon status-available">✅</span>'
820
- : '<span class="status-icon status-unavailable">❌</span>';
821
- }
822
-
823
- function formatHFLink(link, hasHF) {
824
- if (!link) return '';
825
- return `<a href="${link}" target="_blank" class="hf-link">HF</a>`;
826
- }
827
-
828
- function formatModalHFLink(isAvailable, link) {
829
- if (isAvailable && link) {
830
- return `<a href="${link}" target="_blank" class="modal-hf-link">🤗 HuggingFace</a>`;
831
- } else if (isAvailable) {
832
- return `<span class="modal-hf-placeholder">🤗 HuggingFace</span>`;
833
- }
834
- return '';
835
- }
836
-
837
- function openModal(element) {
838
- const modal = document.getElementById('modalOverlay');
839
- const modalTitle = document.getElementById('modalTitle');
840
- const modalImage = document.getElementById('modalImage');
841
- const modalStats = document.getElementById('modalStats');
842
-
843
- // Generate HuggingFace links
844
- const personName = element.key;
845
- const loconHFLink = element.locon
846
- ? `https://huggingface.co/malcolmrey/lycoris/resolve/main/locon_${personName}_v1_from_v1_64_32.safetensors`
847
- : undefined;
848
- const loraHFLink = undefined; // We don't know the hyphenation pattern for loras
849
- const embeddingHFLink = element.embedding
850
- ? `https://huggingface.co/malcolmrey/embeddings/resolve/main/${personName}-ti.safetensors`
851
- : undefined;
852
- const fluxHFLink = element.flux
853
- ? `https://huggingface.co/malcolmrey/flux/resolve/main/flux_${personName}_v1-step00000400.safetensors`
854
- : undefined;
855
- const wanHFLink = element.wan
856
- ? `https://huggingface.co/malcolmrey/wan/resolve/main/wan2.1/wan_${personName}_v1.safetensors`
857
- : undefined;
858
-
859
- modalTitle.textContent = element.key;
860
- modalImage.src = element.imageUrl ?? unknownImage;
861
- modalImage.alt = element.key;
862
-
863
- modalStats.innerHTML = `
864
- <div class="modal-stat-row">
865
- <span class="modal-stat-label">SD LoCon</span>
866
- <span class="modal-stat-value">
867
- ${yesNo(element.locon)}
868
- ${formatModalHFLink(element.locon, loconHFLink)}
869
- </span>
870
- </div>
871
- <div class="modal-stat-row">
872
- <span class="modal-stat-label">SD LoRA</span>
873
- <span class="modal-stat-value">
874
- ${yesNo(element.lora)}
875
- ${formatModalHFLink(element.lora, loraHFLink)}
876
- </span>
877
- </div>
878
- <div class="modal-stat-row">
879
- <span class="modal-stat-label">SD Embedding</span>
880
- <span class="modal-stat-value">
881
- ${yesNo(element.embedding)}
882
- ${formatModalHFLink(element.embedding, embeddingHFLink)}
883
- </span>
884
- </div>
885
- <div class="modal-stat-row">
886
- <span class="modal-stat-label">Flux</span>
887
- <span class="modal-stat-value">
888
- ${yesNo(element.flux)}
889
- ${formatModalHFLink(element.flux, fluxHFLink)}
890
- </span>
891
- </div>
892
- <div class="modal-stat-row">
893
- <span class="modal-stat-label">WAN</span>
894
- <span class="modal-stat-value">
895
- ${yesNo(element.wan)}
896
- ${formatModalHFLink(element.wan, wanHFLink)}
897
- </span>
898
- </div>
899
- <div class="modal-stat-row">
900
- <span class="modal-stat-label">SDXL</span>
901
- <span class="modal-stat-value">
902
- ${yesNo(element.sdxl)}
903
- ${formatModalHFLink(element.sdxl, element.sdxlHFLink)}
904
- </span>
905
- </div>
906
- <div class="modal-stat-row">
907
- <span class="modal-stat-label">Qwen</span>
908
- <span class="modal-stat-value">
909
- ${yesNo(element.qwen)}
910
- ${formatModalHFLink(element.qwen, element.qwenHFLink)}
911
- </span>
912
- </div>
913
- `;
914
-
915
- modal.classList.add('active');
916
- document.body.style.overflow = 'hidden';
917
- }
918
-
919
- function closeModal() {
920
- const modal = document.getElementById('modalOverlay');
921
- modal.classList.remove('active');
922
- document.body.style.overflow = '';
923
- }
924
-
925
- function closeModalOnOverlay(event) {
926
- if (event.target.id === 'modalOverlay') {
927
- closeModal();
928
- }
929
- }
930
-
931
- // Close modal on ESC key
932
- document.addEventListener('keydown', function(event) {
933
- if (event.key === 'Escape') {
934
- closeModal();
935
- }
936
- });
937
-
938
- const notMatched = {
939
- lycoris: [],
940
- lora: [],
941
- embedding: [],
942
- flux: [],
943
- wan: [],
944
- sdxl: [],
945
- qwen: [],
946
- };
947
-
948
- models.lycorises.forEach((lycoris) => {
949
- const key = prepareKey(lycoris.name);
950
- if (presence[key] !== undefined) {
951
- presence[key].loconCivitai = true;
952
- setImageUrl(key, lycoris.imageUrl);
953
- presence[key].loconCivitaiLink = lycoris.url;
954
- } else if (!isKnownSkippableKey(key)) {
955
- notMatched.lycoris.push(key);
956
- }
957
- });
958
-
959
- models.loras.forEach((lora) => {
960
- const key = prepareKey(lora.name);
961
- if (presence[key] !== undefined) {
962
- presence[key].loraCivitai = true;
963
- setImageUrl(key, lora.imageUrl);
964
- presence[key].loraCivitaiLink = lora.url;
965
- } else if (!isKnownSkippableKey(key)) {
966
- notMatched.lora.push(key);
967
- }
968
- });
969
-
970
- models.embeddings.forEach((embedding) => {
971
- const key = prepareKey(embedding.name);
972
- if (presence[key] !== undefined) {
973
- presence[key].embeddingCivitai = true;
974
- setImageUrl(key, embedding.imageUrl);
975
- presence[key].embeddingCivitaiLink = embedding.url;
976
- } else if (!isKnownSkippableKey(key)) {
977
- notMatched.embedding.push(key);
978
- }
979
- });
980
-
981
- models.fluxes.forEach((flux) => {
982
- const key = prepareKey(flux.name);
983
- if (presence[key] !== undefined) {
984
- presence[key].fluxCivitai = true;
985
- setImageUrl(key, flux.imageUrl);
986
- presence[key].fluxCivitaiLink = flux.url;
987
- } else if (!isKnownSkippableKey(key)) {
988
- notMatched.flux.push(key);
989
- }
990
- });
991
-
992
- models.wans.forEach((wan) => {
993
- const key = prepareKey(wan.name);
994
- if (presence[key] !== undefined) {
995
- presence[key].wanCivitai = true;
996
- setImageUrl(key, wan.imageUrl);
997
- presence[key].wanCivitaiLink = wan.url;
998
- } else if (!isKnownSkippableKey(key)) {
999
- notMatched.wan.push(key);
1000
- }
1001
- });
1002
-
1003
- models.sdxls.forEach((sdxl) => {
1004
- const key = prepareKey(sdxl.name);
1005
- if (presence[key] !== undefined) {
1006
- presence[key].sdxlCivitai = true;
1007
- setImageUrl(key, sdxl.imageUrl);
1008
- presence[key].sdxlCivitaiLink = sdxl.url;
1009
- } else if (!isKnownSkippableKey(key)) {
1010
- notMatched.sdxl.push(key);
1011
- }
1012
- });
1013
-
1014
- models.qwens.forEach((qwen) => {
1015
- const key = prepareKey(qwen.name);
1016
- if (presence[key] !== undefined) {
1017
- presence[key].qwenCivitai = true;
1018
- setImageUrl(key, qwen.imageUrl);
1019
- presence[key].qwenCivitaiLink = qwen.url;
1020
- } else if (!isKnownSkippableKey(key)) {
1021
- notMatched.qwen.push(key);
1022
- }
1023
- });
1024
-
1025
- console.log(notMatched);
1026
-
1027
- const presenceModels = [];
1028
- for (const property in presence) {
1029
- const element = {
1030
- key: property,
1031
- locon: presence[property].locon,
1032
- lora: presence[property].lora,
1033
- embedding: presence[property].embedding,
1034
- flux: presence[property].flux,
1035
- wan: presence[property].wan,
1036
- sdxl: presence[property].sdxl,
1037
- qwen: presence[property].qwen,
1038
- mega: undefined,
1039
- imageUrl: presence[property]?.imageUrl ?? undefined,
1040
- loconHFLink: presence[property]?.loconHFLink,
1041
- loraHFLink: presence[property]?.loraHFLink,
1042
- embeddingHFLink: presence[property]?.embeddingHFLink,
1043
- fluxHFLink: presence[property]?.fluxHFLink,
1044
- wanHFLink: presence[property]?.wanHFLink,
1045
- sdxlHFLink: presence[property]?.sdxlHFLink,
1046
- qwenHFLink: presence[property]?.qwenHFLink,
1047
- };
1048
- presenceModels.push(element);
1049
- }
1050
-
1051
- function searchModelsModern(value) {
1052
- const lowerCaseValue = value.toLowerCase();
1053
-
1054
- const filtered = presenceModels.filter((element) => {
1055
- return (
1056
- (element.key.toLowerCase().includes(lowerCaseValue) || value === '*') &&
1057
- filterByType(element)
1058
- );
1059
- });
1060
-
1061
- document.getElementById('found').value = filtered.length;
1062
-
1063
- const contentDiv = document.getElementById('mainContent');
1064
- contentDiv.innerHTML = '';
1065
-
1066
- filtered.forEach((element, index) => {
1067
- const card = document.createElement('div');
1068
- card.className = 'element';
1069
- card.innerHTML = `
1070
- <div class="modelName" title="${escapeHtml(element.key)}">${element.key}</div>
1071
-
1072
- <div class="imageContainer">
1073
- <img src="${element.imageUrl ?? unknownImage}" alt="${escapeHtml(element.key)}" />
1074
- </div>
1075
- `;
1076
-
1077
- card.addEventListener('click', () => openModal(element));
1078
- contentDiv.appendChild(card);
1079
- });
1080
- }
1081
-
1082
- // Override the searchModels function
1083
- window.searchModels = searchModelsModern;
1084
-
1085
- searchModels(getCurrentSearchValue());
1086
- </script>
1087
- </body>
1088
- </html>
 
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>Mal's Models</title>
7
+ <script type="text/javascript" src="data-civitai.js"></script>
8
+ <script type="text/javascript" src="data-huggingface.js"></script>
9
+ <script type="text/javascript" src="comparator.js"></script>
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
13
+ <style>
14
+ * {
15
+ margin: 0;
16
+ padding: 0;
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
22
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
23
+ color: #f1f5f9;
24
+ min-height: 100vh;
25
+ padding: 20px;
26
+ }
27
+
28
+ a {
29
+ text-decoration: none;
30
+ color: #60a5fa;
31
+ transition: color 0.2s;
32
+ }
33
+
34
+ a:hover {
35
+ color: #93c5fd;
36
+ }
37
+
38
+ .header {
39
+ max-width: 1400px;
40
+ margin: 0 auto 30px;
41
+ background: rgba(30, 41, 59, 0.7);
42
+ backdrop-filter: blur(10px);
43
+ border-radius: 16px;
44
+ padding: 24px;
45
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
46
+ border: 1px solid rgba(148, 163, 184, 0.1);
47
+ }
48
+
49
+ .header h1 {
50
+ font-size: 28px;
51
+ font-weight: 700;
52
+ margin-bottom: 12px;
53
+ background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 100%);
54
+ -webkit-background-clip: text;
55
+ -webkit-text-fill-color: transparent;
56
+ background-clip: text;
57
+ text-align: center;
58
+ }
59
+
60
+ .profile-links {
61
+ display: flex;
62
+ flex-wrap: wrap;
63
+ gap: 16px;
64
+ justify-content: center;
65
+ align-items: center;
66
+ margin-bottom: 12px;
67
+ padding: 12px;
68
+ background: rgba(15, 23, 42, 0.4);
69
+ border-radius: 8px;
70
+ }
71
+
72
+ .profile-links a {
73
+ display: inline-flex;
74
+ align-items: center;
75
+ gap: 6px;
76
+ padding: 6px 12px;
77
+ background: rgba(96, 165, 250, 0.1);
78
+ border-radius: 6px;
79
+ font-size: 14px;
80
+ transition: all 0.2s;
81
+ }
82
+
83
+ .profile-links a:hover {
84
+ background: rgba(96, 165, 250, 0.2);
85
+ transform: translateY(-1px);
86
+ }
87
+
88
+ .last-update {
89
+ text-align: center;
90
+ font-size: 13px;
91
+ color: #94a3b8;
92
+ margin-bottom: 16px;
93
+ }
94
+
95
+ .controls {
96
+ display: flex;
97
+ flex-wrap: wrap;
98
+ gap: 12px;
99
+ align-items: center;
100
+ justify-content: center;
101
+ }
102
+
103
+ .search-container {
104
+ flex: 1 1 auto;
105
+ min-width: 300px;
106
+ max-width: 600px;
107
+ position: relative;
108
+ }
109
+
110
+ .search-container input[type="text"] {
111
+ width: 100%;
112
+ padding: 12px 16px;
113
+ padding-right: 100px;
114
+ background: rgba(15, 23, 42, 0.6);
115
+ border: 2px solid rgba(148, 163, 184, 0.2);
116
+ border-radius: 12px;
117
+ color: #f1f5f9;
118
+ font-size: 15px;
119
+ font-family: inherit;
120
+ transition: all 0.3s;
121
+ }
122
+
123
+ .search-container input[type="text"]:focus {
124
+ outline: none;
125
+ border-color: #60a5fa;
126
+ background: rgba(15, 23, 42, 0.8);
127
+ box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.1);
128
+ }
129
+
130
+ .search-container input[type="text"]::placeholder {
131
+ color: #64748b;
132
+ }
133
+
134
+ .clear-btn {
135
+ position: absolute;
136
+ right: 8px;
137
+ top: 50%;
138
+ transform: translateY(-50%);
139
+ padding: 6px 14px;
140
+ background: rgba(239, 68, 68, 0.2);
141
+ border: 1px solid rgba(239, 68, 68, 0.3);
142
+ border-radius: 8px;
143
+ color: #fca5a5;
144
+ font-size: 13px;
145
+ font-weight: 500;
146
+ cursor: pointer;
147
+ transition: all 0.2s;
148
+ }
149
+
150
+ .clear-btn:hover {
151
+ background: rgba(239, 68, 68, 0.3);
152
+ color: #fecaca;
153
+ }
154
+
155
+ .mode-select {
156
+ padding: 12px 40px 12px 16px;
157
+ background: rgba(15, 23, 42, 0.6);
158
+ border: 2px solid rgba(148, 163, 184, 0.2);
159
+ border-radius: 12px;
160
+ color: #f1f5f9;
161
+ font-size: 15px;
162
+ font-family: inherit;
163
+ cursor: pointer;
164
+ transition: all 0.3s;
165
+ font-weight: 500;
166
+ height: 48px;
167
+ line-height: 20px;
168
+ min-width: 150px;
169
+ flex-shrink: 0;
170
+ appearance: none;
171
+ -webkit-appearance: none;
172
+ -moz-appearance: none;
173
+ background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
174
+ background-repeat: no-repeat;
175
+ background-position: right 12px center;
176
+ background-size: 20px;
177
+ box-shadow: none;
178
+ outline: none;
179
+ }
180
+
181
+ .mode-select::-ms-expand {
182
+ display: none;
183
+ }
184
+
185
+ .mode-select:focus {
186
+ outline: none;
187
+ border-color: #60a5fa;
188
+ box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.1);
189
+ }
190
+
191
+ .mode-select option {
192
+ background: #0f172a;
193
+ color: #e2e8f0;
194
+ padding: 16px 20px;
195
+ font-size: 15px;
196
+ font-weight: 500;
197
+ border: none;
198
+ outline: none;
199
+ }
200
+
201
+ .mode-select option:hover {
202
+ background: linear-gradient(90deg, rgba(96, 165, 250, 0.15) 0%, rgba(96, 165, 250, 0.05) 100%);
203
+ color: #60a5fa;
204
+ font-weight: 600;
205
+ }
206
+
207
+ .mode-select option:checked,
208
+ .mode-select option:focus {
209
+ background: linear-gradient(90deg, rgba(96, 165, 250, 0.25) 0%, rgba(96, 165, 250, 0.15) 100%);
210
+ color: #93c5fd;
211
+ font-weight: 600;
212
+ box-shadow: none;
213
+ outline: none;
214
+ }
215
+
216
+ .checkbox-group {
217
+ display: flex;
218
+ flex-wrap: wrap;
219
+ gap: 8px;
220
+ align-items: center;
221
+ }
222
+
223
+ .checkbox-label {
224
+ display: flex;
225
+ align-items: center;
226
+ gap: 8px;
227
+ padding: 10px 16px;
228
+ background: rgba(15, 23, 42, 0.4);
229
+ border: 2px solid rgba(148, 163, 184, 0.2);
230
+ border-radius: 10px;
231
+ cursor: pointer;
232
+ transition: all 0.2s;
233
+ font-size: 14px;
234
+ font-weight: 500;
235
+ user-select: none;
236
+ }
237
+
238
+ .checkbox-label:hover {
239
+ background: rgba(15, 23, 42, 0.6);
240
+ border-color: rgba(148, 163, 184, 0.3);
241
+ }
242
+
243
+ .checkbox-label input[type="checkbox"] {
244
+ width: 18px;
245
+ height: 18px;
246
+ cursor: pointer;
247
+ accent-color: #60a5fa;
248
+ }
249
+
250
+ .checkbox-label input[type="checkbox"]:checked + span {
251
+ color: #60a5fa;
252
+ }
253
+
254
+ .stats-bar {
255
+ display: flex;
256
+ align-items: center;
257
+ gap: 12px;
258
+ padding: 12px 20px;
259
+ background: rgba(15, 23, 42, 0.6);
260
+ border-radius: 10px;
261
+ font-size: 14px;
262
+ font-weight: 600;
263
+ flex-shrink: 0;
264
+ }
265
+
266
+ .stats-bar label {
267
+ color: #94a3b8;
268
+ }
269
+
270
+ .stats-bar input {
271
+ width: 70px;
272
+ padding: 6px 10px;
273
+ background: rgba(30, 41, 59, 0.8);
274
+ border: 1px solid rgba(148, 163, 184, 0.2);
275
+ border-radius: 6px;
276
+ color: #60a5fa;
277
+ text-align: center;
278
+ font-weight: 700;
279
+ font-size: 16px;
280
+ }
281
+
282
+ .mainContent {
283
+ max-width: 1400px;
284
+ margin: 0 auto;
285
+ display: grid;
286
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
287
+ gap: 20px;
288
+ padding: 0 4px;
289
+ }
290
+
291
+ .element {
292
+ background: rgba(30, 41, 59, 0.6);
293
+ backdrop-filter: blur(10px);
294
+ border-radius: 16px;
295
+ overflow: hidden;
296
+ border: 1px solid rgba(148, 163, 184, 0.1);
297
+ transition: all 0.3s;
298
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
299
+ display: flex;
300
+ flex-direction: column;
301
+ cursor: pointer;
302
+ }
303
+
304
+ .element:hover {
305
+ transform: translateY(-4px);
306
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
307
+ border-color: rgba(96, 165, 250, 0.3);
308
+ }
309
+
310
+ .element .modelName {
311
+ padding: 14px 16px;
312
+ background: rgba(15, 23, 42, 0.8);
313
+ font-weight: 600;
314
+ font-size: 14px;
315
+ text-overflow: ellipsis;
316
+ overflow: hidden;
317
+ white-space: nowrap;
318
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
319
+ color: #e2e8f0;
320
+ }
321
+
322
+ .element .imageContainer {
323
+ position: relative;
324
+ width: 100%;
325
+ padding-top: 137.5%; /* 192/264 aspect ratio */
326
+ overflow: hidden;
327
+ background: rgba(15, 23, 42, 0.6);
328
+ }
329
+
330
+ .element .imageContainer img {
331
+ position: absolute;
332
+ top: 0;
333
+ left: 0;
334
+ width: 100%;
335
+ height: 100%;
336
+ object-fit: cover;
337
+ transition: transform 0.3s;
338
+ }
339
+
340
+ .element:hover .imageContainer img {
341
+ transform: scale(1.05);
342
+ }
343
+
344
+ .statsBox {
345
+ padding: 16px;
346
+ background: rgba(15, 23, 42, 0.8);
347
+ font-size: 13px;
348
+ line-height: 1.8;
349
+ flex: 1;
350
+ display: none; /* Hidden in main view */
351
+ }
352
+
353
+ .statsBox .stat-row {
354
+ display: flex;
355
+ align-items: center;
356
+ gap: 8px;
357
+ margin-bottom: 6px;
358
+ }
359
+
360
+ .statsBox .stat-row:last-child {
361
+ margin-bottom: 0;
362
+ }
363
+
364
+ .statsBox .stat-label {
365
+ font-weight: 600;
366
+ color: #94a3b8;
367
+ min-width: 75px;
368
+ }
369
+
370
+ .statsBox .stat-value {
371
+ display: flex;
372
+ align-items: center;
373
+ gap: 6px;
374
+ }
375
+
376
+ /* Modal Styles */
377
+ .modal-overlay {
378
+ display: none;
379
+ position: fixed;
380
+ top: 0;
381
+ left: 0;
382
+ right: 0;
383
+ bottom: 0;
384
+ background: rgba(0, 0, 0, 0.8);
385
+ backdrop-filter: blur(8px);
386
+ z-index: 1000;
387
+ align-items: center;
388
+ justify-content: center;
389
+ padding: 20px;
390
+ animation: fadeIn 0.2s ease-out;
391
+ }
392
+
393
+ .modal-overlay.active {
394
+ display: flex;
395
+ }
396
+
397
+ .modal-content {
398
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
399
+ border-radius: 20px;
400
+ max-width: 1100px;
401
+ width: 100%;
402
+ max-height: 90vh;
403
+ overflow: hidden;
404
+ position: relative;
405
+ border: 2px solid rgba(148, 163, 184, 0.2);
406
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
407
+ animation: slideUp 0.3s ease-out;
408
+ display: flex;
409
+ flex-direction: column;
410
+ }
411
+
412
+ @keyframes slideUp {
413
+ from {
414
+ opacity: 0;
415
+ transform: translateY(30px);
416
+ }
417
+ to {
418
+ opacity: 1;
419
+ transform: translateY(0);
420
+ }
421
+ }
422
+
423
+ .modal-header {
424
+ padding: 24px;
425
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
426
+ display: flex;
427
+ align-items: center;
428
+ justify-content: space-between;
429
+ position: sticky;
430
+ top: 0;
431
+ background: rgba(30, 41, 59, 0.95);
432
+ backdrop-filter: blur(10px);
433
+ z-index: 10;
434
+ }
435
+
436
+ .modal-title {
437
+ font-size: 24px;
438
+ font-weight: 700;
439
+ color: #f1f5f9;
440
+ margin: 0;
441
+ }
442
+
443
+ .modal-close {
444
+ width: 36px;
445
+ height: 36px;
446
+ border-radius: 10px;
447
+ background: rgba(239, 68, 68, 0.2);
448
+ border: 1px solid rgba(239, 68, 68, 0.3);
449
+ color: #fca5a5;
450
+ font-size: 20px;
451
+ cursor: pointer;
452
+ display: flex;
453
+ align-items: center;
454
+ justify-content: center;
455
+ transition: all 0.2s;
456
+ font-weight: 700;
457
+ }
458
+
459
+ .modal-close:hover {
460
+ background: rgba(239, 68, 68, 0.3);
461
+ color: #fecaca;
462
+ transform: rotate(90deg);
463
+ }
464
+
465
+ .modal-body {
466
+ display: flex;
467
+ flex: 1;
468
+ overflow: hidden;
469
+ gap: 0;
470
+ }
471
+
472
+ .modal-left {
473
+ flex: 0 0 45%;
474
+ display: flex;
475
+ align-items: center;
476
+ justify-content: center;
477
+ background: rgba(15, 23, 42, 0.6);
478
+ padding: 24px;
479
+ }
480
+
481
+ .modal-image-container {
482
+ width: 100%;
483
+ border-radius: 12px;
484
+ overflow: hidden;
485
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
486
+ max-height: 100%;
487
+ }
488
+
489
+ .modal-image-container img {
490
+ width: 100%;
491
+ height: auto;
492
+ display: block;
493
+ }
494
+
495
+ .modal-right {
496
+ flex: 1;
497
+ padding: 24px;
498
+ overflow-y: auto;
499
+ display: flex;
500
+ flex-direction: column;
501
+ gap: 16px;
502
+ }
503
+
504
+ .modal-stats {
505
+ display: grid;
506
+ gap: 12px;
507
+ }
508
+
509
+ .modal-stat-row {
510
+ display: flex;
511
+ align-items: center;
512
+ justify-content: space-between;
513
+ padding: 16px 20px;
514
+ background: rgba(15, 23, 42, 0.6);
515
+ border-radius: 12px;
516
+ border: 1px solid rgba(148, 163, 184, 0.1);
517
+ transition: all 0.2s;
518
+ }
519
+
520
+ .modal-stat-row:hover {
521
+ background: rgba(15, 23, 42, 0.8);
522
+ border-color: rgba(148, 163, 184, 0.2);
523
+ }
524
+
525
+ .modal-stat-label {
526
+ font-weight: 600;
527
+ font-size: 15px;
528
+ color: #cbd5e1;
529
+ }
530
+
531
+ .modal-stat-value {
532
+ display: flex;
533
+ align-items: center;
534
+ gap: 12px;
535
+ }
536
+
537
+ .modal-hf-link {
538
+ padding: 6px 12px;
539
+ background: rgba(255, 107, 0, 0.15);
540
+ border: 1px solid rgba(255, 107, 0, 0.3);
541
+ border-radius: 8px;
542
+ font-size: 12px;
543
+ font-weight: 600;
544
+ color: #ff9d5c;
545
+ transition: all 0.2s;
546
+ text-decoration: none;
547
+ display: inline-flex;
548
+ align-items: center;
549
+ gap: 6px;
550
+ }
551
+
552
+ .modal-hf-link:hover {
553
+ background: rgba(255, 107, 0, 0.25);
554
+ border-color: rgba(255, 107, 0, 0.5);
555
+ transform: translateY(-1px);
556
+ color: #ffb380;
557
+ }
558
+
559
+ .modal-hf-placeholder {
560
+ padding: 6px 12px;
561
+ background: rgba(100, 116, 139, 0.15);
562
+ border: 1px solid rgba(100, 116, 139, 0.3);
563
+ border-radius: 8px;
564
+ font-size: 12px;
565
+ font-weight: 600;
566
+ color: #94a3b8;
567
+ display: inline-flex;
568
+ align-items: center;
569
+ gap: 6px;
570
+ opacity: 0.5;
571
+ }
572
+
573
+ .hf-logo {
574
+ width: 16px;
575
+ height: 16px;
576
+ display: inline-block;
577
+ }
578
+
579
+ @media (max-width: 900px) {
580
+ .modal-body {
581
+ flex-direction: column;
582
+ }
583
+
584
+ .modal-left {
585
+ flex: 0 0 auto;
586
+ max-height: 400px;
587
+ }
588
+
589
+ .modal-right {
590
+ flex: 1;
591
+ }
592
+ }
593
+
594
+ .status-icon {
595
+ font-size: 16px;
596
+ display: inline-block;
597
+ }
598
+
599
+ .status-available {
600
+ color: #4ade80;
601
+ }
602
+
603
+ .status-unavailable {
604
+ color: #f87171;
605
+ }
606
+
607
+ .status-unknown {
608
+ color: #fbbf24;
609
+ }
610
+
611
+ .hf-link {
612
+ padding: 2px 8px;
613
+ background: rgba(96, 165, 250, 0.1);
614
+ border: 1px solid rgba(96, 165, 250, 0.2);
615
+ border-radius: 6px;
616
+ font-size: 11px;
617
+ font-weight: 600;
618
+ color: #60a5fa;
619
+ transition: all 0.2s;
620
+ display: inline-block;
621
+ }
622
+
623
+ .hf-link:hover {
624
+ background: rgba(96, 165, 250, 0.2);
625
+ border-color: rgba(96, 165, 250, 0.4);
626
+ }
627
+
628
+ @media (max-width: 768px) {
629
+ .controls {
630
+ flex-direction: column;
631
+ align-items: stretch;
632
+ }
633
+
634
+ .checkbox-group {
635
+ justify-content: center;
636
+ }
637
+
638
+ .mainContent {
639
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
640
+ gap: 16px;
641
+ }
642
+ }
643
+
644
+ /* Loading animation */
645
+ @keyframes fadeIn {
646
+ from {
647
+ opacity: 0;
648
+ transform: translateY(20px);
649
+ }
650
+ to {
651
+ opacity: 1;
652
+ transform: translateY(0);
653
+ }
654
+ }
655
+
656
+ .element {
657
+ animation: fadeIn 0.4s ease-out;
658
+ }
659
+
660
+ /* Scrollbar styling */
661
+ ::-webkit-scrollbar {
662
+ width: 12px;
663
+ }
664
+
665
+ ::-webkit-scrollbar-track {
666
+ background: rgba(15, 23, 42, 0.5);
667
+ }
668
+
669
+ ::-webkit-scrollbar-thumb {
670
+ background: rgba(148, 163, 184, 0.3);
671
+ border-radius: 6px;
672
+ }
673
+
674
+ ::-webkit-scrollbar-thumb:hover {
675
+ background: rgba(148, 163, 184, 0.5);
676
+ }
677
+ </style>
678
+ </head>
679
+ <body>
680
+ <div class="header">
681
+ <h1>🎨 Mal's Models</h1>
682
+
683
+ <div class="profile-links">
684
+ <a href="https://civitai.com/user/malcolmrey" target="_blank">🎨 CivitAI</a>
685
+ <a href="https://huggingface.com/malcolmrey" target="_blank">🤗 HuggingFace</a>
686
+ <a href="https://buymeacoffee.com/malcolmrey" target="_blank">☕ BuyMeACoffee</a>
687
+ <a href="https://reddit.com/r/malcolmrey" target="_blank">💬 Reddit</a>
688
+ </div>
689
+
690
+ <div class="last-update">Last updated: 2025.11.02</div>
691
+
692
+ <div class="controls">
693
+ <div class="search-container">
694
+ <input
695
+ id="search"
696
+ type="text"
697
+ onkeyup="javascript:searchModels(this.value);"
698
+ placeholder="Search models..."
699
+ />
700
+ <button
701
+ class="clear-btn"
702
+ onclick="javascript:clearCurrentSearchValue(); javascript:searchModels(getCurrentSearchValue())"
703
+ >
704
+ Clear
705
+ </button>
706
+ </div>
707
+
708
+ <select
709
+ class="mode-select"
710
+ id="searchMode"
711
+ onchange="javascript:searchModels(getCurrentSearchValue());"
712
+ >
713
+ <option value="available">Include</option>
714
+ <option value="missing">Exclude</option>
715
+ </select>
716
+
717
+ <div class="stats-bar">
718
+ <label>Found:</label>
719
+ <input id="found" type="text" readonly />
720
+ </div>
721
+ </div>
722
+
723
+ <div class="controls" style="margin-top: 16px;">
724
+ <div class="checkbox-group">
725
+ <label class="checkbox-label">
726
+ <input
727
+ id="selectedLora"
728
+ type="checkbox"
729
+ onclick="javascript:searchModels(getCurrentSearchValue());"
730
+ />
731
+ <span>SD LoRA</span>
732
+ </label>
733
+
734
+ <label class="checkbox-label">
735
+ <input
736
+ id="selectedLocon"
737
+ type="checkbox"
738
+ onclick="javascript:searchModels(getCurrentSearchValue());"
739
+ />
740
+ <span>SD LoCon</span>
741
+ </label>
742
+
743
+ <label class="checkbox-label">
744
+ <input
745
+ id="selectedEmbedding"
746
+ type="checkbox"
747
+ onclick="javascript:searchModels(getCurrentSearchValue());"
748
+ />
749
+ <span>SD Embedding</span>
750
+ </label>
751
+
752
+ <label class="checkbox-label">
753
+ <input
754
+ id="selectedFlux"
755
+ type="checkbox"
756
+ onclick="javascript:searchModels(getCurrentSearchValue());"
757
+ />
758
+ <span>Flux</span>
759
+ </label>
760
+
761
+ <label class="checkbox-label">
762
+ <input
763
+ id="selectedWan"
764
+ type="checkbox"
765
+ onclick="javascript:searchModels(getCurrentSearchValue());"
766
+ />
767
+ <span>WAN</span>
768
+ </label>
769
+
770
+ <label class="checkbox-label">
771
+ <input
772
+ id="selectedSdxl"
773
+ type="checkbox"
774
+ onclick="javascript:searchModels(getCurrentSearchValue());"
775
+ />
776
+ <span>SDXL</span>
777
+ </label>
778
+
779
+ <label class="checkbox-label">
780
+ <input
781
+ id="selectedQwen"
782
+ type="checkbox"
783
+ onclick="javascript:searchModels(getCurrentSearchValue());"
784
+ />
785
+ <span>Qwen</span>
786
+ </label>
787
+ </div>
788
+ </div>
789
+ </div>
790
+
791
+ <div id="mainContent" class="mainContent"></div>
792
+
793
+ <!-- Modal -->
794
+ <div id="modalOverlay" class="modal-overlay" onclick="closeModalOnOverlay(event)">
795
+ <div class="modal-content" onclick="event.stopPropagation()">
796
+ <div class="modal-header">
797
+ <h2 id="modalTitle" class="modal-title"></h2>
798
+ <button class="modal-close" onclick="closeModal()" aria-label="Close">×</button>
799
+ </div>
800
+ <div class="modal-body">
801
+ <div class="modal-left">
802
+ <div class="modal-image-container">
803
+ <img id="modalImage" src="" alt="" />
804
+ </div>
805
+ </div>
806
+ <div class="modal-right">
807
+ <div id="modalStats" class="modal-stats"></div>
808
+ </div>
809
+ </div>
810
+ </div>
811
+ </div>
812
+
813
+ <script type="text/javascript">
814
+ function yesNo(value) {
815
+ if (value === undefined) {
816
+ return '<span class="status-icon status-unknown">❓</span>';
817
+ }
818
+ return value
819
+ ? '<span class="status-icon status-available">✅</span>'
820
+ : '<span class="status-icon status-unavailable">❌</span>';
821
+ }
822
+
823
+ function formatHFLink(link, hasHF) {
824
+ if (!link) return '';
825
+ return `<a href="${link}" target="_blank" class="hf-link">HF</a>`;
826
+ }
827
+
828
+ function formatModalHFLink(isAvailable, link) {
829
+ if (isAvailable && link) {
830
+ return `<a href="${link}" target="_blank" class="modal-hf-link">🤗 HuggingFace</a>`;
831
+ } else if (isAvailable) {
832
+ return `<span class="modal-hf-placeholder">🤗 HuggingFace</span>`;
833
+ }
834
+ return '';
835
+ }
836
+
837
+ function openModal(element) {
838
+ const modal = document.getElementById('modalOverlay');
839
+ const modalTitle = document.getElementById('modalTitle');
840
+ const modalImage = document.getElementById('modalImage');
841
+ const modalStats = document.getElementById('modalStats');
842
+
843
+ // Generate HuggingFace links
844
+ const personName = element.key;
845
+ const loconHFLink = element.locon
846
+ ? `https://huggingface.co/malcolmrey/lycoris/resolve/main/locon_${personName}_v1_from_v1_64_32.safetensors`
847
+ : undefined;
848
+ const loraHFLink = undefined; // We don't know the hyphenation pattern for loras
849
+ const embeddingHFLink = element.embedding
850
+ ? `https://huggingface.co/malcolmrey/embeddings/resolve/main/${personName}-ti.safetensors`
851
+ : undefined;
852
+ const fluxHFLink = element.flux
853
+ ? `https://huggingface.co/malcolmrey/flux/resolve/main/flux_${personName}_v1-step00000400.safetensors`
854
+ : undefined;
855
+ const wanHFLink = element.wan
856
+ ? `https://huggingface.co/malcolmrey/wan/resolve/main/wan2.1/wan_${personName}_v1.safetensors`
857
+ : undefined;
858
+
859
+ modalTitle.textContent = element.key;
860
+ modalImage.src = element.imageUrl ?? unknownImage;
861
+ modalImage.alt = element.key;
862
+
863
+ modalStats.innerHTML = `
864
+ <div class="modal-stat-row">
865
+ <span class="modal-stat-label">SD LoCon</span>
866
+ <span class="modal-stat-value">
867
+ ${yesNo(element.locon)}
868
+ ${formatModalHFLink(element.locon, loconHFLink)}
869
+ </span>
870
+ </div>
871
+ <div class="modal-stat-row">
872
+ <span class="modal-stat-label">SD LoRA</span>
873
+ <span class="modal-stat-value">
874
+ ${yesNo(element.lora)}
875
+ ${formatModalHFLink(element.lora, loraHFLink)}
876
+ </span>
877
+ </div>
878
+ <div class="modal-stat-row">
879
+ <span class="modal-stat-label">SD Embedding</span>
880
+ <span class="modal-stat-value">
881
+ ${yesNo(element.embedding)}
882
+ ${formatModalHFLink(element.embedding, embeddingHFLink)}
883
+ </span>
884
+ </div>
885
+ <div class="modal-stat-row">
886
+ <span class="modal-stat-label">Flux</span>
887
+ <span class="modal-stat-value">
888
+ ${yesNo(element.flux)}
889
+ ${formatModalHFLink(element.flux, fluxHFLink)}
890
+ </span>
891
+ </div>
892
+ <div class="modal-stat-row">
893
+ <span class="modal-stat-label">WAN</span>
894
+ <span class="modal-stat-value">
895
+ ${yesNo(element.wan)}
896
+ ${formatModalHFLink(element.wan, wanHFLink)}
897
+ </span>
898
+ </div>
899
+ <div class="modal-stat-row">
900
+ <span class="modal-stat-label">SDXL</span>
901
+ <span class="modal-stat-value">
902
+ ${yesNo(element.sdxl)}
903
+ ${formatModalHFLink(element.sdxl, element.sdxlHFLink)}
904
+ </span>
905
+ </div>
906
+ <div class="modal-stat-row">
907
+ <span class="modal-stat-label">Qwen</span>
908
+ <span class="modal-stat-value">
909
+ ${yesNo(element.qwen)}
910
+ ${formatModalHFLink(element.qwen, element.qwenHFLink)}
911
+ </span>
912
+ </div>
913
+ `;
914
+
915
+ modal.classList.add('active');
916
+ document.body.style.overflow = 'hidden';
917
+ }
918
+
919
+ function closeModal() {
920
+ const modal = document.getElementById('modalOverlay');
921
+ modal.classList.remove('active');
922
+ document.body.style.overflow = '';
923
+ }
924
+
925
+ function closeModalOnOverlay(event) {
926
+ if (event.target.id === 'modalOverlay') {
927
+ closeModal();
928
+ }
929
+ }
930
+
931
+ // Close modal on ESC key
932
+ document.addEventListener('keydown', function(event) {
933
+ if (event.key === 'Escape') {
934
+ closeModal();
935
+ }
936
+ });
937
+
938
+ const notMatched = {
939
+ lycoris: [],
940
+ lora: [],
941
+ embedding: [],
942
+ flux: [],
943
+ wan: [],
944
+ sdxl: [],
945
+ qwen: [],
946
+ };
947
+
948
+ models.lycorises.forEach((lycoris) => {
949
+ const key = prepareKey(lycoris.name);
950
+ if (presence[key] !== undefined) {
951
+ presence[key].loconCivitai = true;
952
+ setImageUrl(key, lycoris.imageUrl);
953
+ presence[key].loconCivitaiLink = lycoris.url;
954
+ } else if (!isKnownSkippableKey(key)) {
955
+ notMatched.lycoris.push(key);
956
+ }
957
+ });
958
+
959
+ models.loras.forEach((lora) => {
960
+ const key = prepareKey(lora.name);
961
+ if (presence[key] !== undefined) {
962
+ presence[key].loraCivitai = true;
963
+ setImageUrl(key, lora.imageUrl);
964
+ presence[key].loraCivitaiLink = lora.url;
965
+ } else if (!isKnownSkippableKey(key)) {
966
+ notMatched.lora.push(key);
967
+ }
968
+ });
969
+
970
+ models.embeddings.forEach((embedding) => {
971
+ const key = prepareKey(embedding.name);
972
+ if (presence[key] !== undefined) {
973
+ presence[key].embeddingCivitai = true;
974
+ setImageUrl(key, embedding.imageUrl);
975
+ presence[key].embeddingCivitaiLink = embedding.url;
976
+ } else if (!isKnownSkippableKey(key)) {
977
+ notMatched.embedding.push(key);
978
+ }
979
+ });
980
+
981
+ models.fluxes.forEach((flux) => {
982
+ const key = prepareKey(flux.name);
983
+ if (presence[key] !== undefined) {
984
+ presence[key].fluxCivitai = true;
985
+ setImageUrl(key, flux.imageUrl);
986
+ presence[key].fluxCivitaiLink = flux.url;
987
+ } else if (!isKnownSkippableKey(key)) {
988
+ notMatched.flux.push(key);
989
+ }
990
+ });
991
+
992
+ models.wans.forEach((wan) => {
993
+ const key = prepareKey(wan.name);
994
+ if (presence[key] !== undefined) {
995
+ presence[key].wanCivitai = true;
996
+ setImageUrl(key, wan.imageUrl);
997
+ presence[key].wanCivitaiLink = wan.url;
998
+ } else if (!isKnownSkippableKey(key)) {
999
+ notMatched.wan.push(key);
1000
+ }
1001
+ });
1002
+
1003
+ models.sdxls.forEach((sdxl) => {
1004
+ const key = prepareKey(sdxl.name);
1005
+ if (presence[key] !== undefined) {
1006
+ presence[key].sdxlCivitai = true;
1007
+ setImageUrl(key, sdxl.imageUrl);
1008
+ presence[key].sdxlCivitaiLink = sdxl.url;
1009
+ } else if (!isKnownSkippableKey(key)) {
1010
+ notMatched.sdxl.push(key);
1011
+ }
1012
+ });
1013
+
1014
+ models.qwens.forEach((qwen) => {
1015
+ const key = prepareKey(qwen.name);
1016
+ if (presence[key] !== undefined) {
1017
+ presence[key].qwenCivitai = true;
1018
+ setImageUrl(key, qwen.imageUrl);
1019
+ presence[key].qwenCivitaiLink = qwen.url;
1020
+ } else if (!isKnownSkippableKey(key)) {
1021
+ notMatched.qwen.push(key);
1022
+ }
1023
+ });
1024
+
1025
+ console.log(notMatched);
1026
+
1027
+ const presenceModels = [];
1028
+ for (const property in presence) {
1029
+ const element = {
1030
+ key: property,
1031
+ locon: presence[property].locon,
1032
+ lora: presence[property].lora,
1033
+ embedding: presence[property].embedding,
1034
+ flux: presence[property].flux,
1035
+ wan: presence[property].wan,
1036
+ sdxl: presence[property].sdxl,
1037
+ qwen: presence[property].qwen,
1038
+ mega: undefined,
1039
+ imageUrl: presence[property]?.imageUrl ?? undefined,
1040
+ loconHFLink: presence[property]?.loconHFLink,
1041
+ loraHFLink: presence[property]?.loraHFLink,
1042
+ embeddingHFLink: presence[property]?.embeddingHFLink,
1043
+ fluxHFLink: presence[property]?.fluxHFLink,
1044
+ wanHFLink: presence[property]?.wanHFLink,
1045
+ sdxlHFLink: presence[property]?.sdxlHFLink,
1046
+ qwenHFLink: presence[property]?.qwenHFLink,
1047
+ };
1048
+ presenceModels.push(element);
1049
+ }
1050
+
1051
+ function searchModelsModern(value) {
1052
+ const lowerCaseValue = value.toLowerCase();
1053
+
1054
+ const filtered = presenceModels.filter((element) => {
1055
+ return (
1056
+ (element.key.toLowerCase().includes(lowerCaseValue) || value === '*') &&
1057
+ filterByType(element)
1058
+ );
1059
+ });
1060
+
1061
+ document.getElementById('found').value = filtered.length;
1062
+
1063
+ const contentDiv = document.getElementById('mainContent');
1064
+ contentDiv.innerHTML = '';
1065
+
1066
+ filtered.forEach((element, index) => {
1067
+ const card = document.createElement('div');
1068
+ card.className = 'element';
1069
+ card.innerHTML = `
1070
+ <div class="modelName" title="${escapeHtml(element.key)}">${element.key}</div>
1071
+
1072
+ <div class="imageContainer">
1073
+ <img src="${element.imageUrl ?? unknownImage}" alt="${escapeHtml(element.key)}" />
1074
+ </div>
1075
+ `;
1076
+
1077
+ card.addEventListener('click', () => openModal(element));
1078
+ contentDiv.appendChild(card);
1079
+ });
1080
+ }
1081
+
1082
+ // Override the searchModels function
1083
+ window.searchModels = searchModelsModern;
1084
+
1085
+ searchModels(getCurrentSearchValue());
1086
+ </script>
1087
+ </body>
1088
+ </html>