madebydanny.uk written in html, css, and a lot of JavaScript I don't understand
madeydanny.uk
html
css
javascript
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>MBD CDN - madebydanny.uk</title>
7 <script src="https://kit.fontawesome.com/0ca27f8db1.js" crossorigin="anonymous"></script>
8 <link rel="icon" href="https://public-cdn.madebydanny.uk/user-content/2026-01-30/33913bec-bc2f-4e6c-a474-2ef8f8c00197">
9 <meta name="description" content="The MBD CDN is a network of servers located around the world powered by the Cloudflare global network.">
10 <meta property="og:title" content="MBD CDN - madebydanny.uk">
11 <meta property="og:description" content="The MBD CDN is a network of servers located around the world powered by the Cloudflare global network.">
12 <meta property="og:image" content="https://imrs.madebydanny.uk?url=https://public-cdn.madebydanny.uk/user-content/2026-01-30/cb09a559-ae35-4617-971c-9230521f7a9c.png">
13 <meta property="og:type" content="website">
14
15 <style>
16 :root {
17 --bg: #121212;
18 --card-bg: #4a2b32;
19 --post-bg: #1e1e1e;
20 --stat-bg: #2a1a21;
21 --text: #ffffff;
22 --subtext: #dcbaba;
23 --link: #ffcccc;
24 --border: rgba(255,255,255,0.1);
25 --green: #34c759;
26 --red: #ff6b6b;
27 }
28
29 *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
30
31 body {
32 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
33 background: var(--bg);
34 color: var(--text);
35 min-height: 100vh;
36 line-height: 1.5;
37 }
38
39 a { color: var(--link); text-decoration: none; }
40 a:hover { text-decoration: underline; color: #ffd9d9; }
41
42 /* ── HEADER ─────────────────────────────── */
43 .site-header {
44 text-align: center;
45 padding: 44px 20px 10px;
46 max-width: 900px;
47 margin: 0 auto;
48 }
49
50 .site-header h1 {
51 font-size: 2.2rem;
52 font-weight: 700;
53 letter-spacing: -1px;
54 margin-bottom: 10px;
55 }
56
57 .site-header p {
58 color: var(--subtext);
59 font-size: 0.95rem;
60 max-width: 560px;
61 margin: 0 auto;
62 }
63
64 /* ── STATS ──────────────────────────────── */
65 .stats-wrap {
66 max-width: 900px;
67 margin: 30px auto 0;
68 padding: 0 20px;
69 }
70
71 .stats-grid {
72 display: grid;
73 grid-template-columns: repeat(4, 1fr);
74 gap: 14px;
75 }
76
77 .stats-grid-storage {
78 display: grid;
79 grid-template-columns: 1fr;
80 gap: 14px;
81 margin-top: 14px;
82 }
83
84 .stat-card.storage-card {
85 display: flex;
86 align-items: center;
87 justify-content: center;
88 gap: 16px;
89 padding: 18px 28px;
90 text-align: left;
91 }
92
93 .stat-card.storage-card .stat-icon {
94 font-size: 2rem;
95 margin-bottom: 0;
96 flex-shrink: 0;
97 }
98
99 .stat-card.storage-card .stat-value {
100 font-size: 2rem;
101 margin: 0;
102 }
103
104 .stat-card.storage-card .stat-label {
105 margin-top: 2px;
106 }
107
108 .stat-card {
109 background: linear-gradient(135deg, var(--stat-bg) 0%, var(--card-bg) 100%);
110 border: 1px solid var(--border);
111 border-radius: 12px;
112 padding: 20px;
113 text-align: center;
114 position: relative;
115 overflow: hidden;
116 transition: transform 0.2s, box-shadow 0.2s;
117 }
118
119 .stat-card::before {
120 content: '';
121 position: absolute;
122 top: 0; left: 0; right: 0;
123 height: 3px;
124 background: linear-gradient(90deg, var(--link), rgba(255,204,204,0.25));
125 opacity: 0;
126 transition: opacity 0.3s;
127 }
128
129 .stat-card:hover { transform: translateY(-4px); box-shadow: 0 8px 20px rgba(0,0,0,0.4); }
130 .stat-card:hover::before { opacity: 1; }
131
132 .stat-icon { font-size: 1.8rem; margin-bottom: 8px; opacity: 0.75; }
133
134 .stat-value {
135 font-size: 2rem;
136 font-weight: 700;
137 letter-spacing: -1px;
138 margin: 4px 0;
139 }
140
141 .stat-value.loading { opacity: 0.35; animation: blink 1.4s ease-in-out infinite; }
142 @keyframes blink { 0%,100%{opacity:.35} 50%{opacity:.7} }
143
144 .stat-label {
145 font-size: 0.8rem;
146 color: var(--subtext);
147 text-transform: uppercase;
148 letter-spacing: 0.5px;
149 font-weight: 600;
150 }
151
152 /* ── TABS ───────────────────────────────── */
153 .tabs-wrap {
154 max-width: 900px;
155 margin: 36px auto 0;
156 padding: 0 20px;
157 }
158
159 .tab-bar {
160 display: flex;
161 gap: 4px;
162 border-bottom: 1px solid var(--border);
163 overflow-x: auto;
164 scrollbar-width: none;
165 }
166
167 .tab-bar::-webkit-scrollbar { display: none; }
168
169 .tab-btn {
170 font-family: inherit;
171 font-size: 0.9rem;
172 font-weight: 600;
173 color: var(--subtext);
174 background: none;
175 border: none;
176 border-bottom: 2px solid transparent;
177 padding: 10px 18px;
178 cursor: pointer;
179 white-space: nowrap;
180 transition: color 0.2s, border-color 0.2s;
181 margin-bottom: -1px;
182 }
183
184 .tab-btn:hover { color: var(--text); }
185 .tab-btn.active { color: var(--link); border-bottom-color: var(--link); }
186
187 .tab-content { padding: 32px 0 80px; }
188
189 .tab-pane { display: none; }
190 .tab-pane.active { display: block; }
191
192 /* ── CARD ───────────────────────────────── */
193 .card {
194 background: var(--card-bg);
195 border: 1px solid var(--border);
196 border-radius: 16px;
197 padding: 32px;
198 box-shadow: 0 6px 12px rgba(0,0,0,0.35);
199 }
200
201 .card + .card { margin-top: 20px; }
202
203 .card h2 {
204 font-size: 1.25rem;
205 font-weight: 700;
206 letter-spacing: -0.4px;
207 margin-bottom: 8px;
208 }
209
210 .card .desc {
211 color: var(--subtext);
212 font-size: 0.9rem;
213 margin-bottom: 20px;
214 }
215
216 hr.divider {
217 border: none;
218 border-top: 1px solid var(--border);
219 margin: 28px 0;
220 }
221
222 /* ── UPLOAD ─────────────────────────────── */
223 .drop-zone {
224 display: block;
225 border: 2px dashed var(--border);
226 border-radius: 12px;
227 padding: 50px 20px;
228 text-align: center;
229 cursor: pointer;
230 background: rgba(0,0,0,0.2);
231 color: var(--subtext);
232 transition: all 0.25s;
233 }
234
235 .drop-zone:hover {
236 border-color: var(--link);
237 background: rgba(255,255,255,0.02);
238 transform: scale(1.005);
239 }
240
241 .drop-zone.drag-over {
242 border-color: var(--green);
243 background: rgba(52,199,89,0.05);
244 transform: scale(1.01);
245 }
246
247 .drop-zone i { font-size: 2.4rem; display: block; margin-bottom: 12px; opacity: 0.65; }
248 .drop-zone input { display: none; }
249
250 #file-name { font-size: 0.95rem; font-weight: 500; display: block; margin-top: 4px; }
251
252 .file-info {
253 display: none;
254 margin-top: 14px;
255 padding: 12px 16px;
256 background: var(--post-bg);
257 border: 1px solid var(--border);
258 border-radius: 8px;
259 font-size: 0.82rem;
260 color: var(--subtext);
261 }
262
263 .file-info.show { display: block; }
264
265 .progress-wrap { display: none; margin-top: 14px; }
266 .progress-wrap.show { display: block; }
267
268 .progress-track {
269 height: 7px;
270 background: var(--post-bg);
271 border-radius: 999px;
272 overflow: hidden;
273 border: 1px solid var(--border);
274 }
275
276 .progress-fill {
277 height: 100%;
278 width: 0%;
279 background: linear-gradient(90deg, var(--link), var(--green));
280 border-radius: 999px;
281 transition: width 0.2s ease;
282 }
283
284 .progress-label {
285 text-align: right;
286 font-size: 0.75rem;
287 color: var(--subtext);
288 margin-top: 5px;
289 }
290
291 .upload-btn {
292 display: block;
293 width: 100%;
294 margin-top: 18px;
295 padding: 14px 24px;
296 background: linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%);
297 color: #000;
298 border: none;
299 border-radius: 999px;
300 font-family: inherit;
301 font-size: 1rem;
302 font-weight: 600;
303 cursor: pointer;
304 box-shadow: 0 2px 8px rgba(255,255,255,0.08);
305 transition: all 0.2s;
306 }
307
308 .upload-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 14px rgba(255,255,255,0.18); }
309 .upload-btn:active { transform: scale(0.985); }
310 .upload-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: none; }
311
312 .status-msg {
313 text-align: center;
314 margin-top: 14px;
315 font-size: 0.9rem;
316 font-weight: 500;
317 min-height: 1.3em;
318 color: var(--link);
319 }
320
321 .result-box {
322 display: none;
323 margin-top: 18px;
324 padding: 18px;
325 background: var(--post-bg);
326 border: 1px solid var(--green);
327 border-radius: 12px;
328 animation: slideIn 0.3s ease;
329 }
330
331 .result-box.show { display: block; }
332
333 @keyframes slideIn {
334 from { opacity: 0; transform: translateY(-8px); }
335 to { opacity: 1; transform: translateY(0); }
336 }
337
338 .result-label {
339 font-size: 0.72rem;
340 font-weight: 700;
341 text-transform: uppercase;
342 letter-spacing: 0.6px;
343 color: var(--subtext);
344 margin-bottom: 8px;
345 }
346
347 .result-url {
348 font-family: 'Monaco', 'Courier New', monospace;
349 font-size: 0.85rem;
350 padding: 10px 12px;
351 background: rgba(0,0,0,0.3);
352 border: 1px solid var(--border);
353 border-radius: 6px;
354 word-break: break-all;
355 color: var(--text);
356 margin-bottom: 12px;
357 }
358
359 .copy-btn {
360 display: inline-flex;
361 align-items: center;
362 gap: 7px;
363 padding: 9px 16px;
364 background: rgba(255,255,255,0.05);
365 border: 1px solid var(--border);
366 border-radius: 8px;
367 color: var(--link);
368 font-family: inherit;
369 font-size: 0.85rem;
370 font-weight: 600;
371 cursor: pointer;
372 transition: all 0.15s;
373 margin-right: 8px;
374 }
375
376 .copy-btn:hover { background: rgba(255,255,255,0.09); }
377 .copy-btn.copied { background: var(--green); color: #000; border-color: var(--green); }
378
379 .open-btn {
380 display: inline-flex;
381 align-items: center;
382 gap: 7px;
383 padding: 9px 16px;
384 background: rgba(255,255,255,0.05);
385 border: 1px solid var(--border);
386 border-radius: 8px;
387 color: var(--link);
388 font-size: 0.85rem;
389 font-weight: 600;
390 transition: all 0.15s;
391 }
392
393 .open-btn:hover { background: rgba(255,255,255,0.09); text-decoration: none; }
394
395 /* ── ABOUT ──────────────────────────────── */
396 .about-text {
397 color: var(--subtext);
398 font-size: 0.92rem;
399 line-height: 1.7;
400 }
401
402 .about-text p + p { margin-top: 12px; }
403
404 .social-row {
405 display: flex;
406 flex-wrap: wrap;
407 gap: 10px;
408 justify-content: center;
409 }
410
411 .social-btn {
412 display: inline-flex;
413 align-items: center;
414 gap: 8px;
415 padding: 9px 16px;
416 background: rgba(255,255,255,0.04);
417 border: 1px solid var(--border);
418 border-radius: 999px;
419 color: var(--text);
420 font-size: 0.88rem;
421 font-weight: 600;
422 transition: all 0.2s;
423 }
424
425 .social-btn:hover {
426 background: rgba(255,255,255,0.08);
427 transform: translateY(-2px);
428 box-shadow: 0 4px 12px rgba(0,0,0,0.3);
429 text-decoration: none;
430 }
431
432 /* ── HOW IT WORKS ───────────────────────── */
433 .steps {
434 display: flex;
435 flex-direction: column;
436 gap: 18px;
437 margin-bottom: 28px;
438 }
439
440 .step {
441 display: flex;
442 align-items: flex-start;
443 gap: 16px;
444 }
445
446 .step-num {
447 flex-shrink: 0;
448 width: 34px; height: 34px;
449 border-radius: 50%;
450 background: linear-gradient(135deg, var(--stat-bg), var(--card-bg));
451 border: 1px solid var(--border);
452 display: flex;
453 align-items: center;
454 justify-content: center;
455 font-size: 0.78rem;
456 font-weight: 700;
457 color: var(--link);
458 }
459
460 .step-body h3 { font-size: 0.95rem; font-weight: 700; margin-bottom: 3px; }
461 .step-body p { color: var(--subtext); font-size: 0.85rem; line-height: 1.55; }
462
463 .how-grid {
464 display: grid;
465 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
466 gap: 14px;
467 }
468
469 .how-card {
470 background: var(--post-bg);
471 border: 1px solid var(--border);
472 border-radius: 10px;
473 padding: 18px;
474 }
475
476 .how-card i { font-size: 1.5rem; color: var(--link); margin-bottom: 10px; display: block; }
477 .how-card h3 { font-size: 0.9rem; font-weight: 700; margin-bottom: 5px; }
478 .how-card p { font-size: 0.8rem; color: var(--subtext); line-height: 1.55; }
479
480 /* ── LIMITS ─────────────────────────────── */
481 .limits-grid {
482 display: grid;
483 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
484 gap: 14px;
485 margin-bottom: 22px;
486 }
487
488 .limit-card {
489 background: var(--post-bg);
490 border: 1px solid var(--border);
491 border-radius: 12px;
492 padding: 22px 18px;
493 text-align: center;
494 position: relative;
495 overflow: hidden;
496 }
497
498 .limit-card::before {
499 content: '';
500 position: absolute;
501 top: 0; left: 0; right: 0;
502 height: 3px;
503 background: linear-gradient(90deg, var(--link), rgba(255,204,204,0.2));
504 }
505
506 .limit-card i { font-size: 1.8rem; color: var(--link); margin-bottom: 12px; display: block; opacity: 0.8; }
507 .limit-value { font-size: 1.7rem; font-weight: 700; letter-spacing: -0.5px; margin-bottom: 4px; }
508 .limit-label { font-size: 0.82rem; color: var(--subtext); font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; }
509 .limit-note { margin-top: 5px; font-size: 0.72rem; color: rgba(220,186,186,0.5); }
510
511 /* ── USAGE BARS ─────────────────────────── */
512 .usage-section { margin-top: 28px; }
513
514 .usage-section h2 { font-size: 1rem; font-weight: 700; margin-bottom: 16px; }
515
516 .usage-item { margin-bottom: 16px; }
517
518 .usage-row {
519 display: flex;
520 justify-content: space-between;
521 font-size: 0.82rem;
522 color: var(--subtext);
523 margin-bottom: 6px;
524 }
525
526 .usage-row span:first-child { font-weight: 600; color: var(--text); }
527
528 .usage-track {
529 height: 8px;
530 background: var(--post-bg);
531 border-radius: 999px;
532 overflow: hidden;
533 border: 1px solid var(--border);
534 }
535
536 .usage-fill {
537 height: 100%;
538 border-radius: 999px;
539 background: linear-gradient(90deg, var(--link), var(--green));
540 transition: width 0.6s cubic-bezier(.4,0,.2,1);
541 }
542
543 .usage-fill.warn { background: linear-gradient(90deg, #ffaa00, #ff6b00); }
544 .usage-fill.danger { background: linear-gradient(90deg, var(--red), #cc0000); }
545
546 .usage-loading { font-size: 0.8rem; color: var(--subtext); opacity: 0.5; }
547
548 .limits-note {
549 padding: 14px 18px;
550 background: rgba(255,107,107,0.06);
551 border: 1px solid rgba(255,107,107,0.18);
552 border-radius: 10px;
553 font-size: 0.83rem;
554 color: var(--subtext);
555 line-height: 1.6;
556 }
557
558 .limits-note i { color: var(--red); margin-right: 4px; }
559
560 /* ── FOOTER ─────────────────────────────── */
561 .site-footer {
562 text-align: center;
563 padding: 0 20px 32px;
564 font-size: 0.82rem;
565 color: var(--subtext);
566 }
567
568 /* ── RESPONSIVE ─────────────────────────── */
569 @media (max-width: 600px) {
570 .stats-grid { grid-template-columns: repeat(2, 1fr); }
571 .stat-card.storage-card { flex-direction: column; text-align: center; gap: 8px; padding: 20px; }
572 .stat-card.storage-card .stat-icon { margin-bottom: 0; }
573 .site-header h1 { font-size: 1.8rem; }
574 .stat-value { font-size: 1.5rem; }
575 .how-grid, .limits-grid { grid-template-columns: 1fr; }
576 .card { padding: 22px 18px; }
577 }
578 </style>
579</head>
580<body>
581
582 <header class="site-header">
583 <h1><i class="fa-solid fa-database" style="color:#fff"></i> MBD CDN</h1>
584 <p>A simple easy to use CDN, free for life</p>
585 <div id="total-visitors" style="font-size: 0.8em; color: gray; text-align: center;">
586 Calculating total visits...
587 </div>
588 </header>
589
590 <!-- STATS -->
591 <div class="stats-wrap">
592 <div class="stats-grid">
593 <div class="stat-card">
594 <div class="stat-icon"><i class="fa-regular fa-image"></i></div>
595 <div class="stat-value loading" id="stat-images">--</div>
596 <div class="stat-label">Images</div>
597 </div>
598 <div class="stat-card">
599 <div class="stat-icon"><i class="fa-solid fa-video"></i></div>
600 <div class="stat-value loading" id="stat-videos">--</div>
601 <div class="stat-label">Videos</div>
602 </div>
603 <div class="stat-card">
604 <div class="stat-icon"><i class="fa-solid fa-photo-film"></i></div>
605 <div class="stat-value loading" id="stat-gifs">--</div>
606 <div class="stat-label">GIFs</div>
607 </div>
608 <div class="stat-card">
609 <div class="stat-icon"><i class="fa-solid fa-file-code"></i></div>
610 <div class="stat-value loading" id="stat-documents">--</div>
611 <div class="stat-label">Documents</div>
612 </div>
613 </div>
614 <div class="stats-grid-storage">
615 <div class="stat-card storage-card">
616 <div class="stat-icon"><i class="fa-solid fa-database"></i></div>
617 <div>
618 <div class="stat-value loading" id="stat-storage">--</div>
619 <div class="stat-label">Storage Used</div>
620 </div>
621 </div>
622 </div>
623 </div>
624
625 <!-- TABS -->
626 <div class="tabs-wrap">
627 <div class="tab-bar">
628 <button class="tab-btn active" onclick="switchTab('upload', this)">
629 <i class="fa-solid fa-cloud-arrow-up"></i> Upload
630 </button>
631 <a class="tab-btn" href="/cdn/about.html">
632 <i class="fa-solid fa-circle-info"></i> About
633 </a>
634 <a class="tab-btn" href="how-it-works.html">
635 <i class="fa-solid fa-gears"></i> How it Works
636 </a>
637 <a class="tab-btn" href="limits.html">
638 <i class="fa-solid fa-gauge-high"></i> Limits
639 </a>
640 </div>
641
642 <div class="tab-content">
643
644 <!-- UPLOAD -->
645 <div class="tab-pane active" id="tab-upload">
646 <div class="card">
647 <h2>Upload a File</h2>
648 <p class="desc">Images, GIFs, videos, and documents accepted. Files are served globally via Cloudflare immediately after upload.</p>
649
650 <label class="drop-zone" id="drop-zone" for="file-input">
651 <i class="fa-solid fa-cloud-arrow-up" id="drop-icon"></i>
652 <span id="file-name">Click to select or drag a file here</span>
653 <input type="file" id="file-input" accept="image/*,video/*,text/html,text/css,text/javascript,text/plain,text/csv,text/xml,text/markdown,text/yaml,application/json,application/xml,application/pdf,application/javascript,application/x-yaml">
654 </label>
655
656 <div class="file-info" id="file-info">
657 <div><b>File:</b> <span id="detail-name"></span></div>
658 <div style="margin-top:4px"><b>Size:</b> <span id="detail-size"></span> · <b>Type:</b> <span id="detail-type"></span></div>
659 </div>
660
661 <div class="progress-wrap" id="progress-wrap">
662 <div class="progress-track">
663 <div class="progress-fill" id="progress-fill"></div>
664 </div>
665 <div class="progress-label" id="progress-label">0%</div>
666 </div>
667
668 <button class="upload-btn" id="upload-btn">
669 <i class="fa-solid fa-cloud-arrow-up"></i> Upload to MBD CDN
670 </button>
671
672 <div class="status-msg" id="status"></div>
673
674 <div class="result-box" id="result-box">
675 <div class="result-label">✅ Public URL</div>
676 <div class="result-url" id="result-url"></div>
677 <button class="copy-btn" id="copy-btn" onclick="copyUrl()">
678 <i class="fa-solid fa-copy"></i> Copy URL
679 </button>
680 <a class="open-btn" id="open-link" href="#" target="_blank" rel="noopener">
681 <i class="fa-solid fa-arrow-up-right-from-square"></i> Open
682 </a>
683 </div>
684 </div>
685 </div>
686
687 </div>
688 </div>
689
690 <footer class="site-footer">
691 © <script>document.write(new Date().getFullYear())</script> <a href="https://madebydanny.uk" target="_blank">madebydanny.uk</a>
692 </footer>
693 <script src="https://visit-counter.madebydanny.uk?url=mbd-cdn"></script>
694 <script>
695 const API = 'https://cdn.madebydanny.uk';
696
697 function formatBytes(b) {
698 if (!b) return '0 B';
699 const k = 1024, s = ['B','KB','MB','GB','TB'];
700 const i = Math.floor(Math.log(b) / Math.log(k));
701 return (b / Math.pow(k, i)).toFixed(1).replace(/\.0$/,'') + ' ' + s[i];
702 }
703
704 function fmt(n) { return Number(n).toLocaleString(); }
705
706 // ── TABS ─────────────────────────────────────────────
707 function switchTab(name, btn) {
708 document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
709 document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
710 document.getElementById('tab-' + name).classList.add('active');
711 if (btn) btn.classList.add('active');
712 if (name === 'limits') loadLimits();
713 }
714
715 // ── STATS ─────────────────────────────────────────────
716 async function loadStats() {
717 try {
718 const r = await fetch(`${API}/stats`);
719 const d = await r.json();
720 if (d.success) {
721 const cat = d.stats.byCategory || {};
722 document.getElementById('stat-images').textContent = fmt(cat.image || 0);
723 document.getElementById('stat-videos').textContent = fmt(cat.video || 0);
724 document.getElementById('stat-gifs').textContent = fmt(cat.gif || 0);
725 document.getElementById('stat-documents').textContent = fmt(cat.document || 0);
726 document.getElementById('stat-storage').textContent = formatBytes(d.stats.totalSize);
727 document.querySelectorAll('.stat-value').forEach(v => v.classList.remove('loading'));
728 }
729 } catch {
730 document.querySelectorAll('.stat-value').forEach(v => {
731 v.textContent = '—';
732 v.classList.remove('loading');
733 });
734 }
735 }
736
737 // ── LIMITS (live from API) ────────────────────────────
738 async function loadLimits() {
739 try {
740 const r = await fetch(`${API}/limits`);
741 const d = await r.json();
742 if (!d.success) throw new Error();
743
744 const { file_count, total_size, max_files, max_bytes, max_file } = d.limits;
745
746 document.getElementById('limit-max-file').textContent = formatBytes(max_file);
747 document.getElementById('limit-max-bytes').textContent = formatBytes(max_bytes);
748 document.getElementById('limit-max-files').textContent = max_files;
749
750 const filePct = Math.min((file_count / max_files) * 100, 100);
751 const bytesPct = Math.min((total_size / max_bytes) * 100, 100);
752
753 const filesFill = document.getElementById('usage-files-fill');
754 const bytesFill = document.getElementById('usage-bytes-fill');
755
756 filesFill.style.width = filePct + '%';
757 bytesFill.style.width = bytesPct + '%';
758
759 function fillClass(pct) {
760 if (pct >= 90) return 'danger';
761 if (pct >= 70) return 'warn';
762 return '';
763 }
764
765 filesFill.className = 'usage-fill ' + fillClass(filePct);
766 bytesFill.className = 'usage-fill ' + fillClass(bytesPct);
767
768 document.getElementById('usage-files-label').textContent =
769 `${fmt(file_count)} / ${fmt(max_files)}`;
770 document.getElementById('usage-bytes-label').textContent =
771 `${formatBytes(total_size)} / ${formatBytes(max_bytes)}`;
772
773 document.getElementById('usage-loading').style.display = 'none';
774 document.getElementById('usage-bars').style.display = 'block';
775
776 } catch {
777 document.getElementById('usage-loading').textContent = 'Could not load usage data.';
778 }
779 }
780
781 // ── FILE TYPE HELPERS ─────────────────────────────────
782 const DOCUMENT_TYPES = new Set([
783 'text/html','text/css','text/javascript','text/plain','text/csv',
784 'text/xml','text/markdown','text/yaml','application/json',
785 'application/xml','application/pdf','application/javascript',
786 'application/x-yaml',
787 ]);
788
789 function getFileIcon(type) {
790 if (!type) return 'fa-solid fa-cloud-arrow-up';
791 if (type.startsWith('video/')) return 'fa-solid fa-film';
792 if (type === 'image/gif') return 'fa-solid fa-photo-film';
793 if (type.startsWith('image/')) return 'fa-regular fa-image';
794 if (type === 'application/pdf') return 'fa-solid fa-file-pdf';
795 if (type === 'application/json') return 'fa-solid fa-file-code';
796 if (type === 'text/html') return 'fa-solid fa-file-code';
797 if (type === 'text/css') return 'fa-solid fa-file-code';
798 if (type === 'text/javascript' ||
799 type === 'application/javascript') return 'fa-solid fa-file-code';
800 if (type === 'text/csv') return 'fa-solid fa-file-csv';
801 if (type === 'text/plain') return 'fa-solid fa-file-lines';
802 if (DOCUMENT_TYPES.has(type)) return 'fa-solid fa-file-code';
803 return 'fa-solid fa-cloud-arrow-up';
804 }
805
806 // ── FILE SELECT ───────────────────────────────────────
807 const fileInput = document.getElementById('file-input');
808 const dropZone = document.getElementById('drop-zone');
809 const fileInfo = document.getElementById('file-info');
810
811 function showFile(file) {
812 const type = file.type || '';
813 document.getElementById('drop-icon').className = getFileIcon(type);
814 document.getElementById('file-name').textContent = file.name;
815 document.getElementById('detail-name').textContent = file.name;
816 document.getElementById('detail-size').textContent = formatBytes(file.size);
817 document.getElementById('detail-type').textContent = type || 'Unknown';
818 fileInfo.classList.add('show');
819 }
820
821 fileInput.addEventListener('change', () => {
822 if (fileInput.files[0]) showFile(fileInput.files[0]);
823 });
824
825 dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('drag-over'); });
826 dropZone.addEventListener('dragleave', e => { e.preventDefault(); dropZone.classList.remove('drag-over'); });
827 dropZone.addEventListener('drop', e => {
828 e.preventDefault();
829 dropZone.classList.remove('drag-over');
830 const f = e.dataTransfer.files[0];
831 if (f) { fileInput.files = e.dataTransfer.files; showFile(f); }
832 });
833
834 // ── UPLOAD (XHR for real progress) ───────────────────
835 const uploadBtn = document.getElementById('upload-btn');
836 const progressWrap = document.getElementById('progress-wrap');
837 const progressFill = document.getElementById('progress-fill');
838 const progressLabel= document.getElementById('progress-label');
839 const statusEl = document.getElementById('status');
840 const resultBox = document.getElementById('result-box');
841 const resultUrl = document.getElementById('result-url');
842
843 function setStatus(msg, color) {
844 statusEl.textContent = msg;
845 statusEl.style.color = color || 'var(--link)';
846 }
847
848 function setProgress(pct) {
849 progressFill.style.width = pct + '%';
850 progressLabel.textContent = Math.round(pct) + '%';
851 }
852
853 function resetUploadUI(delay = 0) {
854 setTimeout(() => {
855 fileInput.value = '';
856 document.getElementById('drop-icon').className = 'fa-solid fa-cloud-arrow-up';
857 document.getElementById('file-name').textContent = 'Click to select or drag a file here';
858 fileInfo.classList.remove('show');
859 progressWrap.classList.remove('show');
860 setProgress(0);
861 }, delay);
862 }
863
864 uploadBtn.addEventListener('click', () => {
865 if (!fileInput.files[0]) {
866 setStatus('⚠️ Please select a file first.', 'var(--red)');
867 return;
868 }
869
870 const file = fileInput.files[0];
871 uploadBtn.disabled = true;
872 uploadBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Uploading…';
873 setStatus('');
874 resultBox.classList.remove('show');
875 progressWrap.classList.add('show');
876 setProgress(0);
877
878 const xhr = new XMLHttpRequest();
879
880 xhr.upload.addEventListener('progress', e => {
881 if (e.lengthComputable) setProgress((e.loaded / e.total) * 100);
882 });
883
884 xhr.addEventListener('load', () => {
885 setProgress(100);
886 try {
887 const data = JSON.parse(xhr.responseText);
888 if (data.success) {
889 resultUrl.textContent = data.url;
890 document.getElementById('open-link').href = data.url;
891 resultBox.classList.add('show');
892 setStatus('');
893 setTimeout(() => loadStats(), 600);
894 resetUploadUI(3000);
895 } else {
896 throw new Error(data.error || 'Upload failed');
897 }
898 } catch (err) {
899 progressWrap.classList.remove('show');
900 setStatus('❌ ' + err.message, 'var(--red)');
901 }
902
903 uploadBtn.disabled = false;
904 uploadBtn.innerHTML = '<i class="fa-solid fa-cloud-arrow-up"></i> Upload to MBD CDN';
905 });
906
907 xhr.addEventListener('error', () => {
908 progressWrap.classList.remove('show');
909 setStatus('❌ Network error. Please try again.', 'var(--red)');
910 uploadBtn.disabled = false;
911 uploadBtn.innerHTML = '<i class="fa-solid fa-cloud-arrow-up"></i> Upload to MBD CDN';
912 });
913
914 xhr.addEventListener('abort', () => {
915 progressWrap.classList.remove('show');
916 setStatus('Upload cancelled.', 'var(--subtext)');
917 uploadBtn.disabled = false;
918 uploadBtn.innerHTML = '<i class="fa-solid fa-cloud-arrow-up"></i> Upload to MBD CDN';
919 });
920
921 xhr.open('POST', API);
922 xhr.setRequestHeader('Content-Type', file.type || 'application/octet-stream');
923 xhr.send(file);
924 });
925
926 // ── COPY ─────────────────────────────────────────────
927 function copyUrl() {
928 navigator.clipboard.writeText(resultUrl.textContent);
929 const btn = document.getElementById('copy-btn');
930 btn.classList.add('copied');
931 btn.innerHTML = '<i class="fa-solid fa-check"></i> Copied!';
932 setTimeout(() => {
933 btn.classList.remove('copied');
934 btn.innerHTML = '<i class="fa-solid fa-copy"></i> Copy URL';
935 }, 2000);
936 }
937
938 // ── SOCIAL LINKS FALLBACK ─────────────────────────────
939 window.addEventListener('load', () => {
940 const el = document.getElementById('social-links');
941 if (el && !el.children.length) {
942 el.innerHTML = `
943 <a class="social-btn" href="https://madebydanny.uk" target="_blank" rel="noopener">
944 <i class="fa-solid fa-globe"></i> madebydanny.uk
945 </a>`;
946 }
947 });
948
949 // ── INIT ─────────────────────────────────────────────
950 loadStats();
951 </script>
952
953 <!-- social-links.js is optional; silence 404s by loading it async -->
954 <script>
955 (function() {
956 const s = document.createElement('script');
957 s.src = '/js/social-links.js';
958 s.onerror = () => {};
959 document.body.appendChild(s);
960 })();
961 </script>
962
963</body>
964</html>