diff --git a/css/imrs.css b/css/imrs.css new file mode 100644 index 0000000..bde5553 --- /dev/null +++ b/css/imrs.css @@ -0,0 +1,396 @@ +:root { + --bg-deep: #100b09; + --bg: #1c1310; + --bg-raised: #251a15; + --bg-card: #2e2018; + --border: #3d2b1f; + --border-hover:#5c3d28; + --text: #f4deb8; + --text-muted: #e8c28a; + --text-dim: #b37a48; + --accent: #d4975c; + --accent-bright:#e8c28a; + --accent-dim: rgba(212, 151, 92, 0.14); + --green: #4caf7d; + --red: #e06c6c; + --font-serif: 'Lota', Georgia, 'Times New Roman', serif; + --font-mono: 'Sometype Mono', 'Monaco', 'Courier New', monospace; + --font-sans: 'DM Sans', system-ui, sans-serif; + --radius: 10px; + --transition: 0.2s ease; + --max-w: 680px; +} + +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +html { scroll-behavior: smooth; } + +body { + font-family: var(--font-serif); + font-size: 16px; + line-height: 1.7; + color: var(--text); + background-color: var(--bg); + -webkit-font-smoothing: antialiased; +} + +/* ── Typography ── */ + +h1, h2, h3 { + font-family: var(--font-mono); + font-weight: 600; + line-height: 1.2; +} + +a { + color: var(--accent); + text-decoration: none; + transition: opacity var(--transition); +} +a:hover { opacity: 0.75; } + +code { + font-family: var(--font-mono); + font-size: 0.875em; + background: var(--bg-raised); + padding: 0.15em 0.4em; + border-radius: 4px; + color: var(--accent); + word-break: break-all; +} + +/* ── Header ── */ + +.site-header { + position: sticky; + top: 0; + z-index: 100; + background: var(--bg); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--border); +} + +.nav-container { + max-width: var(--max-w); + margin: 0 auto; + padding: 0.875rem 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.nav-logo { + font-family: var(--font-serif); + font-style: italic; + font-size: 1.25rem; + color: var(--text); +} +.nav-logo:hover { opacity: 0.6; } + +.nav-menu { + list-style: none; + display: flex; + gap: 1.5rem; +} + +.nav-menu a { + font-size: 0.875rem; + color: var(--text-muted); + letter-spacing: 0.01em; +} +.nav-menu a:hover { color: var(--text); opacity: 1; } + +/* ── Main ── */ + +.main-content { + max-width: var(--max-w); + margin: 0 auto; + padding: 0 1.5rem; +} + +/* ── Hero ── */ + +.hero { + padding: 4rem 0 3rem; +} + +.hero-eyebrow { + font-size: 0.875rem; + color: var(--text-muted); + margin-bottom: 0.25rem; + letter-spacing: 0.04em; +} + +.hero-title { + font-size: clamp(2.5rem, 6vw, 3.5rem); + color: var(--text); + margin-bottom: 1.25rem; + letter-spacing: -0.02em; +} + +.hero-bio { + font-size: 1.0625rem; + color: var(--text-muted); + line-height: 1.8; + max-width: 560px; + margin-bottom: 1.5rem; +} + +.hero-bio a { + color: var(--accent); + border-bottom: 1px solid var(--accent-dim); + transition: border-color var(--transition), opacity var(--transition); +} +.hero-bio a:hover { + opacity: 1; + border-bottom-color: var(--accent); +} + +/* ── Sections ── */ + +.content-section { + padding: 2.5rem 0; + border-top: 1px solid var(--border); +} + +.section-label { + font-family: var(--font-sans); + font-size: 0.75rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--text-dim); + margin-bottom: 1.25rem; +} + +/* ── Try it card ── */ + +.try-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} + +.try-card-body { padding: 1.25rem; } + +.url-row { + display: flex; + gap: 0.5rem; +} + +.url-input { + flex: 1; + background: var(--bg-raised); + border: 1px solid var(--border); + border-radius: 999px; + padding: 0.6rem 1rem; + font-family: var(--font-mono); + font-size: 0.8125rem; + color: var(--text); + outline: none; + transition: border-color var(--transition); +} +.url-input::placeholder { color: var(--text-dim); } +.url-input:focus { border-color: var(--accent); } + +.try-btn { + background: var(--accent); + color: var(--bg); + border: none; + border-radius: 999px; + padding: 0.6rem 1.25rem; + font-family: var(--font-sans); + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + white-space: nowrap; + transition: opacity var(--transition); + flex-shrink: 0; +} +.try-btn:hover { opacity: 0.85; } +.try-btn:disabled { opacity: 0.4; cursor: not-allowed; } + +.try-result { + display: none; + margin-top: 1rem; +} +.try-result.show { display: block; } + +.result-img-wrap { + border-radius: 8px; + overflow: hidden; + border: 1px solid var(--border); + margin-bottom: 0.875rem; + background: var(--bg-raised); + min-height: 120px; + display: flex; + align-items: center; + justify-content: center; +} + +.result-img-wrap img { + width: 100%; + height: auto; + max-height: 360px; + object-fit: contain; + display: block; +} + +.result-url-row { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.result-url-box { + flex: 1; + font-family: var(--font-mono); + font-size: 0.775rem; + padding: 0.5rem 0.875rem; + background: var(--bg-raised); + border: 1px solid var(--border); + border-radius: 8px; + color: var(--text-muted); + word-break: break-all; + line-height: 1.5; +} + +.copy-btn { + background: var(--bg-raised); + border: 1px solid var(--border); + border-radius: 999px; + padding: 0.5rem 1rem; + font-family: var(--font-sans); + font-size: 0.8125rem; + color: var(--text-muted); + cursor: pointer; + white-space: nowrap; + transition: border-color var(--transition), color var(--transition); + flex-shrink: 0; +} +.copy-btn:hover { border-color: var(--accent); color: var(--accent); } +.copy-btn.copied { background: var(--green); border-color: var(--green); color: var(--bg); } + +.try-error { + display: none; + font-size: 0.875rem; + color: var(--red); + margin-top: 0.75rem; +} +.try-error.show { display: block; } + +.try-card-meta { + font-size: 0.8125rem; + color: var(--text-dim); + padding: 0.75rem 1.25rem; + border-top: 1px solid var(--border); +} + +/* ── Usage examples ── */ + +.usage-list { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.usage-item h3 { + font-family: var(--font-sans); + font-size: 0.875rem; + font-weight: 500; + color: var(--text); + margin-bottom: 0.375rem; +} + +.usage-item p { + font-size: 0.875rem; + color: var(--text-muted); + margin-bottom: 0.5rem; + line-height: 1.6; +} + +.code-block { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 0.875rem 1rem; + font-family: var(--font-mono); + font-size: 0.8rem; + color: var(--text-muted); + overflow-x: auto; + line-height: 1.7; + white-space: pre; +} + +.code-block .hl { color: var(--accent); } + +/* ── How it works ── */ + +.steps { + display: flex; + flex-direction: column; +} + +.step { + display: flex; + gap: 1rem; + padding: 1rem 0; + border-bottom: 1px solid var(--border); +} +.step:last-child { border-bottom: none; } + +.step-num { + flex-shrink: 0; + width: 1.75rem; + height: 1.75rem; + border-radius: 50%; + background: var(--bg-card); + border: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: center; + font-size: 0.75rem; + font-weight: 500; + color: var(--accent); + margin-top: 0.1rem; + font-family: var(--font-mono); +} + +.step-body h3 { + font-family: var(--font-serif); + font-size: 0.9375rem; + font-weight: 400; + color: var(--text); + margin-bottom: 0.2rem; +} +.step-body p { + font-size: 0.875rem; + color: var(--text-muted); + line-height: 1.7; +} + +/* ── Footer ── */ + +.site-footer { + max-width: var(--max-w); + margin: 0 auto; + padding: 2.5rem 1.5rem 3rem; + border-top: 1px solid var(--border); + font-size: 0.8125rem; + color: var(--text-dim); + line-height: 1.8; +} + +.site-footer a { color: var(--text-muted); } +.site-footer a:hover { color: var(--text); opacity: 1; } + +/* ── Responsive ── */ + +@media (max-width: 600px) { + .hero { padding: 2.5rem 0 2rem; } + .info-grid { grid-template-columns: 1fr; } + .url-row { flex-direction: column; } + .try-btn { border-radius: var(--radius); } + .nav-menu { display: none; } +} \ No newline at end of file diff --git a/imrs.html b/imrs.html index 08715e9..9b8b34e 100644 --- a/imrs.html +++ b/imrs.html @@ -3,466 +3,48 @@ - IMRS — Image Resizing Service · madebydanny.uk + [IMRS]: Image Media Reduction System - danielmorrisey.com - + - + - - + + + + - - - - + + + + @@ -470,18 +52,10 @@
-

madebydanny.uk

+

danielmorrisey.com/imrs

IMRS

- Image media reduction system, easily transform images built on Cloudflare Workers. - Pass any public image URL and get it back converted to AVIF — smaller, faster, cached at the edge. -

-
- Cloudflare Workers - Edge cached - AVIF output -
-
+IMRS, or Image Media Reduction System, is a simple tool that runs a Cloudflare Worker that converts any image URL that's publicly accessible into a transformed AVIF version of the image, which is then stored on the Cloudflare edge for 1 year. Its goal is to save on bandwidth and improve loading times.
@@ -493,12 +67,12 @@ class="url-input" id="img-url-input" type="url" - placeholder="https://example.com/image.jpg" + placeholder="danielmorrisey.com/media/images/grain-drop-3.jpg" autocomplete="off" spellcheck="false" >

@@ -514,7 +88,7 @@ -
Processed images are cached for 1 year at Cloudflare's edge
+
We will never be able to see tranformed images, no data is stored.
@@ -525,17 +99,17 @@

Query parameter

Pass the image URL as a ?url= parameter.

-
https://imrs.madebydanny.uk?url=https://example.com/photo.jpg
+
https://imrs.danielmorrisey.com?url=https://example.com/photo.jpg

Path passthrough

Append the full URL directly after the origin.

-
https://imrs.madebydanny.uk/https://example.com/photo.jpg
+
https://imrs.danielmorrisey.com/https://example.com/photo.jpg

In an HTML image tag

Wrap any src to serve AVIF automatically to supporting browsers.

-
<img src="https://imrs.madebydanny.uk/?url=https://example.com/photo.jpg">
+
<img src="https://imrs.danielmorrisey.com/?url=https://example.com/photo.jpg">
@@ -567,108 +141,32 @@ - - -
-

response

-
-
- -

Content-Type

-

image/avif — regardless of source format.

-
-
- -

Cache-Control

-

Public, immutable, 1-year TTL. Served from edge on repeat requests.

-
-
- -

CORS

-

Access-Control-Allow-Origin: * — safe to use from any origin.

-
-
- -

Quality

-

85% AVIF quality. Typically 50–70% smaller than an equivalent JPEG.

-
-
-
- + - + - + \ No newline at end of file diff --git a/index.html b/index.html index e62688c..25d0430 100644 --- a/index.html +++ b/index.html @@ -57,10 +57,9 @@
  • Projects
  • Photos
  • CDN
  • +
  • IMRS
  • Blog
  • -
  • - @@ -69,7 +68,7 @@
    -

    hi, I'm

    +

    danielmorrisey.com/

    Daniel Morrisey

    Hello World! I'm Daniel Morrisey, just your average Bluesky/AT Protocol nerd, who spends too much time on Bluesky and listening to Hamilton. @@ -162,14 +161,14 @@ MBD CDN A simple, fast CDN powered by Cloudflare R2. Free for Life + + IMRS (soon™) + Transform images on the edge to improve speed and lower bandwidth + Favfinder (soon™) Fetch favicons for any website in seconds using Google S2 and Cloudflare Workers - - IMRS (soon™) - Transform images on the edge to improve speed and lower bandwidth - Luna (soon™) An alternative to the Bluesky CDN built on Cloudflare Workers diff --git a/js/imrs.js b/js/imrs.js new file mode 100644 index 0000000..f0a642c --- /dev/null +++ b/js/imrs.js @@ -0,0 +1,65 @@ + const BASE = 'https://imrs.madebydanny.uk'; + + function tryUrl() { + const input = document.getElementById('img-url-input'); + const btn = document.getElementById('try-btn'); + const result = document.getElementById('try-result'); + const errEl = document.getElementById('try-error'); + const imgEl = document.getElementById('result-img'); + const urlBox = document.getElementById('result-url'); + + const raw = input.value.trim(); + + errEl.textContent = ''; + errEl.classList.remove('show'); + result.classList.remove('show'); + + if (!raw) { + errEl.textContent = 'Please enter an image URL.'; + errEl.classList.add('show'); + return; + } + + if (!raw.startsWith('http')) { + errEl.textContent = 'URL must start with http:// or https://'; + errEl.classList.add('show'); + return; + } + + btn.disabled = true; + btn.innerHTML = ''; + + const proxied = `${BASE}/?url=${encodeURIComponent(raw)}`; + urlBox.textContent = proxied; + + imgEl.onload = () => { + result.classList.add('show'); + btn.disabled = false; + btn.innerHTML = ' Resize'; + }; + + imgEl.onerror = () => { + errEl.textContent = 'Could not load image. Check the URL is publicly accessible.'; + errEl.classList.add('show'); + btn.disabled = false; + btn.innerHTML = ' Resize'; + }; + + imgEl.src = proxied; + } + + document.getElementById('img-url-input').addEventListener('keydown', e => { + if (e.key === 'Enter') tryUrl(); + }); + + function copyResult() { + const url = document.getElementById('result-url').textContent; + const btn = document.getElementById('copy-btn'); + navigator.clipboard.writeText(url); + btn.classList.add('copied'); + btn.innerHTML = ' Copied'; + setTimeout(() => { + btn.classList.remove('copied'); + btn.innerHTML = ' Copy'; + }, 2000); + } \ No newline at end of file diff --git a/media/images/seo/favicons/imrs.ico b/media/images/seo/favicons/imrs.ico new file mode 100644 index 0000000000000000000000000000000000000000..4d5f5c2bf738ea71ca9df7fc7bf3fdb3b669843d GIT binary patch literal 67646 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 literal 0 HcmV?d00001