madebydanny.uk written in html, css, and a lot of JavaScript I don't understand madebydanny.uk
html css javascript
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix

+133 -33
+85 -25
cdn/index.html
··· 70 70 71 71 .stats-grid { 72 72 display: grid; 73 - grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); 73 + grid-template-columns: repeat(4, 1fr); 74 + gap: 14px; 75 + } 76 + 77 + .stats-grid-storage { 78 + display: grid; 79 + grid-template-columns: 1fr; 74 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; 75 106 } 76 107 77 108 .stat-card { ··· 537 568 /* ── RESPONSIVE ─────────────────────────── */ 538 569 @media (max-width: 600px) { 539 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; } 540 573 .site-header h1 { font-size: 1.8rem; } 541 574 .stat-value { font-size: 1.5rem; } 542 575 .how-grid, .limits-grid { grid-template-columns: 1fr; } ··· 548 581 549 582 <header class="site-header"> 550 583 <h1><i class="fa-solid fa-database" style="color:#fff"></i> MBD CDN</h1> 551 - <p>A simple easy to use CDN, free for life</p> 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> 552 588 </header> 553 589 554 590 <!-- STATS --> ··· 570 606 <div class="stat-label">GIFs</div> 571 607 </div> 572 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"> 573 616 <div class="stat-icon"><i class="fa-solid fa-database"></i></div> 574 - <div class="stat-value loading" id="stat-storage">--</div> 575 - <div class="stat-label">Storage Used</div> 617 + <div> 618 + <div class="stat-value loading" id="stat-storage">--</div> 619 + <div class="stat-label">Storage Used</div> 620 + </div> 576 621 </div> 577 622 </div> 578 623 </div> ··· 600 645 <div class="tab-pane active" id="tab-upload"> 601 646 <div class="card"> 602 647 <h2>Upload a File</h2> 603 - <p class="desc">Images, GIFs, and videos accepted. Files are served globally via Cloudflare immediately after upload.</p> 648 + <p class="desc">Images, GIFs, videos, and documents accepted. Files are served globally via Cloudflare immediately after upload.</p> 604 649 605 650 <label class="drop-zone" id="drop-zone" for="file-input"> 606 651 <i class="fa-solid fa-cloud-arrow-up" id="drop-icon"></i> 607 652 <span id="file-name">Click to select or drag a file here</span> 608 - <input type="file" id="file-input" accept="image/*,video/*"> 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"> 609 654 </label> 610 655 611 656 <div class="file-info" id="file-info"> ··· 645 690 <footer class="site-footer"> 646 691 &copy; <script>document.write(new Date().getFullYear())</script> <a href="https://madebydanny.uk" target="_blank">madebydanny.uk</a> 647 692 </footer> 648 - 693 + <script src="https://visit-counter.madebydanny.uk?url=mbd-cdn"></script> 649 694 <script> 650 695 const API = 'https://cdn.madebydanny.uk'; 651 696 ··· 673 718 const r = await fetch(`${API}/stats`); 674 719 const d = await r.json(); 675 720 if (d.success) { 676 - document.getElementById('stat-images').textContent = fmt(d.stats.images); 677 - document.getElementById('stat-videos').textContent = fmt(d.stats.videos); 678 - document.getElementById('stat-gifs').textContent = fmt(d.stats.gifs); 679 - document.getElementById('stat-storage').textContent = formatBytes(d.stats.totalSize); 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); 680 727 document.querySelectorAll('.stat-value').forEach(v => v.classList.remove('loading')); 681 728 } 682 729 } catch { ··· 731 778 } 732 779 } 733 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 + 734 806 // ── FILE SELECT ─────────────────────────────────────── 735 807 const fileInput = document.getElementById('file-input'); 736 808 const dropZone = document.getElementById('drop-zone'); ··· 738 810 739 811 function showFile(file) { 740 812 const type = file.type || ''; 741 - const icon = document.getElementById('drop-icon'); 742 - 743 - // Update drop zone icon to match file type 744 - if (type.startsWith('video/')) { 745 - icon.className = 'fa-solid fa-film'; 746 - } else if (type === 'image/gif') { 747 - icon.className = 'fa-solid fa-photo-film'; 748 - } else if (type.startsWith('image/')) { 749 - icon.className = 'fa-regular fa-image'; 750 - } else { 751 - icon.className = 'fa-solid fa-cloud-arrow-up'; 752 - } 753 - 813 + document.getElementById('drop-icon').className = getFileIcon(type); 754 814 document.getElementById('file-name').textContent = file.name; 755 815 document.getElementById('detail-name').textContent = file.name; 756 816 document.getElementById('detail-size').textContent = formatBytes(file.size); ··· 901 961 </script> 902 962 903 963 </body> 904 - </html> 964 + </html>
+26 -8
cdn/worker.js
··· 12 12 */ 13 13 14 14 // ── LIMIT CONSTANTS ──────────────────────────────────────────────────────── 15 - const MAX_FILE_BYTES = 100 * 1024 * 1024; // 100 MB per file 16 - const MAX_DAY_BYTES = 1024 * 1024 * 1024; // 1 GB per IP per day 17 - const MAX_DAY_FILES = 30; // 30 files per IP per day 15 + const MAX_FILE_BYTES = 70 * 1024 * 1024; // 70 MB per file 16 + const MAX_DAY_BYTES = 300 * 1024 * 1024; // 300 MB per IP per day 17 + const MAX_DAY_FILES = 20; // 20 files per IP per day 18 + 19 + const CDN_BASE_URL = "https://cdn.madebydanny.uk"; 18 20 19 21 export default { 20 22 async fetch(request, env, ctx) { ··· 45 47 return new Response(null, { headers: cors }); 46 48 } 47 49 50 + // ── GET /user-content/… — Serve file from R2 ─── 51 + if (request.method === "GET" && url.pathname.startsWith("/user-content/")) { 52 + const key = url.pathname.slice(1); // strip leading / 53 + try { 54 + const object = await env.MY_BUCKET.get(key); 55 + if (!object) { 56 + return new Response("Not found", { status: 404, headers: cors }); 57 + } 58 + const headers = new Headers(cors); 59 + object.writeHttpMetadata(headers); 60 + headers.set("Cache-Control", "public, max-age=31536000, immutable"); 61 + headers.set("ETag", object.httpEtag); 62 + return new Response(object.body, { headers }); 63 + } catch (e) { 64 + return new Response("Failed to fetch file", { status: 500, headers: cors }); 65 + } 66 + } 67 + 48 68 // ── GET /stats ────────────────────────────────── 49 69 if (request.method === "GET" && url.pathname === "/stats") { 50 70 try { ··· 110 130 const path = `user-content/${dateStr}/${randomId}${fileInfo.extension}`; 111 131 112 132 // ── 2. Race: DB limit check vs R2 upload ──── 113 - // request.body is a FixedLengthStream so R2 knows the content length. 114 - // obj.size gives us the real byte count after the write completes. 115 133 const [usageResult, uploadResult] = await Promise.allSettled([ 116 134 env.DB.prepare( 117 135 `SELECT file_count, total_size FROM upload_limits WHERE ip = ? AND date = ?` ··· 174 192 ).bind(ip, today, actualSize), 175 193 ]) 176 194 ); 195 + 196 + const fileUrl = `${CDN_BASE_URL}/${path}`; 177 197 178 198 return json({ 179 199 success: true, 180 - url: `https://public-cdn.madebydanny.uk/${path}`, 200 + url: fileUrl, 181 201 path, 182 202 contentType, 183 203 fileType: fileInfo.type, ··· 204 224 env.DB.prepare(`SELECT COUNT(*) AS c FROM uploads WHERE file_type = 'image'`).first(), 205 225 env.DB.prepare(`SELECT COUNT(*) AS c FROM uploads WHERE file_type = 'video'`).first(), 206 226 env.DB.prepare(`SELECT COUNT(*) AS c FROM uploads WHERE file_type = 'gif'`).first(), 207 - // COALESCE avoids NULL when the table is empty, which D1 handles more reliably 208 227 env.DB.prepare(`SELECT COALESCE(SUM(size), 0) AS s FROM uploads`).first(), 209 228 ]); 210 229 ··· 262 281 if (t.includes("audio/wav")) return { type: "audio", extension: ".wav" }; 263 282 if (t.includes("audio/")) return { type: "audio", extension: "" }; 264 283 265 - // Generic image catch-all comes last so it never swallows video/* types 266 284 if (t.includes("image/")) return { type: "image", extension: "" }; 267 285 268 286 return { type: "other", extension: "" };
test.html

This is a binary file and will not be displayed.

+22
test.txt
··· 1 + Your Bluesky Account has been deactivated 2 + 3 + Dear {{handle}}, 4 + 5 + Your Bluesky account, {{handle}}, has been deactivated/taken down. This can happen for multiple reasons: 6 + 7 + 1. You deactivated your account via the Bluesky App (eg, bsky.app, blacksky.community) 8 + 2. Your account was deactivated by the Bluesky Moderation Team 9 + 3. You have migrated your to a different PDS 10 + 4. You have violated our Terms of Service (https://mbdio.uk/tos) 11 + 12 + If you did not deactivate your account or migrate and wish to continue using your account, please contact us at contact@mbdio.uk 13 + 14 + We will never delete your account, even if you violated our TOS 15 + 16 + Cheers, 17 + MBDIO.uk 18 + 19 + --- 20 + 21 + Please do not reply to this message. 22 + If you need support, please contact us at contact@mbdio.uk