Willkommens-Karte einstellbar: Farben, Bild, Texte, Live-Vorschau
Die Karte war komplett fest verdrahtet — Farben, der Schriftzug „WILLKOMMEN", die Zeile „Member #N", das Hintergrund-Raster. Wer etwas ändern wollte, musste das SVG im Code anfassen. Jetzt steht das Aussehen unter Config → Support: vier Farben, die beiden Textzeilen, ein optionales Hintergrundbild per Adresse, der Markenname im Hintergrund an/aus, und die Karte selbst abschaltbar (dann bleibt das Text-Embed mit Avatar als Vorschaubild). Daneben eine Vorschau, die beim Tippen mitrendert — sie geht über /api/welcome-preview.png und bekommt die Werte als Query, damit man sieht, was man einstellt, bevor gespeichert wird. Gespeichert wird davon nichts. Die Akzentfarben greifen auf die Markenfarben zurück, solange nichts Eigenes gesetzt ist. Wer also die Marke umfärbt, hat die Karte gleich mit. Beim Hintergrundbild ist Vorsicht eingebaut: nur http(s), Zeitlimit, Größenbegrenzung, und ein nicht erreichbares Bild lässt die Karte ohne Bild rendern statt sie ausfallen zu lassen. Über einem Bild liegt ein Schleier, sonst säuft der Text ab. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -305,6 +305,31 @@ export default function Settings({ me }) {
|
||||
if (data?.status?.botDescription != null) setBotDesc(data.status.botDescription);
|
||||
}, [data]);
|
||||
|
||||
// Vorschau der Willkommens-Karte. Verzögert, damit nicht jeder Tastendruck
|
||||
// ein Rendering auslöst — die Karte wird serverseitig mit sharp gebaut.
|
||||
const [welcomePreviewUrl, setWelcomePreviewUrl] = useState('/api/welcome-preview.png');
|
||||
useEffect(() => {
|
||||
if (!form) return undefined;
|
||||
const timer = setTimeout(() => {
|
||||
const q = new URLSearchParams({
|
||||
background: form.welcome_card_bg ?? '',
|
||||
accent: form.welcome_card_accent ?? '',
|
||||
accent2: form.welcome_card_accent2 ?? '',
|
||||
text: form.welcome_card_text ?? '',
|
||||
kicker: form.welcome_card_kicker ?? '',
|
||||
sub: form.welcome_card_sub ?? '',
|
||||
image: form.welcome_card_image ?? '',
|
||||
watermark: form.welcome_card_watermark ? '1' : '0',
|
||||
});
|
||||
setWelcomePreviewUrl(`/api/welcome-preview.png?${q}`);
|
||||
}, 450);
|
||||
return () => clearTimeout(timer);
|
||||
}, [
|
||||
form?.welcome_card_bg, form?.welcome_card_accent, form?.welcome_card_accent2,
|
||||
form?.welcome_card_text, form?.welcome_card_kicker, form?.welcome_card_sub,
|
||||
form?.welcome_card_image, form?.welcome_card_watermark,
|
||||
]);
|
||||
|
||||
// Zurück-Taste und von Hand geänderte Adressen sollen den Bereich mitwechseln
|
||||
useEffect(() => {
|
||||
const onHash = () => {
|
||||
@@ -1244,6 +1269,7 @@ export default function Settings({ me }) {
|
||||
);
|
||||
|
||||
const tabSupport = (
|
||||
<>
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Moderation & Kontakt</h2>
|
||||
<div className="field">
|
||||
@@ -1275,6 +1301,88 @@ export default function Settings({ me }) {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Willkommens-Karte</h2>
|
||||
<p className="section-intro">
|
||||
Das Bild, das neue Mitglieder im Willkommens-Kanal begrüßt. Die Vorschau
|
||||
zeigt sofort, wie es aussieht — gespeichert wird erst mit dem Knopf unten.
|
||||
</p>
|
||||
|
||||
<div className="field">
|
||||
<label className="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.welcome_card_enabled}
|
||||
onChange={(e) => setForm({ ...form, welcome_card_enabled: e.target.checked })}
|
||||
/>
|
||||
<span>Karte rendern</span>
|
||||
<span className="field-hint">aus = nur das Text-Embed mit Avatar als Vorschaubild</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{form.welcome_card_enabled && (
|
||||
<>
|
||||
<img className="welcome-preview" src={welcomePreviewUrl} alt="Vorschau der Willkommens-Karte" />
|
||||
|
||||
<div className="wc-colors">
|
||||
{[
|
||||
['welcome_card_bg', 'Hintergrund'],
|
||||
['welcome_card_accent', 'Akzent 1'],
|
||||
['welcome_card_accent2', 'Akzent 2'],
|
||||
['welcome_card_text', 'Name'],
|
||||
].map(([key, label]) => (
|
||||
<label className="wc-color" key={key}>
|
||||
<input
|
||||
className="color-pick"
|
||||
type="color"
|
||||
value={form[key] ?? '#000000'}
|
||||
onChange={(e) => setForm({ ...form, [key]: e.target.value })}
|
||||
/>
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label>Kleine Zeile <span className="field-hint">über dem Namen — leer = weglassen</span></label>
|
||||
<input
|
||||
type="text" maxLength={24} placeholder="WILLKOMMEN"
|
||||
value={form.welcome_card_kicker ?? ''}
|
||||
onChange={(e) => setForm({ ...form, welcome_card_kicker: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Zeile darunter <span className="field-hint">{'{count}'} wird zur Mitglieds-Nummer — leer = weglassen</span></label>
|
||||
<input
|
||||
type="text" maxLength={60} placeholder="Member #{count}"
|
||||
value={form.welcome_card_sub ?? ''}
|
||||
onChange={(e) => setForm({ ...form, welcome_card_sub: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Hintergrundbild <span className="field-hint">https-Adresse, 900×300 passt genau — leer = nur Farbe mit Raster</span></label>
|
||||
<input
|
||||
type="text" placeholder="https://cdn.d4rkst3r.de/welcome.png"
|
||||
value={form.welcome_card_image ?? ''}
|
||||
onChange={(e) => setForm({ ...form, welcome_card_image: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.welcome_card_watermark}
|
||||
onChange={(e) => setForm({ ...form, welcome_card_watermark: e.target.checked })}
|
||||
/>
|
||||
<span>Markenname im Hintergrund</span>
|
||||
<span className="field-hint">großer, schwach sichtbarer Schriftzug hinten rechts</span>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const tabBewerbungen = (
|
||||
|
||||
@@ -1109,6 +1109,22 @@ label.toggle {
|
||||
box-shadow: 0 0 0 3px rgba(245, 197, 24, .12);
|
||||
}
|
||||
|
||||
/* Willkommens-Karte: Vorschau + Farbwähler */
|
||||
.welcome-preview {
|
||||
display: block; width: 100%; max-width: 900px;
|
||||
aspect-ratio: 3 / 1;
|
||||
border: 1px solid var(--border); border-radius: 10px;
|
||||
background: var(--bg3);
|
||||
margin-bottom: 1.1rem;
|
||||
}
|
||||
.wc-colors { display: flex; flex-wrap: wrap; gap: 1.2rem; margin-bottom: 1.2rem; }
|
||||
.wc-color { display: flex; align-items: center; gap: .55rem; cursor: pointer; }
|
||||
.wc-color > span {
|
||||
font-family: var(--mono); font-size: .64rem;
|
||||
letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
|
||||
}
|
||||
.wc-color:hover > span { color: var(--text); }
|
||||
|
||||
/* Branding-Tab: Farb- und Datei-Inputs */
|
||||
.color-pick {
|
||||
width: 3.2rem; height: 2.4rem; flex: none;
|
||||
|
||||
Reference in New Issue
Block a user