diff --git a/README.md b/README.md
index 171c3b7..655caf3 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ auf nichts mehr: keine Posts, keine Hintergrund-Prüfungen.
| 📈 **Level-System** | XP pro Nachricht (Cooldown, MEE6-Formel), `/rank`, Level-Up-Announce, Rollen-Belohnungen, öffentliche Bestenliste |
| ⭐ **Starboard** | Nachrichten mit genug ⭐-Reaktionen landen im Best-of-Kanal |
| 📸 **Screenshot-Galerie** | Bilder aus dem Screenshot-Kanal → öffentliche Galerie (lokal gespeichert, weil Discord-CDN-Links ablaufen) |
-| 💡 **Feature-Voting** | `/wunsch-setup` postet den Ideen-Aufruf: Bereich wählen, Formular ausfüllen → Voting-Post mit 👍 und **Thread zum Reden**. Wünsche haben einen **Stand** (wird geprüft · geplant · in Arbeit · umgesetzt · nicht geplant) samt Begründung — auf der Roadmap und im Discord-Post. Wer gestimmt hat, bekommt eine DM, sobald es umgesetzt ist. Die Roadmap filtert nach Stand und Bereich, sortiert nach Top / Bewegung / Neu und zeigt die Zahl der Beiträge im Thread. Eine Stimme je Person, egal ob 👍 oder Web-Knopf; Doppler lassen sich zusammenführen |
+| 💡 **Feature-Voting** | `/wunsch-setup` postet den Ideen-Aufruf: Bereich wählen, Formular ausfüllen → Voting-Post mit 👍 und **Thread zum Reden**. Wünsche haben einen **Stand** (wird geprüft · geplant · in Arbeit · umgesetzt · nicht geplant) samt Begründung — auf der Roadmap und im Discord-Post. Wer gestimmt hat, bekommt eine DM, sobald es umgesetzt ist. Die Roadmap filtert nach Stand und Bereich, sortiert nach Top / Bewegung / Neu und zeigt die Zahl der Beiträge im Thread. Ist der Voting-Kanal ein **Forum**, wird jeder Wunsch ein Forums-Beitrag mit **Tags** für Bereich und Stand — der Stand zieht beim Ändern automatisch mit. Eine Stimme je Person, egal ob 👍 oder Web-Knopf; Doppler lassen sich zusammenführen |
| 🗳️ **Umfragen** | `/umfrage` erzeugt eine **native Discord-Poll** — keine Reaktions-Bastelei, Discord zählt selbst |
| 🎉 **Giveaways** | `/giveaway` (Admin): Teilnahme-Button, automatische Ziehung; am Gewinner-Post 🔁 Neu auslosen + 👥 Teilnehmerliste |
| 🎂 **Geburtstage** | `/geburtstag` zum Eintragen; morgens Gratulation im Kanal + Tages-Rolle |
diff --git a/frontend/src/components/EmojiPick.jsx b/frontend/src/components/EmojiPick.jsx
new file mode 100644
index 0000000..187930a
--- /dev/null
+++ b/frontend/src/components/EmojiPick.jsx
@@ -0,0 +1,167 @@
+// Emoji-Auswahl fürs Panel.
+//
+// Überall, wo ein Emoji nach Discord geht — Rollen-Menüs, Ticket-Anliegen,
+// Wunsch-Bereiche, Portal-Kacheln — stand bisher ein Textfeld, in das man mit
+// Win+. hineintippen musste. Das hier ist derselbe Wert, nur klickbar.
+//
+// Bewusst eine eigene Liste statt einer Bibliothek: die vollständige
+// Unicode-Tabelle wäre ein halbes Megabyte für eine Handvoll Symbole, und die
+// Suchbegriffe dürfen deutsch sein.
+//
+// Server-Emojis (<:name:id>) tippt man weiter ins Feld — die kennt nur Discord.
+import { useEffect, useRef, useState } from 'react';
+import { IconSearch, IconX } from '../icons.jsx';
+
+const GRUPPEN = [
+ ['Häufig', '⭐', [
+ ['💡', 'idee gluehbirne wunsch'], ['🎫', 'ticket'], ['🐛', 'bug fehler kaefer'],
+ ['🎮', 'spiel gaming controller'], ['🎨', 'design farbe kunst'], ['🔧', 'technik werkzeug'],
+ ['📢', 'ankuendigung megafon'], ['💬', 'chat reden nachricht'], ['⭐', 'stern favorit'],
+ ['✅', 'haken fertig ja'], ['❌', 'kreuz nein fehler'], ['⚠️', 'warnung achtung'],
+ ['🔒', 'schloss privat gesperrt'], ['🚀', 'rakete release start'], ['🔥', 'feuer heiss'],
+ ['❤️', 'herz liebe'], ['👍', 'daumen gut ja'], ['🎉', 'party feier'],
+ ]],
+ ['Spiel', '🎮', [
+ ['🚗', 'auto fahrzeug'], ['🏎️', 'rennwagen'], ['🚓', 'polizei streifenwagen'],
+ ['🚑', 'krankenwagen rettung'], ['🚒', 'feuerwehr'], ['🏍️', 'motorrad'],
+ ['🚁', 'hubschrauber heli'], ['✈️', 'flugzeug'], ['🚤', 'boot schiff'],
+ ['⛵', 'segelboot'], ['🗺️', 'karte map'], ['🧭', 'kompass navigation'],
+ ['⚔️', 'kampf waffen pvp'], ['🛡️', 'schild verteidigung'], ['🏹', 'bogen jagd'],
+ ['💣', 'bombe'], ['🎯', 'ziel treffer'], ['🏆', 'pokal gewinner'],
+ ['🥇', 'gold erster'], ['🎲', 'wuerfel zufall'], ['🕹️', 'joystick arcade'],
+ ['🧪', 'test playtester labor'], ['⚙️', 'zahnrad einstellung'], ['🔫', 'waffe'],
+ ]],
+ ['Wirtschaft', '💰', [
+ ['💰', 'geld sack wirtschaft'], ['💵', 'schein dollar'], ['💳', 'karte bezahlen'],
+ ['🏦', 'bank'], ['📈', 'steigend gewinn'], ['📉', 'fallend verlust'],
+ ['🛒', 'einkauf shop'], ['🏪', 'laden geschaeft'], ['📦', 'paket lieferung'],
+ ['🏭', 'fabrik produktion'], ['⛏️', 'spitzhacke abbau'], ['🌾', 'landwirtschaft ernte'],
+ ['🔨', 'hammer bauen'], ['🧰', 'werkzeugkasten'], ['💎', 'diamant edelstein'],
+ ['🪙', 'muenze'],
+ ]],
+ ['Orte', '🏠', [
+ ['🏠', 'haus zuhause'], ['🏢', 'buero gebaeude'], ['🏥', 'krankenhaus'],
+ ['🏫', 'schule'], ['🏛️', 'amt behoerde'], ['⛽', 'tankstelle'],
+ ['🌳', 'baum natur'], ['🏝️', 'insel'], ['🏔️', 'berg'],
+ ['🌊', 'welle wasser meer'], ['🌆', 'stadt'], ['🛣️', 'strasse'],
+ ['🚧', 'baustelle'], ['🎪', 'zelt event'], ['🎬', 'film kino'],
+ ['🍔', 'essen restaurant'], ['☕', 'kaffee'], ['🍺', 'bier bar'],
+ ]],
+ ['Team', '👥', [
+ ['👥', 'team gruppe'], ['👤', 'person nutzer'], ['👋', 'winken willkommen'],
+ ['🙋', 'melden frage'], ['👮', 'polizist'], ['🧑⚕️', 'arzt sanitaeter'],
+ ['🧑🔧', 'mechaniker'], ['🧑🌾', 'bauer'], ['🧑💻', 'entwickler'],
+ ['🎭', 'rollen maske'], ['🤝', 'handschlag zusammen'], ['🗣️', 'sprechen stimme'],
+ ['📣', 'ruf laut'], ['🎤', 'mikrofon stream'], ['🎧', 'kopfhoerer voice'],
+ ]],
+ ['Technik', '🖥️', [
+ ['🖥️', 'rechner server'], ['💾', 'diskette speichern backup'], ['🗄️', 'archiv ablage'],
+ ['📡', 'antenne verbindung'], ['🔌', 'stecker strom'], ['🔋', 'akku batterie'],
+ ['📊', 'diagramm statistik'], ['📋', 'liste protokoll'], ['📝', 'notiz schreiben'],
+ ['📅', 'kalender termin'], ['⏰', 'wecker zeit'], ['⏳', 'sanduhr warten'],
+ ['🔍', 'lupe suche'], ['🔗', 'link verbindung'], ['🏷️', 'schild tag'],
+ ['🗂️', 'ordner dienst'], ['📁', 'ordner'], ['🧹', 'besen aufraeumen'],
+ ]],
+ ['Zeichen', '🔵', [
+ ['🟢', 'gruen online ok'], ['🟡', 'gelb warnung'], ['🔴', 'rot aus fehler'],
+ ['🔵', 'blau'], ['🟣', 'lila'], ['⚫', 'schwarz'],
+ ['⚪', 'weiss'], ['➕', 'plus hinzufuegen'], ['➖', 'minus entfernen'],
+ ['❓', 'frage hilfe'], ['❗', 'ausruf wichtig'], ['🔔', 'glocke benachrichtigung'],
+ ['🔕', 'stumm'], ['🚫', 'verboten nein'], ['♻️', 'recycling wiederholen'],
+ ['🆕', 'neu'], ['🆓', 'gratis frei'], ['🔝', 'oben top'],
+ ]],
+];
+
+export default function EmojiPick({ value, onChange, placeholder = '🙂' }) {
+ const [offen, setOffen] = useState(false);
+ const [suche, setSuche] = useState('');
+ const wrap = useRef(null);
+
+ // Klick daneben oder Escape schließt — ohne das bleibt das Feld offen
+ // stehen, sobald man woanders weiterarbeitet
+ useEffect(() => {
+ if (!offen) return undefined;
+ const aus = (e) => { if (!wrap.current?.contains(e.target)) setOffen(false); };
+ const taste = (e) => { if (e.key === 'Escape') setOffen(false); };
+ document.addEventListener('mousedown', aus);
+ document.addEventListener('keydown', taste);
+ return () => {
+ document.removeEventListener('mousedown', aus);
+ document.removeEventListener('keydown', taste);
+ };
+ }, [offen]);
+
+ const q = suche.trim().toLowerCase();
+ const gefiltert = GRUPPEN
+ .map(([name, symbol, liste]) => [
+ name,
+ symbol,
+ q ? liste.filter(([e, worte]) => worte.includes(q) || e === q) : liste,
+ ])
+ .filter(([, , liste]) => liste.length > 0);
+
+ return (
+
+
+
+ {/* Das Feld bleibt: Server-Emojis wie <:name:123> gibt es hier nicht */}
+ onChange(e.target.value)}
+ />
+ {value && (
+
+ )}
+
+
+ {offen && (
+
+
+
+ setSuche(e.target.value)}
+ autoFocus
+ />
+
+
+ {gefiltert.length === 0 &&
Nichts gefunden.
}
+ {gefiltert.map(([name, symbol, liste]) => (
+
+
{symbol} {name}
+
+ {liste.map(([e, worte]) => (
+
+ ))}
+
+
+ ))}
+
+
+ )}
+
+ );
+}
diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx
index 321b66f..0e65e4a 100644
--- a/frontend/src/pages/Settings.jsx
+++ b/frontend/src/pages/Settings.jsx
@@ -12,6 +12,7 @@ import { moduleIcon } from '../module-icons.jsx';
import ModulePage from '../components/ModulePage.jsx';
import UptimeTrack from '../components/UptimeTrack.jsx';
import Dialog from '../components/Dialog.jsx';
+import EmojiPick from '../components/EmojiPick.jsx';
import { findSettings, SETTING_INDEX } from '../setting-index.js';
import { Markdown } from '../markdown.jsx';
@@ -1878,6 +1879,14 @@ export default function Settings({ me }) {
der Aufruf-Post in Discord fragt beim Einreichen nach dem Bereich. Nach dem
Ändern /wunsch-setup neu ausführen.
+
+ Ist der Voting-Kanal ein Forum, wird jeder Wunsch ein
+ Forums-Beitrag und bekommt Tags. Leg dort Tags mit denselben Namen an wie hier
+ die Bereiche, dazu Wird geprüft, Geplant, In Arbeit,
+ Umgesetzt und Nicht geplant — dann kann man auch in Discord
+ danach filtern, und der Stand zieht automatisch mit. Fehlt ein Tag, bleibt es
+ einfach ohne.
+
{bereiche.length === 0 &&
Noch keine Bereiche.
}
@@ -1906,10 +1915,10 @@ export default function Settings({ me }) {
)}