larpaso-website/src/lib/fonts.ts
Lara 2fa8dc6615 Serve fonts ourselves and write the privacy policy
Fonts: every Google font the editor offers (13 families, latin and
latin-ext, 1.2 MB) now lives in public/fonts; the layout inlines
@font-face rules only for the fonts in use, built from
src/lib/font-faces.json. scripts/fonts-holen.py re-fetches them.
The admin font preview loads the same files via /admin/fonts and
/admin/api/font-faces. Checked: no request leaves larpaso.de on the
start page, Impressum or Datenschutz, nor in the admin editor.

nginx: access_log off, so visitor IPs are not stored. Caddy has no
access log configured either.

Privacy policy replaces the placeholder and describes only what the
site actually does: controller (from the Impressum), hosting at netcup
in Nuremberg (from the server IP), no access logs, HTTPS, mailto
contact form and e-mail, self-hosted fonts and media, no cookies,
data subject rights and the Lower Saxony supervisory authority.
The closing note is no longer styled as a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:54:24 +02:00

65 lines
3.9 KiB
TypeScript

import fontFaces from './font-faces.json';
export interface FontOption {
name: string;
category: 'serif' | 'sans';
google: boolean; // true = a Google Font we host ourselves in public/fonts; false = system font
fallback: string;
}
/**
* Curated font list for the admin "Design" editor. Kept in sync by hand with
* admin/lib/fonts.js (duplicated rather than shared because the Astro site
* is ESM/TS and the admin backend is CommonJS).
*/
export const FONT_OPTIONS: FontOption[] = [
{ name: 'Playfair Display', category: 'serif', google: true, fallback: "Georgia, 'Times New Roman', serif" },
{ name: 'Lora', category: 'serif', google: true, fallback: "Georgia, 'Times New Roman', serif" },
{ name: 'Merriweather', category: 'serif', google: true, fallback: "Georgia, 'Times New Roman', serif" },
{ name: 'Libre Baskerville', category: 'serif', google: true, fallback: "Georgia, 'Times New Roman', serif" },
{ name: 'DM Serif Display', category: 'serif', google: true, fallback: "Georgia, 'Times New Roman', serif" },
{ name: 'Georgia (Systemschrift)', category: 'serif', google: false, fallback: "Georgia, 'Times New Roman', serif" },
{ name: 'Inter', category: 'sans', google: true, fallback: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" },
{ name: 'Roboto', category: 'sans', google: true, fallback: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" },
{ name: 'Open Sans', category: 'sans', google: true, fallback: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" },
{ name: 'Poppins', category: 'sans', google: true, fallback: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" },
{ name: 'Montserrat', category: 'sans', google: true, fallback: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" },
{ name: 'Nunito', category: 'sans', google: true, fallback: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" },
{ name: 'Work Sans', category: 'sans', google: true, fallback: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" },
{ name: 'Source Sans 3', category: 'sans', google: true, fallback: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" },
{ name: 'System UI (Systemschrift)', category: 'sans', google: false, fallback: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" },
];
export function findFont(name: string): FontOption {
return FONT_OPTIONS.find((f) => f.name === name) || FONT_OPTIONS[0];
}
export function fontFamilyCss(name: string): string {
const font = findFont(name);
return font.google ? `'${font.name}', ${font.fallback}` : font.fallback;
}
/** Builds the @font-face rules for the given font names from the files in
* public/fonts (see font-faces.json). The fonts are served from our own
* server so a page view never contacts Google. System fonts are skipped. */
export function localFontFacesCss(fontNames: string[]): string {
const faces = fontFaces as Record<string, { file: string; weight: string; unicodeRange: string }[]>;
return fontNames
.map(findFont)
.filter((f) => f.google)
.filter((f, i, arr) => arr.findIndex((g) => g.name === f.name) === i)
.flatMap((f) =>
(faces[f.name] || []).map(
(face) =>
`@font-face { font-family: '${f.name}'; font-style: normal; font-weight: ${face.weight}; ` +
`font-display: swap; src: url('${face.file}') format('woff2'); unicode-range: ${face.unicodeRange}; }`,
),
)
.join('\n');
}
/** Builds `[data-font="Name"] { font-family: ...; }` rules for fonts used
* inline via the rich-text editor's per-selection font picker. */
export function inlineFontFacesCss(fontNames: string[]): string {
return fontNames.map((name) => `[data-font="${name}"] { font-family: ${fontFamilyCss(name)}; }`).join('\n');
}