Files
d4rkbot/frontend/src/pages/BotLanding.jsx
T
D4rkst3randClaude Opus 5 25d36c7269
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Webseiten-Redesign: Graphit + Orange, abgeschraegte Ecken
Umsetzung des Entwurfs aus design_handoff_redesign/ -- beide Auftritte
(Produktseite und Community-Hub) plus Dashboard.

  Farbe    #0a0a0a + Neon-Gelb   ->  #131316 + Orange-Verlauf aus dem Logo
  Schrift  Bebas/Barlow/ShareTech ->  Chakra Petch + IBM Plex Mono
  Form     border-radius          ->  clip-path, oben links + unten rechts

Rauschen, Scanlines, Hintergrundschrift, Kopf-Raster und die Partikel sind
raus; der Schein von oben rechts ersetzt sie. Die Abschraegung ist ein
System und keine sechzig Einzelregeln: eine clip-path-Regel mit --bv, jede
Komponente setzt nur die Tiefe.

Der Farbdurchstich ging durch alle ~55 Abschnitte der style.css, nicht nur
durch die fuenf gezeichneten Seiten -- sonst waeren Galerie, Level,
Composer und Emoji-Auswahl im alten Gelb stehen geblieben. Von --neon ist
nichts uebrig.

Die Schriften kommen weiter self-hosted ueber @fontsource. Der Entwurf
schlaegt einen Google-Fonts-Link vor; das waere eine Fremdanfrage mehr, wo
der Aufbau bisher ohne auskam.

ZWEI FEHLER unterwegs gefunden, beide aelter als dieser Umbau:

1. .btn stand in der Datei NACH .btn-save und .btn-ghost. Bei gleicher
   Spezifitaet gewinnt die spaetere Regel, also hat ihr `background: none`
   jeden Verlauf ueberschrieben -- der Speichern-Knopf sah aus wie ein
   Ghost-Knopf, schon in der gelben Fassung. Die Grundform steht jetzt
   vor allen Abwandlungen.

2. .nav-logo ist ein Flex-Container, und `gap` legte sich zwischen die
   Textknoten "D4RKST" und <span>3R</span>. In der Leiste stand
   "D4RKST 3R". Der Abstand haengt jetzt am Bild statt am Container.

DREI ABWEICHUNGEN vom Entwurf, bewusst:

- Die Statuszeile im Hero zeigt nur "ONLINE", nicht "38 MS · UPTIME
  99,2 %". Antwortzeit und Erreichbarkeit des Bots liegen ausschliesslich
  hinter /api/settings und damit hinter der Anmeldung; oeffentlich gibt es
  sie nirgends. Dafuer braeuchte es einen neuen Endpunkt -- erfunden wird
  hier nichts.
- Keine Aura am Hauptknopf: clip-path schneidet den Schatten des eigenen
  Elements mit weg. Im Referenz-HTML stehen box-shadow und clip-path am
  selben Element, dort ist sie also ebenso wenig zu sehen. Tote
  Deklaration weggelassen statt mitgeschleppt.
- Die Config-Bereiche bleiben scharfkantig statt abgeschraegt. In ihnen
  klappen Auswahllisten aus dem Panel heraus, die clip-path abschneiden
  wuerde. Deckt sich mit der Vorgabe, dass die uebrigen Tabs nur die
  Token uebernehmen; die Dashboard-Panels selbst sind abgeschraegt.

Dazu: .content von 860 auf 1160 px, weil die dreispaltigen Raster es
brauchen -- die Devlog-Timeline behaelt 880 px Lesebreite, sonst waeren
die Zeilen zu lang. Der Punkt an den Timeline-Karten ist entfallen: er
sass ausserhalb der Karte und waere von der Abschraegung weggeschnitten
worden.

Welches Wort in einer Ueberschrift den Verlauf bekommt, steht jetzt im
Woerterbuch -- der Teil in eckigen Klammern ("Funk[tionen]", aber
"Fea[tures]"). So entscheidet jede Sprache selbst, wo sich das Wort
teilen laesst.

Mitgezogen: brand.js, das geteilte Design-System fuer die eingebundenen
Dienste. Neue Tokens und Abschraegungen, aber --neon/--neon2 bleiben als
Zweitname stehen -- fremde Apps benutzen sie in ihrem eigenen CSS.

Marken-Dateien nach frontend/public/brand/, verkleinert auf die Groessen,
in denen sie wirklich dargestellt werden: 2,4 MB -> 97 KB.

Geprueft: Build laeuft. Im Browser durchgesehen -- Landing, Funktionen,
Befehle, Hub-Start, Devlogs, Server, Roadmap, Dashboard.

Ungeprueft: das Dashboard mit echter Anmeldung. Eine Sitzung war hier
nicht moeglich, gesehen habe ich es ueber gestubbte API-Antworten. Ebenso
ungeprueft, wie sich die Schriftumstellung auf schmalen Schirmen macht.

NICHT angefasst: die Embed-Farben in runtime-settings.js stehen weiter auf
dem alten Gelb -- die wirken in Discord, nicht auf der Webseite. Und ist
brand_color in der Datenbank gesetzt, behalten die eingebundenen Dienste
ihre bisherige Farbe, unabhaengig von den neuen Vorgabewerten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 15:15:37 +02:00

