pageta/apps/landing/werkzeug/og-bild.mjs
Till JS acc81d4e2a Landing: Unterseiten im neuen Look, FAQ zweispaltig, Vorschaubild mit App
Seitenkopf.astro für /pocket-import, /ueber, /mitwirken; Umzug mit
Hinweis-Kasten, Schritt-Karten und Ergebnisliste; Mitwirken als drei
Karten. FAQ, Kit-Abschnitte und Schluss-Aufruf seitenweit über
rahmen.css (Überschrift links, Fragen rechts; runde Knöpfe).

Richtiggestellt: git.mana.how/till/pageta (Repo, Issues, CLAUDE.md) und
mana.how/mitgliedschaft waren für Besucher 404 — Forgejo zeigt ohne
Anmeldung kein Repo. Keine „öffentlich lesbar“-Zusage mehr, Meldungen
über den Feedback-Dialog, Mitgründen über mana.how/mitgruenden. Auch
„per Teilen-Menü“ gestrichen: die Web-App hat kein share_target.

Umzug breiter (Pocket, Instapaper, Lesezeichen), Bildnachweis unter die
Hero-Aufnahmen, Umschalter rund, Schritt-Karten auf dem Telefon kompakt,
Platz für den Feedback-Knopf im Fuss. Lighthouse: Kontrast der
Schritt-Nummern und aria-label auf span behoben, Barrierefreiheit 100.

og-bild.mjs rendert jetzt per Headless-Chromium mit der Leseansicht.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 20:44:06 +02:00

