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>
100 lines
5.4 KiB
JavaScript
100 lines
5.4 KiB
JavaScript
#!/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 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}`);
|