classlink-66crblus 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.html +918 -0
  2. package/index.js +4 -0
  3. package/package.json +7 -0
package/index.html ADDED
@@ -0,0 +1,918 @@
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
+ <link rel="icon" href="https://cdn.jsdelivr.net/gh/DominumNetwork/Dominum@main/src/assets/images/transparent-dominum.png">
7
+ <title>Dominum Games</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
10
+ <style>
11
+ :root {
12
+ --bg: #0b0a16;
13
+ --accent: #7a5cff;
14
+ --blue: #00bfff;
15
+ --text: #e6e6ff;
16
+ --surface: rgba(255, 255, 255, 0.05);
17
+ --border: rgba(255, 255, 255, 0.1);
18
+ }
19
+
20
+ * { box-sizing: border-box; outline: none; }
21
+
22
+ body {
23
+ background-color: var(--bg);
24
+ color: var(--text);
25
+ font-family: 'Orbitron', sans-serif;
26
+ margin: 0;
27
+ padding: 2rem;
28
+ overflow-x: hidden;
29
+ }
30
+
31
+ .controls-wrapper {
32
+ display: flex;
33
+ flex-wrap: wrap;
34
+ gap: 15px;
35
+ margin-bottom: 2.5rem;
36
+ padding: 20px;
37
+ background: var(--surface);
38
+ backdrop-filter: blur(20px);
39
+ -webkit-backdrop-filter: blur(20px);
40
+ border: 1px solid var(--border);
41
+ border-radius: 20px;
42
+ box-shadow: 0 15px 35px rgba(0,0,0,0.3);
43
+ animation: slideDown 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
44
+ }
45
+
46
+ @keyframes slideDown {
47
+ from { opacity: 0; transform: translateY(-20px); }
48
+ to { opacity: 1; transform: translateY(0); }
49
+ }
50
+
51
+ .control-input {
52
+ padding: 12px 18px;
53
+ border: 1px solid var(--border);
54
+ border-radius: 12px;
55
+ background: rgba(0, 0, 0, 0.2);
56
+ color: var(--text);
57
+ font-family: 'Orbitron', sans-serif;
58
+ font-size: 0.95rem;
59
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
60
+ }
61
+
62
+ .control-input:hover {
63
+ border-color: rgba(255, 255, 255, 0.2);
64
+ }
65
+
66
+ .control-input:focus {
67
+ border-color: var(--accent);
68
+ box-shadow: 0 0 15px rgba(122, 92, 255, 0.3);
69
+ background: rgba(0, 0, 0, 0.4);
70
+ }
71
+
72
+ #searchBar { flex: 1 1 250px; }
73
+
74
+ select.control-input {
75
+ cursor: pointer;
76
+ flex: 0 1 auto;
77
+ appearance: none;
78
+ background-image: linear-gradient(45deg, transparent 50%, var(--accent) 50%), linear-gradient(135deg, var(--accent) 50%, transparent 50%);
79
+ background-position: calc(100% - 18px) center, calc(100% - 12px) center;
80
+ background-size: 6px 6px, 6px 6px;
81
+ background-repeat: no-repeat;
82
+ padding-right: 40px;
83
+ }
84
+
85
+ select.control-input option {
86
+ background-color: var(--bg);
87
+ color: var(--text);
88
+ }
89
+
90
+ #container {
91
+ display: grid;
92
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
93
+ gap: 1.5rem;
94
+ }
95
+
96
+ @keyframes popIn {
97
+ 0% { opacity: 0; transform: scale(0.9) translateY(20px); }
98
+ 100% { opacity: 1; transform: scale(1) translateY(0); }
99
+ }
100
+
101
+ .zone-item {
102
+ background: var(--surface);
103
+ backdrop-filter: blur(10px);
104
+ border: 1px solid var(--border);
105
+ border-radius: 16px;
106
+ cursor: pointer;
107
+ display: flex;
108
+ flex-direction: column;
109
+ overflow: hidden;
110
+ position: relative;
111
+ opacity: 0;
112
+ animation: popIn 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
113
+ transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1), box-shadow 0.3s ease, border-color 0.3s ease;
114
+ }
115
+
116
+ .zone-item:hover {
117
+ transform: translateY(-8px) scale(1.02);
118
+ box-shadow: 0 15px 30px rgba(122, 92, 255, 0.2);
119
+ border-color: var(--accent);
120
+ z-index: 2;
121
+ }
122
+
123
+ .img-wrapper {
124
+ width: 100%;
125
+ aspect-ratio: 1;
126
+ overflow: hidden;
127
+ background-color: rgba(0,0,0,0.3);
128
+ }
129
+
130
+ .zone-item img {
131
+ width: 100%;
132
+ height: 100%;
133
+ object-fit: cover;
134
+ display: block;
135
+ transition: transform 0.5s cubic-bezier(0.25, 0.8, 0.25, 1);
136
+ }
137
+
138
+ .zone-item:hover img { transform: scale(1.15); }
139
+
140
+ .zone-item button {
141
+ padding: 1rem 0.8rem;
142
+ border: none;
143
+ background: transparent;
144
+ color: var(--text);
145
+ font-family: 'Orbitron', sans-serif;
146
+ font-size: 0.9rem;
147
+ cursor: pointer;
148
+ width: 100%;
149
+ white-space: nowrap;
150
+ overflow: hidden;
151
+ text-overflow: ellipsis;
152
+ transition: color 0.3s ease;
153
+ }
154
+
155
+ .zone-item:hover button { color: var(--accent); }
156
+
157
+ #modalOverlay {
158
+ position: fixed;
159
+ inset: 0;
160
+ background: rgba(0, 0, 0, 0.85);
161
+ backdrop-filter: blur(15px);
162
+ -webkit-backdrop-filter: blur(15px);
163
+ display: none;
164
+ justify-content: center;
165
+ align-items: center;
166
+ z-index: 2000;
167
+ padding: 2rem;
168
+ animation: fadeIn 0.3s ease forwards;
169
+ }
170
+
171
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
172
+
173
+ #gameModal {
174
+ width: 100%;
175
+ max-width: 1200px;
176
+ height: 85vh;
177
+ background: var(--surface);
178
+ border: 1px solid var(--border);
179
+ border-radius: 20px;
180
+ display: flex;
181
+ flex-direction: column;
182
+ overflow: hidden;
183
+ box-shadow: 0 25px 60px rgba(0,0,0,0.6);
184
+ transform: scale(0.95);
185
+ animation: modalPop 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
186
+ }
187
+
188
+ @keyframes modalPop { to { transform: scale(1); } }
189
+
190
+ #fullscreenHeader {
191
+ display: flex;
192
+ justify-content: space-between;
193
+ align-items: center;
194
+ padding: 1.2rem 2rem;
195
+ background: rgba(0, 0, 0, 0.4);
196
+ border-bottom: 1px solid var(--border);
197
+ }
198
+
199
+ #gameTitle {
200
+ margin: 0;
201
+ font-size: 1.3rem;
202
+ font-weight: 700;
203
+ color: var(--text);
204
+ text-transform: uppercase;
205
+ letter-spacing: 1px;
206
+ }
207
+
208
+ .header-controls { display: flex; gap: 12px; }
209
+
210
+ .header-btn {
211
+ background: rgba(255, 255, 255, 0.05);
212
+ border: 1px solid var(--border);
213
+ border-radius: 10px;
214
+ color: var(--text);
215
+ font-size: 1.1rem;
216
+ cursor: pointer;
217
+ padding: 0.6rem 1rem;
218
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
219
+ display: flex;
220
+ align-items: center;
221
+ justify-content: center;
222
+ }
223
+
224
+ .header-btn:hover {
225
+ background: var(--accent);
226
+ color: var(--bg);
227
+ border-color: var(--accent);
228
+ transform: translateY(-3px);
229
+ box-shadow: 0 8px 20px rgba(122, 92, 255, 0.4);
230
+ }
231
+
232
+ #zoneFullscreenFrame {
233
+ flex: 1;
234
+ width: 100%;
235
+ border: none;
236
+ background: #000;
237
+ }
238
+
239
+ .alert-backdrop {
240
+ position: fixed;
241
+ inset: 0;
242
+ background: rgba(0, 0, 0, 0.8);
243
+ backdrop-filter: blur(15px);
244
+ -webkit-backdrop-filter: blur(15px);
245
+ display: none;
246
+ justify-content: center;
247
+ align-items: center;
248
+ z-index: 3000;
249
+ animation: fadeIn 0.3s ease forwards;
250
+ }
251
+
252
+ .alert-modal {
253
+ background: var(--surface);
254
+ border: 1px solid var(--border);
255
+ border-radius: 20px;
256
+ padding: 2.5rem;
257
+ max-width: 450px;
258
+ width: 90%;
259
+ text-align: center;
260
+ box-shadow: 0 20px 50px rgba(0,0,0,0.5);
261
+ transform: translateY(30px);
262
+ animation: slideUp 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
263
+ }
264
+
265
+ @keyframes slideUp { to { transform: translateY(0); } }
266
+
267
+ .alert-modal h3 {
268
+ margin-top: 0;
269
+ font-size: 1.6rem;
270
+ color: var(--accent);
271
+ letter-spacing: 1px;
272
+ text-transform: uppercase;
273
+ }
274
+
275
+ .alert-modal p {
276
+ font-size: 1rem;
277
+ line-height: 1.6;
278
+ color: var(--text);
279
+ opacity: 0.8;
280
+ margin-bottom: 2rem;
281
+ }
282
+
283
+ .discord-link {
284
+ display: inline-block;
285
+ background: rgba(88, 101, 242, 0.2);
286
+ border: 1px solid #5865F2;
287
+ color: #fff;
288
+ text-decoration: none;
289
+ padding: 0.8rem 1.5rem;
290
+ border-radius: 12px;
291
+ font-weight: 600;
292
+ margin-bottom: 1.5rem;
293
+ transition: all 0.3s ease;
294
+ }
295
+
296
+ .discord-link:hover {
297
+ background: #5865F2;
298
+ box-shadow: 0 8px 20px rgba(88, 101, 242, 0.4);
299
+ transform: translateY(-2px);
300
+ }
301
+
302
+ .primary-btn {
303
+ background: rgba(0, 0, 0, 0.3);
304
+ color: var(--text);
305
+ border: 1px solid var(--border);
306
+ padding: 1rem 2rem;
307
+ border-radius: 12px;
308
+ font-family: 'Orbitron', sans-serif;
309
+ font-weight: 700;
310
+ font-size: 1rem;
311
+ cursor: pointer;
312
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
313
+ width: 100%;
314
+ text-transform: uppercase;
315
+ letter-spacing: 1px;
316
+ }
317
+
318
+ .primary-btn:hover {
319
+ background: var(--accent);
320
+ border-color: var(--accent);
321
+ color: var(--bg);
322
+ transform: translateY(-3px);
323
+ box-shadow: 0 8px 20px rgba(122, 92, 255, 0.4);
324
+ }
325
+ </style>
326
+ </head>
327
+ <body>
328
+
329
+ <div class="controls-wrapper">
330
+ <select id="providerSelect" class="control-input" onchange="handleProviderChange()">
331
+ <option value="gn-math">GN-Math</option>
332
+ <option value="truffled">Truffled.lol</option>
333
+ <option value="petezah">PeteZah</option>
334
+ <option value="elite">Elite Gamez</option>
335
+ <option value="sea-bean">Sea Bean</option>
336
+ <option value="ugs">UGS (Page may lag)</option>
337
+ <option value="seraph">Seraph</option>
338
+ </select>
339
+
340
+ <select id="truffledProxySelect" class="control-input" style="display: none;" onchange="loadGames()">
341
+ <option value="https://truffled.lol">truffled.lol (Default)</option>
342
+ <option value="https://classlink.com.de">classlink.com.de</option>
343
+ <option value="https://pamson.pl.sophiemaslowski.com">pamson.pl.sophiemaslowski.com</option>
344
+ <option value="https://lightspeed-sucks.9ibee.com">lightspeed-sucks.9ibee.com</option>
345
+ </select>
346
+
347
+ <select id="categorySelect" class="control-input" style="display: none;" onchange="applyFilters()">
348
+ <option value="">All Categories</option>
349
+ <option value="action">Action</option>
350
+ <option value="racing">Racing</option>
351
+ <option value="strategy">Strategy</option>
352
+ <option value="sports">Sports</option>
353
+ <option value="skill">Skill</option>
354
+ <option value="shooting">Shooting</option>
355
+ <option value="2 player">2 Player</option>
356
+ <option value="io">Io</option>
357
+ </select>
358
+
359
+ <input type="text" id="searchBar" class="control-input" placeholder="Search games..." oninput="applyFilters()">
360
+
361
+ <select id="sortSelect" class="control-input" onchange="applyFilters()">
362
+ </select>
363
+ </div>
364
+
365
+ <div id="container"></div>
366
+
367
+ <div id="modalOverlay">
368
+ <div id="gameModal">
369
+ <div id="fullscreenHeader">
370
+ <h2 id="gameTitle">Game Title</h2>
371
+ <div class="header-controls">
372
+ <button id="blankBtn" class="header-btn" title="Open in about:blank tab"><i class="fa-solid fa-up-right-from-square"></i></button>
373
+ <button id="downloadBtn" class="header-btn" title="Download Game"><i class="fas fa-download"></i></button>
374
+ <button id="fullscreenBtn" class="header-btn" title="Fullscreen"><i class="fas fa-expand"></i></button>
375
+ <button id="closeFullscreen" class="header-btn" title="Close"><i class="fas fa-times"></i></button>
376
+ </div>
377
+ </div>
378
+ <iframe id="zoneFullscreenFrame"></iframe>
379
+ </div>
380
+ </div>
381
+
382
+ <div id="noticeModal" class="alert-backdrop">
383
+ <div class="alert-modal">
384
+ <h3>!Notice!</h3>
385
+ <p>I do not own these links; they belong to the truffled community. Please don't get pissed off, if you want a link removed, dm me on discord (<strong>dominus.elitus</strong>). Y'all should support fr support them tho :)</p>
386
+ <a href="https://discord.gg/vVqY36mzvj" target="_blank" class="discord-link"><i class="fab fa-discord"></i> Truffled Discord</a>
387
+ <button id="noticeContinueBtn" class="primary-btn">Continue</button>
388
+ </div>
389
+ </div>
390
+
391
+ <div id="blockedModal" class="alert-backdrop">
392
+ <div class="alert-modal">
393
+ <h3 style="color: #ff4444;"><i class="fas fa-ban"></i> Link Blocked!</h3>
394
+ <p>This truffled link appears to be blocked by your network. Please try selecting another one from the dropdown!</p>
395
+ <button onclick="document.getElementById('blockedModal').style.display='none'" class="primary-btn">Got it</button>
396
+ </div>
397
+ </div>
398
+
399
+ <script>
400
+ const container = document.getElementById('container');
401
+ const searchBar = document.getElementById('searchBar');
402
+ const sortSelect = document.getElementById('sortSelect');
403
+ const categorySelect = document.getElementById('categorySelect');
404
+ const providerSelect = document.getElementById('providerSelect');
405
+ const truffledProxySelect = document.getElementById('truffledProxySelect');
406
+
407
+ const overlay = document.getElementById('modalOverlay');
408
+ const iframe = document.getElementById('zoneFullscreenFrame');
409
+ const closeBtn = document.getElementById('closeFullscreen');
410
+ const fullscreenBtn = document.getElementById('fullscreenBtn');
411
+ const downloadBtn = document.getElementById('downloadBtn');
412
+ const gameTitle = document.getElementById('gameTitle');
413
+
414
+ let allGames = [];
415
+ let currentGame = null;
416
+
417
+ function getFallbackImage(name) {
418
+ return `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=0b0a16&color=7a5cff&size=256&font-size=0.33&bold=true`;
419
+ }
420
+
421
+ function resolveGameUrl(url, provider) {
422
+ if (!url) return '';
423
+ if (url.startsWith('http')) return url;
424
+
425
+ const cleanPath = url.replace(/^\//, '');
426
+
427
+ if (provider === 'seraph') {
428
+ return "https://cdn.jsdelivr.net/gh/a456pur/seraph@main/" + cleanPath;
429
+ }
430
+
431
+ return "https://cdn.jsdelivr.net/gh/tharun9772/tharun9772.github.io@main/" + cleanPath;
432
+ }
433
+
434
+ function handleProviderChange() {
435
+ const provider = providerSelect.value;
436
+ const currentSort = sortSelect.value;
437
+
438
+ sortSelect.innerHTML = '';
439
+ if (provider === 'gn-math') {
440
+ sortSelect.innerHTML += '<option value="latest">Latest to Oldest</option>';
441
+ sortSelect.innerHTML += '<option value="oldest">Oldest to Latest</option>';
442
+ }
443
+ sortSelect.innerHTML += '<option value="a-z">A-Z</option>';
444
+ sortSelect.innerHTML += '<option value="z-a">Z-A</option>';
445
+
446
+ if (currentSort) {
447
+ sortSelect.value = currentSort;
448
+ } else {
449
+ sortSelect.value = 'a-z';
450
+ }
451
+
452
+ if (provider === 'petezah') {
453
+ categorySelect.style.display = 'block';
454
+ } else {
455
+ categorySelect.style.display = 'none';
456
+ categorySelect.value = '';
457
+ }
458
+
459
+ if (provider === 'truffled') {
460
+ if (!localStorage.getItem('truffledNoticeSeen')) {
461
+ document.getElementById('noticeModal').style.display = 'flex';
462
+ return;
463
+ }
464
+ truffledProxySelect.style.display = 'block';
465
+ } else {
466
+ truffledProxySelect.style.display = 'none';
467
+ }
468
+
469
+ loadGames();
470
+ }
471
+
472
+ document.getElementById('noticeContinueBtn').onclick = () => {
473
+ localStorage.setItem('truffledNoticeSeen', 'true');
474
+ document.getElementById('noticeModal').style.display = 'none';
475
+ truffledProxySelect.style.display = 'block';
476
+ loadGames();
477
+ };
478
+
479
+ async function loadGames() {
480
+ container.innerHTML = '<div style="width:100%; text-align:center; padding: 2rem; color: var(--blue); font-size: 1.2rem;"><i class="fas fa-circle-notch fa-spin"></i> Fetching games...</div>';
481
+ allGames = [];
482
+ const provider = providerSelect.value;
483
+
484
+ try {
485
+ if (provider === 'gn-math') {
486
+ const response = await fetch("https://cdn.jsdelivr.net/gh/freebuisness/assets/zones.json");
487
+ const rawZones = await response.json();
488
+
489
+ allGames = rawZones.filter(g => g.id !== -1 && !g.name.startsWith("[!]")).map((z, index) => {
490
+ let coverUrl = (z.cover || "").replace('{COVER_URL}', '');
491
+ if(coverUrl.startsWith('/')) coverUrl = coverUrl.substring(1);
492
+
493
+ return {
494
+ provider: 'gn-math',
495
+ name: z.name,
496
+ cover: 'https://cdn.jsdelivr.net/gh/freebuisness/covers@main/' + coverUrl,
497
+ url: z.url,
498
+ isAbsolute: z.url.startsWith('http'),
499
+ addedOrder: index
500
+ };
501
+ });
502
+
503
+ } else if (provider === 'elite') {
504
+ const response = await fetch("https://cdn.jsdelivr.net/gh/elite-gamez/elite-gamez.github.io@main/games.json");
505
+ const data = await response.json();
506
+
507
+ allGames = data.map((g, index) => {
508
+ let gameTitle = g.title || g.name || "Unknown Game";
509
+ let gameUrl = g.url;
510
+ if (!gameUrl.startsWith('http')) {
511
+ gameUrl = "https://cdn.jsdelivr.net/gh/elite-gamez/elite-gamez.github.io@main/" + gameUrl;
512
+ }
513
+
514
+ return {
515
+ provider: 'elite',
516
+ name: gameTitle,
517
+ cover: g.image ? ('https://cdn.jsdelivr.net/gh/elite-gamez/elite-gamez.github.io@main/' + g.image) : getFallbackImage(gameTitle),
518
+ url: gameUrl,
519
+ isAbsolute: true,
520
+ addedOrder: index
521
+ };
522
+ });
523
+
524
+ } else if (provider === 'sea-bean') {
525
+ const response = await fetch("https://cdn.jsdelivr.net/gh/sea-bean-unblocked/sde@main/zzz.json");
526
+ const data = await response.json();
527
+
528
+ allGames = data.map((g, index) => {
529
+ let gameTitle = g.name || g.id || "Unknown Game";
530
+ let htmlUrl = g.html || g.url || "";
531
+
532
+ if (htmlUrl.includes("{HTML_URL}")) {
533
+ htmlUrl = htmlUrl.replace("{HTML_URL}", "https://cdn.jsdelivr.net/gh/sea-bean-unblocked/Singlemile@main/games/");
534
+ } else {
535
+ htmlUrl = resolveGameUrl(htmlUrl);
536
+ }
537
+
538
+ let cover = (g.cover || g.img || "").replace("{COVER_URL}/", "");
539
+ let finalCover = cover.startsWith("http")
540
+ ? cover
541
+ : (cover ? 'https://cdn.jsdelivr.net/gh/sea-bean-unblocked/Singlemile@main/Icon/' + cover : getFallbackImage(gameTitle));
542
+
543
+ return {
544
+ provider: 'sea-bean',
545
+ name: gameTitle,
546
+ cover: finalCover,
547
+ url: htmlUrl,
548
+ isAbsolute: true,
549
+ addedOrder: index
550
+ };
551
+ });
552
+
553
+ } else if (provider === 'ugs') {
554
+ const repos = [
555
+ "tharun9772/ugs-1",
556
+ "tharun9772/ugs-2",
557
+ "tharun9772/ugs-3"
558
+ ];
559
+ let games = [];
560
+ let globalIndex = 0;
561
+
562
+ for (const repo of repos) {
563
+ try {
564
+ const r = await fetch(`https://api.github.com/repos/${repo}/contents/`);
565
+ const d = await r.json();
566
+
567
+ d.forEach(f => {
568
+ if (f.type === "file" && f.name.startsWith("cl") && f.name.endsWith(".html")) {
569
+ let cleanName = f.name.replace(/^cl/, "").replace(".html", "");
570
+ cleanName = cleanName.charAt(0).toUpperCase() + cleanName.slice(1);
571
+
572
+ games.push({
573
+ provider: 'ugs',
574
+ name: cleanName,
575
+ cover: "https://cdn.jsdelivr.net/gh/tharun9772/game-assets@main/5968517.png",
576
+ url: `https://cdn.jsdelivr.net/gh/${repo}@main/${f.name}`,
577
+ isAbsolute: true,
578
+ addedOrder: globalIndex++
579
+ });
580
+ }
581
+ });
582
+ } catch (e) {
583
+ console.warn("UGS fetch failed for:", repo);
584
+ }
585
+ }
586
+ allGames = games;
587
+
588
+ } else if (provider === 'truffled') {
589
+ const proxyBase = truffledProxySelect.value.replace(/\/$/, "");
590
+
591
+ try {
592
+ await fetch(proxyBase + '/png/logo.png', { mode: 'no-cors', cache: 'no-store' });
593
+ } catch (err) {
594
+ document.getElementById('blockedModal').style.display = 'flex';
595
+ container.innerHTML = '<div style="width:100%; text-align:center; padding: 2rem; color: #ff4444;">Proxy blocked. Please select another from the dropdown.</div>';
596
+ return;
597
+ }
598
+
599
+ const response = await fetch("https://cdn.jsdelivr.net/gh/aukak/truffled@main/public/js/json/g.json");
600
+ const data = await response.json();
601
+
602
+ allGames = (data.games || []).map((g, index) => {
603
+ let rawUrl = g.url;
604
+ let gameUrl = g.url;
605
+ let thumb = g.thumbnail;
606
+
607
+ if (!gameUrl.startsWith('http')) {
608
+ gameUrl = proxyBase + (gameUrl.startsWith('/') ? '' : '/') + gameUrl;
609
+ }
610
+ if (!thumb.startsWith('http')) {
611
+ thumb = proxyBase + (thumb.startsWith('/') ? '' : '/') + thumb;
612
+ }
613
+
614
+ return {
615
+ provider: 'truffled',
616
+ name: g.name,
617
+ cover: thumb,
618
+ url: gameUrl,
619
+ rawUrl: rawUrl,
620
+ isAbsolute: g.url.startsWith('http'),
621
+ frameType: g.frameType || 'iframe',
622
+ addedOrder: index
623
+ };
624
+ });
625
+
626
+ } else if (provider === 'seraph') {
627
+ const response = await fetch('https://cdn.jsdelivr.net/gh/DominumNetwork/dominum@main/src/assets/libraries/seraph/games.json');
628
+ const data = await response.json();
629
+
630
+ allGames = data.map((g, index) => {
631
+ const gamePath = g.url.endsWith('index.html') ? g.url : g.url.replace(/\/?$/, '/index.html');
632
+
633
+ return {
634
+ provider: 'seraph',
635
+ name: g.name,
636
+ cover: g.img ? g.img : getFallbackImage(g.name),
637
+ url: resolveGameUrl(gamePath, 'seraph'),
638
+ isAbsolute: true,
639
+ addedOrder: index
640
+ };
641
+ });
642
+ } else if (provider === 'petezah') {
643
+ const response = await fetch("https://cdn.jsdelivr.net/gh/PeteZah-G/singlefile-json@main/search.json");
644
+ const data = await response.json();
645
+
646
+ allGames = (data.games || []).map((g, index) => {
647
+ let finalUrl = g.url;
648
+ if (finalUrl && !finalUrl.endsWith('index.html') && !finalUrl.match(/\.\w+$/)) {
649
+ finalUrl = finalUrl.replace(/\/$/, '') + '/index.html';
650
+ }
651
+
652
+ return {
653
+ provider: 'petezah',
654
+ name: g.label,
655
+ cover: g.imageUrl || getFallbackImage(g.label),
656
+ url: finalUrl,
657
+ isAbsolute: finalUrl.startsWith('http'),
658
+ addedOrder: index,
659
+ categories: g.categories || []
660
+ };
661
+ });
662
+ }
663
+
664
+ applyFilters();
665
+
666
+ } catch (e) {
667
+ console.error(e);
668
+ container.innerHTML = '<div style="width:100%; text-align:center; padding: 2rem; color: #ff4444;">Failed to load games. Check console for details.</div>';
669
+ }
670
+ }
671
+
672
+ function applyFilters() {
673
+ const query = searchBar.value.toLowerCase();
674
+ const sortMethod = sortSelect.value;
675
+ const categoryFilter = categorySelect.value.toLowerCase();
676
+
677
+ let filtered = allGames.filter(g => {
678
+ const matchesQuery = g.name.toLowerCase().includes(query);
679
+ const matchesCategory = !categoryFilter || (g.categories && g.categories.includes(categoryFilter));
680
+ return matchesQuery && matchesCategory;
681
+ });
682
+
683
+ filtered.sort((a, b) => {
684
+ if (sortMethod === 'a-z') return a.name.localeCompare(b.name);
685
+ if (sortMethod === 'z-a') return b.name.localeCompare(a.name);
686
+ if (sortMethod === 'latest') return b.addedOrder - a.addedOrder;
687
+ if (sortMethod === 'oldest') return a.addedOrder - b.addedOrder;
688
+ return 0;
689
+ });
690
+
691
+ displayGames(filtered);
692
+ }
693
+
694
+ function displayGames(games) {
695
+ container.innerHTML = '';
696
+ if (games.length === 0) {
697
+ container.innerHTML = '<div style="width:100%; text-align:center; padding: 2rem; color: var(--text); opacity: 0.7;">No games found.</div>';
698
+ return;
699
+ }
700
+
701
+ games.forEach((game, index) => {
702
+ const item = document.createElement('div');
703
+ item.className = 'zone-item';
704
+
705
+ const delay = Math.min(index * 0.03, 1.5);
706
+ item.style.animationDelay = `${delay}s`;
707
+
708
+ const imgWrapper = document.createElement('div');
709
+ imgWrapper.className = 'img-wrapper';
710
+
711
+ const img = document.createElement('img');
712
+ img.dataset.src = game.cover;
713
+ img.alt = game.name;
714
+ img.loading = 'lazy';
715
+
716
+ const btn = document.createElement('button');
717
+ btn.textContent = game.name;
718
+
719
+ item.onclick = () => openGame(game);
720
+ btn.onclick = (e) => { e.stopPropagation(); openGame(game); };
721
+
722
+ imgWrapper.appendChild(img);
723
+ item.appendChild(imgWrapper);
724
+ item.appendChild(btn);
725
+ container.appendChild(item);
726
+ });
727
+
728
+ const imgs = document.querySelectorAll('img[data-src]');
729
+ const observer = new IntersectionObserver((entries, obs) => {
730
+ entries.forEach(e => {
731
+ if (e.isIntersecting) {
732
+ const img = e.target;
733
+ img.src = img.dataset.src;
734
+ obs.unobserve(img);
735
+ }
736
+ });
737
+ }, { rootMargin: '100px', threshold: 0.1 });
738
+ imgs.forEach(i => observer.observe(i));
739
+ }
740
+
741
+ async function openGame(game) {
742
+ currentGame = game;
743
+ overlay.style.display = 'flex';
744
+ gameTitle.textContent = game.name;
745
+ downloadBtn.style.display = 'flex';
746
+
747
+ let fullUrl = game.url;
748
+ if (game.provider === 'gn-math' && !game.isAbsolute) {
749
+ fullUrl = 'https://cdn.jsdelivr.net/gh/freebuisness/html@main/' + game.url.replace('{HTML_URL}', '');
750
+ }
751
+
752
+ const needsBlobFix = ['elite', 'sea-bean', 'ugs', 'gn-math', 'seraph'];
753
+
754
+ if (needsBlobFix.includes(game.provider) || game.provider === 'petezah') {
755
+ try {
756
+ const response = await fetch(fullUrl);
757
+ let htmlContent = await response.text();
758
+
759
+ if (game.provider === 'petezah') {
760
+ const baseUrl = fullUrl.replace(/\/[^\/]*$/, '/');
761
+ if (!htmlContent.match(/<head[^>]*>/i)) {
762
+ htmlContent = htmlContent.replace(/<html[^>]*>/i, '__HTML__<head><base href="' + baseUrl + '"></head>');
763
+ } else {
764
+ htmlContent = htmlContent.replace(/<head[^>]*>/i, '__HTML__<base href="' + baseUrl + '">');
765
+ }
766
+ }
767
+
768
+ const blob = new Blob([htmlContent], { type: 'text/html' });
769
+ const blobUrl = URL.createObjectURL(blob);
770
+ iframe.src = blobUrl;
771
+
772
+ iframe.onload = () => URL.revokeObjectURL(blobUrl);
773
+ } catch (e) {
774
+ console.error("Fetch failed, falling back to direct URL:", e);
775
+ iframe.src = fullUrl;
776
+ }
777
+ return;
778
+ }
779
+
780
+ if (game.provider === 'truffled') {
781
+ if (game.frameType === 'unity') {
782
+ const proxyBase = truffledProxySelect.value.replace(/\/$/, "");
783
+ iframe.src = `${proxyBase}/unityframe.html?url=${encodeURIComponent(game.rawUrl)}`;
784
+ } else {
785
+ iframe.src = game.url;
786
+ }
787
+ return;
788
+ }
789
+
790
+ iframe.src = fullUrl;
791
+ }
792
+
793
+ downloadBtn.onclick = async () => {
794
+ if (!currentGame) return;
795
+
796
+ let fileContent = "";
797
+ let fullUrl = currentGame.url;
798
+
799
+ if (currentGame.provider === 'gn-math' && !currentGame.isAbsolute) {
800
+ fullUrl = 'https://cdn.jsdelivr.net/gh/freebuisness/html@main/' + currentGame.url.replace('{HTML_URL}', '');
801
+ }
802
+
803
+ try {
804
+ const response = await fetch(fullUrl);
805
+ if (!response.ok) throw new Error("CORS or Network Error");
806
+ fileContent = await response.text();
807
+ } catch (error) {
808
+ fileContent = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>${currentGame.name}</title><style>body, html { margin: 0; padding: 0; height: 100%; overflow: hidden; background: #000; } iframe { width: 100%; height: 100%; border: none; }</style></head><body><iframe src="${fullUrl}" allowfullscreen="true"></iframe></body></html>`;
809
+ }
810
+
811
+ const blob = new Blob([fileContent], { type: 'text/html' });
812
+ const downloadUrl = URL.createObjectURL(blob);
813
+ const a = document.createElement('a');
814
+ a.href = downloadUrl;
815
+ a.download = `${currentGame.name.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.html`;
816
+ document.body.appendChild(a);
817
+ a.click();
818
+ document.body.removeChild(a);
819
+ URL.revokeObjectURL(downloadUrl);
820
+ };
821
+
822
+ closeBtn.onclick = () => {
823
+ overlay.style.display = 'none';
824
+ iframe.src = '';
825
+ currentGame = null;
826
+ };
827
+
828
+ overlay.onclick = (e) => {
829
+ if (e.target === overlay) closeBtn.click();
830
+ }
831
+
832
+ fullscreenBtn.onclick = () => {
833
+ iframe.requestFullscreen().catch(() => alert('Fullscreen mode is not supported or blocked.'));
834
+ };
835
+
836
+ handleProviderChange();
837
+
838
+ const blankBtn = document.getElementById('blankBtn');
839
+
840
+ document.getElementById('blankBtn').onclick = async () => {
841
+ if (!currentGame) return;
842
+
843
+ let url = currentGame.url;
844
+ if (currentGame.provider === 'gn-math' && !url.startsWith('http')) {
845
+ url = 'https://cdn.jsdelivr.net/gh/freebuisness/html@main/' + currentGame.url.replace('{HTML_URL}', '');
846
+ }
847
+
848
+ const win = window.open('about:blank', '_blank');
849
+ if (!win) return alert("Pop-ups blocked!");
850
+
851
+ win.document.write(`
852
+ <!DOCTYPE html>
853
+ <html>
854
+ <head>
855
+ <title>Google Drive</title>
856
+ <link rel="icon" href="https://ssl.gstatic.com/docs/doclist/images/drive_2022q3_32dp.png">
857
+ <style>
858
+ body, html { margin:0; padding:0; width:100%; height:100%; overflow:hidden; background:#000; }
859
+ iframe { width:100%; height:100%; border:none; }
860
+ </style>
861
+ </head>
862
+ <body><iframe id="f" allowfullscreen="true"></iframe></body>
863
+ </html>
864
+ `);
865
+ win.document.close();
866
+
867
+ const iframe = win.document.getElementById('f');
868
+ const needsBlob = ['gn-math', 'petezah', 'elite', 'seraph', 'sea-bean', 'ugs'];
869
+
870
+ if (needsBlob.includes(currentGame.provider)) {
871
+ try {
872
+ const res = await fetch(url);
873
+ let html = await res.text();
874
+
875
+ if (currentGame.provider === 'petezah') {
876
+ const base = url.substring(0, url.lastIndexOf('/') + 1);
877
+ html = html.replace('<head>', `<head><base href="${base}">`);
878
+ }
879
+
880
+ iframe.contentWindow.document.open();
881
+ iframe.contentWindow.document.write(html);
882
+ iframe.contentWindow.document.close();
883
+ } catch (e) {
884
+ console.error("Direct injection failed, falling back to src:", e);
885
+ iframe.src = url;
886
+ }
887
+ } else {
888
+ iframe.src = url;
889
+ }
890
+ };
891
+ </script>
892
+
893
+ <script>
894
+ const root = document.documentElement;
895
+ const themes = {
896
+ default:{"--bg":"#0b0a16","--accent":"#7a5cff","--blue":"#00bfff","--text":"#e6e6ff","--surface":"rgba(255,255,255,0.05)","--border":"rgba(255,255,255,0.1)"},
897
+ cyber:{"--bg":"#050c1b","--accent":"#39ff14","--blue":"#00ffff","--text":"#e0ffe0","--surface":"rgba(0,255,0,0.05)","--border":"rgba(0,255,0,0.1)"},
898
+ sunset:{"--bg":"#1a0b0b","--accent":"#ff6f61","--blue":"#ffa500","--text":"#fff0e6","--surface":"rgba(255,110,100,0.05)","--border":"rgba(255,110,100,0.1)"},
899
+ neon:{"--bg":"#0f0020","--accent":"#ff00ff","--blue":"#00ffff","--text":"#ffffff","--surface":"rgba(255,0,255,0.05)","--border":"rgba(255,0,255,0.1)"},
900
+ darkred:{"--bg":"#1b0000","--accent":"#ff0000","--blue":"#cc0000","--text":"#ffe6e6","--surface":"rgba(255,0,0,0.05)","--border":"rgba(255,0,0,0.1)"},
901
+ dnv2:{"--bg":"#050007","--accent":"#ff00de","--blue":"#00fff7","--text":"#ffffff","--surface":"rgba(255,0,255,0.05)","--border":"rgba(255,0,255,0.15)"},
902
+ matrix:{"--bg":"#001100","--accent":"#00ff00","--blue":"#33ff33","--text":"#99ff99","--surface":"rgba(0,255,0,0.05)","--border":"rgba(0,255,0,0.15)"}
903
+ };
904
+
905
+ function applyTheme(theme){
906
+ Object.entries(theme).forEach(([k,v])=>root.style.setProperty(k,v));
907
+ }
908
+
909
+ (function(){
910
+ const name = localStorage.getItem("dominum-theme") || "default";
911
+ if(name === "custom"){
912
+ const custom = JSON.parse(localStorage.getItem("dominum-custom-theme") || "{}");
913
+ applyTheme(custom);
914
+ } else applyTheme(themes[name]);
915
+ })();
916
+ </script>
917
+ </body>
918
+ </html>
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ var html = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n<link rel=\"icon\" href=\"https://cdn.jsdelivr.net/gh/DominumNetwork/Dominum@main/src/assets/images/transparent-dominum.png\">\n<title>Dominum Games</title>\n<link href=\"https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700&display=swap\" rel=\"stylesheet\">\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" />\n<style>\n :root {\n --bg: #0b0a16;\n --accent: #7a5cff;\n --blue: #00bfff;\n --text: #e6e6ff;\n --surface: rgba(255, 255, 255, 0.05);\n --border: rgba(255, 255, 255, 0.1);\n }\n\n * { box-sizing: border-box; outline: none; }\n\n body {\n background-color: var(--bg);\n color: var(--text);\n font-family: 'Orbitron', sans-serif;\n margin: 0;\n padding: 2rem;\n overflow-x: hidden;\n }\n\n .controls-wrapper {\n display: flex;\n flex-wrap: wrap;\n gap: 15px;\n margin-bottom: 2.5rem;\n padding: 20px;\n background: var(--surface);\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n border: 1px solid var(--border);\n border-radius: 20px;\n box-shadow: 0 15px 35px rgba(0,0,0,0.3);\n animation: slideDown 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;\n }\n\n @keyframes slideDown {\n from { opacity: 0; transform: translateY(-20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n .control-input {\n padding: 12px 18px;\n border: 1px solid var(--border);\n border-radius: 12px;\n background: rgba(0, 0, 0, 0.2);\n color: var(--text);\n font-family: 'Orbitron', sans-serif;\n font-size: 0.95rem;\n transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);\n }\n\n .control-input:hover {\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n .control-input:focus {\n border-color: var(--accent);\n box-shadow: 0 0 15px rgba(122, 92, 255, 0.3);\n background: rgba(0, 0, 0, 0.4);\n }\n\n #searchBar { flex: 1 1 250px; }\n \n select.control-input {\n cursor: pointer;\n flex: 0 1 auto;\n appearance: none;\n background-image: linear-gradient(45deg, transparent 50%, var(--accent) 50%), linear-gradient(135deg, var(--accent) 50%, transparent 50%);\n background-position: calc(100% - 18px) center, calc(100% - 12px) center;\n background-size: 6px 6px, 6px 6px;\n background-repeat: no-repeat;\n padding-right: 40px;\n }\n \n select.control-input option {\n background-color: var(--bg);\n color: var(--text);\n }\n\n #container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n gap: 1.5rem;\n }\n\n @keyframes popIn {\n 0% { opacity: 0; transform: scale(0.9) translateY(20px); }\n 100% { opacity: 1; transform: scale(1) translateY(0); }\n }\n\n .zone-item {\n background: var(--surface);\n backdrop-filter: blur(10px);\n border: 1px solid var(--border);\n border-radius: 16px;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n position: relative;\n opacity: 0; \n animation: popIn 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;\n transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1), box-shadow 0.3s ease, border-color 0.3s ease;\n }\n\n .zone-item:hover {\n transform: translateY(-8px) scale(1.02);\n box-shadow: 0 15px 30px rgba(122, 92, 255, 0.2);\n border-color: var(--accent);\n z-index: 2;\n }\n\n .img-wrapper {\n width: 100%;\n aspect-ratio: 1;\n overflow: hidden;\n background-color: rgba(0,0,0,0.3);\n }\n\n .zone-item img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n transition: transform 0.5s cubic-bezier(0.25, 0.8, 0.25, 1);\n }\n\n .zone-item:hover img { transform: scale(1.15); }\n\n .zone-item button {\n padding: 1rem 0.8rem;\n border: none;\n background: transparent;\n color: var(--text);\n font-family: 'Orbitron', sans-serif;\n font-size: 0.9rem;\n cursor: pointer;\n width: 100%;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n transition: color 0.3s ease;\n }\n\n .zone-item:hover button { color: var(--accent); }\n\n #modalOverlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.85);\n backdrop-filter: blur(15px);\n -webkit-backdrop-filter: blur(15px);\n display: none;\n justify-content: center;\n align-items: center;\n z-index: 2000;\n padding: 2rem;\n animation: fadeIn 0.3s ease forwards;\n }\n\n @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }\n\n #gameModal {\n width: 100%;\n max-width: 1200px;\n height: 85vh;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 20px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n box-shadow: 0 25px 60px rgba(0,0,0,0.6);\n transform: scale(0.95);\n animation: modalPop 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;\n }\n\n @keyframes modalPop { to { transform: scale(1); } }\n\n #fullscreenHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.2rem 2rem;\n background: rgba(0, 0, 0, 0.4);\n border-bottom: 1px solid var(--border);\n }\n\n #gameTitle {\n margin: 0;\n font-size: 1.3rem;\n font-weight: 700;\n color: var(--text);\n text-transform: uppercase;\n letter-spacing: 1px;\n }\n\n .header-controls { display: flex; gap: 12px; }\n\n .header-btn {\n background: rgba(255, 255, 255, 0.05);\n border: 1px solid var(--border);\n border-radius: 10px;\n color: var(--text);\n font-size: 1.1rem;\n cursor: pointer;\n padding: 0.6rem 1rem;\n transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .header-btn:hover {\n background: var(--accent);\n color: var(--bg);\n border-color: var(--accent);\n transform: translateY(-3px);\n box-shadow: 0 8px 20px rgba(122, 92, 255, 0.4);\n }\n\n #zoneFullscreenFrame {\n flex: 1;\n width: 100%;\n border: none;\n background: #000;\n }\n\n .alert-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.8);\n backdrop-filter: blur(15px);\n -webkit-backdrop-filter: blur(15px);\n display: none;\n justify-content: center;\n align-items: center;\n z-index: 3000;\n animation: fadeIn 0.3s ease forwards;\n }\n\n .alert-modal {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 20px;\n padding: 2.5rem;\n max-width: 450px;\n width: 90%;\n text-align: center;\n box-shadow: 0 20px 50px rgba(0,0,0,0.5);\n transform: translateY(30px);\n animation: slideUp 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;\n }\n\n @keyframes slideUp { to { transform: translateY(0); } }\n\n .alert-modal h3 {\n margin-top: 0;\n font-size: 1.6rem;\n color: var(--accent);\n letter-spacing: 1px;\n text-transform: uppercase;\n }\n\n .alert-modal p {\n font-size: 1rem;\n line-height: 1.6;\n color: var(--text);\n opacity: 0.8;\n margin-bottom: 2rem;\n }\n\n .discord-link {\n display: inline-block;\n background: rgba(88, 101, 242, 0.2);\n border: 1px solid #5865F2;\n color: #fff;\n text-decoration: none;\n padding: 0.8rem 1.5rem;\n border-radius: 12px;\n font-weight: 600;\n margin-bottom: 1.5rem;\n transition: all 0.3s ease;\n }\n\n .discord-link:hover {\n background: #5865F2;\n box-shadow: 0 8px 20px rgba(88, 101, 242, 0.4);\n transform: translateY(-2px);\n }\n\n .primary-btn {\n background: rgba(0, 0, 0, 0.3);\n color: var(--text);\n border: 1px solid var(--border);\n padding: 1rem 2rem;\n border-radius: 12px;\n font-family: 'Orbitron', sans-serif;\n font-weight: 700;\n font-size: 1rem;\n cursor: pointer;\n transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);\n width: 100%;\n text-transform: uppercase;\n letter-spacing: 1px;\n }\n\n .primary-btn:hover {\n background: var(--accent);\n border-color: var(--accent);\n color: var(--bg);\n transform: translateY(-3px);\n box-shadow: 0 8px 20px rgba(122, 92, 255, 0.4);\n }\n</style>\n</head>\n<body>\n\n<div class=\"controls-wrapper\">\n <select id=\"providerSelect\" class=\"control-input\" onchange=\"handleProviderChange()\">\n <option value=\"gn-math\">GN-Math</option>\n <option value=\"truffled\">Truffled.lol</option>\n <option value=\"petezah\">PeteZah</option>\n <option value=\"elite\">Elite Gamez</option>\n <option value=\"sea-bean\">Sea Bean</option>\n <option value=\"ugs\">UGS (Page may lag)</option>\n <option value=\"seraph\">Seraph</option>\n </select>\n\n <select id=\"truffledProxySelect\" class=\"control-input\" style=\"display: none;\" onchange=\"loadGames()\">\n <option value=\"https://truffled.lol\">truffled.lol (Default)</option>\n <option value=\"https://classlink.com.de\">classlink.com.de</option>\n <option value=\"https://pamson.pl.sophiemaslowski.com\">pamson.pl.sophiemaslowski.com</option>\n <option value=\"https://lightspeed-sucks.9ibee.com\">lightspeed-sucks.9ibee.com</option>\n </select>\n\n <select id=\"categorySelect\" class=\"control-input\" style=\"display: none;\" onchange=\"applyFilters()\">\n <option value=\"\">All Categories</option>\n <option value=\"action\">Action</option>\n <option value=\"racing\">Racing</option>\n <option value=\"strategy\">Strategy</option>\n <option value=\"sports\">Sports</option>\n <option value=\"skill\">Skill</option>\n <option value=\"shooting\">Shooting</option>\n <option value=\"2 player\">2 Player</option>\n <option value=\"io\">Io</option>\n </select>\n\n <input type=\"text\" id=\"searchBar\" class=\"control-input\" placeholder=\"Search games...\" oninput=\"applyFilters()\">\n \n <select id=\"sortSelect\" class=\"control-input\" onchange=\"applyFilters()\">\n </select>\n</div>\n\n<div id=\"container\"></div>\n\n<div id=\"modalOverlay\">\n <div id=\"gameModal\">\n <div id=\"fullscreenHeader\">\n <h2 id=\"gameTitle\">Game Title</h2>\n <div class=\"header-controls\">\n <button id=\"blankBtn\" class=\"header-btn\" title=\"Open in about:blank tab\"><i class=\"fa-solid fa-up-right-from-square\"></i></button>\n <button id=\"downloadBtn\" class=\"header-btn\" title=\"Download Game\"><i class=\"fas fa-download\"></i></button>\n <button id=\"fullscreenBtn\" class=\"header-btn\" title=\"Fullscreen\"><i class=\"fas fa-expand\"></i></button>\n <button id=\"closeFullscreen\" class=\"header-btn\" title=\"Close\"><i class=\"fas fa-times\"></i></button>\n </div>\n </div>\n <iframe id=\"zoneFullscreenFrame\"></iframe>\n </div>\n</div>\n\n<div id=\"noticeModal\" class=\"alert-backdrop\">\n <div class=\"alert-modal\">\n <h3>!Notice!</h3>\n <p>I do not own these links; they belong to the truffled community. Please don't get pissed off, if you want a link removed, dm me on discord (<strong>dominus.elitus</strong>). Y'all should support fr support them tho :)</p>\n <a href=\"https://discord.gg/vVqY36mzvj\" target=\"_blank\" class=\"discord-link\"><i class=\"fab fa-discord\"></i> Truffled Discord</a>\n <button id=\"noticeContinueBtn\" class=\"primary-btn\">Continue</button>\n </div>\n</div>\n\n<div id=\"blockedModal\" class=\"alert-backdrop\">\n <div class=\"alert-modal\">\n <h3 style=\"color: #ff4444;\"><i class=\"fas fa-ban\"></i> Link Blocked!</h3>\n <p>This truffled link appears to be blocked by your network. Please try selecting another one from the dropdown!</p>\n <button onclick=\"document.getElementById('blockedModal').style.display='none'\" class=\"primary-btn\">Got it</button>\n </div>\n</div>\n\n<script>\nconst container = document.getElementById('container');\nconst searchBar = document.getElementById('searchBar');\nconst sortSelect = document.getElementById('sortSelect');\nconst categorySelect = document.getElementById('categorySelect');\nconst providerSelect = document.getElementById('providerSelect');\nconst truffledProxySelect = document.getElementById('truffledProxySelect');\n\nconst overlay = document.getElementById('modalOverlay');\nconst iframe = document.getElementById('zoneFullscreenFrame');\nconst closeBtn = document.getElementById('closeFullscreen');\nconst fullscreenBtn = document.getElementById('fullscreenBtn');\nconst downloadBtn = document.getElementById('downloadBtn');\nconst gameTitle = document.getElementById('gameTitle');\n\nlet allGames = [];\nlet currentGame = null; \n\nfunction getFallbackImage(name) {\n return `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=0b0a16&color=7a5cff&size=256&font-size=0.33&bold=true`;\n}\n\nfunction resolveGameUrl(url, provider) {\n if (!url) return '';\n if (url.startsWith('http')) return url;\n\n const cleanPath = url.replace(/^\\//, '');\n\n if (provider === 'seraph') {\n return \"https://cdn.jsdelivr.net/gh/a456pur/seraph@main/\" + cleanPath;\n }\n \n return \"https://cdn.jsdelivr.net/gh/tharun9772/tharun9772.github.io@main/\" + cleanPath;\n}\n\nfunction handleProviderChange() {\n const provider = providerSelect.value;\n const currentSort = sortSelect.value;\n \n sortSelect.innerHTML = '';\n if (provider === 'gn-math') {\n sortSelect.innerHTML += '<option value=\"latest\">Latest to Oldest</option>';\n sortSelect.innerHTML += '<option value=\"oldest\">Oldest to Latest</option>';\n }\n sortSelect.innerHTML += '<option value=\"a-z\">A-Z</option>';\n sortSelect.innerHTML += '<option value=\"z-a\">Z-A</option>';\n\n if (currentSort) {\n sortSelect.value = currentSort;\n } else {\n sortSelect.value = 'a-z';\n }\n\n if (provider === 'petezah') {\n categorySelect.style.display = 'block';\n } else {\n categorySelect.style.display = 'none';\n categorySelect.value = '';\n }\n\n if (provider === 'truffled') {\n if (!localStorage.getItem('truffledNoticeSeen')) {\n document.getElementById('noticeModal').style.display = 'flex';\n return; \n }\n truffledProxySelect.style.display = 'block';\n } else {\n truffledProxySelect.style.display = 'none';\n }\n \n loadGames();\n}\n\ndocument.getElementById('noticeContinueBtn').onclick = () => {\n localStorage.setItem('truffledNoticeSeen', 'true');\n document.getElementById('noticeModal').style.display = 'none';\n truffledProxySelect.style.display = 'block';\n loadGames();\n};\n\nasync function loadGames() {\n container.innerHTML = '<div style=\"width:100%; text-align:center; padding: 2rem; color: var(--blue); font-size: 1.2rem;\"><i class=\"fas fa-circle-notch fa-spin\"></i> Fetching games...</div>';\n allGames = [];\n const provider = providerSelect.value;\n\n try {\n if (provider === 'gn-math') {\n const response = await fetch(\"https://cdn.jsdelivr.net/gh/freebuisness/assets/zones.json\");\n const rawZones = await response.json();\n \n allGames = rawZones.filter(g => g.id !== -1 && !g.name.startsWith(\"[!]\")).map((z, index) => {\n let coverUrl = (z.cover || \"\").replace('{COVER_URL}', '');\n if(coverUrl.startsWith('/')) coverUrl = coverUrl.substring(1);\n\n return {\n provider: 'gn-math',\n name: z.name,\n cover: 'https://cdn.jsdelivr.net/gh/freebuisness/covers@main/' + coverUrl,\n url: z.url,\n isAbsolute: z.url.startsWith('http'),\n addedOrder: index\n };\n });\n\n } else if (provider === 'elite') {\n const response = await fetch(\"https://cdn.jsdelivr.net/gh/elite-gamez/elite-gamez.github.io@main/games.json\");\n const data = await response.json();\n\n allGames = data.map((g, index) => {\n let gameTitle = g.title || g.name || \"Unknown Game\";\n let gameUrl = g.url;\n if (!gameUrl.startsWith('http')) {\n gameUrl = \"https://cdn.jsdelivr.net/gh/elite-gamez/elite-gamez.github.io@main/\" + gameUrl;\n }\n\n return {\n provider: 'elite',\n name: gameTitle,\n cover: g.image ? ('https://cdn.jsdelivr.net/gh/elite-gamez/elite-gamez.github.io@main/' + g.image) : getFallbackImage(gameTitle),\n url: gameUrl,\n isAbsolute: true,\n addedOrder: index\n };\n });\n\n } else if (provider === 'sea-bean') {\n const response = await fetch(\"https://cdn.jsdelivr.net/gh/sea-bean-unblocked/sde@main/zzz.json\");\n const data = await response.json();\n\n allGames = data.map((g, index) => {\n let gameTitle = g.name || g.id || \"Unknown Game\";\n let htmlUrl = g.html || g.url || \"\";\n \n if (htmlUrl.includes(\"{HTML_URL}\")) {\n htmlUrl = htmlUrl.replace(\"{HTML_URL}\", \"https://cdn.jsdelivr.net/gh/sea-bean-unblocked/Singlemile@main/games/\");\n } else {\n htmlUrl = resolveGameUrl(htmlUrl);\n }\n\n let cover = (g.cover || g.img || \"\").replace(\"{COVER_URL}/\", \"\");\n let finalCover = cover.startsWith(\"http\") \n ? cover \n : (cover ? 'https://cdn.jsdelivr.net/gh/sea-bean-unblocked/Singlemile@main/Icon/' + cover : getFallbackImage(gameTitle));\n\n return {\n provider: 'sea-bean',\n name: gameTitle,\n cover: finalCover,\n url: htmlUrl,\n isAbsolute: true,\n addedOrder: index\n };\n });\n\n } else if (provider === 'ugs') {\n const repos = [\n \"tharun9772/ugs-1\",\n \"tharun9772/ugs-2\",\n \"tharun9772/ugs-3\"\n ];\n let games = [];\n let globalIndex = 0;\n\n for (const repo of repos) {\n try {\n const r = await fetch(`https://api.github.com/repos/${repo}/contents/`);\n const d = await r.json();\n\n d.forEach(f => {\n if (f.type === \"file\" && f.name.startsWith(\"cl\") && f.name.endsWith(\".html\")) {\n let cleanName = f.name.replace(/^cl/, \"\").replace(\".html\", \"\");\n cleanName = cleanName.charAt(0).toUpperCase() + cleanName.slice(1);\n \n games.push({\n provider: 'ugs',\n name: cleanName,\n cover: \"https://cdn.jsdelivr.net/gh/tharun9772/game-assets@main/5968517.png\",\n url: `https://cdn.jsdelivr.net/gh/${repo}@main/${f.name}`,\n isAbsolute: true,\n addedOrder: globalIndex++\n });\n }\n });\n } catch (e) {\n console.warn(\"UGS fetch failed for:\", repo);\n }\n }\n allGames = games;\n\n } else if (provider === 'truffled') {\n const proxyBase = truffledProxySelect.value.replace(/\\/$/, \"\");\n\n try {\n await fetch(proxyBase + '/png/logo.png', { mode: 'no-cors', cache: 'no-store' });\n } catch (err) {\n document.getElementById('blockedModal').style.display = 'flex';\n container.innerHTML = '<div style=\"width:100%; text-align:center; padding: 2rem; color: #ff4444;\">Proxy blocked. Please select another from the dropdown.</div>';\n return;\n }\n\n const response = await fetch(\"https://cdn.jsdelivr.net/gh/aukak/truffled@main/public/js/json/g.json\");\n const data = await response.json();\n \n allGames = (data.games || []).map((g, index) => {\n let rawUrl = g.url; \n let gameUrl = g.url;\n let thumb = g.thumbnail;\n \n if (!gameUrl.startsWith('http')) {\n gameUrl = proxyBase + (gameUrl.startsWith('/') ? '' : '/') + gameUrl;\n }\n if (!thumb.startsWith('http')) {\n thumb = proxyBase + (thumb.startsWith('/') ? '' : '/') + thumb;\n }\n\n return {\n provider: 'truffled',\n name: g.name,\n cover: thumb,\n url: gameUrl,\n rawUrl: rawUrl, \n isAbsolute: g.url.startsWith('http'),\n frameType: g.frameType || 'iframe',\n addedOrder: index \n };\n });\n\n } else if (provider === 'seraph') {\n const response = await fetch('https://cdn.jsdelivr.net/gh/DominumNetwork/dominum@main/src/assets/libraries/seraph/games.json');\n const data = await response.json();\n\n allGames = data.map((g, index) => {\n const gamePath = g.url.endsWith('index.html') ? g.url : g.url.replace(/\\/?$/, '/index.html');\n\n return {\n provider: 'seraph',\n name: g.name,\n cover: g.img ? g.img : getFallbackImage(g.name),\n url: resolveGameUrl(gamePath, 'seraph'),\n isAbsolute: true,\n addedOrder: index\n };\n });\n } else if (provider === 'petezah') {\n const response = await fetch(\"https://cdn.jsdelivr.net/gh/PeteZah-G/singlefile-json@main/search.json\");\n const data = await response.json();\n\n allGames = (data.games || []).map((g, index) => {\n let finalUrl = g.url;\n if (finalUrl && !finalUrl.endsWith('index.html') && !finalUrl.match(/\\.\\w+$/)) {\n finalUrl = finalUrl.replace(/\\/$/, '') + '/index.html';\n }\n\n return {\n provider: 'petezah',\n name: g.label,\n cover: g.imageUrl || getFallbackImage(g.label),\n url: finalUrl,\n isAbsolute: finalUrl.startsWith('http'),\n addedOrder: index,\n categories: g.categories || []\n };\n });\n }\n\n applyFilters();\n\n } catch (e) {\n console.error(e);\n container.innerHTML = '<div style=\"width:100%; text-align:center; padding: 2rem; color: #ff4444;\">Failed to load games. Check console for details.</div>';\n }\n}\n\nfunction applyFilters() {\n const query = searchBar.value.toLowerCase();\n const sortMethod = sortSelect.value;\n const categoryFilter = categorySelect.value.toLowerCase();\n\n let filtered = allGames.filter(g => {\n const matchesQuery = g.name.toLowerCase().includes(query);\n const matchesCategory = !categoryFilter || (g.categories && g.categories.includes(categoryFilter));\n return matchesQuery && matchesCategory;\n });\n\n filtered.sort((a, b) => {\n if (sortMethod === 'a-z') return a.name.localeCompare(b.name);\n if (sortMethod === 'z-a') return b.name.localeCompare(a.name);\n if (sortMethod === 'latest') return b.addedOrder - a.addedOrder;\n if (sortMethod === 'oldest') return a.addedOrder - b.addedOrder;\n return 0;\n });\n\n displayGames(filtered);\n}\n\nfunction displayGames(games) {\n container.innerHTML = '';\n if (games.length === 0) {\n container.innerHTML = '<div style=\"width:100%; text-align:center; padding: 2rem; color: var(--text); opacity: 0.7;\">No games found.</div>';\n return;\n }\n\n games.forEach((game, index) => {\n const item = document.createElement('div');\n item.className = 'zone-item';\n \n const delay = Math.min(index * 0.03, 1.5);\n item.style.animationDelay = `${delay}s`;\n\n const imgWrapper = document.createElement('div');\n imgWrapper.className = 'img-wrapper';\n\n const img = document.createElement('img');\n img.dataset.src = game.cover;\n img.alt = game.name;\n img.loading = 'lazy';\n\n const btn = document.createElement('button');\n btn.textContent = game.name;\n \n item.onclick = () => openGame(game);\n btn.onclick = (e) => { e.stopPropagation(); openGame(game); };\n\n imgWrapper.appendChild(img);\n item.appendChild(imgWrapper);\n item.appendChild(btn);\n container.appendChild(item);\n });\n\n const imgs = document.querySelectorAll('img[data-src]');\n const observer = new IntersectionObserver((entries, obs) => {\n entries.forEach(e => {\n if (e.isIntersecting) {\n const img = e.target;\n img.src = img.dataset.src;\n obs.unobserve(img);\n }\n });\n }, { rootMargin: '100px', threshold: 0.1 });\n imgs.forEach(i => observer.observe(i));\n}\n\nasync function openGame(game) {\n currentGame = game;\n overlay.style.display = 'flex';\n gameTitle.textContent = game.name;\n downloadBtn.style.display = 'flex';\n\n let fullUrl = game.url;\n if (game.provider === 'gn-math' && !game.isAbsolute) {\n fullUrl = 'https://cdn.jsdelivr.net/gh/freebuisness/html@main/' + game.url.replace('{HTML_URL}', '');\n }\n\n const needsBlobFix = ['elite', 'sea-bean', 'ugs', 'gn-math', 'seraph'];\n\n if (needsBlobFix.includes(game.provider) || game.provider === 'petezah') {\n try {\n const response = await fetch(fullUrl);\n let htmlContent = await response.text();\n \n if (game.provider === 'petezah') {\n const baseUrl = fullUrl.replace(/\\/[^\\/]*$/, '/');\n if (!htmlContent.match(/<head[^>]*>/i)) {\n htmlContent = htmlContent.replace(/<html[^>]*>/i, '__INDEX_JS__<head><base href=\"' + baseUrl + '\"></head>');\n } else {\n htmlContent = htmlContent.replace(/<head[^>]*>/i, '__INDEX_JS__<base href=\"' + baseUrl + '\">');\n }\n }\n\n const blob = new Blob([htmlContent], { type: 'text/html' });\n const blobUrl = URL.createObjectURL(blob);\n iframe.src = blobUrl;\n \n iframe.onload = () => URL.revokeObjectURL(blobUrl);\n } catch (e) {\n console.error(\"Fetch failed, falling back to direct URL:\", e);\n iframe.src = fullUrl;\n }\n return;\n }\n\n if (game.provider === 'truffled') {\n if (game.frameType === 'unity') {\n const proxyBase = truffledProxySelect.value.replace(/\\/$/, \"\");\n iframe.src = `${proxyBase}/unityframe.html?url=${encodeURIComponent(game.rawUrl)}`;\n } else {\n iframe.src = game.url; \n }\n return;\n }\n\n iframe.src = fullUrl;\n}\n\ndownloadBtn.onclick = async () => {\n if (!currentGame) return;\n\n let fileContent = \"\";\n let fullUrl = currentGame.url;\n\n if (currentGame.provider === 'gn-math' && !currentGame.isAbsolute) {\n fullUrl = 'https://cdn.jsdelivr.net/gh/freebuisness/html@main/' + currentGame.url.replace('{HTML_URL}', '');\n }\n\n try {\n const response = await fetch(fullUrl);\n if (!response.ok) throw new Error(\"CORS or Network Error\");\n fileContent = await response.text();\n } catch (error) {\n fileContent = `<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>${currentGame.name}</title><style>body, html { margin: 0; padding: 0; height: 100%; overflow: hidden; background: #000; } iframe { width: 100%; height: 100%; border: none; }</style></head><body><iframe src=\"${fullUrl}\" allowfullscreen=\"true\"></iframe></body></html>`;\n }\n\n const blob = new Blob([fileContent], { type: 'text/html' });\n const downloadUrl = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = downloadUrl;\n a.download = `${currentGame.name.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.html`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(downloadUrl);\n};\n\ncloseBtn.onclick = () => {\n overlay.style.display = 'none';\n iframe.src = '';\n currentGame = null;\n};\n\noverlay.onclick = (e) => {\n if (e.target === overlay) closeBtn.click();\n}\n\nfullscreenBtn.onclick = () => {\n iframe.requestFullscreen().catch(() => alert('Fullscreen mode is not supported or blocked.'));\n};\n\nhandleProviderChange();\n\nconst blankBtn = document.getElementById('blankBtn');\n\ndocument.getElementById('blankBtn').onclick = async () => {\n if (!currentGame) return;\n \n let url = currentGame.url;\n if (currentGame.provider === 'gn-math' && !url.startsWith('http')) {\n url = 'https://cdn.jsdelivr.net/gh/freebuisness/html@main/' + currentGame.url.replace('{HTML_URL}', '');\n }\n\n const win = window.open('about:blank', '_blank');\n if (!win) return alert(\"Pop-ups blocked!\");\n\n win.document.write(`\n <!DOCTYPE html>\n <html>\n <head>\n <title>Google Drive</title>\n <link rel=\"icon\" href=\"https://ssl.gstatic.com/docs/doclist/images/drive_2022q3_32dp.png\">\n <style>\n body, html { margin:0; padding:0; width:100%; height:100%; overflow:hidden; background:#000; }\n iframe { width:100%; height:100%; border:none; }\n </style>\n </head>\n <body><iframe id=\"f\" allowfullscreen=\"true\"></iframe></body>\n </html>\n `);\n win.document.close();\n\n const iframe = win.document.getElementById('f');\n const needsBlob = ['gn-math', 'petezah', 'elite', 'seraph', 'sea-bean', 'ugs'];\n\n if (needsBlob.includes(currentGame.provider)) {\n try {\n const res = await fetch(url);\n let html = await res.text();\n \n if (currentGame.provider === 'petezah') {\n const base = url.substring(0, url.lastIndexOf('/') + 1);\n html = html.replace('<head>', `<head><base href=\"${base}\">`);\n }\n \n iframe.contentWindow.document.open();\n iframe.contentWindow.document.write(html);\n iframe.contentWindow.document.close();\n } catch (e) { \n console.error(\"Direct injection failed, falling back to src:\", e);\n iframe.src = url; \n }\n } else {\n iframe.src = url;\n }\n};\n</script>\n\n<script>\nconst root = document.documentElement;\nconst themes = {\n default:{\"--bg\":\"#0b0a16\",\"--accent\":\"#7a5cff\",\"--blue\":\"#00bfff\",\"--text\":\"#e6e6ff\",\"--surface\":\"rgba(255,255,255,0.05)\",\"--border\":\"rgba(255,255,255,0.1)\"},\n cyber:{\"--bg\":\"#050c1b\",\"--accent\":\"#39ff14\",\"--blue\":\"#00ffff\",\"--text\":\"#e0ffe0\",\"--surface\":\"rgba(0,255,0,0.05)\",\"--border\":\"rgba(0,255,0,0.1)\"},\n sunset:{\"--bg\":\"#1a0b0b\",\"--accent\":\"#ff6f61\",\"--blue\":\"#ffa500\",\"--text\":\"#fff0e6\",\"--surface\":\"rgba(255,110,100,0.05)\",\"--border\":\"rgba(255,110,100,0.1)\"},\n neon:{\"--bg\":\"#0f0020\",\"--accent\":\"#ff00ff\",\"--blue\":\"#00ffff\",\"--text\":\"#ffffff\",\"--surface\":\"rgba(255,0,255,0.05)\",\"--border\":\"rgba(255,0,255,0.1)\"},\n darkred:{\"--bg\":\"#1b0000\",\"--accent\":\"#ff0000\",\"--blue\":\"#cc0000\",\"--text\":\"#ffe6e6\",\"--surface\":\"rgba(255,0,0,0.05)\",\"--border\":\"rgba(255,0,0,0.1)\"},\n dnv2:{\"--bg\":\"#050007\",\"--accent\":\"#ff00de\",\"--blue\":\"#00fff7\",\"--text\":\"#ffffff\",\"--surface\":\"rgba(255,0,255,0.05)\",\"--border\":\"rgba(255,0,255,0.15)\"},\n matrix:{\"--bg\":\"#001100\",\"--accent\":\"#00ff00\",\"--blue\":\"#33ff33\",\"--text\":\"#99ff99\",\"--surface\":\"rgba(0,255,0,0.05)\",\"--border\":\"rgba(0,255,0,0.15)\"}\n};\n\nfunction applyTheme(theme){\n Object.entries(theme).forEach(([k,v])=>root.style.setProperty(k,v));\n}\n\n(function(){\n const name = localStorage.getItem(\"dominum-theme\") || \"default\";\n if(name === \"custom\"){\n const custom = JSON.parse(localStorage.getItem(\"dominum-custom-theme\") || \"{}\");\n applyTheme(custom);\n } else applyTheme(themes[name]);\n})();\n</script>\n</body>\n</html>";
2
+ module.exports = html;
3
+ module.exports.default = html;
4
+ module.exports.html = html;
package/package.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "classlink-66crblus",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "description": "npm bulk publisher",
6
+ "license": "MIT"
7
+ }