diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0dd6ad8..b43ee5d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "@fontsource/barlow-condensed": "^5.3.0", "@fontsource/bebas-neue": "^5.3.0", "@fontsource/share-tech-mono": "^5.3.0", + "lucide-react": "^1.28.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.1.0" @@ -1541,6 +1542,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.28.0.tgz", + "integrity": "sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 494ec7a..5aef18c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "@fontsource/barlow-condensed": "^5.3.0", "@fontsource/bebas-neue": "^5.3.0", "@fontsource/share-tech-mono": "^5.3.0", + "lucide-react": "^1.28.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.1.0" diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b893aac..5fab1ab 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -15,6 +15,7 @@ import Home from './pages/Home.jsx'; import DevlogDetail from './pages/DevlogDetail.jsx'; import Impressum from './pages/Impressum.jsx'; import Datenschutz from './pages/Datenschutz.jsx'; +import { IconChevronDown, IconMenu, IconX, IconLock } from './icons.jsx'; const COMMUNITY_LINKS = [ ['/galerie', 'Galerie'], @@ -35,7 +36,7 @@ function NavDropdown({ label, active, children }) { return (
{open &&
setOpen(false)}>{children}
}
@@ -151,7 +152,7 @@ export default function App() { @@ -175,8 +176,9 @@ export default function App() {
{gated && (
- - 🚪 Der Login ist Mitgliedern des Discord-Servers vorbehalten — + + + Der Login ist Mitgliedern des Discord-Servers vorbehalten — tritt erst dem Server bei und logg dich dann nochmal ein. {invite && ( @@ -184,7 +186,7 @@ export default function App() { Zum Discord )} - +
)} diff --git a/frontend/src/components/EmbedComposer.jsx b/frontend/src/components/EmbedComposer.jsx index f1696dd..1aa2ab1 100644 --- a/frontend/src/components/EmbedComposer.jsx +++ b/frontend/src/components/EmbedComposer.jsx @@ -2,6 +2,7 @@ // mit Live-Vorschau im Discord-Look + speicherbaren Vorlagen. import { useEffect, useState } from 'react'; import { apiGet, apiPost, apiPut, apiDelete } from '../api.js'; +import { IconBot, IconSave, IconX } from '../icons.jsx'; import { Markdown } from '../markdown.jsx'; const EMPTY_EMBED = { @@ -54,7 +55,7 @@ function DiscordPreview({ content, embed, useEmbed }) { return (
-
🤖
+
d4rkbot APP heute um {new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} Uhr
{content.trim() &&
} @@ -179,8 +180,8 @@ export default function EmbedComposer({ channelOptions, flash }) { {templates.map((t) => )} setTemplateName(e.target.value)} /> - - + +
diff --git a/frontend/src/components/Lightbox.jsx b/frontend/src/components/Lightbox.jsx index 3947112..fc13ae7 100644 --- a/frontend/src/components/Lightbox.jsx +++ b/frontend/src/components/Lightbox.jsx @@ -3,6 +3,7 @@ // die Pfeile stehen NEBEN dem Bild, die Info-Zeile darunter. import { useCallback, useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; +import { IconX, IconChevronLeft, IconChevronRight } from '../icons.jsx'; export default function Lightbox({ images, start = 0, onClose }) { const [index, setIndex] = useState(start); @@ -42,10 +43,10 @@ export default function Lightbox({ images, start = 0, onClose }) { }} role="dialog" aria-modal="true" > - +
{images.length > 1 && ( - + )}
e.stopPropagation()}> {img.alt @@ -55,7 +56,7 @@ export default function Lightbox({ images, start = 0, onClose }) {
{images.length > 1 && ( - + )}
, diff --git a/frontend/src/icons.jsx b/frontend/src/icons.jsx new file mode 100644 index 0000000..21460d7 --- /dev/null +++ b/frontend/src/icons.jsx @@ -0,0 +1,105 @@ +// Zentrale Icon-Sammlung (Lucide) — ein Set, eine Strichstärke, überall gleich. +// +// Bewusst NICHT ersetzt werden Emojis, die im Discord landen oder von Nutzern +// kommen: Spiel-Icons der Server-Presets, Rollen-Menü-Emojis, Embed-Inhalte. +// Discord kennt keine SVGs — dort sind Emojis die richtige Wahl. +import { + Activity, AlertTriangle, Archive, ArrowLeft, ArrowRight, Award, Bell, Bot, + Cake, Calendar, Camera, Check, ChevronDown, ChevronLeft, ChevronRight, + Clock, Copy, Database, Download, Ticket, FileText, Gamepad2, Gauge, Gift, + Globe, Hash, Heart, Image, Key, Layers, Link2, List, Lock, LogOut, Mail, + MapPin, Megaphone, MessageSquare, Mic, Monitor, Moon, Package, Palette, UserCog, + PartyPopper, Pencil, Play, Plus, RefreshCw, Rocket, Save, Search, Send, + Server, Settings, Shield, Sparkles, Star, Tag, Terminal, ThumbsUp, Trash2, + TrendingUp, Trophy, Upload, User, Users, Wand2, Wrench, X, Zap, +} from 'lucide-react'; + +/** Einheitliche Darstellung: 18px, dünne Linien, am Text ausgerichtet */ +const defaults = { size: 18, strokeWidth: 1.75, 'aria-hidden': true }; + +const make = (Cmp) => function Icon(props) { + return ; +}; + +/* ── Bereiche & Navigation ──────────────────────────── */ +export const IconDevlog = make(FileText); +export const IconRoadmap = make(MapPin); +export const IconMapPin = make(MapPin); +export const IconServer = make(Server); +export const IconGallery = make(Image); +export const IconLevel = make(TrendingUp); +export const IconEvents = make(Calendar); +export const IconChangelog = make(Rocket); +export const IconCommits = make(Terminal); +export const IconSetup = make(Settings); +export const IconProfile = make(User); +export const IconHome = make(Layers); +export const IconLogout = make(LogOut); +export const IconMenu = make(List); + +/* ── Setup-Tabs ─────────────────────────────────────── */ +export const IconStatus = make(Gauge); +export const IconBrand = make(Palette); +export const IconFeeds = make(Megaphone); +export const IconCommunity = make(Users); +export const IconRoles = make(Shield); +export const IconSupport = make(Ticket); +export const IconApplications = make(FileText); +export const IconComposer = make(Pencil); +export const IconSystem = make(Wrench); +export const IconApi = make(Key); +export const IconTeam = make(UserCog); + +/* ── Aktionen & Zustände ────────────────────────────── */ +export const IconCheck = make(Check); +export const IconX = make(X); +export const IconPlus = make(Plus); +export const IconTrash = make(Trash2); +export const IconEdit = make(Pencil); +export const IconSave = make(Save); +export const IconSend = make(Send); +export const IconCopy = make(Copy); +export const IconSearch = make(Search); +export const IconRefresh = make(RefreshCw); +export const IconUpload = make(Upload); +export const IconDownload = make(Download); +export const IconLink = make(Link2); +export const IconPlay = make(Play); +export const IconWarning = make(AlertTriangle); +export const IconLock = make(Lock); +export const IconArrowLeft = make(ArrowLeft); +export const IconArrowRight = make(ArrowRight); +export const IconChevronDown = make(ChevronDown); +export const IconChevronLeft = make(ChevronLeft); +export const IconChevronRight = make(ChevronRight); + +/* ── Inhalte & Features ─────────────────────────────── */ +export const IconBot = make(Bot); +export const IconBell = make(Bell); +export const IconBirthday = make(Cake); +export const IconGiveaway = make(Gift); +export const IconStar = make(Star); +export const IconTag = make(Tag); +export const IconKey = make(Key); +export const IconTrophy = make(Trophy); +export const IconAward = make(Award); +export const IconThumbsUp = make(ThumbsUp); +export const IconIdea = make(Sparkles); +export const IconMessage = make(MessageSquare); +export const IconMail = make(Mail); +export const IconVoice = make(Mic); +export const IconGame = make(Gamepad2); +export const IconMonitor = make(Monitor); +export const IconDatabase = make(Database); +export const IconArchive = make(Archive); +export const IconPackage = make(Package); +export const IconGlobe = make(Globe); +export const IconChannel = make(Hash); +export const IconClock = make(Clock); +export const IconActivity = make(Activity); +export const IconCamera = make(Camera); +export const IconHeart = make(Heart); +export const IconParty = make(PartyPopper); +export const IconMagic = make(Wand2); +export const IconZap = make(Zap); +export const IconMoon = make(Moon); diff --git a/frontend/src/pages/Changelog.jsx b/frontend/src/pages/Changelog.jsx index a6fed42..c629382 100644 --- a/frontend/src/pages/Changelog.jsx +++ b/frontend/src/pages/Changelog.jsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import { apiGet } from '../api.js'; +import { IconChevronLeft, IconChevronRight } from '../icons.jsx'; import { SkeletonCards } from '../components/Skeleton.jsx'; import { Markdown } from '../markdown.jsx'; @@ -52,7 +53,7 @@ export default function Changelog() { {r.url && (
- Release in Gitea ansehen → + Release in Gitea ansehen
)} @@ -61,13 +62,13 @@ export default function Changelog() { {pages > 1 && (
{String(page).padStart(2, '0')} / {String(pages).padStart(2, '0')}
)} diff --git a/frontend/src/pages/Commits.jsx b/frontend/src/pages/Commits.jsx index 6462450..c2530a3 100644 --- a/frontend/src/pages/Commits.jsx +++ b/frontend/src/pages/Commits.jsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import { apiGet } from '../api.js'; +import { IconChevronLeft, IconChevronRight } from '../icons.jsx'; const dateFmt = new Intl.DateTimeFormat('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit', @@ -90,13 +91,13 @@ export default function Commits({ me }) { {pages > 1 && (
{String(page).padStart(2, '0')} / {String(pages).padStart(2, '0')}
)} diff --git a/frontend/src/pages/DevlogDetail.jsx b/frontend/src/pages/DevlogDetail.jsx index cda6dfd..e46c188 100644 --- a/frontend/src/pages/DevlogDetail.jsx +++ b/frontend/src/pages/DevlogDetail.jsx @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'; import { Link, useParams } from 'react-router-dom'; import { apiGet } from '../api.js'; +import { IconChevronLeft, IconCheck, IconLink } from '../icons.jsx'; import { Markdown } from '../markdown.jsx'; import Lightbox from '../components/Lightbox.jsx'; import { SkeletonCards } from '../components/Skeleton.jsx'; @@ -79,8 +80,8 @@ export default function DevlogDetail() {
- ← Alle Devlogs - + Alle Devlogs +
{lightbox !== null && ( diff --git a/frontend/src/pages/Devlogs.jsx b/frontend/src/pages/Devlogs.jsx index cff47ee..8272ecb 100644 --- a/frontend/src/pages/Devlogs.jsx +++ b/frontend/src/pages/Devlogs.jsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import { apiGet, apiDelete } from '../api.js'; +import { IconChevronLeft, IconChevronRight, IconSearch, IconLink, IconX } from '../icons.jsx'; import { Markdown } from '../markdown.jsx'; import Lightbox from '../components/Lightbox.jsx'; import { SkeletonCards } from '../components/Skeleton.jsx'; @@ -102,7 +103,7 @@ export default function Devlogs({ me }) {
- + {project && {project}} {relative(d.posted_at)} - 🔗 + {me?.admin && ( )}
@@ -187,13 +188,13 @@ export default function Devlogs({ me }) { {pages > 1 && (
{String(page).padStart(2, '0')} / {String(pages).padStart(2, '0')}
)} diff --git a/frontend/src/pages/Events.jsx b/frontend/src/pages/Events.jsx index 4448d49..58526f2 100644 --- a/frontend/src/pages/Events.jsx +++ b/frontend/src/pages/Events.jsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import { apiGet } from '../api.js'; +import { IconStar, IconChevronRight } from '../icons.jsx'; import { Markdown } from '../markdown.jsx'; const dateFmt = new Intl.DateTimeFormat('de-DE', { @@ -37,7 +38,7 @@ export default function Events() { {e.cover && }
{e.start ? dateFmt.format(new Date(e.start)) : 'Termin folgt'} - {e.interested != null && ⭐ {e.interested} interessiert} + {e.interested != null && {e.interested} interessiert}

{e.name}

{e.description && ( @@ -46,7 +47,7 @@ export default function Events() { )}
- Im Discord ansehen → + Im Discord ansehen
))} diff --git a/frontend/src/pages/Galerie.jsx b/frontend/src/pages/Galerie.jsx index 3f89126..068f6c4 100644 --- a/frontend/src/pages/Galerie.jsx +++ b/frontend/src/pages/Galerie.jsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import { apiGet } from '../api.js'; +import { IconChevronLeft, IconChevronRight } from '../icons.jsx'; import Lightbox from '../components/Lightbox.jsx'; import { SkeletonGrid } from '../components/Skeleton.jsx'; @@ -68,13 +69,13 @@ export default function Galerie() { {pages > 1 && (
{String(page).padStart(2, '0')} / {String(pages).padStart(2, '0')}
)} diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index b1e842a..73138d8 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -4,14 +4,18 @@ import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { apiGet } from '../api.js'; import { Markdown } from '../markdown.jsx'; +import { + IconDevlog, IconRoadmap, IconServer, IconGallery, IconLevel, IconEvents, + IconMessage, IconArrowRight, IconLink, +} from '../icons.jsx'; const TILES = [ - ['/devlogs', '📔', 'Devlog', 'Entwicklungs-Updates, frisch aus dem Editor'], - ['/roadmap', '🗺️', 'Roadmap', 'Meilensteine + Community-Wünsche zum Abstimmen'], - ['/server', '🎮', 'Server', 'Live-Status aller Game-Server mit Verlauf'], - ['/galerie', '🖼️', 'Galerie', 'Screenshots aus der Community'], - ['/level', '📈', 'Level', 'XP-Bestenliste — wer redet am meisten mit?'], - ['/events', '📅', 'Events', 'Playtests, Streams und was sonst ansteht'], + ['/devlogs', IconDevlog, 'Devlog', 'Entwicklungs-Updates, frisch aus dem Editor'], + ['/roadmap', IconRoadmap, 'Roadmap', 'Meilensteine + Community-Wünsche zum Abstimmen'], + ['/server', IconServer, 'Server', 'Live-Status aller Game-Server mit Verlauf'], + ['/galerie', IconGallery, 'Galerie', 'Screenshots aus der Community'], + ['/level', IconLevel, 'Level', 'XP-Bestenliste — wer redet am meisten mit?'], + ['/events', IconEvents, 'Events', 'Playtests, Streams und was sonst ansteht'], ]; /** Devlog-Text für den Teaser kürzen (erste Sätze, ohne Titel-Zeile) */ @@ -57,8 +61,14 @@ export default function Home() { an einem Ort, direkt aus dem Discord.

- {invite && 💬 Zum Discord} - 📔 Devlogs lesen + {invite && ( + + Zum Discord + + )} + + Devlogs lesen +
@@ -88,9 +98,9 @@ export default function Home() { )}
- {TILES.map(([to, icon, title, desc]) => ( + {TILES.map(([to, Icon, title, desc]) => ( - {icon} + {title} {desc} @@ -101,10 +111,13 @@ export default function Home() {

// Dienste

+ {/* Dienst-Icons pflegt der Owner selbst — daher bleiben es Emojis */} {services.map((s) => ( - {s.icon} - {s.name} + {s.icon} + + {s.name} + {s.description && {s.description}} ))} @@ -119,7 +132,7 @@ export default function Home() {
- Weiterlesen → + Weiterlesen
)} diff --git a/frontend/src/pages/Impressum.jsx b/frontend/src/pages/Impressum.jsx index a8a4955..e8a7529 100644 --- a/frontend/src/pages/Impressum.jsx +++ b/frontend/src/pages/Impressum.jsx @@ -29,7 +29,7 @@ export default function Impressum() {
{incomplete && (

- ⚠️ Die Impressums-Angaben sind noch nicht vollständig hinterlegt + ✗ Die Impressums-Angaben sind noch nicht vollständig hinterlegt (Setup → System → Impressum).

)} diff --git a/frontend/src/pages/Level.jsx b/frontend/src/pages/Level.jsx index c41d541..0f77911 100644 --- a/frontend/src/pages/Level.jsx +++ b/frontend/src/pages/Level.jsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { apiGet } from '../api.js'; import { SkeletonRows } from '../components/Skeleton.jsx'; +import { IconTrophy } from '../icons.jsx'; /** Muss der Formel in src/bot/levels.js entsprechen */ function xpForLevel(level) { @@ -9,7 +10,8 @@ function xpForLevel(level) { return total; } -const MEDALS = ['🥇', '🥈', '🥉']; +// Platz 1–3 bekommen einen Pokal in Gold/Silber/Bronze, danach schlicht die Nummer +const MEDAL_COLORS = ['#f5c518', '#c0c4cc', '#c07a3e']; /** Nachrichten/Tag der letzten 30 Tage als Balken */ function ActivityChart({ stats }) { @@ -66,7 +68,7 @@ export default function Level() {
{error &&

Bestenliste konnte nicht geladen werden.

} {!error && !rows && } - {rows?.length === 0 &&

Noch keine XP vergeben — schreibt was im Discord! ✍️

} + {rows?.length === 0 &&

Noch keine XP vergeben — schreibt was im Discord!

} {rows?.length > 0 && (
@@ -76,7 +78,11 @@ export default function Level() { const pct = Math.floor(((r.xp - base) / (next - base)) * 100); return (
- {MEDALS[i] ?? `#${i + 1}`} + + {i < 3 + ? + : `#${i + 1}`} + {r.username ?? r.user_id}
diff --git a/frontend/src/pages/Profil.jsx b/frontend/src/pages/Profil.jsx index 20d3d02..769be2d 100644 --- a/frontend/src/pages/Profil.jsx +++ b/frontend/src/pages/Profil.jsx @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { apiGet, apiPost, apiDelete } from '../api.js'; +import { IconCheck, IconMagic } from '../icons.jsx'; function fmtDate(iso) { if (!iso) return '—'; @@ -52,10 +53,10 @@ export default function Profil({ me }) { try { const res = await apiDelete('/api/profile'); const total = Object.values(res.deleted).reduce((a, b) => a + b, 0); - setWipeMsg(`✅ ${total} Einträge gelöscht.`); + setWipeMsg(`✓ ${total} Einträge gelöscht.`); apiGet('/api/profile').then(setProfile).catch(() => {}); } catch { - setWipeMsg('⚠️ Löschen fehlgeschlagen — melde dich bitte beim Team.'); + setWipeMsg('✗ Löschen fehlgeschlagen — melde dich bitte beim Team.'); } } @@ -96,7 +97,7 @@ export default function Profil({ me }) {
- {error &&

⚠️ {error}

} + {error &&

✗ {error}

} {!profile && !error &&

Lade …

} {profile && ( @@ -108,7 +109,9 @@ export default function Profil({ me }) {

{profile.user.displayName}

Dabei seit {fmtDate(profile.user.joinedAt)} - {profile.playtester && 🧪 Playtester} + {profile.playtester && ( + Playtester + )}

{profile.roles.length > 0 && (
@@ -145,7 +148,9 @@ export default function Profil({ me }) {

// Dein Alpha-Key

{profile.alphaKey} - +
)} diff --git a/frontend/src/pages/Roadmap.jsx b/frontend/src/pages/Roadmap.jsx index 1cbca4b..21e6638 100644 --- a/frontend/src/pages/Roadmap.jsx +++ b/frontend/src/pages/Roadmap.jsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { apiGet, apiPost } from '../api.js'; import { Markdown } from '../markdown.jsx'; import { SkeletonCards } from '../components/Skeleton.jsx'; +import { IconThumbsUp } from '../icons.jsx'; const dueFmt = new Intl.DateTimeFormat('de-DE', { month: 'long', year: 'numeric' }); @@ -104,20 +105,20 @@ export default function Roadmap() { ? w : { ...w, voted: res.voted, score: w.score + (res.voted ? 1 : -1) })); } catch (e) { - if (e.status === 403) setSubmitMsg('⚠️ Voten dürfen nur Discord-Member.'); + if (e.status === 403) setSubmitMsg('✗ Voten dürfen nur Discord-Member.'); } } async function submitWish() { const text = idea.trim(); - if (text.length < 5) { setSubmitMsg('⚠️ Etwas mehr Text bitte.'); return; } + if (text.length < 5) { setSubmitMsg('✗ Etwas mehr Text bitte.'); return; } try { await apiPost('/api/wishes', { idea: text }); setIdea(''); - setSubmitMsg('✅ Wunsch eingereicht — er steht jetzt auch im Discord zur Abstimmung!'); + setSubmitMsg('✓ Wunsch eingereicht — er steht jetzt auch im Discord zur Abstimmung!'); apiGet('/api/wishes').then((d) => setWishes(d.wishes)).catch(() => {}); } catch (e) { - setSubmitMsg(e.status === 403 ? '⚠️ Einreichen dürfen nur Discord-Member.' : '⚠️ Konnte den Wunsch nicht einreichen.'); + setSubmitMsg(e.status === 403 ? '✗ Einreichen dürfen nur Discord-Member.' : '✗ Konnte den Wunsch nicht einreichen.'); } } @@ -151,14 +152,14 @@ export default function Roadmap() {
{canVote && w.message_id ? ( ) : ( - 👍 {w.score} + {w.score} )} {w.idea} {w.author} diff --git a/frontend/src/pages/Server.jsx b/frontend/src/pages/Server.jsx index d62f402..2ccdcbd 100644 --- a/frontend/src/pages/Server.jsx +++ b/frontend/src/pages/Server.jsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'; import { apiGet } from '../api.js'; import { SkeletonCards } from '../components/Skeleton.jsx'; +import { IconGame, IconMapPin, IconClock, IconCheck } from '../icons.jsx'; const timeFmt = new Intl.DateTimeFormat('de-DE', { hour: '2-digit', minute: '2-digit' }); @@ -68,9 +69,13 @@ function ServerCard({ s }) {
{s.name} - {s.online === null ? '⏳ Warte auf Check' : s.online ? '🟢 Online' : '🔴 Offline'} + {s.online === null + ? <> Warte auf Check + : <> {s.online ? 'Online' : 'Offline'}} {s.ping != null && s.online && · {s.ping}ms} - {s.map && s.online && · 🗺️ {s.map}} + {s.map && s.online && ( + · {s.map} + )}
{s.online && s.players != null && ( @@ -102,10 +107,14 @@ function ServerCard({ s }) { {(s.connect_url || s.address) && (
- {s.connect_url && 🎮 Connect} + {s.connect_url && ( + + Connect + + )} {s.address && ( )}
diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index 52872b6..86ceb8e 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -1,6 +1,11 @@ import { useEffect, useRef, useState } from 'react'; import { apiGet, apiPut, apiPost, apiDelete } from '../api.js'; import EmbedComposer from '../components/EmbedComposer.jsx'; +import { + IconStatus, IconBrand, IconFeeds, IconCommunity, IconRoles, IconSupport, + IconApplications, IconComposer, IconServer, IconSystem, IconApi, IconTeam, + IconLock, IconBot, IconUpload, IconChevronDown, IconKey, +} from '../icons.jsx'; const API_SCOPES = [ { id: 'message', label: 'message', hint: 'Nachrichten/Embeds in Kanäle posten' }, @@ -10,18 +15,18 @@ const API_SCOPES = [ ]; const TABS = [ - { id: 'status', icon: '📊', label: 'Status' }, - { id: 'brand', icon: '🎨', label: 'Brand' }, - { id: 'feeds', icon: '📔', label: 'Feeds' }, - { id: 'community', icon: '🙌', label: 'Community' }, - { id: 'rollen', icon: '🎭', label: 'Rollen' }, - { id: 'support', icon: '📬', label: 'Support' }, - { id: 'bewerbungen', icon: '📋', label: 'Bewerbungen' }, - { id: 'composer', icon: '📝', label: 'Composer' }, - { id: 'server', icon: '🎮', label: 'Server' }, - { id: 'system', icon: '⚙️', label: 'System' }, - { id: 'api', icon: '🔑', label: 'API' }, - { id: 'team', icon: '👥', label: 'Team' }, + { id: 'status', Icon: IconStatus, label: 'Status' }, + { id: 'brand', Icon: IconBrand, label: 'Brand' }, + { id: 'feeds', Icon: IconFeeds, label: 'Feeds' }, + { id: 'community', Icon: IconCommunity, label: 'Community' }, + { id: 'rollen', Icon: IconRoles, label: 'Rollen' }, + { id: 'support', Icon: IconSupport, label: 'Support' }, + { id: 'bewerbungen', Icon: IconApplications, label: 'Bewerbungen' }, + { id: 'composer', Icon: IconComposer, label: 'Composer' }, + { id: 'server', Icon: IconServer, label: 'Server' }, + { id: 'system', Icon: IconSystem, label: 'System' }, + { id: 'api', Icon: IconApi, label: 'API' }, + { id: 'team', Icon: IconTeam, label: 'Team' }, ]; // Welcher Team-Scope schaltet welchen Tab frei (null = nur Owner) @@ -574,7 +579,7 @@ export default function Settings({ me }) { {header}
-

🔒 Nur für den Admin.

+

Nur für den Admin.

{!me.user && Login mit Discord}
@@ -622,15 +627,15 @@ export default function Settings({ me }) {
{data.status.botAvatar ? Bot-Avatar - :
🤖
} + :
}
@@ -844,7 +849,7 @@ export default function Settings({ me }) { onClick={distributeKeys} disabled={!alphaKeys || alphaKeys.free === 0 || alphaKeys.playtestersWithout.length === 0} > - 🎟️ An {alphaKeys?.playtestersWithout?.length ?? 0} Playtester verteilen + An {alphaKeys?.playtestersWithout?.length ?? 0} Playtester verteilen
{alphaKeys?.assigned?.length > 0 && ( @@ -937,7 +942,7 @@ export default function Settings({ me }) {

// Rollen-Menüs

- Selbstbedienungs-Rollen wie bei Carl-bot — nur ohne Paywall. 😄 Der Bot postet + Selbstbedienungs-Rollen wie bei Carl-bot — nur ohne Paywall. Der Bot postet ein Embed mit Buttons; Klick = Rolle nehmen, nochmal = abgeben. Menüs sind jederzeit editierbar, der Discord-Post zieht automatisch mit.

@@ -1712,7 +1717,7 @@ export default function Settings({ me }) { className={`settings-nav-item ${tab === t.id ? 'active' : ''}`} onClick={() => setTab(t.id)} > - {t.icon} + {t.label} ))} diff --git a/frontend/src/style.css b/frontend/src/style.css index 2640251..4429f21 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -1512,3 +1512,54 @@ a.sha:hover { border-color: var(--neon); background: rgba(245, 197, 24, .07); } .btn-danger { border-color: rgba(242, 63, 67, .5); color: #f23f43; } .btn-danger:hover { border-color: #f23f43; background: rgba(242, 63, 67, .1); } .home-tile-ext { font-size: .8rem; color: var(--muted2); } + +/* ── ICONS (Lucide) ────────────────────────────────── */ +/* Einheitliche Ausrichtung: Icons sitzen auf der Textlinie, erben die Farbe */ +.ic { + display: inline-block; + vertical-align: -0.18em; + flex-shrink: 0; +} +/* Text + Icon nebeneinander, ohne Zeilenumbruch dazwischen */ +.with-icon { + display: inline-flex; + align-items: center; + gap: .45rem; +} +button.with-icon, a.with-icon { justify-content: center; } + +/* Nav-Pfeil des Dropdowns */ +.nav-caret { transition: transform .2s; } +.nav-drop.open .nav-caret { transform: rotate(180deg); } + +/* Setup-Sidebar: Icons in Markenfarbe, aktiver Tab hebt sich ab */ +.settings-nav-icon { display: inline-flex; color: var(--muted); transition: color .2s; } +.settings-nav-item:hover .settings-nav-icon { color: var(--text); } +.settings-nav-item.active .settings-nav-icon { color: var(--neon); } + +/* Kachel-Icons auf der Startseite */ +.home-tile-icon { color: var(--neon); display: inline-flex; } +.home-tile:hover .home-tile-icon { color: var(--neon2); } +.home-tile-emoji { font-size: 1.6rem; line-height: 1; } +.home-tile-ext { color: var(--muted2); } + +/* Server-Status: Punkt statt Emoji */ +.srv-status { flex-wrap: wrap; row-gap: .1rem; } +.srv-status > span { white-space: nowrap; } +.srv-dot { + width: 8px; height: 8px; border-radius: 50%; + background: var(--muted2); display: inline-block; flex-shrink: 0; +} +.srv-status.on .srv-dot { background: var(--success); box-shadow: 0 0 8px #00ff8866; } +.srv-status.off .srv-dot { background: #f23f43; box-shadow: 0 0 8px #f23f4366; } +.srv-status { display: inline-flex; align-items: center; gap: .4rem; } +.srv-map, .srv-ping { display: inline-flex; align-items: center; gap: .3rem; } + +/* Bestenliste: Pokal statt Medaillen-Emoji */ +.lvl-rank { display: inline-flex; align-items: center; justify-content: center; } + +/* Bot-Avatar-Platzhalter */ +.bot-avatar-empty { display: flex; align-items: center; justify-content: center; color: var(--muted); } + +/* Discord-Vorschau im Composer */ +.dpv-avatar { display: flex; align-items: center; justify-content: center; color: var(--muted); }