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 ');
}