this string has no description
ignore
edited
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(auto-fit, minmax(190px, 1fr));
74 gap: 14px;
75 }
76
77 .stat-card {
78 background: linear-gradient(135deg, var(--stat-bg) 0%, var(--card-bg) 100%);
79 border: 1px solid var(--border);
80 border-radius: 12px;
81 padding: 20px;
82 text-align: center;
83 position: relative;
84 overflow: hidden;
85 transition: transform 0.2s, box-shadow 0.2s;
86 }
87
88 .stat-card::before {
89 content: '';
90 position: absolute;
91 top: 0; left: 0; right: 0;
92 height: 3px;
93 background: linear-gradient(90deg, var(--link), rgba(255,204,204,0.25));
94 opacity: 0;
95 transition: opacity 0.3s;
96 }
97
98 .stat-card:hover { transform: translateY(-4px); box-shadow: 0 8px 20px rgba(0,0,0,0.4); }
99 .stat-card:hover::before { opacity: 1; }
100
101 .stat-icon { font-size: 1.8rem; margin-bottom: 8px; opacity: 0.75; }
102
103 .stat-value {
104 font-size: 2rem;
105 font-weight: 700;
106 letter-spacing: -1px;
107 margin: 4px 0;
108 }
109
110 .stat-value.loading { opacity: 0.35; animation: blink 1.4s ease-in-out infinite; }
111 @keyframes blink { 0%,100%{opacity:.35} 50%{opacity:.7} }
112
113 .stat-label {
114 font-size: 0.8rem;
115 color: var(--subtext);
116 text-transform: uppercase;
117 letter-spacing: 0.5px;
118 font-weight: 600;
119 }
120
121 /* ── TABS ───────────────────────────────── */
122 .tabs-wrap {
123 max-width: 900px;
124 margin: 36px auto 0;
125 padding: 0 20px;
126 }
127
128 .tab-bar {
129 display: flex;
130 gap: 4px;
131 border-bottom: 1px solid var(--border);
132 overflow-x: auto;
133 scrollbar-width: none;
134 }
135
136 .tab-bar::-webkit-scrollbar { display: none; }
137
138 .tab-btn {
139 font-family: inherit;
140 font-size: 0.9rem;
141 font-weight: 600;
142 color: var(--subtext);
143 background: none;
144 border: none;
145 border-bottom: 2px solid transparent;
146 padding: 10px 18px;
147 cursor: pointer;
148 white-space: nowrap;
149 transition: color 0.2s, border-color 0.2s;
150 margin-bottom: -1px;
151 }
152
153 .tab-btn:hover { color: var(--text); }
154 .tab-btn.active { color: var(--link); border-bottom-color: var(--link); }
155
156 .tab-content { padding: 32px 0 80px; }
157
158 .tab-pane { display: none; }
159 .tab-pane.active { display: block; }
160
161 /* ── CARD ───────────────────────────────── */
162 .card {
163 background: var(--card-bg);
164 border: 1px solid var(--border);
165 border-radius: 16px;
166 padding: 32px;
167 box-shadow: 0 6px 12px rgba(0,0,0,0.35);
168 }
169
170 .card + .card { margin-top: 20px; }
171
172 .card h2 {
173 font-size: 1.25rem;
174 font-weight: 700;
175 letter-spacing: -0.4px;
176 margin-bottom: 8px;
177 }
178
179 .card .desc {
180 color: var(--subtext);
181 font-size: 0.9rem;
182 margin-bottom: 20px;
183 }
184
185 hr.divider {
186 border: none;
187 border-top: 1px solid var(--border);
188 margin: 28px 0;
189 }
190
191 /* ── UPLOAD ─────────────────────────────── */
192 .drop-zone {
193 display: block;
194 border: 2px dashed var(--border);
195 border-radius: 12px;
196 padding: 50px 20px;
197 text-align: center;
198 cursor: pointer;
199 background: rgba(0,0,0,0.2);
200 color: var(--subtext);
201 transition: all 0.25s;
202 }
203
204 .drop-zone:hover {
205 border-color: var(--link);
206 background: rgba(255,255,255,0.02);
207 transform: scale(1.005);
208 }
209
210 .drop-zone.drag-over {
211 border-color: var(--green);
212 background: rgba(52,199,89,0.05);
213 transform: scale(1.01);
214 }
215
216 .drop-zone i { font-size: 2.4rem; display: block; margin-bottom: 12px; opacity: 0.65; }
217 .drop-zone input { display: none; }
218
219 #file-name { font-size: 0.95rem; font-weight: 500; display: block; margin-top: 4px; }
220
221 .file-info {
222 display: none;
223 margin-top: 14px;
224 padding: 12px 16px;
225 background: var(--post-bg);
226 border: 1px solid var(--border);
227 border-radius: 8px;
228 font-size: 0.82rem;
229 color: var(--subtext);
230 }
231
232 .file-info.show { display: block; }
233
234 .progress-wrap { display: none; margin-top: 14px; }
235 .progress-wrap.show { display: block; }
236
237 .progress-track {
238 height: 7px;
239 background: var(--post-bg);
240 border-radius: 999px;
241 overflow: hidden;
242 border: 1px solid var(--border);
243 }
244
245 .progress-fill {
246 height: 100%;
247 width: 0%;
248 background: linear-gradient(90deg, var(--link), var(--green));
249 border-radius: 999px;
250 transition: width 0.3s ease;
251 }
252
253 .upload-btn {
254 display: block;
255 width: 100%;
256 margin-top: 18px;
257 padding: 14px 24px;
258 background: linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%);
259 color: #000;
260 border: none;
261 border-radius: 999px;
262 font-family: inherit;
263 font-size: 1rem;
264 font-weight: 600;
265 cursor: pointer;
266 box-shadow: 0 2px 8px rgba(255,255,255,0.08);
267 transition: all 0.2s;
268 }
269
270 .upload-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 14px rgba(255,255,255,0.18); }
271 .upload-btn:active { transform: scale(0.985); }
272 .upload-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: none; }
273
274 .status-msg {
275 text-align: center;
276 margin-top: 14px;
277 font-size: 0.9rem;
278 font-weight: 500;
279 min-height: 1.3em;
280 color: var(--link);
281 }
282
283 .result-box {
284 display: none;
285 margin-top: 18px;
286 padding: 18px;
287 background: var(--post-bg);
288 border: 1px solid var(--green);
289 border-radius: 12px;
290 animation: slideIn 0.3s ease;
291 }
292
293 .result-box.show { display: block; }
294
295 @keyframes slideIn {
296 from { opacity: 0; transform: translateY(-8px); }
297 to { opacity: 1; transform: translateY(0); }
298 }
299
300 .result-label {
301 font-size: 0.72rem;
302 font-weight: 700;
303 text-transform: uppercase;
304 letter-spacing: 0.6px;
305 color: var(--subtext);
306 margin-bottom: 8px;
307 }
308
309 .result-url {
310 font-family: 'Monaco', 'Courier New', monospace;
311 font-size: 0.85rem;
312 padding: 10px 12px;
313 background: rgba(0,0,0,0.3);
314 border: 1px solid var(--border);
315 border-radius: 6px;
316 word-break: break-all;
317 color: var(--text);
318 margin-bottom: 12px;
319 }
320
321 .copy-btn {
322 display: inline-flex;
323 align-items: center;
324 gap: 7px;
325 padding: 9px 16px;
326 background: rgba(255,255,255,0.05);
327 border: 1px solid var(--border);
328 border-radius: 8px;
329 color: var(--link);
330 font-family: inherit;
331 font-size: 0.85rem;
332 font-weight: 600;
333 cursor: pointer;
334 transition: all 0.15s;
335 margin-right: 8px;
336 }
337
338 .copy-btn:hover { background: rgba(255,255,255,0.09); }
339 .copy-btn.copied { background: var(--green); color: #000; border-color: var(--green); }
340
341 .open-btn {
342 display: inline-flex;
343 align-items: center;
344 gap: 7px;
345 padding: 9px 16px;
346 background: rgba(255,255,255,0.05);
347 border: 1px solid var(--border);
348 border-radius: 8px;
349 color: var(--link);
350 font-size: 0.85rem;
351 font-weight: 600;
352 transition: all 0.15s;
353 }
354
355 .open-btn:hover { background: rgba(255,255,255,0.09); text-decoration: none; }
356
357 /* ── ABOUT ──────────────────────────────── */
358 .about-text {
359 color: var(--subtext);
360 font-size: 0.92rem;
361 line-height: 1.7;
362 }
363
364 .about-text p + p { margin-top: 12px; }
365
366 .social-row {
367 display: flex;
368 flex-wrap: wrap;
369 gap: 10px;
370 justify-content: center;
371 }
372
373 .social-btn {
374 display: inline-flex;
375 align-items: center;
376 gap: 8px;
377 padding: 9px 16px;
378 background: rgba(255,255,255,0.04);
379 border: 1px solid var(--border);
380 border-radius: 999px;
381 color: var(--text);
382 font-size: 0.88rem;
383 font-weight: 600;
384 transition: all 0.2s;
385 }
386
387 .social-btn:hover {
388 background: rgba(255,255,255,0.08);
389 transform: translateY(-2px);
390 box-shadow: 0 4px 12px rgba(0,0,0,0.3);
391 text-decoration: none;
392 }
393
394 /* ── HOW IT WORKS ───────────────────────── */
395 .steps {
396 display: flex;
397 flex-direction: column;
398 gap: 18px;
399 margin-bottom: 28px;
400 }
401
402 .step {
403 display: flex;
404 align-items: flex-start;
405 gap: 16px;
406 }
407
408 .step-num {
409 flex-shrink: 0;
410 width: 34px; height: 34px;
411 border-radius: 50%;
412 background: linear-gradient(135deg, var(--stat-bg), var(--card-bg));
413 border: 1px solid var(--border);
414 display: flex;
415 align-items: center;
416 justify-content: center;
417 font-size: 0.78rem;
418 font-weight: 700;
419 color: var(--link);
420 }
421
422 .step-body h3 { font-size: 0.95rem; font-weight: 700; margin-bottom: 3px; }
423 .step-body p { color: var(--subtext); font-size: 0.85rem; line-height: 1.55; }
424
425 .how-grid {
426 display: grid;
427 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
428 gap: 14px;
429 }
430
431 .how-card {
432 background: var(--post-bg);
433 border: 1px solid var(--border);
434 border-radius: 10px;
435 padding: 18px;
436 }
437
438 .how-card i { font-size: 1.5rem; color: var(--link); margin-bottom: 10px; display: block; }
439 .how-card h3 { font-size: 0.9rem; font-weight: 700; margin-bottom: 5px; }
440 .how-card p { font-size: 0.8rem; color: var(--subtext); line-height: 1.55; }
441
442 /* ── LIMITS ─────────────────────────────── */
443 .limits-grid {
444 display: grid;
445 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
446 gap: 14px;
447 margin-bottom: 22px;
448 }
449
450 .limit-card {
451 background: var(--post-bg);
452 border: 1px solid var(--border);
453 border-radius: 12px;
454 padding: 22px 18px;
455 text-align: center;
456 position: relative;
457 overflow: hidden;
458 }
459
460 .limit-card::before {
461 content: '';
462 position: absolute;
463 top: 0; left: 0; right: 0;
464 height: 3px;
465 background: linear-gradient(90deg, var(--link), rgba(255,204,204,0.2));
466 }
467
468 .limit-card i { font-size: 1.8rem; color: var(--link); margin-bottom: 12px; display: block; opacity: 0.8; }
469 .limit-value { font-size: 1.7rem; font-weight: 700; letter-spacing: -0.5px; margin-bottom: 4px; }
470 .limit-label { font-size: 0.82rem; color: var(--subtext); font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; }
471 .limit-note { margin-top: 5px; font-size: 0.72rem; color: rgba(220,186,186,0.5); }
472
473 .limits-note {
474 padding: 14px 18px;
475 background: rgba(255,107,107,0.06);
476 border: 1px solid rgba(255,107,107,0.18);
477 border-radius: 10px;
478 font-size: 0.83rem;
479 color: var(--subtext);
480 line-height: 1.6;
481 }
482
483 .limits-note i { color: var(--red); margin-right: 4px; }
484
485 /* ── FOOTER ─────────────────────────────── */
486 .site-footer {
487 text-align: center;
488 padding: 0 20px 32px;
489 font-size: 0.82rem;
490 color: var(--subtext);
491 }
492
493 /* ── RESPONSIVE ─────────────────────────── */
494 @media (max-width: 600px) {
495 .stats-grid { grid-template-columns: repeat(2, 1fr); }
496 .site-header h1 { font-size: 1.8rem; }
497 .stat-value { font-size: 1.5rem; }
498 .how-grid, .limits-grid { grid-template-columns: 1fr; }
499 .card { padding: 22px 18px; }
500 }
501 </style>
502</head>
503<body>
504
505 <header class="site-header">
506 <h1><i class="fa-solid fa-database" style="color:#fff"></i> MBD CDN</h1>
507 <p>A network of servers located around the world powered by the Cloudflare global network — delivering media at extremely fast speeds with 100% uptime.</p>
508 </header>
509
510 <!-- STATS -->
511 <div class="stats-wrap">
512 <div class="stats-grid">
513 <div class="stat-card">
514 <div class="stat-icon"><i class="fa-regular fa-image"></i></div>
515 <div class="stat-value loading" id="stat-images">--</div>
516 <div class="stat-label">Images</div>
517 </div>
518 <div class="stat-card">
519 <div class="stat-icon"><i class="fa-solid fa-video"></i></div>
520 <div class="stat-value loading" id="stat-videos">--</div>
521 <div class="stat-label">Videos</div>
522 </div>
523 <div class="stat-card">
524 <div class="stat-icon"><i class="fa-solid fa-photo-film"></i></div>
525 <div class="stat-value loading" id="stat-gifs">--</div>
526 <div class="stat-label">GIFs</div>
527 </div>
528 <div class="stat-card">
529 <div class="stat-icon"><i class="fa-solid fa-database"></i></div>
530 <div class="stat-value loading" id="stat-storage">--</div>
531 <div class="stat-label">Storage Used</div>
532 </div>
533 </div>
534 </div>
535
536 <!-- TABS -->
537 <div class="tabs-wrap">
538 <div class="tab-bar">
539 <button class="tab-btn active" onclick="switchTab('upload', this)">
540 <i class="fa-solid fa-cloud-arrow-up"></i> Upload
541 </button>
542 <button class="tab-btn" onclick="switchTab('about', this)">
543 <i class="fa-solid fa-circle-info"></i> About
544 </button>
545 <button class="tab-btn" onclick="switchTab('how', this)">
546 <i class="fa-solid fa-gears"></i> How it Works
547 </button>
548 <button class="tab-btn" onclick="switchTab('limits', this)">
549 <i class="fa-solid fa-gauge-high"></i> Limits
550 </button>
551 </div>
552
553 <div class="tab-content">
554
555 <!-- UPLOAD -->
556 <div class="tab-pane active" id="tab-upload">
557 <div class="card">
558 <h2>Upload a File</h2>
559 <p class="desc">Images, GIFs, and videos accepted. Files are served globally via Cloudflare immediately after upload.</p>
560
561 <label class="drop-zone" id="drop-zone" for="file-input">
562 <i class="fa-solid fa-cloud-arrow-up"></i>
563 <span id="file-name">Click to select or drag a file here</span>
564 <input type="file" id="file-input" accept="image/*,video/*">
565 </label>
566
567 <div class="file-info" id="file-info">
568 <div><b>File:</b> <span id="detail-name"></span></div>
569 <div style="margin-top:4px"><b>Size:</b> <span id="detail-size"></span> · <b>Type:</b> <span id="detail-type"></span></div>
570 </div>
571
572 <div class="progress-wrap" id="progress-wrap">
573 <div class="progress-track">
574 <div class="progress-fill" id="progress-fill"></div>
575 </div>
576 </div>
577
578 <button class="upload-btn" id="upload-btn">
579 <i class="fa-solid fa-cloud-arrow-up"></i> Upload to MBD CDN
580 </button>
581
582 <div class="status-msg" id="status"></div>
583
584 <div class="result-box" id="result-box">
585 <div class="result-label">✅ Public URL</div>
586 <div class="result-url" id="result-url"></div>
587 <button class="copy-btn" id="copy-btn" onclick="copyUrl()">
588 <i class="fa-solid fa-copy"></i> Copy URL
589 </button>
590 <a class="open-btn" id="open-link" href="#" target="_blank" rel="noopener">
591 <i class="fa-solid fa-arrow-up-right-from-square"></i> Open
592 </a>
593 </div>
594 </div>
595 </div>
596
597 <!-- ABOUT -->
598 <div class="tab-pane" id="tab-about">
599 <div class="card">
600 <h2>About MBD CDN</h2>
601 <div class="about-text">
602 <p>The MBD CDN is a content delivery network built by <a href="https://madebydanny.uk" target="_blank">madebydanny.uk</a> to host and serve media files — images, GIFs, and videos — at extremely fast speeds with global availability.</p>
603 <p>Files uploaded to the CDN are stored in Cloudflare R2 object storage and served from Cloudflare's global edge network, which spans over 310 cities worldwide. This means your media is delivered from a server geographically close to whoever is viewing it — minimising latency and maximising load speed.</p>
604 <p>The platform is designed to be simple and permanent. Files are stored indefinitely once uploaded and are immediately available via a public URL.</p>
605 </div>
606
607 <hr class="divider">
608
609 <h2 style="margin-bottom:16px;">Social Links</h2>
610 <div id="social-links" class="social-row"></div>
611 </div>
612 </div>
613
614 <!-- HOW IT WORKS -->
615 <div class="tab-pane" id="tab-how">
616 <div class="card">
617 <h2>How it Works</h2>
618 <p class="desc">From the moment you click Upload to the moment someone loads your file — here's what happens.</p>
619
620 <div class="steps">
621 <div class="step">
622 <div class="step-num">1</div>
623 <div class="step-body">
624 <h3>You select a file</h3>
625 <p>Your file is read locally in the browser and sent directly to the CDN API over HTTPS. It goes straight to the Cloudflare edge — no intermediate servers involved.</p>
626 </div>
627 </div>
628 <div class="step">
629 <div class="step-num">2</div>
630 <div class="step-body">
631 <h3>The Worker receives it</h3>
632 <p>A Cloudflare Worker handles the upload at the edge. It assigns a UUID filename, detects the file type, and streams the body directly into R2 object storage — with no cold starts and near-instant response times.</p>
633 </div>
634 </div>
635 <div class="step">
636 <div class="step-num">3</div>
637 <div class="step-body">
638 <h3>R2 stores it permanently</h3>
639 <p>The file is written to Cloudflare R2 — S3-compatible storage with zero egress fees and 11 nines of durability. Upload metadata is logged to D1 (Cloudflare's edge SQL database) to track stats.</p>
640 </div>
641 </div>
642 <div class="step">
643 <div class="step-num">4</div>
644 <div class="step-body">
645 <h3>You get a public URL</h3>
646 <p>A permanent <code style="font-size:0.8rem;color:var(--link)">public-cdn.madebydanny.uk</code> link is returned instantly. Anyone with it can access the file — served from whichever Cloudflare PoP is closest to them.</p>
647 </div>
648 </div>
649 </div>
650
651 <div class="how-grid">
652 <div class="how-card">
653 <i class="fa-brands fa-cloudflare"></i>
654 <h3>310+ Edge Locations</h3>
655 <p>Files are cached and served globally — sub-50ms for most users regardless of where they are.</p>
656 </div>
657 <div class="how-card">
658 <i class="fa-solid fa-database"></i>
659 <h3>R2 Object Storage</h3>
660 <p>Zero egress fees, no expiry. Files are stored in Cloudflare R2 with enterprise-grade durability.</p>
661 </div>
662 <div class="how-card">
663 <i class="fa-solid fa-bolt"></i>
664 <h3>Zero Cold Starts</h3>
665 <p>Workers run at the edge with no spin-up delay — every upload and file request is handled immediately.</p>
666 </div>
667 </div>
668 </div>
669 </div>
670
671 <!-- LIMITS -->
672 <div class="tab-pane" id="tab-limits">
673 <div class="card">
674 <h2>Usage Limits</h2>
675 <p class="desc">Fair-use limits are in place to keep the CDN reliable and available for everyone.</p>
676
677 <div class="limits-grid">
678 <div class="limit-card">
679 <i class="fa-solid fa-file-arrow-up"></i>
680 <div class="limit-value">100 MB</div>
681 <div class="limit-label">Max File Size</div>
682 <div class="limit-note">Per individual upload</div>
683 </div>
684 <div class="limit-card">
685 <i class="fa-solid fa-hard-drive"></i>
686 <div class="limit-value">500 MB</div>
687 <div class="limit-label">Daily Storage</div>
688 <div class="limit-note">Total uploads per day</div>
689 </div>
690 <div class="limit-card">
691 <i class="fa-solid fa-arrow-up-from-bracket"></i>
692 <div class="limit-value">30</div>
693 <div class="limit-label">Uploads Per Day</div>
694 <div class="limit-note">Resets at midnight UTC</div>
695 </div>
696 </div>
697
698 <div class="limits-note">
699 <i class="fa-solid fa-circle-exclamation"></i>
700 All limits reset daily at <strong>midnight UTC</strong>. They are enforced globally to protect performance for all users. If you need higher limits, consider self-hosting the stack or get in touch via the social links in the About section.
701 </div>
702
703 <hr class="divider">
704
705 <h2 style="font-size:1rem; margin-bottom:10px;">Accepted File Types</h2>
706 <p style="color:var(--subtext); font-size:0.85rem; line-height:1.7;">
707 <strong style="color:var(--text)">Images:</strong> JPEG, PNG, WebP, AVIF, SVG
708 <strong style="color:var(--text)">Animated:</strong> GIF
709 <strong style="color:var(--text)">Video:</strong> MP4, WebM, MOV
710 </p>
711 </div>
712 </div>
713
714 </div>
715 </div>
716
717 <footer class="site-footer" id="site-footer"></footer>
718 <script src="/js/social-links.js"></script>
719
720 <script>
721 const API = 'https://cdn.madebydanny.uk';
722
723 function formatBytes(b) {
724 if (!b) return '0 B';
725 const k = 1024, s = ['B','KB','MB','GB','TB'];
726 const i = Math.floor(Math.log(b) / Math.log(k));
727 return (b / Math.pow(k, i)).toFixed(1).replace(/\.0$/,'') + ' ' + s[i];
728 }
729
730 function fmt(n) { return Number(n).toLocaleString(); }
731
732 // ── TABS ────────────────────────────────────────────
733 function switchTab(name, btn) {
734 document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
735 document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
736 document.getElementById('tab-' + name).classList.add('active');
737 if (btn) btn.classList.add('active');
738 }
739
740 // ── STATS ───────────────────────────────────────────
741 async function loadStats() {
742 try {
743 const r = await fetch(`${API}/stats`);
744 const d = await r.json();
745 if (d.success) {
746 document.getElementById('stat-images').textContent = fmt(d.stats.images);
747 document.getElementById('stat-videos').textContent = fmt(d.stats.videos);
748 document.getElementById('stat-gifs').textContent = fmt(d.stats.gifs);
749 document.getElementById('stat-storage').textContent = formatBytes(d.stats.totalSize);
750 document.querySelectorAll('.stat-value').forEach(v => v.classList.remove('loading'));
751 }
752 } catch {
753 document.querySelectorAll('.stat-value').forEach(v => {
754 v.textContent = '—';
755 v.classList.remove('loading');
756 });
757 }
758 }
759
760 // ── FILE SELECT ─────────────────────────────────────
761 const fileInput = document.getElementById('file-input');
762 const dropZone = document.getElementById('drop-zone');
763 const fileInfo = document.getElementById('file-info');
764
765 function showFile(file) {
766 document.getElementById('file-name').textContent = file.name;
767 document.getElementById('detail-name').textContent = file.name;
768 document.getElementById('detail-size').textContent = formatBytes(file.size);
769 document.getElementById('detail-type').textContent = file.type || 'Unknown';
770 fileInfo.classList.add('show');
771 }
772
773 fileInput.addEventListener('change', () => {
774 if (fileInput.files[0]) showFile(fileInput.files[0]);
775 });
776
777 dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('drag-over'); });
778 dropZone.addEventListener('dragleave', e => { e.preventDefault(); dropZone.classList.remove('drag-over'); });
779 dropZone.addEventListener('drop', e => {
780 e.preventDefault();
781 dropZone.classList.remove('drag-over');
782 const f = e.dataTransfer.files[0];
783 if (f) { fileInput.files = e.dataTransfer.files; showFile(f); }
784 });
785
786 // ── UPLOAD ──────────────────────────────────────────
787 const uploadBtn = document.getElementById('upload-btn');
788 const progressWrap = document.getElementById('progress-wrap');
789 const progressFill = document.getElementById('progress-fill');
790 const statusEl = document.getElementById('status');
791 const resultBox = document.getElementById('result-box');
792 const resultUrl = document.getElementById('result-url');
793
794 function setStatus(msg, color) {
795 statusEl.textContent = msg;
796 statusEl.style.color = color || 'var(--link)';
797 }
798
799 uploadBtn.addEventListener('click', async () => {
800 if (!fileInput.files[0]) {
801 setStatus('⚠️ Please select a file first.', 'var(--red)');
802 return;
803 }
804
805 const file = fileInput.files[0];
806 uploadBtn.disabled = true;
807 uploadBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Uploading…';
808 setStatus('');
809 resultBox.classList.remove('show');
810 progressWrap.classList.add('show');
811 progressFill.style.width = '0%';
812
813 let prog = 0;
814 const tick = setInterval(() => {
815 prog += Math.random() * 15;
816 if (prog > 90) prog = 90;
817 progressFill.style.width = prog + '%';
818 }, 200);
819
820 try {
821 const res = await fetch(API, {
822 method: 'POST',
823 body: file,
824 headers: { 'Content-Type': file.type || 'application/octet-stream' }
825 });
826
827 clearInterval(tick);
828 progressFill.style.width = '100%';
829
830 const data = await res.json();
831
832 if (data.success) {
833 resultUrl.textContent = data.url;
834 document.getElementById('open-link').href = data.url;
835 resultBox.classList.add('show');
836 setStatus('');
837 setTimeout(() => loadStats(), 600);
838 setTimeout(() => {
839 fileInput.value = '';
840 document.getElementById('file-name').textContent = 'Click to select or drag a file here';
841 fileInfo.classList.remove('show');
842 progressWrap.classList.remove('show');
843 }, 3000);
844 } else {
845 throw new Error(data.error || 'Upload failed');
846 }
847 } catch(err) {
848 clearInterval(tick);
849 progressWrap.classList.remove('show');
850 setStatus('❌ ' + err.message, 'var(--red)');
851 } finally {
852 uploadBtn.disabled = false;
853 uploadBtn.innerHTML = '<i class="fa-solid fa-cloud-arrow-up"></i> Upload to MBD CDN';
854 }
855 });
856
857 // ── COPY ────────────────────────────────────────────
858 function copyUrl() {
859 navigator.clipboard.writeText(resultUrl.textContent);
860 const btn = document.getElementById('copy-btn');
861 btn.classList.add('copied');
862 btn.innerHTML = '<i class="fa-solid fa-check"></i> Copied!';
863 setTimeout(() => {
864 btn.classList.remove('copied');
865 btn.innerHTML = '<i class="fa-solid fa-copy"></i> Copy URL';
866 }, 2000);
867 }
868
869 // ── INIT ────────────────────────────────────────────
870 loadStats();
871 </script>
872</body>
873</html>