Update README and localization files to remove "self-hosted" references
- Revised the README.md to simplify the description of Nexumi. - Updated layout.tsx to reflect the change in the bot's description. - Modified German and English localization files to remove "self-hosted" from the bot's description and terms of service.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Nexumi
|
||||
|
||||
Nexumi is a self-hosted public Discord bot with dashboard architecture, built as a TypeScript monorepo.
|
||||
Nexumi is a public Discord bot with dashboard architecture, built as a TypeScript monorepo.
|
||||
|
||||
## Phase 1 scope
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ const inter = Inter({ subsets: ['latin'], variable: '--font-inter', display: 'sw
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Nexumi — Discord Bot',
|
||||
description: 'Self-hosted Discord bot with dashboard, moderation, and community modules'
|
||||
description: 'Discord bot with dashboard, moderation, and community modules'
|
||||
};
|
||||
|
||||
export default async function RootLayout({ children }: { children: ReactNode }) {
|
||||
|
||||
@@ -737,7 +737,7 @@
|
||||
},
|
||||
"landing": {
|
||||
"hero": {
|
||||
"subtitle": "Selbst gehosteter Discord-Bot mit Moderations-, Community- und Integrationsmodulen sowie Web-Dashboard.",
|
||||
"subtitle": "Discord-Bot mit Moderations-, Community- und Integrationsmodulen sowie Web-Dashboard.",
|
||||
"invite": "Bot einladen",
|
||||
"login": "Zum Dashboard",
|
||||
"dashboard": "Dashboard öffnen",
|
||||
@@ -789,13 +789,13 @@
|
||||
"title": "Datenschutzerklärung",
|
||||
"p1": "Nexumi verarbeitet Discord-Daten, soweit dies für den Betrieb des Bots und des Dashboards erforderlich ist (z. B. Server-IDs, Nutzer-IDs, Moderationsfälle, Modul-Einstellungen).",
|
||||
"p2": "Die Anmeldung am Dashboard erfolgt über Discord OAuth2 (Scopes identify und guilds). Es werden Sitzungsdaten in Redis gespeichert.",
|
||||
"p3": "Daten werden in der selbst gehosteten PostgreSQL-Datenbank gespeichert. Aufbewahrungsdauern sind über Modul-Einstellungen bzw. Löschanfragen steuerbar.",
|
||||
"p3": "Daten werden in unserer PostgreSQL-Datenbank gespeichert. Aufbewahrungsdauern sind über Modul-Einstellungen bzw. Löschanfragen steuerbar.",
|
||||
"p4": "Es findet keine Weitergabe an Dritte statt, außer soweit für den Betrieb technisch erforderlich (z. B. Discord API, optional Sentry).",
|
||||
"p5": "Betroffene können Auskunft, Berichtigung und Löschung verlangen. Kontakt über die im Impressum genannte Adresse oder den Bot-Command /gdpr sofern verfügbar."
|
||||
},
|
||||
"terms": {
|
||||
"title": "Nutzungsbedingungen",
|
||||
"p1": "Nexumi wird als selbst gehosteter Discord-Bot bereitgestellt. Die Nutzung erfolgt auf eigene Verantwortung.",
|
||||
"p1": "Nexumi wird als Discord-Bot bereitgestellt. Die Nutzung erfolgt auf eigene Verantwortung.",
|
||||
"p2": "Missbrauch, Spam oder Verstöße gegen die Discord Terms of Service können zum Ausschluss (Blacklist) führen.",
|
||||
"p3": "Wir übernehmen keine Gewähr für unterbrechungsfreien Betrieb. Wartungsfenster werden nach Möglichkeit angekündigt.",
|
||||
"p4": "Es gilt das Recht der Bundesrepublik Deutschland, soweit zwingende Verbraucherschutzvorschriften nichts anderes vorsehen."
|
||||
|
||||
@@ -737,7 +737,7 @@
|
||||
},
|
||||
"landing": {
|
||||
"hero": {
|
||||
"subtitle": "Self-hosted Discord bot with moderation, community, and integration modules plus a web dashboard.",
|
||||
"subtitle": "Discord bot with moderation, community, and integration modules plus a web dashboard.",
|
||||
"invite": "Invite bot",
|
||||
"login": "Open dashboard",
|
||||
"dashboard": "Open dashboard",
|
||||
@@ -789,13 +789,13 @@
|
||||
"title": "Privacy policy",
|
||||
"p1": "Nexumi processes Discord data as required to operate the bot and dashboard (e.g. guild IDs, user IDs, moderation cases, module settings).",
|
||||
"p2": "Dashboard login uses Discord OAuth2 (identify and guilds scopes). Session data is stored in Redis.",
|
||||
"p3": "Data is stored in the self-hosted PostgreSQL database. Retention can be configured per module or via deletion requests.",
|
||||
"p3": "Data is stored in our PostgreSQL database. Retention can be configured per module or via deletion requests.",
|
||||
"p4": "Data is not shared with third parties except where technically required (e.g. Discord API, optional Sentry).",
|
||||
"p5": "Data subjects may request access, correction, and deletion via the contact in the legal notice or bot commands where available."
|
||||
},
|
||||
"terms": {
|
||||
"title": "Terms of service",
|
||||
"p1": "Nexumi is provided as a self-hosted Discord bot. Use is at your own risk.",
|
||||
"p1": "Nexumi is provided as a Discord bot. Use is at your own risk.",
|
||||
"p2": "Abuse, spam, or violations of Discord Terms of Service may result in blacklisting.",
|
||||
"p3": "We do not guarantee uninterrupted service. Maintenance windows are announced when possible.",
|
||||
"p4": "German law applies unless mandatory consumer protection rules require otherwise."
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Nexumi – Vollständige Feature-Spezifikation (Cursor-Prompt)
|
||||
|
||||
Baue „Nexumi", einen vollwertigen, selbst gehosteten, **öffentlichen** Discord-Bot mit Web-Dashboard. Domain: `https://nexumi.de`. Repository: `https://git.hexahost.dev/smueller/Nexumi`. Der Name „Nexumi" wird konsistent verwendet: Bot-Präsenz, WebUI-Branding, Embed-Footer, Docker-Image-Namen, Dokumentation. Halte dich an diese Spezifikation. Frage nach, bevor du Module weglässt oder den Stack änderst.
|
||||
Baue „Nexumi", einen vollwertigen, **öffentlichen** Discord-Bot mit Web-Dashboard. Domain: `https://nexumi.de`. Repository: `https://git.hexahost.dev/smueller/Nexumi`. Der Name „Nexumi" wird konsistent verwendet: Bot-Präsenz, WebUI-Branding, Embed-Footer, Docker-Image-Namen, Dokumentation. Halte dich an diese Spezifikation. Frage nach, bevor du Module weglässt oder den Stack änderst.
|
||||
|
||||
## Tech-Stack & Architektur
|
||||
|
||||
|
||||
37
landingpage/assets/nexumi-logo.svg
Normal file
37
landingpage/assets/nexumi-logo.svg
Normal file
@@ -0,0 +1,37 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Nexumi logo">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#181C2B"/>
|
||||
<stop offset="1" stop-color="#0C0E15"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="mark" x1="0" y1="1" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#6366F1"/>
|
||||
<stop offset="1" stop-color="#8B5CF6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- App-Icon-Grundflaeche -->
|
||||
<rect width="512" height="512" rx="116" fill="url(#bg)"/>
|
||||
<rect x="1.5" y="1.5" width="509" height="509" rx="114.5" fill="none" stroke="#FFFFFF" stroke-opacity="0.06" stroke-width="3"/>
|
||||
|
||||
<!-- Glow: gestaffelte Deckkraft statt Filter, rendersicher -->
|
||||
<g fill="none" stroke="url(#mark)" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M170 362 V150 L342 362 V150" stroke-width="78" stroke-opacity="0.10"/>
|
||||
<path d="M170 362 V150 L342 362 V150" stroke-width="52" stroke-opacity="0.16"/>
|
||||
</g>
|
||||
|
||||
<!-- Kanten des N -->
|
||||
<path d="M170 362 V150 L342 362 V150" fill="none" stroke="url(#mark)"
|
||||
stroke-width="30" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Knoten -->
|
||||
<g fill="url(#mark)">
|
||||
<circle cx="170" cy="150" r="34"/>
|
||||
<circle cx="170" cy="362" r="34"/>
|
||||
<circle cx="342" cy="362" r="34"/>
|
||||
<circle cx="342" cy="150" r="34"/>
|
||||
</g>
|
||||
|
||||
<!-- Aktiver Knoten oben rechts -->
|
||||
<circle cx="342" cy="150" r="14" fill="#C7D2FE"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
327
landingpage/assets/style.css
Normal file
327
landingpage/assets/style.css
Normal file
@@ -0,0 +1,327 @@
|
||||
:root {
|
||||
--background: #f8f8fb;
|
||||
--foreground: #111116;
|
||||
--card: #ffffff;
|
||||
--muted: #f1f1f5;
|
||||
--muted-foreground: #6b7280;
|
||||
--primary: #6366f1;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #f1f1f5;
|
||||
--secondary-foreground: #111116;
|
||||
--border: #e4e4ea;
|
||||
--radius: 0.625rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0d;
|
||||
--foreground: #e6e6ea;
|
||||
--card: #131318;
|
||||
--muted: #1b1b22;
|
||||
--muted-foreground: #9a9aa5;
|
||||
--secondary: #1b1b22;
|
||||
--secondary-foreground: #e6e6ea;
|
||||
--border: #23232b;
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.shell {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.border-b {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.border-t {
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
header .bar {
|
||||
display: flex;
|
||||
height: 3.5rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.brand img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
height: 2.25rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: calc(var(--radius) - 2px);
|
||||
padding: 0 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
height: 2.75rem;
|
||||
padding: 0 1.25rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
filter: brightness(0.95);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--secondary);
|
||||
color: var(--secondary-foreground);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.btn-ghost:hover,
|
||||
.btn-outline:hover,
|
||||
.btn-secondary:hover {
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
padding-top: 4rem;
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.hero {
|
||||
padding-top: 5rem;
|
||||
padding-bottom: 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.hero-brand img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
margin: 0;
|
||||
font-size: 2.25rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.hero h1 {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero p {
|
||||
margin: 0;
|
||||
max-width: 42rem;
|
||||
font-size: 1.125rem;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
padding-top: 3rem;
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.stats {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
.card-label {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.card-value {
|
||||
margin: 0;
|
||||
font-size: 1.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.features {
|
||||
padding-top: 3rem;
|
||||
padding-bottom: 3rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.features > .intro h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.features > .intro p {
|
||||
margin: 0.5rem 0 0;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.group h3 {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.module-title {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.module-desc {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
footer .bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
footer .bar {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
footer nav {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
color: var(--foreground);
|
||||
}
|
||||
24
landingpage/config.php
Normal file
24
landingpage/config.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* Public site config for Plesk (nexumi.de).
|
||||
* No secrets here — only URLs and the Discord application client ID (public).
|
||||
*/
|
||||
return [
|
||||
// Discord application client ID (used for bot invite link)
|
||||
'bot_client_id' => '1529428118010462278',
|
||||
|
||||
// Dashboard (Traefik / WebUI)
|
||||
'dashboard_url' => 'https://dashboard.nexumi.de',
|
||||
|
||||
// Optional support invite
|
||||
'support_url' => 'https://discord.gg/RZ9JcQrPDz',
|
||||
|
||||
// Live stats from the WebUI public API (falls back to placeholders on failure)
|
||||
'stats_api_url' => 'https://dashboard.nexumi.de/api/public/stats',
|
||||
|
||||
// Footer legal pages (still hosted on the dashboard app)
|
||||
'status_url' => 'https://dashboard.nexumi.de/status',
|
||||
'impressum_url' => 'https://dashboard.nexumi.de/impressum',
|
||||
'privacy_url' => 'https://dashboard.nexumi.de/privacy',
|
||||
'terms_url' => 'https://dashboard.nexumi.de/terms',
|
||||
];
|
||||
192
landingpage/index.php
Normal file
192
landingpage/index.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$config = require __DIR__ . '/config.php';
|
||||
|
||||
$clientId = (string) $config['bot_client_id'];
|
||||
$inviteUrl = 'https://discord.com/api/oauth2/authorize?' . http_build_query([
|
||||
'client_id' => $clientId,
|
||||
'permissions' => '8',
|
||||
'scope' => 'bot applications.commands',
|
||||
], '', '&', PHP_QUERY_RFC3986);
|
||||
|
||||
$dashboardUrl = rtrim((string) $config['dashboard_url'], '/');
|
||||
$loginUrl = $dashboardUrl . '/login';
|
||||
$supportUrl = isset($config['support_url']) && $config['support_url'] !== ''
|
||||
? (string) $config['support_url']
|
||||
: null;
|
||||
|
||||
$guildCount = 0;
|
||||
$userCount = 0;
|
||||
|
||||
$statsApi = (string) ($config['stats_api_url'] ?? '');
|
||||
if ($statsApi !== '') {
|
||||
$ctx = stream_context_create([
|
||||
'http' => [
|
||||
'timeout' => 3,
|
||||
'header' => "Accept: application/json\r\n",
|
||||
],
|
||||
]);
|
||||
$raw = @file_get_contents($statsApi, false, $ctx);
|
||||
if ($raw !== false) {
|
||||
$json = json_decode($raw, true);
|
||||
if (is_array($json)) {
|
||||
$guildCount = (int) ($json['guildCount'] ?? 0);
|
||||
$userCount = (int) ($json['approximateUserCount'] ?? 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$year = (int) date('Y');
|
||||
|
||||
$groups = [
|
||||
'moderation' => [
|
||||
'label' => 'Moderation',
|
||||
'modules' => [
|
||||
['label' => 'Moderation', 'description' => 'Bans, Verwarnungen, Timeouts und das Fall-System'],
|
||||
['label' => 'Auto-Moderation', 'description' => 'Spam-, Raid- und Nuke-Schutz'],
|
||||
['label' => 'Logging', 'description' => 'Log-Kanäle für Server-Events'],
|
||||
],
|
||||
],
|
||||
'engagement' => [
|
||||
'label' => 'Engagement',
|
||||
'modules' => [
|
||||
['label' => 'Willkommen & Abschied', 'description' => 'Join- und Leave-Nachrichten, Autoroles'],
|
||||
['label' => 'Verifizierung', 'description' => 'Button- und Captcha-Verifizierung'],
|
||||
['label' => 'Leveling & XP', 'description' => 'Text- und Voice-XP, Rollen-Belohnungen'],
|
||||
['label' => 'Economy', 'description' => 'Währung, Shop und Spiele'],
|
||||
['label' => 'Fun & Spiele', 'description' => 'Minispiele und Medien-Commands'],
|
||||
],
|
||||
],
|
||||
'community' => [
|
||||
'label' => 'Community',
|
||||
'modules' => [
|
||||
['label' => 'Giveaways', 'description' => 'Button-basierte Giveaways'],
|
||||
['label' => 'Tickets', 'description' => 'Support-Ticket-Panels und Transkripte'],
|
||||
['label' => 'Self Roles', 'description' => 'Reaktions-, Button- und Dropdown-Rollenpanels'],
|
||||
['label' => 'Starboard', 'description' => 'Beliebte Nachrichten hervorheben'],
|
||||
['label' => 'Vorschläge', 'description' => 'Community-Vorschläge mit Abstimmung'],
|
||||
['label' => 'Geburtstage', 'description' => 'Geburtstags-Ankündigungen und -Rollen'],
|
||||
],
|
||||
],
|
||||
'utility' => [
|
||||
'label' => 'Utility',
|
||||
'modules' => [
|
||||
['label' => 'Tags', 'description' => 'Custom Commands und Auto-Responder'],
|
||||
['label' => 'Temp-Voice', 'description' => 'Join-to-Create Voice-Kanäle'],
|
||||
],
|
||||
],
|
||||
'integrations' => [
|
||||
'label' => 'Integrationen',
|
||||
'modules' => [
|
||||
['label' => 'Server-Statistiken', 'description' => 'Automatische Statistik-Kanäle und Invites'],
|
||||
['label' => 'Social Feeds', 'description' => 'Twitch-, YouTube-, RSS- und Reddit-Benachrichtigungen'],
|
||||
['label' => 'Scheduler', 'description' => 'Geplante und wiederkehrende Ankündigungen'],
|
||||
['label' => 'Backups', 'description' => 'Server-Backup und -Wiederherstellung'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
function h(?string $value): string
|
||||
{
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nexumi</title>
|
||||
<meta name="description" content="Discord-Bot mit Moderations-, Community- und Integrationsmodulen sowie Web-Dashboard.">
|
||||
<link rel="icon" href="assets/nexumi-logo.svg" type="image/svg+xml">
|
||||
<link rel="stylesheet" href="assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="shell">
|
||||
<header class="border-b">
|
||||
<div class="container bar">
|
||||
<a class="brand" href="/">
|
||||
<img src="assets/nexumi-logo.svg" alt="Nexumi" width="28" height="28">
|
||||
Nexumi
|
||||
</a>
|
||||
<nav>
|
||||
<a class="btn btn-ghost" href="<?= h((string) $config['status_url']) ?>">Status</a>
|
||||
<a class="btn btn-ghost" href="<?= h($inviteUrl) ?>" target="_blank" rel="noreferrer">Einladen</a>
|
||||
<?php if ($supportUrl): ?>
|
||||
<a class="btn btn-ghost" href="<?= h($supportUrl) ?>" target="_blank" rel="noreferrer">Support</a>
|
||||
<?php endif; ?>
|
||||
<a class="btn btn-primary" href="<?= h($loginUrl) ?>">Anmelden</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="border-b">
|
||||
<div class="container hero">
|
||||
<div class="hero-brand">
|
||||
<img src="assets/nexumi-logo.svg" alt="" width="48" height="48">
|
||||
<h1>Nexumi</h1>
|
||||
</div>
|
||||
<p>Discord-Bot mit Moderations-, Community- und Integrationsmodulen sowie Web-Dashboard.</p>
|
||||
<div class="actions">
|
||||
<a class="btn btn-lg btn-primary" href="<?= h($inviteUrl) ?>" target="_blank" rel="noreferrer">Bot einladen</a>
|
||||
<a class="btn btn-lg btn-secondary" href="<?= h($loginUrl) ?>">Zum Dashboard</a>
|
||||
<?php if ($supportUrl): ?>
|
||||
<a class="btn btn-lg btn-outline" href="<?= h($supportUrl) ?>" target="_blank" rel="noreferrer">Support-Server</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="border-b">
|
||||
<div class="container stats">
|
||||
<div class="card">
|
||||
<p class="card-label">Server</p>
|
||||
<p class="card-value"><?= h(number_format($guildCount, 0, ',', '.')) ?></p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="card-label">Nutzer (ca.)</p>
|
||||
<p class="card-value"><?= h(number_format($userCount, 0, ',', '.')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container features">
|
||||
<div class="intro">
|
||||
<h2>Module</h2>
|
||||
<p>Funktionen, die Nexumi pro Server bereitstellt.</p>
|
||||
</div>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<div class="group">
|
||||
<h3><?= h($group['label']) ?></h3>
|
||||
<div class="grid">
|
||||
<?php foreach ($group['modules'] as $module): ?>
|
||||
<div class="card">
|
||||
<p class="module-title"><?= h($module['label']) ?></p>
|
||||
<p class="module-desc"><?= h($module['description']) ?></p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="border-t">
|
||||
<div class="container bar">
|
||||
<p>© <?= $year ?> Nexumi</p>
|
||||
<nav>
|
||||
<a href="<?= h((string) $config['status_url']) ?>">Status</a>
|
||||
<a href="<?= h((string) $config['impressum_url']) ?>">Impressum</a>
|
||||
<a href="<?= h((string) $config['privacy_url']) ?>">Datenschutz</a>
|
||||
<a href="<?= h((string) $config['terms_url']) ?>">Nutzungsbedingungen</a>
|
||||
<a href="<?= h($dashboardUrl . '/dashboard') ?>">Dashboard</a>
|
||||
</nav>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user