Logo einbinden: Favicons, Apple-Icon und Marken-Quellen
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

Das Logo lag bisher nirgends im Einsatz — das Favicon war ein handgeschriebenes
Prisma in Gold/Orange, das Wasserzeichen der Willkommens-Karte der blosse
Markenname als Text.

- docs/brand/ mit lockup.png (volles Logo) und mark.png (nur der Wolfskopf)
  plus build.py, das beide und alle abgeleiteten Icons erzeugt
- frontend: favicon.svg ersetzt, apple-touch-icon.png dazu und verlinkt
- Zwei Fassungen, weil das Lockup bei 16 px zu einem orangen Balken wird; der
  Kopfzuschnitt wurde gegen drei Varianten geprueft
- SVG mit eingebettetem PNG statt Vektorisierung: das Original hat Verlaeufe
  und Schattierung, nachgezeichnet wuerde es matschen
- Die Icons auf dunklem Grund sind deckend, sonst legt iOS hinter transparente
  Lesezeichen Schwarz oder Weiss und der Wolf ist selbst fast schwarz

Geprueft: beide Dateien werden mit korrektem MIME-Typ ausgeliefert und das SVG
rendert wirklich (128x128), die Link-Tags stehen im gebauten HTML.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 03:40:19 +02:00
co-authored by Claude Fable 5
parent 5360c8229f
commit 33c48cb898
8 changed files with 169 additions and 6 deletions
+60
View File
@@ -0,0 +1,60 @@
# Marken-Dateien
Alles hier stammt aus einem einzigen Original: dem D4RKST3R-Logo in 2048×2048
mit Transparenz (Wolfskopf über dem Schriftzug-Banner).
## Was hier liegt
| Datei | Was | Wofür |
|---|---|---|
| `lockup.png` | volles Logo, auf den Inhalt beschnitten | Kopfzeilen, Discord-Banner, OG-Bilder |
| `mark.png` | nur der Wolfskopf, 1024×1024 | alles Quadratische — Favicon, Avatar |
| `build.py` | erzeugt beide plus die abgeleiteten Icons | Neubau nach Logo-Änderung |
## Warum zwei Fassungen
Das volle Lockup ist bei 16 oder 32 Pixeln unbrauchbar — der Schriftzug wird zu
einem orangen Balken. Für Favicon, Avatar und Navbar-Logo wird deshalb nur der
Kopf verwendet, mit Ohrenspitzen und etwas Schulter. Der Zuschnitt steht in
`build.py` als `KOPF` und wurde von Hand gegen drei Varianten geprüft: enger
schneidet die Ohren ab, weiter fängt schon den Banner ein.
## Warum SVG mit eingebettetem PNG
Gitea will `logo.svg` und `favicon.svg` — echte SVG-Dateien. Das Original ist
aber eine Rastergrafik mit Verläufen und Schattierung; nachvektorisieren würde
sie matschig machen. Deshalb wird das PNG in eine SVG-Hülle gelegt
(`<svg><image href="data:image/png;base64,…"/></svg>`). Das ist gültiges SVG,
Gitea nimmt es an, und es sieht aus wie das Original.
Die eingebettete Auflösung ist bewusst klein gehalten: das Navbar-Logo hängt an
jedem Seitenaufruf, 192 px reichen für eine Darstellung mit rund 30 px.
## Wohin die erzeugten Dateien gehen
`build.py` schreibt direkt an beide Ziele:
```
A:/eco/Gitea/data/gitea/public/assets/img/
logo.svg Navbar + App-Icon
favicon.svg Browser-Tab
apple-touch-icon.png iOS-Lesezeichen (180 px, deckend)
avatar_default.png Standardavatar für Konten ohne Bild (512 px)
frontend/public/
favicon.svg
apple-touch-icon.png
```
Die Icons auf dunklem Grund sind **deckend** (`#0a0a0a`, abgerundet) — iOS legt
hinter transparente Lesezeichen sonst Schwarz oder Weiß, je nach Laune, und der
Wolf ist selbst fast schwarz.
## Neu bauen
```bash
python docs/brand/build.py
```
Danach für Gitea `docker restart gitea` — die Dateien unter `custom/` werden
beim Start eingelesen. Fürs Frontend reicht der normale Build.
+107
View File
@@ -0,0 +1,107 @@
# Baut aus dem Original-Logo den Satz an Marken-Dateien:
# - die Wolfskopf-Marke freigestellt (fuer Favicon, Avatar, kleine Groessen)
# - das volle Lockup beschnitten (fuer Kopfzeilen, Discord, OG-Bilder)
# - Icons auf dunklem Grund (iOS und Gitea-Standardavatar moegen kein Alpha)
# - SVG-Huellen mit eingebettetem PNG (Gitea will .svg, das Original ist
# eine Rastergrafik mit Verlaeufen — nachvektorisieren wuerde matschen)
import base64
import io
import pathlib
from PIL import Image, ImageDraw
QUELLE = pathlib.Path('A:/Users/DAR/logo.png')
MARKE = pathlib.Path('A:/eco/dc_devlog_bot/docs/brand')
GITEA = pathlib.Path('A:/eco/Gitea/data/gitea/public/assets/img')
FRONTEND = pathlib.Path('A:/eco/dc_devlog_bot/frontend/public')
# Kopf mit Ohrenspitzen und etwas Schulter, ohne Schriftzug (im 2048er Original)
KOPF = (330, 20, 1740, 1430)
DUNKEL = (10, 10, 10, 255) # --bg der Marke
for d in (MARKE, GITEA, FRONTEND):
d.mkdir(parents=True, exist_ok=True)
original = Image.open(QUELLE).convert('RGBA')
marke = original.crop(KOPF)
lockup = original.crop(original.getchannel('A').getbbox())
def png_bytes(bild, groesse=None):
aus = bild if groesse is None else bild.resize((groesse, groesse), Image.LANCZOS)
puffer = io.BytesIO()
aus.save(puffer, 'PNG', optimize=True)
return puffer.getvalue()
def gerundet(bild, kante, radius_anteil=0.22, rand=0.08):
"""Marke mittig auf dunklem, abgerundetem Quadrat — deckend."""
flaeche = Image.new('RGBA', (kante, kante), (0, 0, 0, 0))
maske = Image.new('L', (kante, kante), 0)
ImageDraw.Draw(maske).rounded_rectangle(
(0, 0, kante - 1, kante - 1), radius=int(kante * radius_anteil), fill=255)
flaeche.paste(Image.new('RGBA', (kante, kante), DUNKEL), (0, 0), maske)
innen = int(kante * (1 - 2 * rand))
klein = bild.resize((innen, innen), Image.LANCZOS)
flaeche.alpha_composite(klein, ((kante - innen) // 2, (kante - innen) // 2))
return flaeche
def svg_huelle(daten, kante):
"""SVG mit eingebettetem PNG — gueltig, und Gitea nimmt nur .svg."""
b64 = base64.b64encode(daten).decode('ascii')
return (
f'<svg xmlns="http://www.w3.org/2000/svg" '
f'xmlns:xlink="http://www.w3.org/1999/xlink" '
f'viewBox="0 0 {kante} {kante}" width="{kante}" height="{kante}">'
f'<image width="{kante}" height="{kante}" '
f'xlink:href="data:image/png;base64,{b64}"/>'
f'</svg>\n'
)
geschrieben = []
def schreib(pfad, daten):
pfad.write_bytes(daten if isinstance(daten, bytes) else daten.encode('utf-8'))
geschrieben.append((pfad, len(daten)))
# ── Quelldateien im Repo, versioniert ────────────────
schreib(MARKE / 'mark.png', png_bytes(marke, 1024))
schreib(MARKE / 'lockup.png', png_bytes(lockup))
# ── Gitea ────────────────────────────────────────────
# Navbar-Logo und Favicon: die Marke, das Lockup ist bei 30 px unlesbar.
# 192 px reichen — Gitea zeigt das Logo in der Navbar mit rund 30 px, und die
# Datei haengt an jedem Seitenaufruf.
schreib(GITEA / 'logo.svg', svg_huelle(png_bytes(marke, 192), 192))
schreib(GITEA / 'favicon.svg', svg_huelle(png_bytes(gerundet(marke, 128), 128), 128))
schreib(GITEA / 'apple-touch-icon.png', png_bytes(gerundet(marke, 180)))
schreib(GITEA / 'avatar_default.png', png_bytes(gerundet(marke, 512)))
# ── Hub + Bot-Produktseite ───────────────────────────
schreib(FRONTEND / 'favicon.svg', svg_huelle(png_bytes(gerundet(marke, 128), 128), 128))
schreib(FRONTEND / 'apple-touch-icon.png', png_bytes(gerundet(marke, 180)))
for pfad, n in geschrieben:
print(f'{n/1024:8.1f} KB {pfad}')
# ── Vorschau: liest die Marke in klein noch? ─────────
# Landet neben diesem Skript, oben auf hellem, unten auf dunklem Grund —
# so sieht man vor dem Ausrollen, ab welcher Groesse es kippt.
KANTEN = (16, 32, 64, 128)
breite = sum(k + 40 for k in KANTEN) + 30
vorschau = Image.new('RGBA', (breite, 200), (24, 24, 28, 255))
x = 30
for kante in KANTEN:
for grund, y in (((250, 250, 250, 255), 20), (DUNKEL, 110)):
feld = Image.new('RGBA', (kante + 16, kante + 16), grund)
feld.alpha_composite(gerundet(marke, kante), (8, 8))
vorschau.alpha_composite(feld, (x, y))
x += kante + 40
ziel = pathlib.Path(__file__).parent / 'preview.png'
vorschau.convert('RGB').save(ziel)
print(f'\nVorschau ({"/".join(map(str, KANTEN))} px): {ziel}')
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+1
View File
@@ -6,6 +6,7 @@
<title>D4RKST3R // DEVLOG</title>
<meta name="description" content="D4RKST3R — Devlog-Archiv, Changelog & Community." />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta name="theme-color" content="#0a0a0a" />
</head>
<body>
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 26 KiB