diff --git a/docs/brand/README.md b/docs/brand/README.md index f27887a..960e9b2 100644 --- a/docs/brand/README.md +++ b/docs/brand/README.md @@ -45,8 +45,15 @@ A:/eco/Gitea/data/gitea/public/assets/img/ frontend/public/ favicon.svg apple-touch-icon.png + og-default.png Vorschaubild geteilter Links, 1200×630 + +src/bot/assets/ + mark-card.png Wasserzeichen der Willkommens-Karte ``` +`src/bot/assets/` statt `docs/` ist Absicht: das Dockerfile kopiert nur `src/` +und `frontend/dist/`, alles andere existiert im Container nicht. + Die Icons auf dunklem Grund sind **deckend** (`#0a0a0a`, abgerundet) — iOS legt hinter transparente Lesezeichen sonst Schwarz oder Weiß, je nach Laune, und die Katze ist selbst fast schwarz. diff --git a/docs/brand/build.py b/docs/brand/build.py index bba5ec4..362e39c 100644 --- a/docs/brand/build.py +++ b/docs/brand/build.py @@ -14,13 +14,16 @@ 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') +# Das Dockerfile kopiert nur src/ und frontend/dist/ — was der Bot zur Laufzeit +# braucht, muss deshalb unter src/ liegen, nicht in docs/. +BOT_ASSETS = pathlib.Path('A:/eco/dc_devlog_bot/src/bot/assets') # Katzenkopf mit Ohrenspitzen und etwas Schulter, ohne Schriftzug # (Koordinaten im 2048er Original) KOPF = (330, 20, 1740, 1430) DUNKEL = (10, 10, 10, 255) # --bg der Marke -for d in (MARKE, GITEA, FRONTEND): +for d in (MARKE, GITEA, FRONTEND, BOT_ASSETS): d.mkdir(parents=True, exist_ok=True) original = Image.open(QUELLE).convert('RGBA') @@ -90,6 +93,46 @@ schreib(GITEA / 'avatar_default.png', png_bytes(gerundet(marke, 512))) schreib(FRONTEND / 'favicon.svg', svg_huelle(png_bytes(gerundet(marke, 128), 128), 128)) schreib(FRONTEND / 'apple-touch-icon.png', png_bytes(gerundet(marke, 180))) + +def og_bild(breite=1200, hoehe=630): + """Rueckfall-Vorschaubild fuer geteilte Links (Discord, WhatsApp, Twitter). + + 1200x630 ist das Format, das alle Netze erwarten. Ohne so ein Bild zeigt + Discord bei einem geteilten Link gar nichts. + """ + blatt = Image.new('RGBA', (breite, hoehe), DUNKEL) + + # Akzentstreifen oben und unten, wie auf der Willkommens-Karte + streifen = Image.new('RGBA', (breite, 6), (0, 0, 0, 0)) + zeichner = ImageDraw.Draw(streifen) + for x in range(breite): + t = x / (breite - 1) + # Gold nach Orange, an den Raendern ausblendend + r = int(245 + (255 - 245) * t) + g = int(197 + (77 - 197) * t) + b = int(24 + (0 - 24) * t) + rand = min(t, 1 - t) / 0.3 + zeichner.line([(x, 0), (x, 5)], fill=(r, g, b, int(255 * min(1.0, rand)))) + blatt.alpha_composite(streifen, (0, 0)) + blatt.alpha_composite(streifen, (0, hoehe - 6)) + + # Lockup mittig, auf 70 % der Hoehe + ziel_h = int(hoehe * 0.70) + ziel_b = int(lockup.width * ziel_h / lockup.height) + klein = lockup.resize((ziel_b, ziel_h), Image.LANCZOS) + blatt.alpha_composite(klein, ((breite - ziel_b) // 2, (hoehe - ziel_h) // 2)) + return blatt + + +puffer = io.BytesIO() +og_bild().convert('RGB').save(puffer, 'PNG', optimize=True) +schreib(FRONTEND / 'og-default.png', puffer.getvalue()) + +# ── Bot zur Laufzeit ───────────────────────────────── +# Wasserzeichen der Willkommens-Karte. Klein halten: die Datei wird beim Rendern +# als Data-URI ins SVG gelegt, und die Karte laeuft bei jedem Beitritt. +schreib(BOT_ASSETS / 'mark-card.png', png_bytes(marke, 220)) + for pfad, n in geschrieben: print(f'{n/1024:8.1f} KB {pfad}') diff --git a/frontend/public/og-default.png b/frontend/public/og-default.png new file mode 100644 index 0000000..814293d Binary files /dev/null and b/frontend/public/og-default.png differ diff --git a/src/bot/assets/mark-card.png b/src/bot/assets/mark-card.png new file mode 100644 index 0000000..eb202c1 Binary files /dev/null and b/src/bot/assets/mark-card.png differ diff --git a/src/bot/welcome-card.js b/src/bot/welcome-card.js index bece5fa..d7d3dd3 100644 --- a/src/bot/welcome-card.js +++ b/src/bot/welcome-card.js @@ -1,13 +1,27 @@ // Willkommens-Karte: gerendertes PNG (SVG → sharp). Aussehen kommt aus den // Einstellungen (Config → Support → Willkommens-Karte), die Standardwerte sind // der Marken-Look: Avatar mit Neon-Ring, großer Schriftzug, Member-Nummer. +import { readFileSync } from 'node:fs'; import sharp from 'sharp'; -import { brandName, welcomeCard } from '../runtime-settings.js'; +import { welcomeCard } from '../runtime-settings.js'; const W = 900; const H = 300; const MAX_IMAGE_BYTES = 8 * 1024 * 1024; +// Marke fürs Wasserzeichen: einmal beim Start lesen, nicht bei jedem Beitritt. +// Fehlt die Datei, bleibt die Karte einfach ohne — wie bei den anderen Bildern +// hier auch soll ein fehlendes Detail nicht die ganze Karte kosten. +// Erzeugt von docs/brand/build.py. +const MARK_URI = (() => { + try { + const pfad = new URL('./assets/mark-card.png', import.meta.url); + return `data:image/png;base64,${readFileSync(pfad).toString('base64')}`; + } catch { + return ''; + } +})(); + /** XML-Sonderzeichen im Usernamen entschärfen */ function esc(s) { return String(s).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>') @@ -80,9 +94,9 @@ export async function buildWelcomeCard({ username, avatarUrl, memberNumber }, ov ? `` : ''} ${veil} - ${look.watermark - ? `${esc(brandName())}` + ${look.watermark && MARK_URI + ? `` : ''} diff --git a/src/modules.js b/src/modules.js index 0f33052..3c90920 100644 --- a/src/modules.js +++ b/src/modules.js @@ -183,7 +183,7 @@ export const MODULES = [ { setting: 'welcome_card_accent', label: 'Akzent 1', art: 'farbe' }, { setting: 'welcome_card_accent2', label: 'Akzent 2', art: 'farbe' }, { setting: 'welcome_card_text', label: 'Name', art: 'farbe' }, - { setting: 'welcome_card_watermark', label: 'Markenname im Hintergrund', art: 'schalter' }, + { setting: 'welcome_card_watermark', label: 'Logo im Hintergrund', art: 'schalter' }, ], vorschau: 'welcome', }, diff --git a/src/web/server.js b/src/web/server.js index da8ce8b..f668dc8 100644 --- a/src/web/server.js +++ b/src/web/server.js @@ -146,6 +146,11 @@ export async function startWebServer(client) { } } + // Ohne Bild zeigt Discord bei einem geteilten Link nur eine graue + // Textzeile. Devlogs mit Screenshot behalten ihren, alles andere + // bekommt das Logo. + if (!image) image = `${base}/og-default.png`; + return [ ``, ``, @@ -154,8 +159,8 @@ export async function startWebServer(client) { ``, ``, ``, - image ? `` : '', - image ? `` : '', + ``, + ``, ].filter(Boolean).join('\n '); }