100 lines
5.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Das Vorschaubild fürs Teilen — `public/og-default.png` (1200×630).
*
* node werkzeug/og-bild.mjs schreibt public/og-default.png
* node werkzeug/og-bild.mjs --probe schreibt nach $TMPDIR, fasst public/ nicht an
*
* Seit 2026-09-29 mit der echten Leseansicht rechts (public/app/lesen-1600.webp)
* statt reinem Text: ein geteilter Link soll zeigen, WIE Pageta aussieht.
* Gerendert als HTML im Headless-Chromium, damit Schrift und Farben die der
* Seite sind (Source Serif 4 aus @mana/themes, Papier-Palette aus app.ts).
*
* Voraussetzung: Playwright + Chromium. Standardmässig der aus platform/mana;
* sonst PLAYWRIGHT_MODUL und CHROMIUM setzen. Ergebnis ist eine eingecheckte
* PNG — der Bau selbst braucht kein Systemwerkzeug.
*
* Aus dem Vorgänger (rsvg-Fassung, bis 2026-09-29) übernommen:
*
* 🛑 KEINE MENGENANGABE, KEIN DATUM. Ein OG-Bild wird von Messengern und
* Suchmaschinen über Monate zwischengespeichert; eine gedruckte Zahl altert
* dort, ohne dass es jemand merkt. Auf dem Bild steht, WAS die Fläche ist.
*
* 🛑 KEINE RECHTSFORM. „von mana", nicht „vom Verein mana" — der Verein ist in
* Gründung, und ein zwischengespeichertes Bild lässt sich nicht nachbessern.
*
* 🔴 Wer das Bild tauscht, purgt danach `https://pageta.com/og-default.png`
* bei Cloudflare — gleicher Dateiname, sonst liefert die Kante die alte Fassung.
*/
import { readFileSync, readdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { tmpdir, homedir } from 'node:os';
import { fileURLToPath } from 'node:url';
const HIER = dirname(fileURLToPath(import.meta.url));
const APP = join(HIER, '..');
const probe = process.argv.includes('--probe');
const ZIEL = probe ? join(tmpdir(), 'pageta-og-probe.png') : join(APP, 'public', 'og-default.png');
const PLAYWRIGHT =
process.env.PLAYWRIGHT_MODUL ??
join(homedir(), 'Developer/platform/mana/node_modules/.pnpm/playwright@1.60.0/node_modules/playwright/index.mjs');
const CHROMIUM =
process.env.CHROMIUM ??
join(homedir(), 'Library/Caches/ms-playwright/chromium_headless_shell-1243/chrome-headless-shell-mac-arm64/chrome-headless-shell');
/** Schriften aus @mana/themes — der Ordner liegt irgendwo im pnpm-Speicher. */
function schrift(name) {
const pnpm = join(APP, '../../node_modules/.pnpm');
for (const d of readdirSync(pnpm)) {
const p = join(pnpm, d, 'node_modules/@mana/themes/src/fonts', name);
try {
return `data:font/woff2;base64,${readFileSync(p).toString('base64')}`;
} catch {}
}
throw new Error(`Schrift ${name} nicht gefunden`);
}
/** Als data:-URL — eine per setContent geladene Seite darf keine file:-Adressen holen. */
const datei = (p) => {
const typ = p.endsWith('.svg') ? 'image/svg+xml' : 'image/webp';
return `data:${typ};base64,${readFileSync(join(APP, 'public', p)).toString('base64')}`;
};
const html = `<!doctype html><html><head><meta charset="utf-8"><style>
@font-face { font-family: 'PagetaSerif'; src: url(${schrift('source-serif-4-latin-wght-normal.woff2')}); font-weight: 200 900; }
@font-face { font-family: 'PagetaSans'; src: url(${schrift('inter-latin-wght-normal.woff2')}); font-weight: 100 900; }
* { box-sizing: border-box; margin: 0; }
body { width: 1200px; height: 630px; overflow: hidden; position: relative; font-family: 'PagetaSans';
background: radial-gradient(700px 420px at 95% 0%, hsl(18 50% 38% / .16), transparent 70%),
radial-gradient(520px 320px at 0% 100%, #fde68a55, transparent 70%), hsl(38 28% 95%); color: hsl(20 14% 16%); }
.links { position: absolute; left: 80px; top: 72px; width: 560px; }
.marke { display: flex; align-items: center; gap: 16px; font-family: 'PagetaSerif'; font-weight: 600; font-size: 38px; }
.marke img { width: 52px; height: 52px; }
h1 { margin-top: 64px; font-family: 'PagetaSerif'; font-weight: 600; font-size: 66px; line-height: 1.06; letter-spacing: -1.5px; }
.strich { background: linear-gradient(transparent 58%, #fde68ad9 58%, #fde68ad9 92%, transparent 92%); padding: 0 4px; }
.fuss { position: absolute; left: 80px; bottom: 64px; font-size: 26px; color: hsl(20 14% 40%); }
.fuss b { color: hsl(18 50% 38%); font-weight: 600; }
.fenster { position: absolute; left: 700px; top: 70px; width: 600px; border-radius: 18px; overflow: hidden;
border: 1px solid hsl(38 18% 80%); background: #fff; transform: rotate(-2.5deg);
box-shadow: 0 30px 70px -20px hsl(24 30% 20% / .45); }
.leiste { height: 40px; background: hsl(38 20% 90%); border-bottom: 1px solid hsl(38 18% 80%); display: flex; align-items: center; gap: 8px; padding-left: 16px; }
.leiste i { width: 11px; height: 11px; border-radius: 50%; background: hsl(38 18% 78%); }
.fenster img { display: block; width: 100%; }
</style></head><body>
<div class="links">
<div class="marke"><img src="${datei('icon.svg')}" alt="">Pageta</div>
<h1>Sammle, was du lesen willst — und lies es <span class="strich">in&nbsp;Ruhe.</span></h1>
</div>
<p class="fuss">Ohne Algorithmus, ohne Werbung · <b>pageta.com</b></p>
<div class="fenster"><div class="leiste"><i></i><i></i><i></i></div><img src="${datei('app/lesen-1600.webp')}" alt=""></div>
</body></html>`;
const { chromium } = await import(PLAYWRIGHT);
const browser = await chromium.launch({ executablePath: CHROMIUM });
const seite = await browser.newPage({ viewport: { width: 1200, height: 630 } });
await seite.setContent(html, { waitUntil: 'load' });
await seite.evaluate(() => document.fonts.ready);
await seite.screenshot({ path: ZIEL });
await browser.close();
console.log(`✓ ${ZIEL}`);