Logo auf der Willkommens-Karte und in geteilten Links
Zwei Stellen, an denen die Marke bisher fehlte. Willkommens-Karte: Das Wasserzeichen war der Markenname als Text mit 5 % Deckkraft — jetzt der Katzenkopf. Die Datei wird einmal beim Start gelesen statt bei jedem Beitritt, und fehlt sie, rendert die Karte wie bisher ohne Wasserzeichen. Der Schalter heisst entsprechend "Logo im Hintergrund". Link-Vorschau: og:image war nur gesetzt, wenn ein Devlog einen Screenshot hatte. Startseite, Roadmap, Funktionen und jedes Devlog ohne Bild kamen in Discord als graue Textzeile. Jetzt faellt alles ohne eigenes Bild auf og-default.png zurueck (1200x630, Lockup auf Schwarz mit Akzentstreifen), und twitter:card ist durchgehend summary_large_image. Die Laufzeit-Datei liegt unter src/bot/assets/, nicht in docs/ — das Dockerfile kopiert nur src/ und frontend/dist/. Geprueft: librsvg zeichnet das eingebettete <image> wirklich (42726 Pixel unterscheiden sich gegen eine Fassung ohne), das OG-Bild landet im dist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
+44
-1
@@ -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}')
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
+18
-4
@@ -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
|
||||
? `<image href="${bgData}" x="0" y="0" width="${W}" height="${H}" clip-path="url(#card)" preserveAspectRatio="xMidYMid slice"/>`
|
||||
: ''}
|
||||
${veil}
|
||||
${look.watermark
|
||||
? `<text x="${W - 18}" y="${H - 24}" text-anchor="end" font-family="DejaVu Sans, sans-serif" font-weight="bold"
|
||||
font-size="120" fill="${look.accent}" fill-opacity=".05">${esc(brandName())}</text>`
|
||||
${look.watermark && MARK_URI
|
||||
? `<image href="${MARK_URI}" x="${W - 300}" y="5" width="290" height="290" opacity=".08"
|
||||
preserveAspectRatio="xMidYMid meet"/>`
|
||||
: ''}
|
||||
<rect x="0" y="0" width="${W}" height="3" fill="url(#line)"/>
|
||||
<rect x="0" y="${H - 3}" width="${W}" height="3" fill="url(#line)"/>
|
||||
|
||||
+1
-1
@@ -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',
|
||||
},
|
||||
|
||||
+7
-2
@@ -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 [
|
||||
`<meta property="og:site_name" content="${esc(brandName())}">`,
|
||||
`<meta property="og:title" content="${esc(title)}">`,
|
||||
@@ -154,8 +159,8 @@ export async function startWebServer(client) {
|
||||
`<meta property="og:type" content="website">`,
|
||||
`<meta name="theme-color" content="${isBotSite ? '#ff4d00' : '#f5c518'}">`,
|
||||
`<meta name="description" content="${esc(description)}">`,
|
||||
image ? `<meta property="og:image" content="${esc(image)}">` : '',
|
||||
image ? `<meta name="twitter:card" content="summary_large_image">` : '<meta name="twitter:card" content="summary">',
|
||||
`<meta property="og:image" content="${esc(image)}">`,
|
||||
`<meta name="twitter:card" content="summary_large_image">`,
|
||||
].filter(Boolean).join('\n ');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user