malcolmrey commited on
Commit
4844f3f
·
verified ·
1 Parent(s): 06fca79

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +1378 -335
index.html CHANGED
@@ -1,335 +1,1378 @@
1
- <!DOCTYPE html>
2
- <head>
3
- <script type="text/javascript" src="data-civitai.js"></script>
4
- <style>
5
- body {
6
- color: whitesmoke;
7
- background-color: rgb(13, 46, 11);
8
- }
9
-
10
- .mainContent {
11
- position: absolute;
12
- margin-top: 20px;
13
- display: flex;
14
- justify-content: center;
15
- flex-wrap: wrap;
16
- }
17
-
18
- .searchBox {
19
- margin-top: 15px;
20
- margin-left: 20px;
21
- }
22
-
23
- .found {
24
- width: 30px;
25
- }
26
-
27
- .element {
28
- padding: 0;
29
- margin: 3px;
30
- border: 1px solid rgb(5, 90, 0);
31
- display: inline-block;
32
- position: relative;
33
- width: 192px;
34
- height: 283px;
35
- }
36
-
37
- .element .modelType {
38
- position: absolute;
39
- padding: 2px;
40
- padding-left: 4px;
41
- padding-right: 4px;
42
- right: 0;
43
- background-color: rgba(0, 0, 0, 0.5);
44
- bottom: 22px;
45
- }
46
-
47
- .element .modelName {
48
- background-color: rgba(50, 217, 8, 0.15);
49
- text-overflow: ellipsis;
50
- overflow: hidden;
51
- white-space: nowrap;
52
- padding-left: 2px;
53
- }
54
-
55
- .element .modelCreator {
56
- position: absolute;
57
- background-color: rgba(0, 0, 0, 0.5);
58
- padding: 2px;
59
- padding-left: 4px;
60
- padding-right: 4px;
61
- bottom: 22px;
62
- }
63
-
64
- .statsBox {
65
- position: relative;
66
- top: -26px;
67
- height: 22px;
68
- background-color: rgba(0, 0, 0, 0.5);
69
- }
70
-
71
- .statsBox .statsDownloadCount {
72
- position: absolute;
73
- top: 0;
74
- left: 0;
75
- }
76
-
77
- .statsBox .statsFavoriteCount {
78
- position: absolute;
79
- top: 0;
80
- left: 52px;
81
- }
82
-
83
- .statsBox .statsCommentCount {
84
- position: absolute;
85
- top: 0;
86
- left: 105px;
87
- }
88
-
89
- .statsBox .statsRating {
90
- position: absolute;
91
- top: 0;
92
- left: 141px;
93
- }
94
- </style>
95
- </head>
96
- <body>
97
- <input
98
- class="searchBox"
99
- id="search"
100
- type="text"
101
- onkeyup="javascript:searchModels(this.value);"
102
- placeholder="search"
103
- />
104
- <button
105
- onclick="javascript:clearCurrentSearchValue(); javascript:searchModels(getCurrentSearchValue())"
106
- >
107
- Clear
108
- </button>
109
-
110
- Found: <input class="found" id="found" type="text" readonly />
111
-
112
- <label>
113
- <input
114
- id="groupByType"
115
- type="checkbox"
116
- onclick="javascript:searchModels(getCurrentSearchValue());"
117
- />
118
- Group
119
- </label>
120
-
121
- <label>
122
- <input
123
- id="selectedLora"
124
- type="checkbox"
125
- onclick="javascript:regenerateData(); searchModels(getCurrentSearchValue());"
126
- checked
127
- />
128
- LoRA
129
- </label>
130
-
131
- <label>
132
- <input
133
- id="selectedLocon"
134
- type="checkbox"
135
- onclick="javascript:regenerateData(); searchModels(getCurrentSearchValue());"
136
- checked
137
- />
138
- LoCon
139
- </label>
140
-
141
- <label>
142
- <input
143
- id="selectedEmbedding"
144
- type="checkbox"
145
- onclick="javascript:regenerateData(); searchModels(getCurrentSearchValue());"
146
- checked
147
- />
148
- Embedding
149
- </label>
150
-
151
- <label>
152
- <input
153
- id="selectedCheckpoint"
154
- type="checkbox"
155
- onclick="javascript:regenerateData(); searchModels(getCurrentSearchValue());"
156
- />
157
- Checkpoint
158
- </label>
159
-
160
- <label>
161
- <input
162
- id="selectedFlux"
163
- type="checkbox"
164
- onclick="javascript:regenerateData(); searchModels(getCurrentSearchValue());"
165
- />
166
- Flux
167
- </label>
168
-
169
- <label>
170
- <input
171
- id="selectedWan"
172
- type="checkbox"
173
- onclick="javascript:regenerateData(); searchModels(getCurrentSearchValue());"
174
- />
175
- WAN
176
- </label>
177
-
178
- <div id="mainContent" class="mainContent"></div>
179
-
180
- <script type="text/javascript">
181
- let allElements = [
182
- ...models.loras,
183
- ...models.checkpoints,
184
- ...models.lycorises,
185
- ...models.embeddings,
186
- ...models.fluxes,
187
- ...models.wans,
188
- ].sort(compare);
189
- let allElementsGroupped = [
190
- ...models.loras.sort(compare),
191
- ...models.checkpoints.sort(compare),
192
- ...models.lycorises.sort(compare),
193
- ...models.embeddings.sort(compare),
194
- ...models.fluxes.sort(compare),
195
- ...models.wans.sort(compare),
196
- ];
197
- const creator = 'malcolmrey';
198
-
199
- function regenerateData() {
200
- allElements = [
201
- ...(document.getElementById('selectedLora').checked
202
- ? models.loras
203
- : []),
204
- ...(document.getElementById('selectedCheckpoint').checked
205
- ? models.checkpoints
206
- : []),
207
- ...(document.getElementById('selectedLocon').checked
208
- ? models.lycorises
209
- : []),
210
- ...(document.getElementById('selectedEmbedding').checked
211
- ? models.embeddings
212
- : []),
213
- ...(document.getElementById('selectedFlux').checked
214
- ? models.fluxes
215
- : []),
216
- ...(document.getElementById('selectedWan').checked
217
- ? models.wans
218
- : []),
219
- ].sort(compare);
220
-
221
- allElementsGroupped = [
222
- ...(document.getElementById('selectedLora').checked
223
- ? models.loras
224
- : []
225
- ).sort(compare),
226
- ...(document.getElementById('selectedCheckpoint').checked
227
- ? models.checkpoints
228
- : []
229
- ).sort(compare),
230
- ...(document.getElementById('selectedLocon').checked
231
- ? models.lycorises
232
- : []
233
- ).sort(compare),
234
- ...(document.getElementById('selectedEmbedding').checked
235
- ? models.embeddings
236
- : []
237
- ).sort(compare),
238
- ...(document.getElementById('selectedFlux').checked
239
- ? models.fluxes
240
- : []
241
- ).sort(compare),
242
- ...(document.getElementById('selectedWan').checked
243
- ? models.wans
244
- : []
245
- ).sort(compare),
246
- ];
247
- }
248
-
249
- function clearCurrentSearchValue() {
250
- document.getElementById('search').value = '';
251
- }
252
-
253
- function getCurrentSearchValue() {
254
- return document.getElementById('search').value;
255
- }
256
-
257
- function escapeHtml(unsafe) {
258
- return unsafe
259
- .replace(/&/g, '&amp;')
260
- .replace(/</g, '&lt;')
261
- .replace(/>/g, '&gt;')
262
- .replace(/"/g, '&quot;')
263
- .replace(/'/g, '&#039;');
264
- }
265
-
266
- function formatNumber(num) {
267
- return Math.abs(num) > 999
268
- ? Math.sign(num) * (Math.abs(num) / 1000).toFixed(1) + 'k'
269
- : Math.sign(num) * Math.abs(num);
270
- }
271
-
272
- function compare(a, b) {
273
- if (a.name < b.name) {
274
- return -1;
275
- }
276
- if (a.name > b.name) {
277
- return 1;
278
- }
279
- return 0;
280
- }
281
-
282
- function searchModels(value) {
283
- const lowerCaseValue = value.toLowerCase();
284
-
285
- const elementsToFilter = document.getElementById('groupByType').checked
286
- ? allElementsGroupped
287
- : allElements;
288
-
289
- const filtered = elementsToFilter.filter((element) => {
290
- return (
291
- element.name.toLowerCase().includes(lowerCaseValue) || value === '*'
292
- );
293
- });
294
-
295
- document.getElementById('found').value = filtered.length;
296
-
297
- let htmlContent = '';
298
- filtered.forEach((element) => {
299
- htmlContent += `<div class="element">
300
- <div class="modelName" title="${escapeHtml(element.name)}">${
301
- element.name
302
- }</div>
303
- <div class="modelType">${
304
- element.type === 'TextualInversion' ? 'Embedding' : element.type
305
- }</div>
306
- <div class="modelCreator" title="${creator}">${creator}</div>
307
- <div><a href='${element.url}' target='_blank'><img src='${
308
- element.imageUrl
309
- }' height="264" width="192" title='[${element.id}] ${
310
- element.url
311
- }'/></a></div>
312
- <div class="statsBox">
313
- <div class="statsDownloadCount" title="⬇️${
314
- element.stats.downloadCount
315
- }">⬇️${formatNumber(element.stats.downloadCount)}</div>
316
- <div class="statsFavoriteCount" title="❤️${
317
- element.stats.favoriteCount
318
- }">❤️${element.stats.favoriteCount}</div>
319
- <div class="statsCommentCount" title="📃${
320
- element.stats.commentCount
321
- }">📃${element.stats.commentCount}</div>
322
- <div class="statsRating" title="📈${
323
- element.stats.ratingCount
324
- } / ⭐${element.stats.rating}">⭐${element.stats.rating}</div>
325
- </div>
326
- </div>`;
327
- });
328
-
329
- const contentDiv = document.getElementById('mainContent');
330
- contentDiv.innerHTML = htmlContent;
331
- }
332
-
333
- searchModels(getCurrentSearchValue());
334
- </script>
335
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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="data-filenames.js"></script>
10
+ <script type="text/javascript" src="data-hf-images.js"></script>
11
+ <script type="text/javascript" src="comparator.js"></script>
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
15
+ <style>
16
+ * {
17
+ margin: 0;
18
+ padding: 0;
19
+ box-sizing: border-box;
20
+ }
21
+
22
+ body {
23
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
24
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
25
+ color: #f1f5f9;
26
+ min-height: 100vh;
27
+ padding: 20px;
28
+ }
29
+
30
+ a {
31
+ text-decoration: none;
32
+ color: #60a5fa;
33
+ transition: color 0.2s;
34
+ }
35
+
36
+ a:hover {
37
+ color: #93c5fd;
38
+ }
39
+
40
+ .header {
41
+ max-width: 1400px;
42
+ margin: 0 auto 30px;
43
+ background: rgba(30, 41, 59, 0.7);
44
+ backdrop-filter: blur(10px);
45
+ border-radius: 16px;
46
+ padding: 24px;
47
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
48
+ border: 1px solid rgba(148, 163, 184, 0.1);
49
+ }
50
+
51
+ .header h1 {
52
+ font-size: 28px;
53
+ font-weight: 700;
54
+ margin-bottom: 12px;
55
+ background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 100%);
56
+ -webkit-background-clip: text;
57
+ -webkit-text-fill-color: transparent;
58
+ background-clip: text;
59
+ text-align: center;
60
+ }
61
+
62
+ .profile-links {
63
+ display: flex;
64
+ flex-wrap: wrap;
65
+ gap: 16px;
66
+ justify-content: center;
67
+ align-items: center;
68
+ margin-bottom: 12px;
69
+ padding: 12px;
70
+ background: rgba(15, 23, 42, 0.4);
71
+ border-radius: 8px;
72
+ }
73
+
74
+ .profile-links a {
75
+ display: inline-flex;
76
+ align-items: center;
77
+ gap: 6px;
78
+ padding: 6px 12px;
79
+ background: rgba(96, 165, 250, 0.1);
80
+ border-radius: 6px;
81
+ font-size: 14px;
82
+ transition: all 0.2s;
83
+ }
84
+
85
+ .profile-links a:hover {
86
+ background: rgba(96, 165, 250, 0.2);
87
+ transform: translateY(-1px);
88
+ }
89
+
90
+ .last-update {
91
+ text-align: center;
92
+ font-size: 13px;
93
+ color: #94a3b8;
94
+ margin-bottom: 16px;
95
+ }
96
+
97
+ .controls {
98
+ display: flex;
99
+ flex-wrap: wrap;
100
+ gap: 12px;
101
+ align-items: center;
102
+ justify-content: center;
103
+ }
104
+
105
+ .search-container {
106
+ flex: 1 1 auto;
107
+ min-width: 300px;
108
+ max-width: 600px;
109
+ position: relative;
110
+ }
111
+
112
+ .search-container input[type="text"] {
113
+ width: 100%;
114
+ padding: 12px 16px;
115
+ padding-right: 100px;
116
+ background: rgba(15, 23, 42, 0.6);
117
+ border: 2px solid rgba(148, 163, 184, 0.2);
118
+ border-radius: 12px;
119
+ color: #f1f5f9;
120
+ font-size: 15px;
121
+ font-family: inherit;
122
+ transition: all 0.3s;
123
+ }
124
+
125
+ .search-container input[type="text"]:focus {
126
+ outline: none;
127
+ border-color: #60a5fa;
128
+ background: rgba(15, 23, 42, 0.8);
129
+ box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.1);
130
+ }
131
+
132
+ .search-container input[type="text"]::placeholder {
133
+ color: #64748b;
134
+ }
135
+
136
+ .clear-btn {
137
+ position: absolute;
138
+ right: 8px;
139
+ top: 50%;
140
+ transform: translateY(-50%);
141
+ padding: 6px 14px;
142
+ background: rgba(239, 68, 68, 0.2);
143
+ border: 1px solid rgba(239, 68, 68, 0.3);
144
+ border-radius: 8px;
145
+ color: #fca5a5;
146
+ font-size: 13px;
147
+ font-weight: 500;
148
+ cursor: pointer;
149
+ transition: all 0.2s;
150
+ }
151
+
152
+ .clear-btn:hover {
153
+ background: rgba(239, 68, 68, 0.3);
154
+ color: #fecaca;
155
+ }
156
+
157
+ .mode-select {
158
+ padding: 12px 40px 12px 16px;
159
+ background: rgba(15, 23, 42, 0.6);
160
+ border: 2px solid rgba(148, 163, 184, 0.2);
161
+ border-radius: 12px;
162
+ color: #f1f5f9;
163
+ font-size: 15px;
164
+ font-family: inherit;
165
+ cursor: pointer;
166
+ transition: all 0.3s;
167
+ font-weight: 500;
168
+ height: 48px;
169
+ line-height: 20px;
170
+ min-width: 150px;
171
+ flex-shrink: 0;
172
+ appearance: none;
173
+ -webkit-appearance: none;
174
+ -moz-appearance: none;
175
+ 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");
176
+ background-repeat: no-repeat;
177
+ background-position: right 12px center;
178
+ background-size: 20px;
179
+ box-shadow: none;
180
+ outline: none;
181
+ }
182
+
183
+ .mode-select::-ms-expand {
184
+ display: none;
185
+ }
186
+
187
+ .mode-select:focus {
188
+ outline: none;
189
+ border-color: #60a5fa;
190
+ box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.1);
191
+ }
192
+
193
+ .mode-select option {
194
+ background: #0f172a;
195
+ color: #e2e8f0;
196
+ padding: 16px 20px;
197
+ font-size: 15px;
198
+ font-weight: 500;
199
+ border: none;
200
+ outline: none;
201
+ }
202
+
203
+ .mode-select option:hover {
204
+ background: linear-gradient(90deg, rgba(96, 165, 250, 0.15) 0%, rgba(96, 165, 250, 0.05) 100%);
205
+ color: #60a5fa;
206
+ font-weight: 600;
207
+ }
208
+
209
+ .mode-select option:checked,
210
+ .mode-select option:focus {
211
+ background: linear-gradient(90deg, rgba(96, 165, 250, 0.25) 0%, rgba(96, 165, 250, 0.15) 100%);
212
+ color: #93c5fd;
213
+ font-weight: 600;
214
+ box-shadow: none;
215
+ outline: none;
216
+ }
217
+
218
+ .checkbox-group {
219
+ display: flex;
220
+ flex-wrap: wrap;
221
+ gap: 8px;
222
+ align-items: center;
223
+ }
224
+
225
+ .checkbox-label {
226
+ display: flex;
227
+ align-items: center;
228
+ gap: 8px;
229
+ padding: 10px 16px;
230
+ background: rgba(15, 23, 42, 0.4);
231
+ border: 2px solid rgba(148, 163, 184, 0.2);
232
+ border-radius: 10px;
233
+ cursor: pointer;
234
+ transition: all 0.2s;
235
+ font-size: 14px;
236
+ font-weight: 500;
237
+ user-select: none;
238
+ }
239
+
240
+ .checkbox-label:hover {
241
+ background: rgba(15, 23, 42, 0.6);
242
+ border-color: rgba(148, 163, 184, 0.3);
243
+ }
244
+
245
+ .checkbox-label input[type="checkbox"] {
246
+ width: 18px;
247
+ height: 18px;
248
+ cursor: pointer;
249
+ accent-color: #60a5fa;
250
+ }
251
+
252
+ .checkbox-label input[type="checkbox"]:checked + span {
253
+ color: #60a5fa;
254
+ }
255
+
256
+ .stats-bar {
257
+ display: flex;
258
+ align-items: center;
259
+ gap: 12px;
260
+ padding: 12px 20px;
261
+ background: rgba(15, 23, 42, 0.6);
262
+ border-radius: 10px;
263
+ font-size: 14px;
264
+ font-weight: 600;
265
+ flex-shrink: 0;
266
+ }
267
+
268
+ .stats-bar label {
269
+ color: #94a3b8;
270
+ }
271
+
272
+ .stats-bar input {
273
+ width: 70px;
274
+ padding: 6px 10px;
275
+ background: rgba(30, 41, 59, 0.8);
276
+ border: 1px solid rgba(148, 163, 184, 0.2);
277
+ border-radius: 6px;
278
+ color: #60a5fa;
279
+ text-align: center;
280
+ font-weight: 700;
281
+ font-size: 16px;
282
+ }
283
+
284
+ .mainContent {
285
+ max-width: 1400px;
286
+ margin: 0 auto;
287
+ display: grid;
288
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
289
+ gap: 20px;
290
+ padding: 0 4px;
291
+ }
292
+
293
+ .element {
294
+ background: rgba(30, 41, 59, 0.6);
295
+ backdrop-filter: blur(10px);
296
+ border-radius: 16px;
297
+ overflow: hidden;
298
+ border: 1px solid rgba(148, 163, 184, 0.1);
299
+ transition: all 0.3s;
300
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
301
+ display: flex;
302
+ flex-direction: column;
303
+ cursor: pointer;
304
+ }
305
+
306
+ .element:hover {
307
+ transform: translateY(-4px);
308
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
309
+ border-color: rgba(96, 165, 250, 0.3);
310
+ }
311
+
312
+ .element .modelName {
313
+ padding: 14px 16px;
314
+ background: rgba(15, 23, 42, 0.8);
315
+ font-weight: 600;
316
+ font-size: 14px;
317
+ text-overflow: ellipsis;
318
+ overflow: hidden;
319
+ white-space: nowrap;
320
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
321
+ color: #e2e8f0;
322
+ }
323
+
324
+ .element .imageContainer {
325
+ position: relative;
326
+ width: 100%;
327
+ padding-top: 137.5%; /* 192/264 aspect ratio */
328
+ overflow: hidden;
329
+ background: rgba(15, 23, 42, 0.6);
330
+ }
331
+
332
+ .element .imageContainer img {
333
+ position: absolute;
334
+ top: 0;
335
+ left: 0;
336
+ width: 100%;
337
+ height: 100%;
338
+ object-fit: cover;
339
+ transition: transform 0.3s;
340
+ }
341
+
342
+ .element:hover .imageContainer img {
343
+ transform: scale(1.05);
344
+ }
345
+
346
+ .statsBox {
347
+ padding: 16px;
348
+ background: rgba(15, 23, 42, 0.8);
349
+ font-size: 13px;
350
+ line-height: 1.8;
351
+ flex: 1;
352
+ display: none; /* Hidden in main view */
353
+ }
354
+
355
+ .statsBox .stat-row {
356
+ display: flex;
357
+ align-items: center;
358
+ gap: 8px;
359
+ margin-bottom: 6px;
360
+ }
361
+
362
+ .statsBox .stat-row:last-child {
363
+ margin-bottom: 0;
364
+ }
365
+
366
+ .statsBox .stat-label {
367
+ font-weight: 600;
368
+ color: #94a3b8;
369
+ min-width: 75px;
370
+ }
371
+
372
+ .statsBox .stat-value {
373
+ display: flex;
374
+ align-items: center;
375
+ gap: 6px;
376
+ }
377
+
378
+ /* Modal Styles */
379
+ .modal-overlay {
380
+ display: none;
381
+ position: fixed;
382
+ top: 0;
383
+ left: 0;
384
+ right: 0;
385
+ bottom: 0;
386
+ background: rgba(0, 0, 0, 0.8);
387
+ backdrop-filter: blur(8px);
388
+ z-index: 1000;
389
+ align-items: center;
390
+ justify-content: center;
391
+ padding: 20px;
392
+ animation: fadeIn 0.2s ease-out;
393
+ }
394
+
395
+ .modal-overlay.active {
396
+ display: flex;
397
+ }
398
+
399
+ .modal-content {
400
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
401
+ border-radius: 20px;
402
+ max-width: 1100px;
403
+ width: 100%;
404
+ max-height: 90vh;
405
+ overflow: hidden;
406
+ position: relative;
407
+ border: 2px solid rgba(148, 163, 184, 0.2);
408
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
409
+ animation: slideUp 0.3s ease-out;
410
+ display: flex;
411
+ flex-direction: column;
412
+ }
413
+
414
+ @keyframes slideUp {
415
+ from {
416
+ opacity: 0;
417
+ transform: translateY(30px);
418
+ }
419
+ to {
420
+ opacity: 1;
421
+ transform: translateY(0);
422
+ }
423
+ }
424
+
425
+ .modal-header {
426
+ padding: 24px;
427
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
428
+ display: flex;
429
+ align-items: center;
430
+ justify-content: space-between;
431
+ position: sticky;
432
+ top: 0;
433
+ background: rgba(30, 41, 59, 0.95);
434
+ backdrop-filter: blur(10px);
435
+ z-index: 10;
436
+ }
437
+
438
+ .modal-title {
439
+ font-size: 24px;
440
+ font-weight: 700;
441
+ color: #f1f5f9;
442
+ margin: 0;
443
+ }
444
+
445
+ .modal-close {
446
+ width: 36px;
447
+ height: 36px;
448
+ border-radius: 10px;
449
+ background: rgba(239, 68, 68, 0.2);
450
+ border: 1px solid rgba(239, 68, 68, 0.3);
451
+ color: #fca5a5;
452
+ font-size: 20px;
453
+ cursor: pointer;
454
+ display: flex;
455
+ align-items: center;
456
+ justify-content: center;
457
+ transition: all 0.2s;
458
+ font-weight: 700;
459
+ }
460
+
461
+ .modal-close:hover {
462
+ background: rgba(239, 68, 68, 0.3);
463
+ color: #fecaca;
464
+ transform: rotate(90deg);
465
+ }
466
+
467
+ .modal-body {
468
+ display: flex;
469
+ flex: 1;
470
+ overflow: hidden;
471
+ gap: 0;
472
+ }
473
+
474
+ .modal-left {
475
+ flex: 0 0 45%;
476
+ display: flex;
477
+ align-items: center;
478
+ justify-content: center;
479
+ background: rgba(15, 23, 42, 0.6);
480
+ padding: 24px;
481
+ }
482
+
483
+ .modal-image-container {
484
+ width: 100%;
485
+ border-radius: 12px;
486
+ overflow: hidden;
487
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
488
+ max-height: 100%;
489
+ }
490
+
491
+ .modal-image-container img {
492
+ width: 100%;
493
+ height: auto;
494
+ display: block;
495
+ }
496
+
497
+ .modal-right {
498
+ flex: 1;
499
+ padding: 24px;
500
+ overflow-y: auto;
501
+ display: flex;
502
+ flex-direction: column;
503
+ gap: 16px;
504
+ }
505
+
506
+ .modal-stats {
507
+ display: grid;
508
+ gap: 12px;
509
+ }
510
+
511
+ .modal-stat-row {
512
+ display: flex;
513
+ align-items: center;
514
+ justify-content: space-between;
515
+ padding: 16px 20px;
516
+ background: rgba(15, 23, 42, 0.6);
517
+ border-radius: 12px;
518
+ border: 1px solid rgba(148, 163, 184, 0.1);
519
+ transition: all 0.2s;
520
+ }
521
+
522
+ .modal-stat-row:hover {
523
+ background: rgba(15, 23, 42, 0.8);
524
+ border-color: rgba(148, 163, 184, 0.2);
525
+ }
526
+
527
+ .modal-stat-label {
528
+ font-weight: 600;
529
+ font-size: 15px;
530
+ color: #cbd5e1;
531
+ display: flex;
532
+ align-items: center;
533
+ gap: 8px;
534
+ }
535
+
536
+ .modal-stat-value {
537
+ display: flex;
538
+ align-items: flex-start;
539
+ gap: 12px;
540
+ flex-wrap: wrap;
541
+ }
542
+
543
+ .modal-hf-links-container {
544
+ display: flex;
545
+ flex-direction: column;
546
+ gap: 6px;
547
+ margin-left: 8px;
548
+ }
549
+
550
+ .modal-hf-link {
551
+ padding: 6px 12px;
552
+ background: rgba(255, 107, 0, 0.15);
553
+ border: 1px solid rgba(255, 107, 0, 0.3);
554
+ border-radius: 8px;
555
+ font-size: 12px;
556
+ font-weight: 600;
557
+ color: #ff9d5c;
558
+ transition: all 0.2s;
559
+ text-decoration: none;
560
+ display: inline-flex;
561
+ align-items: center;
562
+ gap: 6px;
563
+ white-space: nowrap;
564
+ }
565
+
566
+ .modal-hf-link:hover {
567
+ background: rgba(255, 107, 0, 0.25);
568
+ border-color: rgba(255, 107, 0, 0.5);
569
+ transform: translateY(-1px);
570
+ color: #ffb380;
571
+ }
572
+
573
+ .modal-hf-placeholder {
574
+ padding: 6px 12px;
575
+ background: rgba(100, 116, 139, 0.15);
576
+ border: 1px solid rgba(100, 116, 139, 0.3);
577
+ border-radius: 8px;
578
+ font-size: 12px;
579
+ font-weight: 600;
580
+ color: #94a3b8;
581
+ display: inline-flex;
582
+ align-items: center;
583
+ gap: 6px;
584
+ opacity: 0.5;
585
+ }
586
+
587
+ .hf-logo {
588
+ width: 16px;
589
+ height: 16px;
590
+ display: inline-block;
591
+ }
592
+
593
+ @media (max-width: 900px) {
594
+ .modal-body {
595
+ flex-direction: column;
596
+ }
597
+
598
+ .modal-left {
599
+ flex: 0 0 auto;
600
+ max-height: 400px;
601
+ }
602
+
603
+ .modal-right {
604
+ flex: 1;
605
+ }
606
+ }
607
+
608
+ .status-icon {
609
+ font-size: 16px;
610
+ display: inline-block;
611
+ }
612
+
613
+ .status-available {
614
+ color: #4ade80;
615
+ }
616
+
617
+ .status-unavailable {
618
+ color: #f87171;
619
+ }
620
+
621
+ .status-unknown {
622
+ color: #fbbf24;
623
+ }
624
+
625
+ .hf-link {
626
+ padding: 2px 8px;
627
+ background: rgba(96, 165, 250, 0.1);
628
+ border: 1px solid rgba(96, 165, 250, 0.2);
629
+ border-radius: 6px;
630
+ font-size: 11px;
631
+ font-weight: 600;
632
+ color: #60a5fa;
633
+ transition: all 0.2s;
634
+ display: inline-block;
635
+ }
636
+
637
+ .hf-link:hover {
638
+ background: rgba(96, 165, 250, 0.2);
639
+ border-color: rgba(96, 165, 250, 0.4);
640
+ }
641
+
642
+ @media (max-width: 768px) {
643
+ .controls {
644
+ flex-direction: column;
645
+ align-items: stretch;
646
+ }
647
+
648
+ .checkbox-group {
649
+ justify-content: center;
650
+ }
651
+
652
+ .mainContent {
653
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
654
+ gap: 16px;
655
+ }
656
+ }
657
+
658
+ /* Loading animation */
659
+ @keyframes fadeIn {
660
+ from {
661
+ opacity: 0;
662
+ transform: translateY(20px);
663
+ }
664
+ to {
665
+ opacity: 1;
666
+ transform: translateY(0);
667
+ }
668
+ }
669
+
670
+ .element {
671
+ animation: fadeIn 0.4s ease-out;
672
+ }
673
+
674
+ /* Scrollbar styling */
675
+ ::-webkit-scrollbar {
676
+ width: 12px;
677
+ }
678
+
679
+ ::-webkit-scrollbar-track {
680
+ background: rgba(15, 23, 42, 0.5);
681
+ }
682
+
683
+ ::-webkit-scrollbar-thumb {
684
+ background: rgba(148, 163, 184, 0.3);
685
+ border-radius: 6px;
686
+ }
687
+
688
+ ::-webkit-scrollbar-thumb:hover {
689
+ background: rgba(148, 163, 184, 0.5);
690
+ }
691
+
692
+ /* Image viewer with navigation */
693
+ .image-viewer {
694
+ position: relative;
695
+ display: flex;
696
+ align-items: center;
697
+ justify-content: center;
698
+ width: 100%;
699
+ height: 100%;
700
+ }
701
+
702
+ .modal-image {
703
+ max-width: 100%;
704
+ max-height: 70vh;
705
+ object-fit: contain;
706
+ border-radius: 8px;
707
+ }
708
+
709
+ .image-nav-arrow {
710
+ position: absolute;
711
+ top: 50%;
712
+ transform: translateY(-50%);
713
+ background: rgba(15, 23, 42, 0.8);
714
+ border: 2px solid rgba(96, 165, 250, 0.3);
715
+ color: #60a5fa;
716
+ font-size: 48px;
717
+ font-weight: bold;
718
+ width: 60px;
719
+ height: 60px;
720
+ border-radius: 50%;
721
+ cursor: pointer;
722
+ display: flex;
723
+ align-items: center;
724
+ justify-content: center;
725
+ transition: all 0.3s ease;
726
+ z-index: 10;
727
+ line-height: 1;
728
+ padding: 0;
729
+ user-select: none;
730
+ }
731
+
732
+ .image-nav-arrow:hover {
733
+ background: rgba(96, 165, 250, 0.3);
734
+ border-color: #60a5fa;
735
+ color: #93c5fd;
736
+ transform: translateY(-50%) scale(1.1);
737
+ box-shadow: 0 0 20px rgba(96, 165, 250, 0.5);
738
+ }
739
+
740
+ .image-nav-arrow:active {
741
+ transform: translateY(-50%) scale(0.95);
742
+ }
743
+
744
+ .image-nav-prev {
745
+ left: 10px;
746
+ }
747
+
748
+ .image-nav-next {
749
+ right: 10px;
750
+ }
751
+
752
+ .image-info {
753
+ display: flex;
754
+ justify-content: space-between;
755
+ align-items: center;
756
+ margin-top: 12px;
757
+ padding: 8px 12px;
758
+ background: rgba(15, 23, 42, 0.6);
759
+ border-radius: 8px;
760
+ gap: 12px;
761
+ }
762
+
763
+ .image-counter {
764
+ font-size: 14px;
765
+ font-weight: 500;
766
+ color: #94a3b8;
767
+ }
768
+
769
+ .framework-label {
770
+ font-size: 13px;
771
+ font-weight: 600;
772
+ padding: 4px 12px;
773
+ border-radius: 12px;
774
+ background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 100%);
775
+ color: #fff;
776
+ text-transform: uppercase;
777
+ letter-spacing: 0.5px;
778
+ box-shadow: 0 2px 8px rgba(96, 165, 250, 0.3);
779
+ }
780
+
781
+ .framework-label[data-framework="WAN"] {
782
+ background: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%);
783
+ }
784
+
785
+ .framework-label[data-framework="LyCORIS"] {
786
+ background: linear-gradient(135deg, #10b981 0%, #14b8a6 100%);
787
+ }
788
+
789
+ .framework-label[data-framework="Lora"] {
790
+ background: linear-gradient(135deg, #3b82f6 0%, #6366f1 100%);
791
+ }
792
+
793
+ .framework-label[data-framework="Embedding"] {
794
+ background: linear-gradient(135deg, #f97316 0%, #fbbf24 100%);
795
+ }
796
+
797
+ .framework-label[data-framework="Qwen"] {
798
+ background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%);
799
+ }
800
+
801
+ .framework-label[data-framework="Flux"] {
802
+ background: linear-gradient(135deg, #a855f7 0%, #d946ef 100%);
803
+ }
804
+
805
+ .framework-label[data-framework="SDXL"] {
806
+ background: linear-gradient(135deg, #84cc16 0%, #22c55e 100%);
807
+ }
808
+
809
+ .framework-label[data-framework="ZImage"] {
810
+ background: linear-gradient(135deg, #ec4899 0%, #f97316 100%);
811
+ }
812
+ </style>
813
+ </head>
814
+ <body>
815
+ <div class="header">
816
+ <h1>🎨 Mal's Models</h1>
817
+
818
+ <div class="profile-links">
819
+ <a href="https://civitai.com/user/malcolmrey" target="_blank">🎨 CivitAI</a>
820
+ <a href="https://huggingface.com/malcolmrey" target="_blank">🤗 HuggingFace</a>
821
+ <a href="https://buymeacoffee.com/malcolmrey" target="_blank">☕ BuyMeACoffee</a>
822
+ <a href="https://reddit.com/r/malcolmrey" target="_blank">💬 Reddit</a>
823
+ </div>
824
+
825
+ <div class="last-update">Last updated: 2025.11.10</div>
826
+
827
+ <div class="controls">
828
+ <div class="search-container">
829
+ <input
830
+ id="search"
831
+ type="text"
832
+ onkeyup="javascript:searchModels(this.value);"
833
+ placeholder="Search models..."
834
+ />
835
+ <button
836
+ class="clear-btn"
837
+ onclick="javascript:clearCurrentSearchValue(); javascript:searchModels(getCurrentSearchValue())"
838
+ >
839
+ Clear
840
+ </button>
841
+ </div>
842
+
843
+ <select
844
+ class="mode-select"
845
+ id="searchMode"
846
+ onchange="javascript:searchModels(getCurrentSearchValue());"
847
+ >
848
+ <option value="available">Trained</option>
849
+ <option value="missing">Not trained</option>
850
+ </select>
851
+
852
+ <div class="stats-bar">
853
+ <label>Found:</label>
854
+ <input id="found" type="text" readonly />
855
+ </div>
856
+ </div>
857
+
858
+ <div class="controls" style="margin-top: 16px;">
859
+ <div class="checkbox-group">
860
+ <label class="checkbox-label">
861
+ <input
862
+ id="selectedLora"
863
+ type="checkbox"
864
+ onclick="javascript:searchModels(getCurrentSearchValue());"
865
+ />
866
+ <span>SD LoRA</span>
867
+ </label>
868
+
869
+ <label class="checkbox-label">
870
+ <input
871
+ id="selectedLocon"
872
+ type="checkbox"
873
+ onclick="javascript:searchModels(getCurrentSearchValue());"
874
+ />
875
+ <span>SD LoCon</span>
876
+ </label>
877
+
878
+ <label class="checkbox-label">
879
+ <input
880
+ id="selectedEmbedding"
881
+ type="checkbox"
882
+ onclick="javascript:searchModels(getCurrentSearchValue());"
883
+ />
884
+ <span>SD Embedding</span>
885
+ </label>
886
+
887
+ <label class="checkbox-label">
888
+ <input
889
+ id="selectedFlux"
890
+ type="checkbox"
891
+ onclick="javascript:searchModels(getCurrentSearchValue());"
892
+ />
893
+ <span>Flux</span>
894
+ </label>
895
+
896
+ <label class="checkbox-label">
897
+ <input
898
+ id="selectedWan"
899
+ type="checkbox"
900
+ onclick="javascript:searchModels(getCurrentSearchValue());"
901
+ />
902
+ <span>WAN</span>
903
+ </label>
904
+
905
+ <label class="checkbox-label">
906
+ <input
907
+ id="selectedSdxl"
908
+ type="checkbox"
909
+ onclick="javascript:searchModels(getCurrentSearchValue());"
910
+ />
911
+ <span>SDXL</span>
912
+ </label>
913
+
914
+ <label class="checkbox-label">
915
+ <input
916
+ id="selectedZimage"
917
+ type="checkbox"
918
+ onclick="javascript:searchModels(getCurrentSearchValue());"
919
+ />
920
+ <span>ZImage</span>
921
+ </label>
922
+
923
+ <label class="checkbox-label">
924
+ <input
925
+ id="selectedQwen"
926
+ type="checkbox"
927
+ onclick="javascript:searchModels(getCurrentSearchValue());"
928
+ />
929
+ <span>Qwen</span>
930
+ </label>
931
+ </div>
932
+ </div>
933
+ </div>
934
+
935
+ <div id="mainContent" class="mainContent"></div>
936
+
937
+ <!-- Modal -->
938
+ <div id="modalOverlay" class="modal-overlay" onclick="closeModalOnOverlay(event)">
939
+ <div class="modal-content" onclick="event.stopPropagation()">
940
+ <div class="modal-header">
941
+ <h2 id="modalTitle" class="modal-title"></h2>
942
+ <button class="modal-close" onclick="closeModal()" aria-label="Close">×</button>
943
+ </div>
944
+ <div class="modal-body">
945
+ <div class="modal-left">
946
+ <div class="modal-image-container">
947
+ <div class="image-viewer">
948
+ <button id="prevImageBtn" class="image-nav-arrow image-nav-prev" onclick="previousImage()" aria-label="Previous image" style="display:none;">‹</button>
949
+ <img id="modalImage" src="" alt="" class="modal-image" />
950
+ <button id="nextImageBtn" class="image-nav-arrow image-nav-next" onclick="nextImage()" aria-label="Next image" style="display:none;">›</button>
951
+ </div>
952
+ <div id="imageInfo" class="image-info" style="display:none;">
953
+ <div id="imageCounter" class="image-counter"></div>
954
+ <div id="frameworkLabel" class="framework-label"></div>
955
+ </div>
956
+ </div>
957
+ </div>
958
+ <div class="modal-right">
959
+ <div id="modalStats" class="modal-stats"></div>
960
+ </div>
961
+ </div>
962
+ </div>
963
+ </div>
964
+
965
+ <script type="text/javascript">
966
+ function yesNo(value) {
967
+ if (value === undefined) {
968
+ return '<span class="status-icon status-unknown">❓</span>';
969
+ }
970
+ return value
971
+ ? '<span class="status-icon status-available">✅</span>'
972
+ : '<span class="status-icon status-unavailable">❌</span>';
973
+ }
974
+
975
+ function formatHFLink(link, hasHF) {
976
+ if (!link) return '';
977
+ return `<a href="${link}" target="_blank" class="hf-link">HF</a>`;
978
+ }
979
+
980
+ function formatModalHFLink(isAvailable, link) {
981
+ if (isAvailable && link) {
982
+ return `<a href="${link}" target="_blank" class="modal-hf-link">🤗 HuggingFace</a>`;
983
+ } else if (isAvailable) {
984
+ return `<span class="modal-hf-placeholder">🤗 HuggingFace</span>`;
985
+ }
986
+ return '';
987
+ }
988
+
989
+ function formatModalHFLinks(isAvailable, folder, filenames, isWan = false) {
990
+ if (!isAvailable || !filenames || filenames.length === 0) {
991
+ return isAvailable ? `<span class="modal-hf-placeholder">🤗 No files found</span>` : '';
992
+ }
993
+
994
+ // Generate a link for each filename, wrapped in a container for vertical layout
995
+ const links = filenames.map((filename, index) => {
996
+ // For WAN models, add wan2.1/ prefix to the filename in the path
997
+ const filePath = isWan ? `wan2.1/${filename}` : filename;
998
+ const link = `https://huggingface.co/malcolmrey/${folder}/resolve/main/${filePath}?download=true`;
999
+ const versionLabel = filenames.length > 1 ? ` (v${index + 1})` : '';
1000
+ return `<a href="${link}" target="_blank" class="modal-hf-link">🤗 HuggingFace${versionLabel}</a>`;
1001
+ }).join('');
1002
+
1003
+ return `<div class="modal-hf-links-container">${links}</div>`;
1004
+ }
1005
+
1006
+ function openModal(element) {
1007
+ const modal = document.getElementById('modalOverlay');
1008
+ const modalTitle = document.getElementById('modalTitle');
1009
+ const modalImage = document.getElementById('modalImage');
1010
+ const modalStats = document.getElementById('modalStats');
1011
+
1012
+ // Generate HuggingFace links using actual filenames from data-filenames.js
1013
+ const personName = element.key;
1014
+ const filenamesData = window.filenames || {};
1015
+ const personFilenames = filenamesData[personName] || {};
1016
+
1017
+ modalTitle.textContent = element.key;
1018
+ modalImage.src = element.imageUrl ?? unknownImage;
1019
+ modalImage.alt = element.key;
1020
+
1021
+ modalStats.innerHTML = `
1022
+ <div class="modal-stat-row">
1023
+ <span class="modal-stat-label">${yesNo(element.locon)} SD LoCon</span>
1024
+ <span class="modal-stat-value">
1025
+ ${formatModalHFLinks(element.locon, 'lycoris', personFilenames.locon)}
1026
+ </span>
1027
+ </div>
1028
+ <div class="modal-stat-row">
1029
+ <span class="modal-stat-label">${yesNo(element.lora)} SD LoRA</span>
1030
+ <span class="modal-stat-value">
1031
+ ${formatModalHFLinks(element.lora, 'small-loras', personFilenames.lora)}
1032
+ </span>
1033
+ </div>
1034
+ <div class="modal-stat-row">
1035
+ <span class="modal-stat-label">${yesNo(element.embedding)} SD Embedding</span>
1036
+ <span class="modal-stat-value">
1037
+ ${formatModalHFLinks(element.embedding, 'embeddings', personFilenames.embedding)}
1038
+ </span>
1039
+ </div>
1040
+ <div class="modal-stat-row">
1041
+ <span class="modal-stat-label">${yesNo(element.flux)} Flux</span>
1042
+ <span class="modal-stat-value">
1043
+ ${formatModalHFLinks(element.flux, 'flux', personFilenames.flux)}
1044
+ </span>
1045
+ </div>
1046
+ <div class="modal-stat-row">
1047
+ <span class="modal-stat-label">${yesNo(element.wan)} WAN</span>
1048
+ <span class="modal-stat-value">
1049
+ ${formatModalHFLinks(element.wan, 'wan', personFilenames.wan, true)}
1050
+ </span>
1051
+ </div>
1052
+ <div class="modal-stat-row">
1053
+ <span class="modal-stat-label">${yesNo(element.sdxl)} SDXL</span>
1054
+ <span class="modal-stat-value">
1055
+ ${formatModalHFLinks(element.sdxl, 'sdxl', personFilenames.sdxl)}
1056
+ </span>
1057
+ </div>
1058
+ <div class="modal-stat-row">
1059
+ <span class="modal-stat-label">${yesNo(element.zimage)} ZImage</span>
1060
+ <span class="modal-stat-value">
1061
+ ${formatModalHFLinks(element.zimage, 'zimage', personFilenames.zimage)}
1062
+ </span>
1063
+ </div>
1064
+ <div class="modal-stat-row">
1065
+ <span class="modal-stat-label">${yesNo(element.qwen)} Qwen</span>
1066
+ <span class="modal-stat-value">
1067
+ ${formatModalHFLinks(element.qwen, 'qwen', personFilenames.qwen)}
1068
+ </span>
1069
+ </div>
1070
+ `;
1071
+
1072
+ modal.classList.add('active');
1073
+ document.body.style.overflow = 'hidden';
1074
+ }
1075
+
1076
+ function closeModal() {
1077
+ const modal = document.getElementById('modalOverlay');
1078
+ modal.classList.remove('active');
1079
+ document.body.style.overflow = '';
1080
+ }
1081
+
1082
+ function closeModalOnOverlay(event) {
1083
+ if (event.target.id === 'modalOverlay') {
1084
+ closeModal();
1085
+ }
1086
+ }
1087
+
1088
+ // Close modal on ESC key
1089
+ document.addEventListener('keydown', function(event) {
1090
+ if (event.key === 'Escape') {
1091
+ closeModal();
1092
+ }
1093
+ });
1094
+
1095
+ const notMatched = {
1096
+ lycoris: [],
1097
+ lora: [],
1098
+ embedding: [],
1099
+ flux: [],
1100
+ wan: [],
1101
+ sdxl: [],
1102
+ qwen: [],
1103
+ };
1104
+
1105
+ models.lycorises.forEach((lycoris) => {
1106
+ const key = prepareKey(lycoris.name);
1107
+ if (presence[key] !== undefined) {
1108
+ presence[key].loconCivitai = true;
1109
+ setImageUrl(key, lycoris.imageUrl);
1110
+ presence[key].loconCivitaiLink = lycoris.url;
1111
+ } else if (!isKnownSkippableKey(key)) {
1112
+ notMatched.lycoris.push(key);
1113
+ }
1114
+ });
1115
+
1116
+ models.loras.forEach((lora) => {
1117
+ const key = prepareKey(lora.name);
1118
+ if (presence[key] !== undefined) {
1119
+ presence[key].loraCivitai = true;
1120
+ setImageUrl(key, lora.imageUrl);
1121
+ presence[key].loraCivitaiLink = lora.url;
1122
+ } else if (!isKnownSkippableKey(key)) {
1123
+ notMatched.lora.push(key);
1124
+ }
1125
+ });
1126
+
1127
+ models.embeddings.forEach((embedding) => {
1128
+ const key = prepareKey(embedding.name);
1129
+ if (presence[key] !== undefined) {
1130
+ presence[key].embeddingCivitai = true;
1131
+ setImageUrl(key, embedding.imageUrl);
1132
+ presence[key].embeddingCivitaiLink = embedding.url;
1133
+ } else if (!isKnownSkippableKey(key)) {
1134
+ notMatched.embedding.push(key);
1135
+ }
1136
+ });
1137
+
1138
+ models.fluxes.forEach((flux) => {
1139
+ const key = prepareKey(flux.name);
1140
+ if (presence[key] !== undefined) {
1141
+ presence[key].fluxCivitai = true;
1142
+ setImageUrl(key, flux.imageUrl);
1143
+ presence[key].fluxCivitaiLink = flux.url;
1144
+ } else if (!isKnownSkippableKey(key)) {
1145
+ notMatched.flux.push(key);
1146
+ }
1147
+ });
1148
+
1149
+ models.wans.forEach((wan) => {
1150
+ const key = prepareKey(wan.name);
1151
+ if (presence[key] !== undefined) {
1152
+ presence[key].wanCivitai = true;
1153
+ setImageUrl(key, wan.imageUrl);
1154
+ presence[key].wanCivitaiLink = wan.url;
1155
+ } else if (!isKnownSkippableKey(key)) {
1156
+ notMatched.wan.push(key);
1157
+ }
1158
+ });
1159
+
1160
+ models.sdxls.forEach((sdxl) => {
1161
+ const key = prepareKey(sdxl.name);
1162
+ if (presence[key] !== undefined) {
1163
+ presence[key].sdxlCivitai = true;
1164
+ setImageUrl(key, sdxl.imageUrl);
1165
+ presence[key].sdxlCivitaiLink = sdxl.url;
1166
+ } else if (!isKnownSkippableKey(key)) {
1167
+ notMatched.sdxl.push(key);
1168
+ }
1169
+ });
1170
+
1171
+ models.qwens.forEach((qwen) => {
1172
+ const key = prepareKey(qwen.name);
1173
+ if (presence[key] !== undefined) {
1174
+ presence[key].qwenCivitai = true;
1175
+ setImageUrl(key, qwen.imageUrl);
1176
+ presence[key].qwenCivitaiLink = qwen.url;
1177
+ } else if (!isKnownSkippableKey(key)) {
1178
+ notMatched.qwen.push(key);
1179
+ }
1180
+ });
1181
+
1182
+ console.log(notMatched);
1183
+
1184
+ // Set primary imageUrl based on WAN > Civitai > HF samples priority
1185
+ for (const key in presence) {
1186
+ const personImages = buildImagesList(key, presence[key]);
1187
+ if (personImages.length > 0) {
1188
+ // Use the first image from our prioritized list (WAN first, then Civitai by type, then others)
1189
+ presence[key].imageUrl = personImages[0].url;
1190
+ }
1191
+ }
1192
+
1193
+ const presenceModels = [];
1194
+ for (const property in presence) {
1195
+ const element = {
1196
+ key: property,
1197
+ locon: presence[property].locon,
1198
+ lora: presence[property].lora,
1199
+ embedding: presence[property].embedding,
1200
+ flux: presence[property].flux,
1201
+ wan: presence[property].wan,
1202
+ sdxl: presence[property].sdxl,
1203
+ zimage: presence[property].zimage,
1204
+ qwen: presence[property].qwen,
1205
+ mega: undefined,
1206
+ imageUrl: presence[property]?.imageUrl ?? undefined,
1207
+ loconHFLink: presence[property]?.loconHFLink,
1208
+ loraHFLink: presence[property]?.loraHFLink,
1209
+ embeddingHFLink: presence[property]?.embeddingHFLink,
1210
+ fluxHFLink: presence[property]?.fluxHFLink,
1211
+ wanHFLink: presence[property]?.wanHFLink,
1212
+ sdxlHFLink: presence[property]?.sdxlHFLink,
1213
+ zimageHFLink: presence[property]?.zimageHFLink,
1214
+ qwenHFLink: presence[property]?.qwenHFLink,
1215
+ };
1216
+ presenceModels.push(element);
1217
+ }
1218
+
1219
+ function searchModelsModern(value) {
1220
+ const lowerCaseValue = value.toLowerCase();
1221
+
1222
+ const filtered = presenceModels.filter((element) => {
1223
+ return (
1224
+ (element.key.toLowerCase().includes(lowerCaseValue) || value === '*') &&
1225
+ filterByType(element)
1226
+ );
1227
+ });
1228
+
1229
+ document.getElementById('found').value = filtered.length;
1230
+
1231
+ const contentDiv = document.getElementById('mainContent');
1232
+ contentDiv.innerHTML = '';
1233
+
1234
+ filtered.forEach((element, index) => {
1235
+ const card = document.createElement('div');
1236
+ card.className = 'element';
1237
+ card.innerHTML = `
1238
+ <div class="modelName" title="${escapeHtml(element.key)}">${element.key}</div>
1239
+
1240
+ <div class="imageContainer">
1241
+ <img src="${element.imageUrl ?? unknownImage}" alt="${escapeHtml(element.key)}" />
1242
+ </div>
1243
+ `;
1244
+
1245
+ card.addEventListener('click', () => openModal(element));
1246
+ contentDiv.appendChild(card);
1247
+ });
1248
+ }
1249
+
1250
+ // Override the searchModels function
1251
+ window.searchModels = searchModelsModern;
1252
+
1253
+ searchModels(getCurrentSearchValue());
1254
+
1255
+ // Image navigation for modal
1256
+ let currentImages = [];
1257
+ let currentImageIndex = 0;
1258
+ let currentPersonKey = '';
1259
+
1260
+ function buildImagesList(personKey, personData) {
1261
+ const images = [];
1262
+ const hfImages = window.hfImages || {};
1263
+ const personHFImages = hfImages[personKey] || {};
1264
+ const civitaiTypMapping = { 'LoCon': 'LyCORIS', 'LORA': 'Lora', 'TextualInversion': 'Embedding' };
1265
+
1266
+ // 1. Add WAN samples first (from HF samples data)
1267
+ if (personHFImages.WAN) {
1268
+ images.push(...personHFImages.WAN);
1269
+ }
1270
+
1271
+ // 2. Add Civitai images by type
1272
+ // Check each framework in personData for Civitai models
1273
+ const checkCivitaiImage = (framework, civitaiModels) => {
1274
+ if (!civitaiModels) return;
1275
+ const civitaiModel = civitaiModels.find(m => {
1276
+ const normalizedName = m.name.toLowerCase().replaceAll(' ', '').replaceAll('-', '').replaceAll("'", '');
1277
+ return normalizedName === personKey;
1278
+ });
1279
+ if (civitaiModel && civitaiModel.imageUrl) {
1280
+ const mappedFramework = civitaiTypMapping[civitaiModel.type] || civitaiModel.type;
1281
+ if (mappedFramework !== 'Civitai') {
1282
+ images.push({ url: civitaiModel.imageUrl, framework: mappedFramework });
1283
+ }
1284
+ }
1285
+ };
1286
+
1287
+ checkCivitaiImage('LyCORIS', models.lycorises);
1288
+ checkCivitaiImage('Lora', models.loras);
1289
+ checkCivitaiImage('Embedding', models.embeddings);
1290
+ checkCivitaiImage('Flux', models.fluxes);
1291
+ checkCivitaiImage('WAN', models.wans);
1292
+ checkCivitaiImage('SDXL', models.sdxls);
1293
+ checkCivitaiImage('Qwen', models.qwens);
1294
+
1295
+ // 3. Add other HF samples
1296
+ const frameworkOrder = ['LyCORIS', 'Lora', 'Embedding', 'Flux', 'ZImage', 'SDXL', 'Qwen'];
1297
+ for (const fw of frameworkOrder) {
1298
+ if (personHFImages[fw]) {
1299
+ images.push(...personHFImages[fw]);
1300
+ }
1301
+ }
1302
+
1303
+ return images;
1304
+ }
1305
+
1306
+ function updateImageDisplay() {
1307
+ const modalImage = document.getElementById('modalImage');
1308
+ const imageCounter = document.getElementById('imageCounter');
1309
+ const frameworkLabel = document.getElementById('frameworkLabel');
1310
+ const imageInfo = document.getElementById('imageInfo');
1311
+ const prevBtn = document.getElementById('prevImageBtn');
1312
+ const nextBtn = document.getElementById('nextImageBtn');
1313
+
1314
+ if (currentImages.length > 0) {
1315
+ const currentImage = currentImages[currentImageIndex];
1316
+ modalImage.src = currentImage.url;
1317
+
1318
+ if (currentImages.length > 1) {
1319
+ imageCounter.textContent = `${currentImageIndex + 1} / ${currentImages.length}`;
1320
+ imageInfo.style.display = 'flex';
1321
+ prevBtn.style.display = 'flex';
1322
+ nextBtn.style.display = 'flex';
1323
+ } else {
1324
+ imageCounter.textContent = '';
1325
+ prevBtn.style.display = 'none';
1326
+ nextBtn.style.display = 'none';
1327
+ imageInfo.style.display = 'flex';
1328
+ }
1329
+
1330
+ frameworkLabel.textContent = currentImage.framework;
1331
+ frameworkLabel.setAttribute('data-framework', currentImage.framework);
1332
+ }
1333
+ }
1334
+
1335
+ function previousImage() {
1336
+ if (currentImages.length > 1) {
1337
+ currentImageIndex = (currentImageIndex - 1 + currentImages.length) % currentImages.length;
1338
+ updateImageDisplay();
1339
+ }
1340
+ }
1341
+
1342
+ function nextImage() {
1343
+ if (currentImages.length > 1) {
1344
+ currentImageIndex = (currentImageIndex + 1) % currentImages.length;
1345
+ updateImageDisplay();
1346
+ }
1347
+ }
1348
+
1349
+ // Update openModal to use image navigation
1350
+ const originalOpenModal = openModal;
1351
+ openModal = function(element) {
1352
+ currentPersonKey = element.key;
1353
+ currentImages = buildImagesList(element.key, element);
1354
+ currentImageIndex = 0;
1355
+
1356
+ originalOpenModal(element);
1357
+
1358
+ if (currentImages.length > 0) {
1359
+ updateImageDisplay();
1360
+ }
1361
+ };
1362
+
1363
+ // Keyboard navigation
1364
+ document.addEventListener('keydown', function(e) {
1365
+ const modal = document.getElementById('modalOverlay');
1366
+ if (modal.style.display === 'flex') {
1367
+ if (e.key === 'ArrowLeft') {
1368
+ e.preventDefault();
1369
+ previousImage();
1370
+ } else if (e.key === 'ArrowRight') {
1371
+ e.preventDefault();
1372
+ nextImage();
1373
+ }
1374
+ }
1375
+ });
1376
+ </script>
1377
+ </body>
1378
+ </html>