feat(admin): Logo und Favicon per Bild-Upload austauschbar

Neue Gruppe "Logo und Favicon" unter Admin > Inhalte mit drei Bildfeldern:
Logo (Kopfzeile), optionales Logo fuer dunkle Flaechen (Mobilmenue,
Fusszeile, Admin-Leiste; faellt sonst auf das normale Logo zurueck) und
Favicon. Leer bleibt alles wie bisher (gezeichnetes Waben-Zeichen bzw.
Standard-Icon).

- lib/brand.ts liest die Pfade und prueft sie erneut gegen das
  Upload-Muster, bevor sie in ein src-Attribut gelangen.
- Statische app/icon.svg und app/favicon.ico ersetzt durch dynamische
  app/icon.tsx (512 px PNG) und app/apple-icon.tsx (180 px auf Creme);
  lib/brand-icon.ts rendert Upload oder Standard-Icon per sharp und liest
  Dateien nur ueber resolveUploadPath. Cache-Control no-cache, damit ein
  neues Favicon ohne Tage Verzoegerung erscheint.
- /favicon.ico per Rewrite auf /icon, Manifest nutzt /icon.
- Hochgeladenes Logo mit fester Hoehe und begrenzter Breite (Kopfzeile
  mobil 56 px, ab sm 112 px), damit breite Wortmarken nichts sprengen.

Uploads bleiben auf Rasterbilder beschraenkt (kein SVG, XSS-Schutz).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-26 01:02:18 +02:00
1 parent ae2726b0ee
commit 4f442ec06d
19 files changed
+217 -77

No files matched your search

+15 -33
View File
@@ -1,41 +1,23 @@
import { ImageResponse } from "next/og";
import { getBrandAssets } from "@/lib/brand";
import { renderBrandIcon } from "@/lib/brand-icon";
export const size = { width: 180, height: 180 };
export const contentType = "image/png";
// Wie app/icon.tsx: im Admin austauschbar, deshalb nicht zur Build-Zeit festschreiben.
export const dynamic = "force-dynamic";
/**
* Apple-Touch-Icon fuer "Zum Home-Bildschirm hinzufuegen" auf iOS/iPadOS —
* Apple akzeptiert dafuer ausschliesslich PNG/JPG, kein SVG (anders als
* app/icon.svg, das moderne Chrome/Android direkt lesen, siehe
* app/manifest.ts). Ohne diese Datei nimmt iOS ersatzweise einen
* Screenshot der Seite als Icon — unbrandet und meist unbrauchbar. Gleiche
* Grafik wie app/icon.svg, per ImageResponse als Raster gerendert.
* Apple akzeptiert dafuer ausschliesslich PNG/JPG, kein SVG. Ohne diese
* Datei nimmt iOS ersatzweise einen Screenshot der Seite als Icon. Gleiche
* Quelle wie das Favicon, aber auf Creme-Grund: iOS stellt transparente
* Flaechen schwarz dar.
*/
export default function AppleIcon() {
return new ImageResponse(
(
<svg width={180} height={180} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect width={100} height={100} rx={20} fill="#24352B" />
<polygon points="50,12 82,30 82,68 50,86 18,68 18,30" fill="none" stroke="#B88A44" strokeWidth={4} />
<g fill="#F4EFE5">
<g transform="translate(26,42) scale(0.62)">
<polygon points="10,2 5,13 15,13" />
<polygon points="10,10 2,22 18,22" />
<rect x={8} y={22} width={4} height={4} />
</g>
<g transform="translate(40,30) scale(0.82)">
<polygon points="10,2 5,13 15,13" />
<polygon points="10,10 2,22 18,22" />
<rect x={8} y={22} width={4} height={4} />
</g>
<g transform="translate(56,42) scale(0.62)">
<polygon points="10,2 5,13 15,13" />
<polygon points="10,10 2,22 18,22" />
<rect x={8} y={22} width={4} height={4} />
</g>
</g>
</svg>
),
{ ...size }
);
export default async function AppleIcon(): Promise<Response> {
const { favicon } = await getBrandAssets();
const png = await renderBrandIcon(favicon, size.width, "#FAF8F3");
return new Response(new Uint8Array(png), {
headers: { "Content-Type": contentType, "Cache-Control": "no-cache" },
});
}