138 lines
6.3 KiB
React

// Produktseite des Bots: was er kann, wie er aussieht, wo es weitergeht.
// Bewusst ohne „Einladen"-Button — der Bot ist auf einen Server ausgelegt.
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { apiGet } from '../api.js';
import { useT } from '../i18n.jsx';
import { Accent } from '../components/Accent.jsx';
import {
IconDevlog, IconLevel, IconRoles, IconServer, IconSupport, IconGiveaway,
IconBirthday, IconShield, IconMagic, IconArrowRight, IconBot,
} from '../icons.jsx';
const HIGHLIGHTS = [
[IconDevlog, 'bot.hl.devlogs'],
[IconServer, 'bot.hl.monitor'],
[IconLevel, 'bot.hl.level'],
[IconRoles, 'bot.hl.roles'],
[IconSupport, 'bot.hl.support'],
[IconShield, 'bot.hl.mod'],
[IconGiveaway, 'bot.hl.giveaway'],
[IconBirthday, 'bot.hl.birthday'],
[IconMagic, 'bot.hl.welcome'],
];
export default function BotLanding() {
const { t } = useT();
const [status, setStatus] = useState(null);
const [hub, setHub] = useState(null);
const [featureCount, setFeatureCount] = useState(null);
useEffect(() => {
// Nur verlinken, wenn der Hub wirklich woanders liegt
apiGet('/api/legal').then((d) => setHub(d.split ? d.hubUrl : null)).catch(() => {});
apiGet('/api/serverstats').then(setStatus).catch(() => {});
// Zahl kommt aus dem Modul-Register, damit sie nicht veraltet
apiGet('/api/features').then((d) => setFeatureCount(d.features.length)).catch(() => {});
window.scrollTo(0, 0);
}, []);
return (
<>
<section className="hero hero-bot">
<div className="hero-text">
<div className="page-tag dot">{t('bot.tag')}</div>
<h1 className="hero-title">
{t('bot.title1')}<br />
<Accent text={t('bot.title2')} outline />
</h1>
<p className="hero-sub">{t('bot.subtitle')}</p>
<div className="home-cta">
<Link className="btn btn-save with-icon" to="/features">
{t('bot.allFeatures')} <IconArrowRight size={15} />
</Link>
<Link className="btn btn-ghost with-icon" to="/commands">
<IconBot /> {t('bot.viewCommands')}
</Link>
</div>
</div>
{/* Karte mit Logo, Zustand und den vier Kennzahlen. Der versetzte
Rahmen liegt als eigenes Element dahinter — als Pseudo-Element
am Panel würde ihn dessen Abschrägung wegschneiden. */}
<div className="hero-card">
<span className="hero-frame" aria-hidden="true" />
<div className="hero-panel">
<div className="hero-lockup">
<img src="/brand/lockup.png" alt="D4RKST3R" />
</div>
{/* Diese Seite liefert der Bot selbst aus — wenn sie
ankommt, läuft er. Antwortzeit und Erreichbarkeit
stehen bisher nur im Panel, nicht öffentlich. */}
<div className="hero-state">
<span className="hero-dot" />
{t('bot.online')}
</div>
<div className="hero-stats">
<div className="hero-stat">
<span className="hero-stat-n">{featureCount ?? '—'}</span>
<span className="hero-stat-label">{t('bot.statFeatures')}</span>
</div>
<div className="hero-stat">
<span className="hero-stat-n">18</span>
<span className="hero-stat-label">{t('bot.statCommands')}</span>
</div>
<div className="hero-stat">
<span className="hero-stat-n">300+</span>
<span className="hero-stat-label">{t('bot.statGames')}</span>
</div>
<div className="hero-stat">
<span className="hero-stat-n">0 </span>
<span className="hero-stat-label">{t('bot.statCost')}</span>
</div>
</div>
</div>
</div>
</section>
<section className="content">
<h2 className="settings-title">
{t('bot.whatItDoes')}
{featureCount && <span className="section-meta">{t('bot.modulesMeta', featureCount)}</span>}
</h2>
<div className="home-tiles">
{HIGHLIGHTS.map(([Icon, key]) => (
<div className="card home-tile" key={key}>
<span className="home-tile-icon"><Icon size={18} /></span>
<span className="home-tile-title">{t(key)}</span>
<span className="home-tile-desc">{t(`${key}.text`)}</span>
</div>
))}
</div>
<div className="info-panels">
<div className="info-panel">
<span className="info-panel-tag">{t('bot.forDevs')}</span>
<h3 className="info-panel-title">{t('bot.dev.api')}</h3>
<p>{t('bot.dev.api.text')}</p>
</div>
<div className="info-panel warm">
<span className="info-panel-tag">{t('bot.hosting')}</span>
<h3 className="info-panel-title">{t('bot.dev.host')}</h3>
<p>{t('bot.dev.host.text')}</p>
</div>
</div>
{hub && (
<p className="footnote">
{status?.members
? t('bot.runsForWith', status.members)
: t('bot.runsFor')}
{' '}<a href={hub}>{t('bot.toHub')}</a>.
</p>
)}
</section>
</>
);
}