Initial project structure commit.

Static directory for public folders and business logic for the app
within internal, split into domain specific folders to keep clear
seperation of concerns.
This commit is contained in:
2026-04-23 08:09:09 +02:00
parent f3ccbc95c1
commit a8862721cd
18 changed files with 1048 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
function gridCellDimensions() {
const element = document.createElement("div");
element.style.position = "fixed";
element.style.height = "var(--line-height)";
element.style.width = "1ch";
document.body.appendChild(element);
const rect = element.getBoundingClientRect();
document.body.removeChild(element);
return { width: rect.width, height: rect.height };
}
// Add padding to each media to maintain grid.
function adjustMediaPadding() {
const cell = gridCellDimensions();
function setHeightFromRatio(media, ratio) {
const rect = media.getBoundingClientRect();
const realHeight = rect.width / ratio;
const diff = cell.height - (realHeight % cell.height);
media.style.setProperty("padding-bottom", `${diff}px`);
}
function setFallbackHeight(media) {
const rect = media.getBoundingClientRect();
const height = Math.round((rect.width / 2) / cell.height) * cell.height;
media.style.setProperty("height", `${height}px`);
}
function onMediaLoaded(media) {
var width, height;
switch (media.tagName) {
case "IMG":
width = media.naturalWidth;
height = media.naturalHeight;
break;
case "VIDEO":
width = media.videoWidth;
height = media.videoHeight;
break;
}
if (width > 0 && height > 0) {
setHeightFromRatio(media, width / height);
} else {
setFallbackHeight(media);
}
}
const medias = document.querySelectorAll("img, video");
for (media of medias) {
switch (media.tagName) {
case "IMG":
if (media.complete) {
onMediaLoaded(media);
} else {
media.addEventListener("load", () => onMediaLoaded(media));
media.addEventListener("error", function() {
setFallbackHeight(media);
});
}
break;
case "VIDEO":
switch (media.readyState) {
case HTMLMediaElement.HAVE_CURRENT_DATA:
case HTMLMediaElement.HAVE_FUTURE_DATA:
case HTMLMediaElement.HAVE_ENOUGH_DATA:
onMediaLoaded(media);
break;
default:
media.addEventListener("loadeddata", () => onMediaLoaded(media));
media.addEventListener("error", function() {
setFallbackHeight(media);
});
break;
}
break;
}
}
}
adjustMediaPadding();
window.addEventListener("load", adjustMediaPadding);
window.addEventListener("resize", adjustMediaPadding);
function checkOffsets() {
const ignoredTagNames = new Set([
"THEAD",
"TBODY",
"TFOOT",
"TR",
"TD",
"TH",
]);
const cell = gridCellDimensions();
const elements = document.querySelectorAll("body :not(.debug-grid, .debug-toggle)");
for (const element of elements) {
if (ignoredTagNames.has(element.tagName)) {
continue;
}
const rect = element.getBoundingClientRect();
if (rect.width === 0 && rect.height === 0) {
continue;
}
const top = rect.top + window.scrollY;
const left = rect.left + window.scrollX;
const offset = top % (cell.height / 2);
if(offset > 0) {
element.classList.add("off-grid");
console.error("Incorrect vertical offset for", element, "with remainder", top % cell.height, "when expecting divisible by", cell.height / 2);
} else {
element.classList.remove("off-grid");
}
}
}
const debugToggle = document.querySelector(".debug-toggle");
function onDebugToggle() {
document.body.classList.toggle("debug", debugToggle.checked);
}
debugToggle.addEventListener("change", onDebugToggle);
onDebugToggle();