madebydanny.uk written in html, css, and a lot of JavaScript I don't understand madeydanny.uk
html css javascript
at main 964 lines 32 kB view raw
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> &nbsp;·&nbsp; <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 &copy; <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>