diff --git a/README.md b/README.md
index 116e940..e51b81c 100644
--- a/README.md
+++ b/README.md
@@ -128,8 +128,14 @@ Code, die Datenbank enthält nur echte Abweichungen.
| Technik | Server · System |
| Zugang | API · Team |
-Das Suchfeld über der Leiste findet Bereiche auch über Stichwörter — „geburtstag"
-führt zu Community, „willkommen" zu Support. Enter springt zum ersten Treffer.
+Das Suchfeld über der Leiste findet **Bereiche und einzelne Einstellungen**:
+„geburtstag" führt zu Community, „schwellwert" direkt aufs Starboard-Feld, das
+kurz hervorgehoben wird. Enter nimmt den ersten Treffer. Welche Einstellung wo
+liegt, steht in [`frontend/src/setting-index.js`](frontend/src/setting-index.js).
+
+Sobald sich etwas vom gespeicherten Stand unterscheidet, erscheint unten eine
+Leiste mit Anzahl, *Verwerfen* und *Speichern*; beim Verlassen mit offenen
+Änderungen fragt der Browser nach.
Der aktive Bereich steht in der Adresse (`/settings#texte`), Links und Neuladen
landen also wieder dort.
diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx
index dd45c37..18010c4 100644
--- a/frontend/src/pages/Settings.jsx
+++ b/frontend/src/pages/Settings.jsx
@@ -9,6 +9,7 @@ import {
IconSearch, IconSystem as IconTune,
} from '../icons.jsx';
import { moduleIcon } from '../module-icons.jsx';
+import { findSettings } from '../setting-index.js';
import { Markdown } from '../markdown.jsx';
const API_SCOPES = [
@@ -339,6 +340,27 @@ export default function Settings({ me }) {
const foundTabs = query
? visibleTabs.filter((t) => `${t.label} ${t.find}`.toLowerCase().includes(query))
: visibleTabs;
+ // … und zusätzlich auf einzelne Einstellungen, mit Sprung aufs Feld
+ const foundSettings = query ? findSettings(query, canTab) : [];
+
+ /** Bereich wechseln, zum Feld scrollen und es kurz hervorheben */
+ function jumpToSetting(entry) {
+ setTab(entry.tab);
+ setTabSearch('');
+ const key = entry.target ?? entry.id;
+ // Zwei Bilder warten, bis der neue Bereich wirklich im Dokument steht
+ requestAnimationFrame(() => requestAnimationFrame(() => {
+ const el = document.querySelector(`[data-setting="${key}"]`);
+ if (!el) return;
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ el.classList.add('field-found');
+ setTimeout(() => el.classList.remove('field-found'), 2200);
+ // Nur Textfelder fokussieren: bei der Kanal-Auswahl würde der Fokus
+ // die Liste aufklappen und alles darunter verdecken.
+ const feld = el.querySelector('input, textarea');
+ if (feld && !feld.closest('.picksel')) feld.focus({ preventScroll: true });
+ }));
+ }
// Tags + geplante Posts
const [tags, setTags] = useState([]);
const [tagDraft, setTagDraft] = useState({ name: '', content: '' });
@@ -1039,7 +1061,7 @@ export default function Settings({ me }) {
-
+
Bot-Name Discord erlaubt ~2 Umbenennungen pro Stunde
setBotName(e.target.value)} />
@@ -1047,7 +1069,7 @@ export default function Settings({ me }) {
Beschreibung „Über mich" im Bot-Profil (max 400 Zeichen)
-
+
Bot-Status
setForm({ ...form, bot_presence_status: e.target.value })}>
🟢 Online
@@ -1055,7 +1077,7 @@ export default function Settings({ me }) {
⛔ Bitte nicht stören
-
+
Aktivität leer = Server-Monitor darf die Spielerzahl anzeigen
setForm({ ...form, bot_status_type: e.target.value })}>
@@ -1072,11 +1094,11 @@ export default function Settings({ me }) {
// Embeds
-
+
Brand-Name erscheint in allen Embed-Footern („NAME // DEVLOG")
setForm({ ...form, brand_name: e.target.value })} />
-
+
Embed-Farben primär (Embeds) · sekundär (Releases, Mod-Aktionen)
setForm({ ...form, brand_color: e.target.value })} />
@@ -1088,7 +1110,7 @@ export default function Settings({ me }) {
// Aus der Env verlagert
-
+
Guild-ID für sofortige Slash-Commands; greift beim nächsten Neustart
setForm({ ...form, discord_guild_id: e.target.value })} />
@@ -1108,7 +1130,7 @@ export default function Settings({ me }) {
<>
// Kanäle
-
+
Devlog-Kanal öffentlich — hier postet der Bot die Devlogs
-
+
Commit-Kanal privat — hier landen die Push-Embeds
-
+
Release-Kanal Ankündigungen bei neuen Gitea-Releases — leer = aus
// Devlog
-
+
Ping-Rolle wird bei jedem Devlog erwähnt; 🔔-Button am Post — leer = kein Ping
-
+
setForm({ ...form, weekly_recap_enabled: e.target.checked })} />
Wochen-Rückblick (sonntags 20:00)
Commits pro Tag, Devlogs & Top-Projekte
-
+
setForm({ ...form, devlog_threads_enabled: e.target.checked })} />
Diskussions-Thread unter jedem Devlog
@@ -1178,18 +1200,18 @@ export default function Settings({ me }) {
// Commit-Feed
-
+
setForm({ ...form, commit_feed_enabled: e.target.checked })} />
Push-Embeds posten
aus = weiter archivieren, nur nicht posten
-
+
Branch-Filter kommagetrennt, leer = alle
setForm({ ...form, commit_branch_filter: e.target.value })} />
-
+
Ignorierte Repos kommagetrennt, volle Namen
setForm({ ...form, ignored_repos: e.target.value })} />
@@ -1201,7 +1223,7 @@ export default function Settings({ me }) {
<>
// Geburtstage
-
+
Geburtstags-Kanal Member tragen sich mit /geburtstag ein; Gratulation morgens ab 09:00 — leer = aus
-
+
Geburtstags-Rolle gibt's für den Tag, wird am nächsten Morgen wieder entfernt — leer = keine
// Playtester
-
+
Playtester-Rolle Bewerbungs-Post via /playtester-setup; leer = aus
// Starboard & Galerie
-
+
Starboard-Kanal Nachrichten mit genug ⭐ landen hier
setForm({ ...form, starboard_threshold: e.target.value })} />
-
+
Screenshot-Kanal Bilder → öffentliche Galerie; Historie: /galerie-backfill
-
+
Voting-Kanal /wunsch postet hier; Rangliste auf /roadmap
// Level-System
-
+
setForm({ ...form, levels_enabled: e.target.checked })} />
XP fürs Schreiben vergeben
15–25 XP pro Nachricht, 60s Cooldown; /rank + Bestenliste auf /level
-
+
setForm({ ...form, levels_announce: e.target.checked })} />
Level-Up im Kanal feiern
„🎉 Name ist jetzt Level 5!"
-
+
Rollen-Belohnungen Format: Level=Rollen-ID, kommagetrennt — z. B. 5=1234, 10=5678
setForm({ ...form, level_rewards: e.target.value })} />
@@ -1339,7 +1361,7 @@ export default function Settings({ me }) {
<>
// Rollen bei Join
-
+
Auto-Rolle bekommt jeder neue Member automatisch — leer = aus
-
+
setForm({ ...form, sticky_roles_enabled: e.target.checked })} />
Sticky-Roles
@@ -1461,7 +1483,7 @@ export default function Settings({ me }) {
<>
// Moderation & Kontakt
-
+
Modmail-Kanal PRIVATER Staff-Kanal — DMs an den Bot landen hier als Threads
-
+
Ticket-Kanal private Ticket-Threads; Post via /ticket-setup
-
+
Willkommens-Kanal Begrüßungs-Embed für neue Member
-
+
Mod-Log-Kanal PRIVAT — gelöschte/bearbeitete Nachrichten
-
+
-
+
Kleine Zeile über dem Namen — leer = weglassen
setForm({ ...form, welcome_card_kicker: e.target.value })}
/>
-
+
Zeile darunter {'{count}'} wird zur Mitglieds-Nummer — leer = weglassen
setForm({ ...form, welcome_card_sub: e.target.value })}
/>
-
+
Hintergrundbild https-Adresse, 900×300 passt genau — leer = nur Farbe mit Raster
setForm({ ...form, welcome_card_image: e.target.value })}
/>
-
+
// Allgemein
-
+
Öffentliche URL Basis für Buttons, RSS, OAuth-Redirect
setForm({ ...form, public_url: e.target.value })} />
-
+
Gitea-URL
setForm({ ...form, gitea_url: e.target.value })} />
@@ -1801,23 +1823,23 @@ export default function Settings({ me }) {
Sind sie gesetzt, leitet der Server falsch aufgerufene Seiten automatisch
auf die richtige Adresse um.
-
+
Community-Hub Devlogs, Roadmap, Server, Profile — Ziel für Devlog-Buttons, RSS und Link-Vorschauen
setForm({ ...form, hub_url: e.target.value })} />
-
+
Bot-Produktseite Funktionen, Befehle und dieses Dashboard
setForm({ ...form, bot_url: e.target.value })} />
-
+
Login-Domain z. B. .d4rkst3r.de — dann gilt die Anmeldung auf beiden Seiten; leer = getrennte Logins
setForm({ ...form, cookie_domain: e.target.value })} />
-
+
Bug-Repo Ziel für /bug-Issues{data.status.giteaTokenConfigured ? '' : ' — ⚠️ kein GITEA_API_TOKEN, /bug ist aus'}
setForm({ ...form, bug_report_repo: e.target.value })} />
-
+
Roadmap-Repo Milestones → öffentliche /roadmap
setForm({ ...form, roadmap_repo: e.target.value })} />
@@ -1867,15 +1889,15 @@ export default function Settings({ me }) {
ist eine ladungsfähige Anschrift nötig (kein Postfach) — nur der Owner sieht
und ändert diese Felder.
-
+
Name / Verantwortlicher
setForm({ ...form, legal_name: e.target.value })} />
-
+
Anschrift mehrzeilig: Straße, PLZ Ort, Land
-
+
Kontakt-E-Mail Pflichtangabe — muss erreichbar sein
setForm({ ...form, legal_email: e.target.value })} />
@@ -1887,14 +1909,14 @@ export default function Settings({ me }) {
// Web-Login (Member)
-
+
setForm({ ...form, member_gate_enabled: e.target.checked })} />
Login nur für Discord-Member
Nicht-Member werden nach dem Discord-Login abgewiesen (braucht gesetzte Guild-ID)
-
+
Discord-Invite-Link wird Abgewiesenen als „Zum Discord"-Button angezeigt
setForm({ ...form, discord_invite_url: e.target.value })} />
@@ -1902,7 +1924,7 @@ export default function Settings({ me }) {
// Events, Social & Voice
-
+
Event-Ankündigungs-Kanal neue Discord-Events werden hier angekündigt + Events-Seite
-
+
Social-Kanal 🔴 Twitch-Live + ▶️ neue YouTube-Videos
-
+
-
+
Temp-Voice-Hub „Join to Create": Beitritt erzeugt eigenen Voice-Kanal (Bot braucht Kanäle verwalten + Member verschieben)
// Backups & Watchdog
-
+
setForm({ ...form, backup_enabled: e.target.checked })} />
Nächtliches DB-Backup (03:00)
gzip, 14 Tage Rotation
-
+
Backup-Upload-Kanal Offsite-Kopie in PRIVATEN Kanal — leer = nur lokal
sendTest('backup')}>Backup jetzt
-
-
+
Watchdog-URLs kommagetrennt; DM bei Ausfall — leer = aus
setForm({ ...form, watchdog_urls: e.target.value })} />
@@ -2089,7 +2111,7 @@ export default function Settings({ me }) {
Jeder Server bekommt sein eigenes Live-Status-Embed im Status-Kanal (🟢/🔴,
Spieler-Balken, Map, Adresse) — aktualisiert alle 2 Minuten, DiscordGSM-Style.
-
+
Status-Kanal
@@ -2666,9 +2688,11 @@ export default function Settings({ me }) {
aria-label="Bereich suchen"
onChange={(e) => setTabSearch(e.target.value)}
onKeyDown={(e) => {
- if (e.key === 'Enter' && foundTabs[0]) {
- setTab(foundTabs[0].id);
- setTabSearch('');
+ // Einstellungen zuerst: wer „schwellwert" tippt,
+ // will das Feld, nicht den Bereich
+ if (e.key === 'Enter') {
+ if (foundSettings[0]) jumpToSetting(foundSettings[0]);
+ else if (foundTabs[0]) { setTab(foundTabs[0].id); setTabSearch(''); }
}
if (e.key === 'Escape') setTabSearch('');
}}
@@ -2680,7 +2704,25 @@ export default function Settings({ me }) {
)}
- {foundTabs.length === 0 && (
+ {foundSettings.length > 0 && (
+
+ Einstellungen
+ {foundSettings.map((s) => (
+ jumpToSetting(s)}
+ >
+ {s.label}
+
+ {TABS.find((t) => t.id === s.tab)?.label ?? s.tab}
+
+
+ ))}
+
+ )}
+
+ {foundTabs.length === 0 && foundSettings.length === 0 && (
Nichts gefunden.
)}
diff --git a/frontend/src/setting-index.js b/frontend/src/setting-index.js
new file mode 100644
index 0000000..61a86fc
--- /dev/null
+++ b/frontend/src/setting-index.js
@@ -0,0 +1,103 @@
+// Register aller Einstellungen für die Suche in der Config-Seitenleiste.
+//
+// Die Seitenleisten-Suche fand bisher nur Bereiche: „starboard" führte zu
+// Community, aber nicht zum Schwellwert-Feld. Hier steht, wo jede Einstellung
+// wohnt — daraus wird ein Sprung direkt aufs Feld.
+//
+// `id` — Schlüssel wie im Formular; im JSX steht dazu data-setting="
"
+// `target` — nur nötig, wenn sich zwei Einstellungen ein Feld teilen (etwa die
+// beiden Markenfarben): dann wird das Feld des Nachbarn angesprungen
+// `tab` — Bereich, in dem das Feld liegt
+// `label` — was in der Trefferliste steht
+// `find` — zusätzliche Suchwörter (Synonyme, Fachbegriffe, alte Namen)
+//
+// Werte und Textvorlagen haben eigene Bereiche mit eigener Struktur; sie sind
+// hier mit ihrem Bereich vertreten, nicht Feld für Feld.
+
+export const SETTING_INDEX = [
+ /* ── Kanäle ─────────────────────────────────────── */
+ { id: 'devlog_channel_id', tab: 'feeds', label: 'Devlog-Kanal', find: 'devlog posten kanal' },
+ { id: 'commit_channel_id', tab: 'feeds', label: 'Commit-Kanal', find: 'gitea push commits' },
+ { id: 'release_channel_id', tab: 'feeds', label: 'Release-Kanal', find: 'ankündigung version changelog' },
+ { id: 'starboard_channel_id', tab: 'community', label: 'Starboard-Kanal', find: 'stern best-of highlights' },
+ { id: 'screenshot_channel_id', tab: 'community', label: 'Screenshot-Kanal', find: 'galerie bilder' },
+ { id: 'voting_channel_id', tab: 'community', label: 'Voting-Kanal', find: 'wunsch abstimmen ideen' },
+ { id: 'birthday_channel_id', tab: 'community', label: 'Geburtstags-Kanal', find: 'geburtstag gratulation' },
+ { id: 'modmail_channel_id', tab: 'support', label: 'Modmail-Kanal', find: 'direktnachricht dm staff' },
+ { id: 'ticket_channel_id', tab: 'support', label: 'Ticket-Kanal', find: 'support anfrage thread' },
+ { id: 'welcome_channel_id', tab: 'support', label: 'Willkommens-Kanal', find: 'begrüßung neue member' },
+ { id: 'modlog_channel_id', tab: 'support', label: 'Protokoll-Kanal', find: 'modlog logging gelöscht' },
+ { id: 'events_announce_channel_id', tab: 'system', label: 'Event-Kanal', find: 'events ankündigung' },
+ { id: 'social_announce_channel_id', tab: 'system', label: 'Social-Kanal', find: 'twitch youtube live stream' },
+ { id: 'tempvoice_channel_id', tab: 'system', label: 'Sprachkanal-Hub', find: 'temp voice join to create' },
+ { id: 'backup_channel_id', tab: 'system', label: 'Backup-Kanal', find: 'sicherung upload' },
+ { id: 'status_channel_id', tab: 'server', label: 'Server-Status-Kanal', find: 'monitor game embed' },
+ { id: 'server_alert_channel_id', target: 'status_channel_id', tab: 'server', label: 'Alarm-Kanal', find: 'down ausfall alert' },
+
+ /* ── Rollen ─────────────────────────────────────── */
+ { id: 'devlog_ping_role_id', tab: 'feeds', label: 'Devlog-Ping-Rolle', find: 'benachrichtigung abo erwähnen' },
+ { id: 'playtester_role_id', tab: 'community', label: 'Playtester-Rolle', find: 'alpha tester' },
+ { id: 'birthday_role_id', tab: 'community', label: 'Geburtstags-Rolle', find: 'tagesrolle' },
+ { id: 'autorole_id', tab: 'community', label: 'Auto-Rolle', find: 'startrolle beitritt automatisch' },
+
+ /* ── Schalter & Werte in den Bereichen ──────────── */
+ { id: 'commit_feed_enabled', tab: 'feeds', label: 'Push-Embeds posten', find: 'commit feed an aus' },
+ { id: 'commit_branch_filter', tab: 'feeds', label: 'Branch-Filter', find: 'main release zweig' },
+ { id: 'ignored_repos', tab: 'feeds', label: 'Ignorierte Repos', find: 'ausschließen repository' },
+ { id: 'weekly_recap_enabled', tab: 'feeds', label: 'Wochen-Rückblick', find: 'sonntag zusammenfassung' },
+ { id: 'devlog_threads_enabled', tab: 'feeds', label: 'Devlog-Threads', find: 'diskussion kommentare' },
+ { id: 'starboard_threshold', target: 'starboard_channel_id', tab: 'community', label: 'Starboard-Schwellwert', find: 'sterne anzahl schwelle' },
+ { id: 'levels_enabled', tab: 'community', label: 'Level-System', find: 'xp rang erfahrung' },
+ { id: 'levels_announce', tab: 'community', label: 'Level-Up ankündigen', find: 'aufstieg nachricht' },
+ { id: 'level_rewards', tab: 'community', label: 'Level-Belohnungen', find: 'rollen ab level' },
+ { id: 'sticky_roles_enabled', tab: 'community', label: 'Rollen merken', find: 'sticky rejoin wiederherstellen' },
+
+ /* ── Willkommens-Karte ──────────────────────────── */
+ { id: 'welcome_card_enabled', tab: 'support', label: 'Willkommens-Karte rendern', find: 'bild karte begrüßung' },
+ { id: 'welcome_card_kicker', tab: 'support', label: 'Willkommens-Karte: kleine Zeile', find: 'überschrift text karte' },
+ { id: 'welcome_card_sub', tab: 'support', label: 'Willkommens-Karte: Zeile darunter', find: 'member nummer text' },
+ { id: 'welcome_card_image', tab: 'support', label: 'Willkommens-Karte: Hintergrundbild', find: 'grafik bild hintergrund' },
+ { id: 'welcome_card_watermark', tab: 'support', label: 'Willkommens-Karte: Markenname', find: 'wasserzeichen hintergrund' },
+
+ /* ── Marke ──────────────────────────────────────── */
+ { id: 'brand_name', tab: 'brand', label: 'Markenname', find: 'footer embed name' },
+ { id: 'brand_color', tab: 'brand', label: 'Primärfarbe', find: 'farbe embed hex' },
+ { id: 'brand_color2', target: 'brand_color', tab: 'brand', label: 'Sekundärfarbe', find: 'farbe zweite hex' },
+ { id: 'bot_name', tab: 'brand', label: 'Bot-Name', find: 'umbenennen username' },
+ { id: 'bot_presence_status', tab: 'brand', label: 'Bot-Status', find: 'online abwesend nicht stören' },
+ { id: 'bot_status_text', tab: 'brand', label: 'Bot-Aktivität', find: 'spielt schaut status text' },
+
+ /* ── Technik & Zugang ───────────────────────────── */
+ { id: 'bug_report_repo', tab: 'system', label: 'Repo für Fehlermeldungen', find: 'bug issue gitea' },
+ { id: 'roadmap_repo', tab: 'system', label: 'Repo für die Roadmap', find: 'milestones meilensteine' },
+ { id: 'watchdog_urls', tab: 'system', label: 'Zu prüfende Adressen', find: 'watchdog erreichbarkeit uptime' },
+ { id: 'twitch_channel', target: 'youtube_channel_id', tab: 'system', label: 'Twitch-Kanal', find: 'stream live' },
+ { id: 'youtube_channel_id', tab: 'system', label: 'YouTube-Kanal', find: 'video rss' },
+ { id: 'backup_enabled', tab: 'system', label: 'Datenbank-Sicherung', find: 'backup nächtlich' },
+ { id: 'repo_backup_enabled', tab: 'system', label: 'Repository-Sicherung', find: 'gitea bundle backup' },
+ { id: 'member_gate_enabled', tab: 'system', label: 'Login nur für Mitglieder', find: 'gate zugang sperren' },
+ { id: 'discord_invite_url', tab: 'system', label: 'Einladungs-Link', find: 'invite discord' },
+ { id: 'public_url', tab: 'system', label: 'Öffentliche Adresse', find: 'url domain' },
+ { id: 'hub_url', tab: 'system', label: 'Hub-Adresse', find: 'domain zwei seiten community' },
+ { id: 'bot_url', tab: 'system', label: 'Bot-Adresse', find: 'domain zwei seiten produktseite' },
+ { id: 'cookie_domain', tab: 'system', label: 'Cookie-Domain', find: 'login beide domains' },
+ { id: 'gitea_url', tab: 'system', label: 'Gitea-Adresse', find: 'git server url' },
+ { id: 'discord_guild_id', tab: 'brand', label: 'Server-ID', find: 'guild id discord' },
+ { id: 'legal_name', tab: 'system', label: 'Impressum: Name', find: 'betreiber rechtstext' },
+ { id: 'legal_address', tab: 'system', label: 'Impressum: Anschrift', find: 'adresse rechtstext' },
+ { id: 'legal_email', tab: 'system', label: 'Impressum: E-Mail', find: 'kontakt rechtstext' },
+];
+
+/**
+ * Einstellungen zu einer Sucheingabe.
+ * @param {string} query
+ * @param {(tab: string) => boolean} allowed — Rechte-Prüfung je Bereich
+ */
+export function findSettings(query, allowed) {
+ const q = query.trim().toLowerCase();
+ if (q.length < 2) return [];
+ return SETTING_INDEX
+ .filter((s) => allowed(s.tab))
+ .filter((s) => `${s.label} ${s.find} ${s.id}`.toLowerCase().includes(q))
+ .slice(0, 8);
+}
diff --git a/frontend/src/style.css b/frontend/src/style.css
index 4a29f0c..54813f2 100644
--- a/frontend/src/style.css
+++ b/frontend/src/style.css
@@ -679,6 +679,34 @@ a.sha:hover { border-color: var(--neon); background: rgba(245, 197, 24, .07); }
color: var(--muted2);
padding: .3rem .9rem .25rem;
}
+/* Treffer der Einstellungs-Suche */
+.settings-hit {
+ display: flex; flex-direction: column; gap: .1rem;
+ width: 100%; text-align: left;
+ background: none; border: 0; border-left: 2px solid transparent;
+ border-radius: 9px;
+ padding: .4rem .9rem;
+ cursor: pointer;
+ transition: background .15s, border-color .15s;
+}
+.settings-hit:hover { background: rgba(255, 255, 255, .04); border-left-color: var(--neon); }
+.settings-hit-label { color: var(--text); font-size: .88rem; line-height: 1.3; }
+.settings-hit-tab {
+ font-family: var(--mono); font-size: .55rem;
+ letter-spacing: .14em; text-transform: uppercase; color: var(--muted2);
+}
+.settings-hit:hover .settings-hit-tab { color: var(--neon); }
+
+/* Angesprungenes Feld kurz hervorheben, damit man es im Formular findet */
+.field-found {
+ animation: fieldFound 2.2s ease both;
+ border-radius: 8px;
+}
+@keyframes fieldFound {
+ 0%, 55% { box-shadow: 0 0 0 2px var(--neon), 0 0 18px rgba(245, 197, 24, .25); }
+ 100% { box-shadow: 0 0 0 2px transparent, 0 0 0 transparent; }
+}
+
.settings-nav-empty {
font-family: var(--mono); font-size: .66rem; letter-spacing: .1em;
color: var(--muted2); padding: .6rem .9rem;