feat: Initialize new admin application with a landing page, cookie consent, and theme switching functionality.

This commit is contained in:
Timo Knuth
2026-03-02 23:33:11 +01:00
parent 873c5e53af
commit 59f3efaaed
7 changed files with 289 additions and 16 deletions

View File

@@ -169,8 +169,7 @@ export default function DatenschutzPage() {
Metadaten) verarbeitet werden. Die Speicherdauer der Analysedaten betraegt 12 Monate.
</p>
<p>
PostHog wird in der EU-Region betrieben. Sollte im Einzelfall eine abweichende Region genutzt
werden, informieren wir darueber vorab und holen ggf. erforderliche Einwilligungen ein.
PostHog wird in der USA-Region betrieben. Die Daten werden gemäß EU-Standardvertragsklauseln (SCC) mit angemessenen Schutzmassnahmen uebermittelt.
</p>
<p>
Ihre Consent-Entscheidung wird lokal auf Ihrem Geraet gespeichert und kann jederzeit ueber den Link

View File

@@ -221,12 +221,13 @@ export default function RootPage() {
body { background: var(--bg); }
.page {
background: var(--bg); color: var(--ink); min-height: 100vh;
background-image:
radial-gradient(circle at 15% 50%, var(--gold-faint), transparent 25%),
.page {
background: var(--bg); color: var(--ink); min-height: 100vh;
background-image:
radial-gradient(circle at 15% 50%, var(--gold-faint), transparent 25%),
radial-gradient(circle at 85% 30%, var(--gold-faint), transparent 25%);
transition: background 0.3s, color 0.3s;
overflow-x: hidden;
}
/* Nav */
@@ -283,9 +284,12 @@ export default function RootPage() {
color: var(--gold); margin-bottom: 32px; font-weight: 500;
}
.hero-h1 {
font-weight: 800; font-size: clamp(3.25rem, 8vw, 7.5rem);
font-weight: 800; font-size: clamp(1.75rem, 5vw, 7.5rem);
line-height: 0.92; letter-spacing: -0.04em;
margin: 0 0 48px; color: var(--ink);
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
}
.hero-h1 em { color: var(--gold); font-style: normal; }
@@ -341,6 +345,7 @@ export default function RootPage() {
display: grid; grid-template-columns: repeat(2, 1fr);
justify-items: center;
}
@media (min-width: 480px) { .stats { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 768px) { .stats { grid-template-columns: repeat(4, 1fr); } }
.stat {
padding: 28px 24px;
@@ -350,11 +355,11 @@ export default function RootPage() {
}
.stat:last-child { border-right: none; }
.stat-num {
font-weight: 800; font-size: 2.25rem;
font-weight: 800; font-size: clamp(1.5rem, 4vw, 2.25rem);
color: var(--gold); letter-spacing: -0.03em; line-height: 1;
margin-bottom: 6px;
}
.stat-label { font-size: 0.8125rem; color: var(--ink-muted); font-family: 'Georgia', serif; }
.stat-label { font-size: 0.75rem; color: var(--ink-muted); font-family: 'Georgia', serif; }
/* Features */
.features { border-top: 1px solid var(--ink-faint); padding: 96px 0; }
@@ -371,9 +376,12 @@ export default function RootPage() {
.features-sticky { position: sticky; top: 88px; }
}
.features-h2 {
font-weight: 800; font-size: clamp(2.5rem, 5vw, 4rem);
font-weight: 800; font-size: clamp(1.5rem, 5vw, 4rem);
letter-spacing: -0.04em; line-height: 1.0;
margin: 24px 0 28px; color: var(--ink);
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
}
.features-sub { color: var(--ink-muted); font-family: 'Georgia', serif; line-height: 1.65; font-size: 0.9375rem; }
@@ -458,7 +466,12 @@ export default function RootPage() {
/* AEO / SEO Block */
.aeo-section { padding: 96px 0; border-top: 1px solid var(--ink-faint); }
.aeo-inner { max-width: 800px; margin: 0 auto; padding: 0 32px; }
.aeo-text { color: var(--ink-muted); font-size: 1rem; line-height: 1.8; font-family: 'Georgia', serif; }
.aeo-text {
color: var(--ink-muted); font-size: 1rem; line-height: 1.8; font-family: 'Georgia', serif;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
}
.aeo-text p { margin-bottom: 24px; }
.aeo-text strong { color: var(--ink); font-weight: 600; }
@@ -499,8 +512,11 @@ export default function RootPage() {
.cta-inner { flex-direction: row; align-items: flex-end; justify-content: space-between; }
}
.cta-h2 {
font-weight: 800; font-size: clamp(2.25rem, 5vw, 4.5rem);
font-weight: 800; font-size: clamp(1.5rem, 5vw, 4.5rem);
letter-spacing: -0.04em; line-height: 1.0; color: var(--ink);
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
}
.cta-h2 em { color: var(--gold); font-style: normal; }
.cta-right { display: flex; flex-direction: column; gap: 10px; flex-shrink: 0; }
@@ -658,10 +674,43 @@ export default function RootPage() {
}
/* Improved Mobile Responsiveness */
@media (max-width: 480px) {
.nav-inner { padding: 0 12px; }
.hero { padding: 100px 12px 40px; }
.hero-body { gap: 20px; }
.hero-image-wrapper { max-height: 400px; aspect-ratio: 1; }
.stat { padding: 16px 12px; }
.stat-label { font-size: 0.7rem; }
.challenges-section { padding: 40px 0; }
.challenges-inner { padding: 0 12px; }
.challenges-grid { gap: 16px; }
.challenge-card { padding: 16px; }
.challenge-title { font-size: 1rem; }
.features { padding: 40px 0; }
.features-inner { padding: 0 12px; gap: 24px; }
.feature-item { padding: 16px 12px; gap: 12px; }
.feature-num { font-size: 1.5rem; }
.feature-title { font-size: 1rem; }
.feature-desc { font-size: 0.875rem; }
.comparison-section { padding: 40px 0; }
.comparison-inner { padding: 0 12px; }
.comp-card { padding: 16px; }
.comp-title { font-size: 1.125rem; }
.cta-section { padding: 40px 0 60px; }
.cta-inner { padding: 0 12px; }
.aeo-section { padding: 40px 0; }
.aeo-inner { padding: 0 12px; }
.aeo-text { font-size: 0.9375rem; }
.faq-section { padding: 40px 0; }
.faq-inner { padding: 0 12px; }
.faq-question { font-size: 1rem; padding: 16px 0; }
.footer { padding: 20px 0; }
.footer-inner { padding: 0 12px; }
}
@media (max-width: 639px) {
.nav-inner { padding: 0 16px; }
.hero { padding: 120px 16px 60px; }
.hero-h1 { font-size: clamp(2rem, 6vw, 3.25rem); }
.hero-body { gap: 24px; }
.hero-image-wrapper { aspect-ratio: 1; }
.stats { grid-template-columns: repeat(2, 1fr); margin-top: 60px; }

View File

@@ -1,5 +1,7 @@
'use client'
import '../instrumentation-client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { httpBatchLink } from '@trpc/client'
import { useState } from 'react'

View File

@@ -63,7 +63,9 @@ export function middleware(request: NextRequest) {
// Paths that should not be rewritten into the slug folder
// because they are shared across the entire app
const SHARED_PATHS = ['/login', '/api', '/superadmin', '/registrierung', '/impressum', '/datenschutz', '/passwort-aendern']
const isSharedPath = SHARED_PATHS.some((p) => pathname.startsWith(p)) || pathname.startsWith('/_next')
const isSharedPath = SHARED_PATHS.some((p) => pathname.startsWith(p)) ||
pathname.startsWith('/_next') ||
/\.(png|jpg|jpeg|gif|svg|webp|ico|txt|xml)$/i.test(pathname)
if (!isSharedPath && !pathname.startsWith(`/${slug}`)) {
const rewriteUrl = request.nextUrl.clone()
@@ -80,7 +82,8 @@ export function middleware(request: NextRequest) {
// Check if it's a known non-reserved path but could be an organization slug
// We don't want to redirect /login, /api, etc.
const SHARED_PATHS = ['login', 'api', 'superadmin', 'dashboard', 'registrierung', 'impressum', 'datenschutz', '_next', 'uploads', 'favicon.ico', 'passwort-aendern']
if (potentialSlug && !SHARED_PATHS.includes(potentialSlug)) {
const isStaticAsset = /\.(png|jpg|jpeg|gif|svg|webp|ico|txt|xml)$/i.test(potentialSlug)
if (potentialSlug && !SHARED_PATHS.includes(potentialSlug) && !isStaticAsset) {
// This looks like a tenant path being accessed from the root domain.
// Redirect to subdomain.
const baseHost = hostname.split('.').slice(-2).join('.') // Simplistic, assumes domain.tld or localhost

View File

@@ -29,6 +29,7 @@
"openai": "^6.22.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"sharp": "^0.33.0",
"superjson": "^2.2.1",
"tailwind-merge": "^2.5.0",
"zod": "^3.23.0"

View File

@@ -1 +1,7 @@
google-site-verification: googleccd5315437d68a49.html
<!DOCTYPE html>
<html>
<head>
<meta name="google-site-verification" content="googleccd5315437d68a49" />
</head>
<body></body>
</html>