diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d1d8b..3262c9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.1.3 – 2026-10-19 + +The homepage shows Image Update Monitor between the facts and the project list. + +- **Product preview:** a real screenshot, a short explanation about keeping container updates in + sight and a link to the project, in English and German. +- **Phones and themes:** a narrow-screen capture shows a complete container card on phones; the + desktop view shows five rows. Both follow the device's light or dark setting and use optimised WebP. +- **Build checks on Windows:** resolve the output directory as a filesystem path and normalise + directory separators before comparing page routes. + ## 0.1.2 – 2026-10-19 Stellwerk's page explains Codex wherever it explains Claude Code. diff --git a/package-lock.json b/package-lock.json index 39eacae..52e4e86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "azentrius-website", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "azentrius-website", - "version": "0.1.2", + "version": "0.1.3", "license": "Apache-2.0", "dependencies": { "@astrojs/sitemap": "3.7.4", diff --git a/package.json b/package.json index 2edcc54..290c02f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azentrius-website", - "version": "0.1.2", + "version": "0.1.3", "private": true, "description": "The website azentrius.com: project pages for the Azentrius tools, in English and German.", "license": "Apache-2.0", diff --git a/scripts/check-dist.mjs b/scripts/check-dist.mjs index 96323c0..99985bd 100644 --- a/scripts/check-dist.mjs +++ b/scripts/check-dist.mjs @@ -4,10 +4,11 @@ // executable script, links only to pages and files that exist, and names nothing unpublished. import { createHash } from "node:crypto"; import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; -import { join, relative } from "node:path"; +import { join, relative, sep } from "node:path"; +import { fileURLToPath } from "node:url"; import { NOINDEX_PATHS, ROUTES, SITE } from "../src/data/routes.mjs"; -const DIST = new URL("../dist/", import.meta.url).pathname; +const DIST = fileURLToPath(new URL("../dist/", import.meta.url)); const errors = []; const fail = (where, message) => errors.push(`${where}: ${message}`); @@ -66,7 +67,7 @@ for (const file of ["404.html", "robots.txt", "sitemap-index.xml", "favicon.svg" // 2. Each HTML page. const files = walk(DIST); const html = files.filter((f) => f.endsWith(".html")); -const pathOf = (file) => "/" + relative(DIST, file).replace(/index\.html$/, ""); +const pathOf = (file) => "/" + relative(DIST, file).split(sep).join("/").replace(/index\.html$/, ""); for (const file of html) { const where = pathOf(file); diff --git a/src/assets/screenshots/cockpit-image-update-monitor/mobile-dark.png b/src/assets/screenshots/cockpit-image-update-monitor/mobile-dark.png new file mode 100644 index 0000000..aa79952 Binary files /dev/null and b/src/assets/screenshots/cockpit-image-update-monitor/mobile-dark.png differ diff --git a/src/assets/screenshots/cockpit-image-update-monitor/mobile.png b/src/assets/screenshots/cockpit-image-update-monitor/mobile.png new file mode 100644 index 0000000..73b4cd1 Binary files /dev/null and b/src/assets/screenshots/cockpit-image-update-monitor/mobile.png differ diff --git a/src/components/HomePage.astro b/src/components/HomePage.astro index 086195f..1e2ec96 100644 --- a/src/components/HomePage.astro +++ b/src/components/HomePage.astro @@ -5,6 +5,12 @@ import { getCollection } from "astro:content"; import Base from "../layouts/Base.astro"; import ToolCard from "./ToolCard.astro"; import CopyBlock from "./CopyBlock.astro"; +import ProductName from "./ProductName.astro"; +import Shot from "./Shot.astro"; +import updates from "../assets/screenshots/cockpit-image-update-monitor/updates.png"; +import updatesDark from "../assets/screenshots/cockpit-image-update-monitor/updates-dark.png"; +import updatesMobile from "../assets/screenshots/cockpit-image-update-monitor/mobile.png"; +import updatesMobileDark from "../assets/screenshots/cockpit-image-update-monitor/mobile-dark.png"; import { TOOLS } from "../data/tools"; import { SITE } from "../data/routes.mjs"; import type { Lang } from "../i18n/ui"; @@ -15,6 +21,7 @@ interface Props { const { lang } = Astro.props; const entries = await getCollection("tools", (e) => e.data.lang === lang); const bySlug = Object.fromEntries(entries.map((e) => [e.data.slug, e])); +const previewTool = TOOLS.find((tool) => tool.slug === "cockpit-image-update-monitor")!; const text = { en: { @@ -31,6 +38,10 @@ const text = { ["EU", "Our servers and analytics"], ["Transparent", "Dependencies named per project"], ], + previewTitle: "Keep container updates in sight.", + previewText: "See which version is running and which is available. Update your containers directly in Cockpit, with a backup before the update.", + previewLink: "View Image Update Monitor", + previewAlt: "Image Update Monitor in Cockpit: an excerpt of the container overview with installed and available versions, update status and update actions.", projectsEyebrow: "Projects", projectsTitle: "Current projects", allProjects: "All projects compared", @@ -65,6 +76,10 @@ const text = { ["EU", "Eigene Server und Statistik"], ["Transparent", "Abhängigkeiten je Projekt benannt"], ], + previewTitle: "Container-Updates im Blick.", + previewText: "Sieh, welche Version läuft und welche verfügbar ist. Aktualisiere deine Container direkt in Cockpit – mit Sicherung vor dem Update.", + previewLink: "Image Update Monitor ansehen", + previewAlt: "Image Update Monitor in Cockpit: Ausschnitt der Containerübersicht mit installierten und verfügbaren Versionen, Update-Status und Aktionen zum Aktualisieren.", projectsEyebrow: "Projekte", projectsTitle: "Aktuelle Projekte", allProjects: "Alle Projekte im Vergleich", @@ -138,6 +153,34 @@ const prefix = lang === "en" ? "" : "/de"; +
+
+
+ +
+
+

+

{text.previewTitle}

+
+
+

{text.previewText}

+ +
+
+
+
+
+

{text.projectsEyebrow}

diff --git a/src/components/Shot.astro b/src/components/Shot.astro index 5dedb7d..3d6166b 100644 --- a/src/components/Shot.astro +++ b/src/components/Shot.astro @@ -8,6 +8,8 @@ import type { ImageMetadata } from "astro"; interface Props { image: ImageMetadata; dark?: ImageMetadata; + /** A genuine narrow-screen capture, used below the site's 760 px layout breakpoint. */ + mobile?: { image: ImageMetadata; dark?: ImageMetadata }; alt: string; /** Widths to offer; the browser picks by the space the picture gets. */ widths?: number[]; @@ -16,13 +18,15 @@ interface Props { class?: string; } -const { image, dark, alt, widths = [640, 960, 1280], sizes = "(max-width: 1168px) 100vw, 1120px", eager = false } = Astro.props; +const { image, dark, mobile, alt, widths = [640, 960, 1280], sizes = "(max-width: 1168px) 100vw, 1120px", eager = false } = Astro.props; const usable = widths.filter((w) => w <= image.width); if (usable.length === 0) usable.push(image.width); async function variants(source: ImageMetadata) { const list = []; - for (const width of usable) { + const sourceWidths = widths.filter((w) => w <= source.width); + if (sourceWidths.length === 0) sourceWidths.push(source.width); + for (const width of sourceWidths) { const result = await getImage({ src: source, width, format: "webp", quality: 82 }); list.push(`${result.src} ${width}w`); } @@ -31,11 +35,15 @@ async function variants(source: ImageMetadata) { const lightSet = await variants(image); const darkSet = dark ? await variants(dark) : undefined; +const mobileSet = mobile ? await variants(mobile.image) : undefined; +const mobileDarkSet = mobile?.dark ? await variants(mobile.dark) : undefined; const fallback = await getImage({ src: image, width: usable[usable.length - 1], format: "webp", quality: 82 }); const height = Math.round((image.height / image.width) * usable[usable.length - 1]); --- + {mobileDarkSet && } + {mobileSet && } {darkSet && } * { min-width: 0; } +.product-preview__name { font-size: 22px; color: var(--text); } +.product-preview__caption .more-link { margin-top: var(--space-md); } +.product-preview__caption a { display: inline-flex; align-items: center; gap: var(--space-sm); min-height: 44px; } + .section { padding-block: var(--space-section); border-top: 1px solid var(--line); } .section--alt { background: var(--bg-card); } .section--alt .az-label { background: var(--tint-neutral); } @@ -293,12 +311,16 @@ pre code { font-size: inherit; } .split, .split--wide { grid-template-columns: 1fr; gap: 40px; } } @media (max-width: 760px) { + .product-preview__shot { max-width: 390px; margin-inline: auto; } + .product-preview__shot img { aspect-ratio: 390 / 581; } + .product-preview__caption { grid-template-columns: 1fr; gap: var(--space-lg); } .card-grid, .gallery, .values, .hero__pair, .hero__split { grid-template-columns: 1fr; } .hero__split { gap: var(--space-lg); } .hero__split .hero__icon { order: -1; } .hero__icon img { width: 96px; height: 96px; } } @media (max-width: 639px) { + .product-preview { padding-bottom: 72px; } .wrap { padding-inline: 20px; } .hero { padding-top: 56px; padding-bottom: 64px; } .section { padding-block: 72px; }