feat: preload images

This commit is contained in:
2026-03-19 22:07:02 +01:00
parent 1c67eb0cff
commit b384c70c39
9 changed files with 412 additions and 6 deletions
+58
View File
@@ -0,0 +1,58 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@source "../../**/*.templ";
@custom-variant dark (&:where(.dark, .dark *));
@font-face {
font-family: "Outfit";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("../fonts/Outfit-LatinExt-Variable.woff2") format("woff2");
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: "Outfit";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("../fonts/Outfit-Latin-Variable.woff2") format("woff2");
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
U+2215, U+FEFF, U+FFFD;
}
@theme {
/* Font Family Namespace */
--font-sans: "Outfit", "outfit", sans-serif;
/* Text Shadow Namespace */
/* These will automatically generate .text-shadow-sm, .text-shadow, and .text-shadow-lg */
--text-shadow-sm: 0 1px 2px #000;
--text-shadow: 0 2px 4px #000;
--text-shadow-lg: 0px 0px 20px #000;
}
/*
Functional Utility for arbitrary values (e.g., text-shadow-[...])
This replaces the 'matchUtilities' logic from your JS plugin.
*/
@utility text-shadow-* {
text-shadow: --value(--text-shadow-*, [color], [*]);
}
@layer components {
.prose img {
margin-left: auto;
margin-right: auto;
display: block;
max-height: 90vh;
}
}
+63
View File
@@ -0,0 +1,63 @@
/**
* @file preload-links.js
* @description HTMX extension to extract <link rel="preload"> from preloaded HTML responses.
* @generated By opencode (AI Agent)
* @model gemini-3-flash-preview
* @date Thu Mar 19 2026
*/
(function () {
// Save the original XHR methods
const originalOpen = XMLHttpRequest.prototype.open;
const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
const originalSend = XMLHttpRequest.prototype.send;
// We need to track which XHRs are preloads
XMLHttpRequest.prototype.open = function () {
this._isHtmxPreload = false;
return originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
if (header.toLowerCase() === "hx-preloaded" && value === "true") {
this._isHtmxPreload = true;
}
return originalSetRequestHeader.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function () {
if (this._isHtmxPreload) {
this.addEventListener("load", function () {
// Parse the response text as HTML
const parser = new DOMParser();
const doc = parser.parseFromString(this.responseText, "text/html");
// Find all preload links
const links = doc.querySelectorAll('link[rel="preload"]');
links.forEach((link) => {
const href = link.getAttribute("href");
// Only inject if the current document doesn't already have it
if (
href &&
!document.querySelector(`link[rel="preload"][href="${href}"]`)
) {
const newLink = document.createElement("link");
newLink.rel = "preload";
newLink.href = href;
// Copy relevant attributes
["as", "type", "media", "fetchpriority", "crossorigin"].forEach(
(attr) => {
const val = link.getAttribute(attr);
if (val) newLink.setAttribute(attr, val);
},
);
document.head.appendChild(newLink);
}
});
});
}
return originalSend.apply(this, arguments);
};
})();