diff --git a/website/scripts/MarkdownRenderer.js b/website/scripts/MarkdownRenderer.js
index 731d3ee..b1590c3 100644
--- a/website/scripts/MarkdownRenderer.js
+++ b/website/scripts/MarkdownRenderer.js
@@ -115,9 +115,9 @@ export class MarkdownRenderer {
// Standalone image
const imgBlockMatch = block.match(/^!\[([^\]]*)\]\((.+)\)$/);
if (imgBlockMatch) {
- const { src, caption, imageStyle } = MarkdownRenderer.#parseImageMeta(imgBlockMatch[1], imgBlockMatch[2]);
+ const { src, caption, imageAttributes } = MarkdownRenderer.#parseImageMeta(imgBlockMatch[1], imgBlockMatch[2]);
if (src) {
- return `${caption ? `
${caption ? `
`;
+ return `
`;
}
);
@@ -408,16 +408,44 @@ export class MarkdownRenderer {
return null;
}
+ /**
+ * Applies intrinsic pixel scaling to markdown images with a size token.
+ *
+ * @param {ParentNode} container
+ * @returns {void}
+ */
+ static applyImageScale(container) {
+ container.querySelectorAll("img[data-md-scale]").forEach(img => {
+ const scale = parseFloat(img.getAttribute("data-md-scale") || "");
+ if (!Number.isFinite(scale) || scale <= 0) return;
+
+ const applyScale = () => {
+ if (!img.naturalWidth) return;
+ const targetWidthPx = Math.max(1, Math.round(img.naturalWidth * scale));
+ img.style.width = `${targetWidthPx}px`;
+ img.style.height = "auto";
+ img.style.maxWidth = "100%";
+ };
+
+ if (img.complete) {
+ applyScale();
+ return;
+ }
+
+ img.addEventListener("load", applyScale, { once: true });
+ });
+ }
+
/**
* Parses markdown image metadata including optional title and size token.
*
* Supported format: 
- * - Numeric alt-only values (e.g. 0.50) are treated as width scale metadata.
+ * - Numeric alt-only values (e.g. 0.50) are treated as intrinsic size scale metadata.
* - Title text is used as the visible caption when present.
*
* @param {string} rawAlt
* @param {string} rawMeta
- * @returns {{ src: string | null, caption: string, imageStyle: string }}
+ * @returns {{ src: string | null, caption: string, imageAttributes: string }}
*/
static #parseImageMeta(rawAlt, rawMeta) {
const meta = rawMeta.trim();
@@ -426,7 +454,7 @@ export class MarkdownRenderer {
return {
src: null,
caption: MarkdownRenderer.#escape(rawAlt),
- imageStyle: "",
+ imageAttributes: "",
};
}
@@ -435,12 +463,12 @@ export class MarkdownRenderer {
const trimmedAlt = rawAlt.trim();
const sizeScale = MarkdownRenderer.#parseImageSizeScale(trimmedAlt);
const caption = titleCaption || (sizeScale === null ? MarkdownRenderer.#escape(trimmedAlt) : "");
- const imageStyle = sizeScale !== null ? ` style="width: ${sizeScale * 100}%;"` : "";
+ const imageAttributes = sizeScale !== null ? ` data-md-scale="${sizeScale}"` : "";
return {
src,
caption,
- imageStyle,
+ imageAttributes,
};
}
diff --git a/website/scripts/NodeRenderer.js b/website/scripts/NodeRenderer.js
index a1c530a..2a214f3 100644
--- a/website/scripts/NodeRenderer.js
+++ b/website/scripts/NodeRenderer.js
@@ -433,6 +433,7 @@ export class NodeRenderer {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const text = await response.text();
target.innerHTML = MarkdownRenderer.render(text);
+ MarkdownRenderer.applyImageScale(target);
} catch (err) {
target.innerHTML = `
Failed to load content.
`; console.error(`[NodeRenderer] Could not load markdown "${src}":`, err); diff --git a/website/scripts/nodes/BlogPostNode.js b/website/scripts/nodes/BlogPostNode.js index c7a9a93..92598af 100644 --- a/website/scripts/nodes/BlogPostNode.js +++ b/website/scripts/nodes/BlogPostNode.js @@ -99,6 +99,7 @@ export class BlogPostNode extends NodeBase { // Import and use MarkdownRenderer directly const { MarkdownRenderer } = await import('../MarkdownRenderer.js'); contentDiv.innerHTML = MarkdownRenderer.render(post.content); + MarkdownRenderer.applyImageScale(contentDiv); // Add copy button handlers contentDiv.querySelectorAll('.md-copy-btn').forEach(btn => {