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.

at e247b94ec12d22699cdeb3e853ade7d6a033cf36 674 lines 23 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>IMRS — Image Resizing Service · madebydanny.uk</title> 7 <meta name="description" content="A free image resizing and AVIF conversion service built on Cloudflare Workers. Pass any image URL and get it back optimised."> 8 <meta name="author" content="Daniel Morrisey"> 9 <meta name="robots" content="index, follow"> 10 11 <!-- Open Graph --> 12 <meta property="og:title" content="IMRS — Image Resizing Service · madebydanny.uk"> 13 <meta property="og:description" content="A free image resizing and AVIF conversion service built on Cloudflare Workers."> 14 <meta property="og:type" content="website"> 15 <meta property="og:url" content="https://imrs.madebydanny.uk/"> 16 17 <link rel="canonical" href="https://imrs.madebydanny.uk/"> 18 <link rel="icon" type="image/png" href="https://cdn.blueat.net/img/avatar/plain/did:plc:l37td5yhxl2irrzrgvei4qay/bafkreidfielr2nk5xr4v2odm5bth5yjk5y536ex5qpxsheq2ocl2qobwt4"> 19 20 <link rel="preconnect" href="https://fonts.googleapis.com"> 21 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 22 <link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,600;1,400&family=DM+Sans:wght@400;500&display=swap" rel="stylesheet"> 23 <script src="https://kit.fontawesome.com/0ca27f8db1.js" crossorigin="anonymous"></script> 24 25 <style> 26 :root { 27 --bg: #0e0d0c; 28 --bg-raised: #171512; 29 --bg-card: #1a1815; 30 --border: #2d2926; 31 --border-hover:#4a4238; 32 --text: #e8e0d8; 33 --text-muted: #8a7f74; 34 --text-dim: #584f47; 35 --accent: #c9a96e; 36 --accent-dim: rgba(201, 169, 110, 0.12); 37 --green: #4caf7d; 38 --red: #e06c6c; 39 --font-serif: 'Lora', Georgia, serif; 40 --font-sans: 'DM Sans', system-ui, sans-serif; 41 --font-mono: 'Monaco', 'Courier New', monospace; 42 --radius: 10px; 43 --transition: 0.2s ease; 44 --max-w: 680px; 45 } 46 47 *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } 48 html { scroll-behavior: smooth; } 49 50 body { 51 font-family: var(--font-sans); 52 font-size: 16px; 53 line-height: 1.7; 54 color: var(--text); 55 background-color: var(--bg); 56 -webkit-font-smoothing: antialiased; 57 } 58 59 h1, h2, h3 { font-family: var(--font-serif); font-weight: 400; line-height: 1.2; } 60 61 a { color: var(--accent); text-decoration: none; transition: opacity var(--transition); } 62 a:hover { opacity: 0.75; } 63 64 code { 65 font-family: var(--font-mono); 66 font-size: 0.875em; 67 background: var(--bg-raised); 68 padding: 0.15em 0.45em; 69 border-radius: 4px; 70 color: var(--accent); 71 word-break: break-all; 72 } 73 74 /* ── Header ── */ 75 76 .site-header { 77 position: sticky; 78 top: 0; 79 z-index: 100; 80 background: rgba(14, 13, 12, 0.92); 81 backdrop-filter: blur(12px); 82 border-bottom: 1px solid var(--border); 83 } 84 85 .nav-container { 86 max-width: var(--max-w); 87 margin: 0 auto; 88 padding: 0.875rem 1.5rem; 89 display: flex; 90 justify-content: space-between; 91 align-items: center; 92 } 93 94 .nav-logo { 95 font-family: var(--font-serif); 96 font-style: italic; 97 font-size: 1.1rem; 98 color: var(--text-muted); 99 } 100 .nav-logo:hover { color: var(--text); opacity: 1; } 101 102 .nav-title { 103 font-family: var(--font-serif); 104 font-size: 1rem; 105 color: var(--text); 106 } 107 108 /* ── Main ── */ 109 110 .main-content { 111 max-width: var(--max-w); 112 margin: 0 auto; 113 padding: 0 1.5rem; 114 } 115 116 /* ── Hero ── */ 117 118 .hero { padding: 4rem 0 3rem; } 119 120 .hero-eyebrow { 121 font-size: 0.875rem; 122 color: var(--text-muted); 123 margin-bottom: 0.25rem; 124 letter-spacing: 0.04em; 125 } 126 127 .hero-title { 128 font-size: clamp(2.5rem, 6vw, 3.5rem); 129 color: var(--text); 130 margin-bottom: 1.25rem; 131 letter-spacing: -0.02em; 132 } 133 134 .hero-bio { 135 font-size: 1.0625rem; 136 color: var(--text-muted); 137 line-height: 1.8; 138 max-width: 560px; 139 margin-bottom: 1.5rem; 140 } 141 142 .hero-bio a { 143 color: var(--accent); 144 border-bottom: 1px solid var(--accent-dim); 145 transition: border-color var(--transition), opacity var(--transition); 146 } 147 .hero-bio a:hover { opacity: 1; border-bottom-color: var(--accent); } 148 149 .hero-badges { 150 display: flex; 151 flex-wrap: wrap; 152 gap: 0.5rem; 153 } 154 155 .badge { 156 display: inline-flex; 157 align-items: center; 158 gap: 0.4rem; 159 font-size: 0.8125rem; 160 color: var(--text-muted); 161 background: var(--bg-raised); 162 border: 1px solid var(--border); 163 padding: 0.25rem 0.875rem; 164 border-radius: 999px; 165 letter-spacing: 0.01em; 166 } 167 168 .badge i { font-size: 0.75rem; color: var(--accent); } 169 170 /* ── Sections ── */ 171 172 .content-section { 173 padding: 2.5rem 0; 174 border-top: 1px solid var(--border); 175 } 176 177 .section-label { 178 font-family: var(--font-sans); 179 font-size: 0.75rem; 180 font-weight: 500; 181 text-transform: uppercase; 182 letter-spacing: 0.1em; 183 color: var(--text-dim); 184 margin-bottom: 1.25rem; 185 } 186 187 /* ── Try it card ── */ 188 189 .try-card { 190 background: var(--bg-card); 191 border: 1px solid var(--border); 192 border-radius: var(--radius); 193 overflow: hidden; 194 } 195 196 .try-card-body { padding: 1.25rem; } 197 198 .url-row { 199 display: flex; 200 gap: 0.5rem; 201 } 202 203 .url-input { 204 flex: 1; 205 background: var(--bg-raised); 206 border: 1px solid var(--border); 207 border-radius: 999px; 208 padding: 0.6rem 1rem; 209 font-family: var(--font-mono); 210 font-size: 0.8125rem; 211 color: var(--text); 212 outline: none; 213 transition: border-color var(--transition); 214 } 215 .url-input::placeholder { color: var(--text-dim); } 216 .url-input:focus { border-color: var(--accent); } 217 218 .try-btn { 219 background: var(--accent); 220 color: var(--bg); 221 border: none; 222 border-radius: 999px; 223 padding: 0.6rem 1.25rem; 224 font-family: var(--font-sans); 225 font-size: 0.875rem; 226 font-weight: 500; 227 cursor: pointer; 228 white-space: nowrap; 229 transition: opacity var(--transition); 230 flex-shrink: 0; 231 } 232 .try-btn:hover { opacity: 0.85; } 233 .try-btn:disabled { opacity: 0.4; cursor: not-allowed; } 234 235 .try-result { 236 display: none; 237 margin-top: 1rem; 238 } 239 .try-result.show { display: block; } 240 241 .result-img-wrap { 242 border-radius: 8px; 243 overflow: hidden; 244 border: 1px solid var(--border); 245 margin-bottom: 0.875rem; 246 background: var(--bg-raised); 247 min-height: 120px; 248 display: flex; 249 align-items: center; 250 justify-content: center; 251 } 252 253 .result-img-wrap img { 254 width: 100%; 255 height: auto; 256 max-height: 360px; 257 object-fit: contain; 258 display: block; 259 } 260 261 .result-url-row { 262 display: flex; 263 align-items: center; 264 gap: 0.5rem; 265 } 266 267 .result-url-box { 268 flex: 1; 269 font-family: var(--font-mono); 270 font-size: 0.775rem; 271 padding: 0.5rem 0.875rem; 272 background: var(--bg-raised); 273 border: 1px solid var(--border); 274 border-radius: 8px; 275 color: var(--text-muted); 276 word-break: break-all; 277 line-height: 1.5; 278 } 279 280 .copy-btn { 281 background: var(--bg-raised); 282 border: 1px solid var(--border); 283 border-radius: 999px; 284 padding: 0.5rem 1rem; 285 font-family: var(--font-sans); 286 font-size: 0.8125rem; 287 color: var(--text-muted); 288 cursor: pointer; 289 white-space: nowrap; 290 transition: border-color var(--transition), color var(--transition); 291 flex-shrink: 0; 292 } 293 .copy-btn:hover { border-color: var(--accent); color: var(--accent); } 294 .copy-btn.copied { background: var(--green); border-color: var(--green); color: var(--bg); } 295 296 .try-error { 297 display: none; 298 font-size: 0.875rem; 299 color: var(--red); 300 margin-top: 0.75rem; 301 } 302 .try-error.show { display: block; } 303 304 .try-card-meta { 305 font-size: 0.8125rem; 306 color: var(--text-dim); 307 padding: 0.75rem 1.25rem; 308 border-top: 1px solid var(--border); 309 } 310 311 /* ── Usage examples ── */ 312 313 .usage-list { 314 display: flex; 315 flex-direction: column; 316 gap: 1.25rem; 317 } 318 319 .usage-item { } 320 321 .usage-item h3 { 322 font-family: var(--font-sans); 323 font-size: 0.875rem; 324 font-weight: 500; 325 color: var(--text); 326 margin-bottom: 0.375rem; 327 } 328 329 .usage-item p { 330 font-size: 0.875rem; 331 color: var(--text-muted); 332 margin-bottom: 0.5rem; 333 line-height: 1.6; 334 } 335 336 .code-block { 337 background: var(--bg-card); 338 border: 1px solid var(--border); 339 border-radius: var(--radius); 340 padding: 0.875rem 1rem; 341 font-family: var(--font-mono); 342 font-size: 0.8rem; 343 color: var(--text-muted); 344 overflow-x: auto; 345 line-height: 1.7; 346 white-space: pre; 347 } 348 349 .code-block .hl { color: var(--accent); } 350 351 /* ── How it works ── */ 352 353 .steps { 354 display: flex; 355 flex-direction: column; 356 } 357 358 .step { 359 display: flex; 360 gap: 1rem; 361 padding: 1rem 0; 362 border-bottom: 1px solid var(--border); 363 } 364 .step:last-child { border-bottom: none; } 365 366 .step-num { 367 flex-shrink: 0; 368 width: 1.75rem; 369 height: 1.75rem; 370 border-radius: 50%; 371 background: var(--bg-card); 372 border: 1px solid var(--border); 373 display: flex; 374 align-items: center; 375 justify-content: center; 376 font-size: 0.75rem; 377 font-weight: 500; 378 color: var(--accent); 379 margin-top: 0.1rem; 380 } 381 382 .step-body h3 { 383 font-family: var(--font-serif); 384 font-size: 0.9375rem; 385 font-weight: 400; 386 color: var(--text); 387 margin-bottom: 0.2rem; 388 } 389 .step-body p { 390 font-size: 0.875rem; 391 color: var(--text-muted); 392 line-height: 1.7; 393 } 394 395 /* ── Response info grid ── */ 396 397 .info-grid { 398 display: grid; 399 grid-template-columns: repeat(2, 1fr); 400 gap: 0.75rem; 401 } 402 403 .info-card { 404 background: var(--bg-card); 405 border: 1px solid var(--border); 406 border-radius: var(--radius); 407 padding: 1.125rem; 408 transition: border-color var(--transition); 409 } 410 .info-card:hover { border-color: var(--border-hover); } 411 412 .info-card i { 413 font-size: 1rem; 414 color: var(--accent); 415 opacity: 0.8; 416 margin-bottom: 0.5rem; 417 display: block; 418 } 419 420 .info-card h3 { 421 font-family: var(--font-sans); 422 font-size: 0.875rem; 423 font-weight: 500; 424 color: var(--text); 425 margin-bottom: 0.25rem; 426 } 427 428 .info-card p { 429 font-size: 0.8125rem; 430 color: var(--text-muted); 431 line-height: 1.6; 432 } 433 434 /* ── Footer ── */ 435 436 .site-footer { 437 max-width: var(--max-w); 438 margin: 0 auto; 439 padding: 2.5rem 1.5rem 3rem; 440 border-top: 1px solid var(--border); 441 font-size: 0.8125rem; 442 color: var(--text-dim); 443 line-height: 1.8; 444 } 445 446 .site-footer a { color: var(--text-muted); } 447 .site-footer a:hover { color: var(--text); opacity: 1; } 448 .footer-tor { margin-top: 0.25rem; } 449 450 /* ── Responsive ── */ 451 452 @media (max-width: 600px) { 453 .hero { padding: 2.5rem 0 2rem; } 454 .info-grid { grid-template-columns: 1fr; } 455 .url-row { flex-direction: column; } 456 .try-btn { border-radius: var(--radius); } 457 } 458 </style> 459</head> 460<body> 461 462 <header class="site-header"> 463 <nav class="nav-container"> 464 <a href="https://madebydanny.uk" class="nav-logo">← danny</a> 465 <span class="nav-title">IMRS</span> 466 </nav> 467 </header> 468 469 <main class="main-content"> 470 471 <!-- Hero --> 472 <section class="hero"> 473 <p class="hero-eyebrow">madebydanny.uk</p> 474 <h1 class="hero-title">IMRS</h1> 475 <p class="hero-bio"> 476 Image media reduction system, easily transform images built on <a href="https://workers.cloudflare.com" target="_blank">Cloudflare Workers</a>. 477 Pass any public image URL and get it back converted to AVIF — smaller, faster, cached at the edge. 478 </p> 479 <div class="hero-badges"> 480 <span class="badge"><i class="fa-brands fa-cloudflare"></i> Cloudflare Workers</span> 481 <span class="badge"><i class="fa-solid fa-bolt"></i> Edge cached</span> 482 <span class="badge"><i class="fa-solid fa-image"></i> AVIF output</span> 483 </div> 484 </section> 485 486 <!-- Try it --> 487 <section class="content-section"> 488 <h2 class="section-label">try it</h2> 489 <div class="try-card"> 490 <div class="try-card-body"> 491 <div class="url-row"> 492 <input 493 class="url-input" 494 id="img-url-input" 495 type="url" 496 placeholder="https://example.com/image.jpg" 497 autocomplete="off" 498 spellcheck="false" 499 > 500 <button class="try-btn" id="try-btn" onclick="tryUrl()"> 501 <i class="fa-solid fa-arrow-right"></i> Resize 502 </button> 503 </div> 504 <p class="try-error" id="try-error"></p> 505 <div class="try-result" id="try-result"> 506 <div class="result-img-wrap"> 507 <img id="result-img" src="" alt="Resized image"> 508 </div> 509 <div class="result-url-row"> 510 <div class="result-url-box" id="result-url"></div> 511 <button class="copy-btn" id="copy-btn" onclick="copyResult()"> 512 <i class="fa-solid fa-copy"></i> Copy 513 </button> 514 </div> 515 </div> 516 </div> 517 <div class="try-card-meta">Processed images are cached for 1 year at Cloudflare's edge</div> 518 </div> 519 </section> 520 521 <!-- Usage --> 522 <section class="content-section"> 523 <h2 class="section-label">usage</h2> 524 <div class="usage-list"> 525 <div class="usage-item"> 526 <h3>Query parameter</h3> 527 <p>Pass the image URL as a <code>?url=</code> parameter.</p> 528 <div class="code-block"><span class="hl">https://imrs.madebydanny.uk</span>?url=https://example.com/photo.jpg</div> 529 </div> 530 <div class="usage-item"> 531 <h3>Path passthrough</h3> 532 <p>Append the full URL directly after the origin.</p> 533 <div class="code-block"><span class="hl">https://imrs.madebydanny.uk/</span>https://example.com/photo.jpg</div> 534 </div> 535 <div class="usage-item"> 536 <h3>In an HTML image tag</h3> 537 <p>Wrap any <code>src</code> to serve AVIF automatically to supporting browsers.</p> 538 <div class="code-block">&lt;img src="<span class="hl">https://imrs.madebydanny.uk/?url=</span>https://example.com/photo.jpg"&gt;</div> 539 </div> 540 </div> 541 </section> 542 543 <!-- How it works --> 544 <section class="content-section"> 545 <h2 class="section-label">how it works</h2> 546 <div class="steps"> 547 <div class="step"> 548 <div class="step-num">1</div> 549 <div class="step-body"> 550 <h3>You pass an image URL</h3> 551 <p>Either via <code>?url=</code> or as a path suffix. The URL must be a publicly accessible image.</p> 552 </div> 553 </div> 554 <div class="step"> 555 <div class="step-num">2</div> 556 <div class="step-body"> 557 <h3>The Worker fetches and transforms it</h3> 558 <p>Cloudflare's image processing pipeline converts the image to AVIF at 85% quality, scaled down to fit its original dimensions.</p> 559 </div> 560 </div> 561 <div class="step"> 562 <div class="step-num">3</div> 563 <div class="step-body"> 564 <h3>You get a cached AVIF back</h3> 565 <p>The response is served with <code>Cache-Control: immutable, max-age=31536000</code> — one year at the edge. Subsequent requests for the same URL are instant.</p> 566 </div> 567 </div> 568 </div> 569 </section> 570 571 <!-- Response headers --> 572 <section class="content-section"> 573 <h2 class="section-label">response</h2> 574 <div class="info-grid"> 575 <div class="info-card"> 576 <i class="fa-solid fa-file-image"></i> 577 <h3>Content-Type</h3> 578 <p><code>image/avif</code> — regardless of source format.</p> 579 </div> 580 <div class="info-card"> 581 <i class="fa-solid fa-clock"></i> 582 <h3>Cache-Control</h3> 583 <p>Public, immutable, 1-year TTL. Served from edge on repeat requests.</p> 584 </div> 585 <div class="info-card"> 586 <i class="fa-solid fa-globe"></i> 587 <h3>CORS</h3> 588 <p><code>Access-Control-Allow-Origin: *</code> — safe to use from any origin.</p> 589 </div> 590 <div class="info-card"> 591 <i class="fa-solid fa-compress"></i> 592 <h3>Quality</h3> 593 <p>85% AVIF quality. Typically 50–70% smaller than an equivalent JPEG.</p> 594 </div> 595 </div> 596 </section> 597 598 </main> 599 600 <footer class="site-footer"> 601 <p>© 2024–26 Daniel Morrisey · <a href="https://madebydanny.uk" target="_blank">madebydanny.uk</a> · built on <a href="https://workers.cloudflare.com" target="_blank">Cloudflare Workers</a></p> 602 <p class="footer-tor"><a href="http://irgwdhat74pqcpkk7ynrphvohnnt574yvwmhredrfusemgu6wj2ik5id.onion/" target="_blank" rel="noopener noreferrer">Open in Tor</a></p> 603 </footer> 604 605 <script> 606 const BASE = 'https://imrs.madebydanny.uk'; 607 608 function tryUrl() { 609 const input = document.getElementById('img-url-input'); 610 const btn = document.getElementById('try-btn'); 611 const result = document.getElementById('try-result'); 612 const errEl = document.getElementById('try-error'); 613 const imgEl = document.getElementById('result-img'); 614 const urlBox = document.getElementById('result-url'); 615 616 const raw = input.value.trim(); 617 618 errEl.textContent = ''; 619 errEl.classList.remove('show'); 620 result.classList.remove('show'); 621 622 if (!raw) { 623 errEl.textContent = 'Please enter an image URL.'; 624 errEl.classList.add('show'); 625 return; 626 } 627 628 if (!raw.startsWith('http')) { 629 errEl.textContent = 'URL must start with http:// or https://'; 630 errEl.classList.add('show'); 631 return; 632 } 633 634 btn.disabled = true; 635 btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>'; 636 637 const proxied = `${BASE}/?url=${encodeURIComponent(raw)}`; 638 urlBox.textContent = proxied; 639 640 imgEl.onload = () => { 641 result.classList.add('show'); 642 btn.disabled = false; 643 btn.innerHTML = '<i class="fa-solid fa-arrow-right"></i> Resize'; 644 }; 645 646 imgEl.onerror = () => { 647 errEl.textContent = 'Could not load image. Check the URL is publicly accessible.'; 648 errEl.classList.add('show'); 649 btn.disabled = false; 650 btn.innerHTML = '<i class="fa-solid fa-arrow-right"></i> Resize'; 651 }; 652 653 imgEl.src = proxied; 654 } 655 656 document.getElementById('img-url-input').addEventListener('keydown', e => { 657 if (e.key === 'Enter') tryUrl(); 658 }); 659 660 function copyResult() { 661 const url = document.getElementById('result-url').textContent; 662 const btn = document.getElementById('copy-btn'); 663 navigator.clipboard.writeText(url); 664 btn.classList.add('copied'); 665 btn.innerHTML = '<i class="fa-solid fa-check"></i> Copied'; 666 setTimeout(() => { 667 btn.classList.remove('copied'); 668 btn.innerHTML = '<i class="fa-solid fa-copy"></i> Copy'; 669 }, 2000); 670 } 671 </script> 672 673</body> 674</html>