feat: implement user authentication and admin management

- Add user management API endpoints for listing and creating users.
- Implement login and logout functionality with rate limiting.
- Create UI components for login form, logout button, and animated elements.
- Add session management with JWT and secure cookie handling.
- Seed initial admin user if no users exist.
- Introduce utility functions for password hashing and user authentication.
- Set up proxy middleware for route protection and security headers.
- Create a JSON file for user data storage.
- Add links configuration for external resources.
This commit is contained in:
Anders Böttcher
2026-05-12 14:58:11 +02:00
parent 50566f2a22
commit 0d58fafe4e
34 changed files with 4263 additions and 5012 deletions
+275 -8
View File
@@ -1,19 +1,286 @@
@import "tailwindcss";
:root {
--background: #0b0f1a;
--foreground: #e2e8f0;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-background: #060810;
--color-foreground: #e2e8f0;
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--animate-fade-in: fadeIn 0.8s ease-out both;
--animate-slide-up: slideUp 0.8s ease-out both;
--animate-glow: glow 3s ease-in-out infinite alternate;
--animate-float: float 6s ease-in-out infinite;
--animate-gradient: gradientShift 8s ease infinite;
--animate-blink: blink 1s step-end infinite;
--animate-spin-slow: spinSlow 25s linear infinite;
--animate-pulse-slow: pulseSlow 5s ease-in-out infinite;
--animate-shimmer: shimmerSlide 3s ease infinite;
--animate-scan: scanLine 4s linear infinite;
--animate-aurora-1: aurora1 18s ease-in-out infinite alternate;
--animate-aurora-2: aurora2 23s ease-in-out infinite alternate-reverse;
--animate-aurora-3: aurora3 15s ease-in-out infinite alternate;
--animate-border-glow: borderGlow 3s ease-in-out infinite alternate;
}
:root {
--background: #060810;
--foreground: #e2e8f0;
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(32px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes glow {
from {
box-shadow: 0 0 20px rgba(6, 182, 212, 0.15), 0 0 40px rgba(6, 182, 212, 0.05);
}
to {
box-shadow: 0 0 40px rgba(6, 182, 212, 0.45), 0 0 80px rgba(6, 182, 212, 0.2), 0 0 120px rgba(6, 182, 212, 0.05);
}
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
33% { transform: translateY(-14px); }
66% { transform: translateY(-7px); }
}
@keyframes gradientShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
@keyframes spinSlow {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes pulseSlow {
0%, 100% { opacity: 0.12; transform: scale(0.97); }
50% { opacity: 0.35; transform: scale(1.03); }
}
@keyframes shimmerSlide {
0% { transform: translateX(-100%); }
100% { transform: translateX(200%); }
}
@keyframes scanLine {
0% { transform: translateY(-100%); }
100% { transform: translateY(500%); }
}
/* ── Glassmorphism ─────────────────────────────────────────── */
.glass {
background: rgba(255, 255, 255, 0.025);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.06);
}
.glass-heavy {
background: rgba(6, 8, 16, 0.85);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
/* ── Gradient text ─────────────────────────────────────────── */
.gradient-text {
background: linear-gradient(135deg, #22d3ee 0%, #a78bfa 55%, #22d3ee 100%);
background-size: 200% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: gradientShift 8s ease infinite;
}
/* ── Grid background ───────────────────────────────────────── */
.grid-bg {
background-image:
linear-gradient(rgba(6, 182, 212, 0.035) 1px, transparent 1px),
linear-gradient(90deg, rgba(6, 182, 212, 0.035) 1px, transparent 1px);
background-size: 48px 48px;
}
/* ── Card hover glow ───────────────────────────────────────── */
.card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.card-hover:hover {
transform: translateY(-3px);
border-color: rgba(6, 182, 212, 0.3) !important;
box-shadow:
0 0 0 1px rgba(6, 182, 212, 0.06),
0 16px 48px rgba(0, 0, 0, 0.5),
0 0 40px rgba(6, 182, 212, 0.08);
}
/* ── Neon glow border ──────────────────────────────────────── */
.neon-cyan {
box-shadow:
0 0 0 1px rgba(6, 182, 212, 0.5),
0 0 16px rgba(6, 182, 212, 0.2);
}
/* ── Shimmer overlay ───────────────────────────────────────── */
.shimmer {
position: relative;
overflow: hidden;
}
.shimmer::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 40%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.04), transparent);
animation: shimmerSlide 3s ease infinite;
pointer-events: none;
}
/* ── Terminal scan effect ──────────────────────────────────── */
.terminal-scan {
position: relative;
overflow: hidden;
}
.terminal-scan::before {
content: '';
position: absolute;
left: 0;
right: 0;
height: 60px;
background: linear-gradient(to bottom, transparent, rgba(6, 182, 212, 0.025), transparent);
animation: scanLine 4s linear infinite;
pointer-events: none;
z-index: 1;
}
/* ── Step connector line ───────────────────────────────────── */
.step-connector {
position: relative;
}
.step-connector::after {
content: '';
position: absolute;
top: 20px;
left: calc(100% + 0px);
width: calc(100% - 40px);
height: 1px;
background: linear-gradient(90deg, rgba(6, 182, 212, 0.4), rgba(6, 182, 212, 0.05));
}
/* ── Aurora blob keyframes ────────────────────────────────── */
@keyframes aurora1 {
0% { transform: translate( 0px, 0px) scale(1.00); }
33% { transform: translate( 80px, -70px) scale(1.15); }
66% { transform: translate(-50px, 55px) scale(0.90); }
100% { transform: translate( 25px, -20px) scale(1.05); }
}
@keyframes aurora2 {
0% { transform: translate( 0px, 0px) scale(1.00); }
33% { transform: translate(-75px, 65px) scale(0.88); }
66% { transform: translate( 90px, -45px) scale(1.12); }
100% { transform: translate(-25px, 30px) scale(0.95); }
}
@keyframes aurora3 {
0% { transform: translate( 0px, 0px) scale(1.00); }
33% { transform: translate( 55px, 75px) scale(1.10); }
66% { transform: translate(-65px, -55px) scale(0.92); }
100% { transform: translate( 15px, 45px) scale(1.08); }
}
/* ── Border glow pulse ─────────────────────────────────────── */
@keyframes borderGlow {
from { box-shadow: 0 0 12px rgba(6,182,212,0.3), 0 0 24px rgba(6,182,212,0.1); }
to { box-shadow: 0 0 24px rgba(6,182,212,0.6), 0 0 60px rgba(6,182,212,0.25), 0 0 100px rgba(139,92,246,0.1); }
}
/* ── Spotlight card ────────────────────────────────────────── */
.spotlight {
--mx: -9999px;
--my: -9999px;
position: relative;
}
.spotlight::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: radial-gradient(
280px circle at var(--mx) var(--my),
rgba(6, 182, 212, 0.09),
transparent 70%
);
opacity: 0;
transition: opacity 0.4s ease;
pointer-events: none;
z-index: 0;
}
.spotlight:hover::before {
opacity: 1;
}
.spotlight > * {
position: relative;
z-index: 1;
}
/* ── Nav link underline animation ──────────────────────────── */
.nav-link {
position: relative;
}
.nav-link::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 1px;
background: linear-gradient(90deg, #22d3ee, #818cf8);
transition: width 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.nav-link:hover::after {
width: 100%;
}
/* ── Scrollbar ─────────────────────────────────────────────── */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #060810;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.2);
}