madebydanny.uk written in html, css, and a lot of JavaScript I don't understand
madebydanny.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>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"><img src="<span class="hl">https://imrs.madebydanny.uk/?url=</span>https://example.com/photo.jpg"></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>