fix gradients
This commit is contained in:
@@ -12,62 +12,136 @@ templ Layout(title string, username string) {
|
||||
<title>{ title }</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"/>
|
||||
</head>
|
||||
<body class="bg-gray-900 min-h-screen text-gray-100">
|
||||
{ children... }
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
|
||||
<script>
|
||||
// Tailwind config for custom styles
|
||||
// Tailwind config - must be set before DOM loads
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
primary: {
|
||||
DEFAULT: '#3b82f6',
|
||||
dark: '#2563eb',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: '#8b5cf6',
|
||||
dark: '#7c3aed',
|
||||
light: '#a78bfa',
|
||||
},
|
||||
surface: {
|
||||
DEFAULT: '#1f2937',
|
||||
dark: '#111827',
|
||||
lighter: '#374151',
|
||||
}
|
||||
}
|
||||
accent: {
|
||||
DEFAULT: '#c084fc',
|
||||
dark: '#a855f7',
|
||||
light: '#d8b4fe',
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
'float': 'float 6s ease-in-out infinite',
|
||||
'glow': 'glow 2s ease-in-out infinite alternate',
|
||||
'slide-up': 'slideUp 0.3s ease-out',
|
||||
'fade-in': 'fadeIn 0.3s ease-out',
|
||||
},
|
||||
keyframes: {
|
||||
float: {
|
||||
'0%, 100%': { transform: 'translateY(0)' },
|
||||
'50%': { transform: 'translateY(-10px)' },
|
||||
},
|
||||
glow: {
|
||||
'0%': { boxShadow: '0 0 20px rgba(139, 92, 246, 0.4)' },
|
||||
'100%': { boxShadow: '0 0 40px rgba(139, 92, 246, 0.8)' },
|
||||
},
|
||||
slideUp: {
|
||||
'0%': { transform: 'translateY(10px)', opacity: '0' },
|
||||
'100%': { transform: 'translateY(0)', opacity: '1' },
|
||||
},
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' },
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Theme management - apply immediately
|
||||
(function() {
|
||||
const theme = localStorage.getItem('theme') || 'dark';
|
||||
if (theme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
})();
|
||||
|
||||
function toggleTheme() {
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
if (isDark) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('theme', 'light');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-slate-100 dark:bg-slate-950 min-h-screen text-slate-900 dark:text-slate-100 font-sans transition-colors duration-300">
|
||||
<!-- Animated background elements (dark mode only) -->
|
||||
<div class="fixed inset-0 overflow-hidden pointer-events-none hidden dark:block">
|
||||
<div class="absolute -top-40 -right-40 w-96 h-96 bg-violet-600/30 rounded-full blur-3xl"></div>
|
||||
<div class="absolute top-1/2 -left-40 w-96 h-96 bg-indigo-600/30 rounded-full blur-3xl"></div>
|
||||
<div class="absolute -bottom-40 right-1/3 w-96 h-96 bg-fuchsia-600/20 rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10">
|
||||
{ children... }
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
// Header renders the admin header with navigation
|
||||
templ Header(username string) {
|
||||
<header class="bg-surface-dark border-b border-gray-700 px-6 py-4 mb-6">
|
||||
<header class="backdrop-blur-xl bg-white/80 dark:bg-slate-900/80 border-b border-slate-200 dark:border-slate-700/50 px-6 py-4 mb-8 sticky top-0 z-40 transition-colors duration-300">
|
||||
<div class="max-w-7xl mx-auto flex justify-between items-center">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-gradient-to-br from-primary to-accent rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-database text-white text-lg"></i>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="relative">
|
||||
<div class="w-12 h-12 bg-gradient-to-br from-violet-500 via-purple-500 to-fuchsia-500 rounded-2xl flex items-center justify-center shadow-lg shadow-violet-500/30">
|
||||
<i class="fas fa-database text-white text-xl"></i>
|
||||
</div>
|
||||
<div class="absolute -bottom-1 -right-1 w-4 h-4 bg-emerald-400 rounded-full border-2 border-white dark:border-slate-900"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-white">ZFS Backup</h1>
|
||||
<p class="text-xs text-gray-400">Admin Panel</p>
|
||||
<h1 class="text-xl font-bold text-slate-900 dark:text-white">ZFS Backup</h1>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400 font-medium tracking-wide">Admin Panel</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2 text-gray-300">
|
||||
<i class="fas fa-user-circle text-lg"></i>
|
||||
<span class="text-sm">{ username }</span>
|
||||
<!-- Theme Toggle -->
|
||||
<button
|
||||
onclick="toggleTheme()"
|
||||
class="px-4 py-2 rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 flex items-center gap-2 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all duration-200"
|
||||
title="Toggle theme"
|
||||
>
|
||||
<i class="fas fa-moon text-violet-500 dark:hidden"></i>
|
||||
<i class="fas fa-sun text-amber-400 hidden dark:block"></i>
|
||||
<span class="text-sm font-medium text-slate-600 dark:text-slate-300 dark:hidden">Dark</span>
|
||||
<span class="text-sm font-medium text-slate-300 hidden dark:block">Light</span>
|
||||
</button>
|
||||
|
||||
<div class="flex items-center gap-3 px-4 py-2 rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700">
|
||||
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-violet-500 to-fuchsia-500 flex items-center justify-center">
|
||||
<i class="fas fa-user text-white text-sm"></i>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-slate-700 dark:text-slate-200">{ username }</span>
|
||||
</div>
|
||||
<button
|
||||
class="px-4 py-2 bg-red-500/10 hover:bg-red-500/20 text-red-400 rounded-lg transition-colors flex items-center gap-2"
|
||||
class="group px-5 py-2.5 bg-red-50 dark:bg-red-500/10 hover:bg-red-100 dark:hover:bg-red-500/20 text-red-600 dark:text-red-400 rounded-xl transition-all duration-300 flex items-center gap-2 border border-red-200 dark:border-red-500/30"
|
||||
onclick="logout()"
|
||||
>
|
||||
<i class="fas fa-sign-out-alt"></i>
|
||||
<span>Logout</span>
|
||||
<i class="fas fa-sign-out-alt group-hover:scale-110 transition-transform"></i>
|
||||
<span class="font-medium">Logout</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,12 +150,17 @@ templ Header(username string) {
|
||||
|
||||
// StatsCard renders a single statistics card
|
||||
templ StatsCard(title string, value string) {
|
||||
<div class="bg-surface rounded-xl p-6 border border-gray-700 hover:border-primary/50 transition-colors">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-gray-400 text-sm">{ title }</span>
|
||||
<i class={ "fas " + statsIcon(title) + " text-primary" }></i>
|
||||
<div class="group relative bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl p-6 border border-slate-200 dark:border-slate-700/50 hover:border-violet-400 dark:hover:border-violet-500/50 transition-all duration-300 overflow-hidden shadow-sm dark:shadow-none">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-violet-500/5 via-purple-500/5 to-transparent dark:from-violet-600/10 dark:via-purple-600/5"></div>
|
||||
<div class="relative">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<span class="text-slate-500 dark:text-slate-400 text-sm font-medium">{ title }</span>
|
||||
<div class="w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center">
|
||||
<i class={ "fas " + statsIcon(title) + " text-violet-600 dark:text-violet-400" }></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-3xl font-bold text-slate-900 dark:text-white">{ value }</div>
|
||||
</div>
|
||||
<div class="text-3xl font-bold text-white">{ value }</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -102,7 +181,7 @@ func statsIcon(title string) string {
|
||||
// TabButton renders a tab navigation button
|
||||
templ TabButton(id string, label string, active bool) {
|
||||
<button
|
||||
class={ "px-5 py-2.5 rounded-lg font-medium transition-all flex items-center gap-2", tabButtonClass(active) }
|
||||
class={ "px-6 py-3 rounded-xl font-medium transition-all duration-300 flex items-center gap-2.5", tabButtonClass(active) }
|
||||
data-tab={ id }
|
||||
>
|
||||
<i class={ "fas " + tabIcon(id) }></i>
|
||||
@@ -127,20 +206,20 @@ func tabIcon(id string) string {
|
||||
// tabButtonClass returns the CSS class for a tab button
|
||||
func tabButtonClass(active bool) string {
|
||||
if active {
|
||||
return "bg-primary text-white shadow-lg shadow-primary/25"
|
||||
return "bg-gradient-to-r from-violet-600 to-fuchsia-600 text-white shadow-lg shadow-violet-500/25"
|
||||
}
|
||||
return "bg-surface text-gray-400 hover:bg-surface-lighter hover:text-white"
|
||||
return "bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 hover:text-slate-900 dark:hover:text-white border border-slate-200 dark:border-slate-700"
|
||||
}
|
||||
|
||||
// Modal renders a modal dialog
|
||||
templ Modal(id string, title string) {
|
||||
<div id={ id } class="fixed inset-0 bg-black/70 backdrop-blur-sm hidden items-center justify-center z-50" data-modal-id={ id }>
|
||||
<div class="bg-surface rounded-2xl max-w-lg w-full max-h-[90vh] overflow-y-auto border border-gray-700 shadow-2xl modal-content">
|
||||
<div class="flex justify-between items-center p-6 border-b border-gray-700">
|
||||
<h3 class="text-lg font-semibold text-white">{ title }</h3>
|
||||
<div id={ id } class="fixed inset-0 bg-black/50 dark:bg-black/70 backdrop-blur-sm hidden items-center justify-center z-50" data-modal-id={ id }>
|
||||
<div class="bg-white dark:bg-slate-900 rounded-3xl max-w-lg w-full max-h-[90vh] overflow-y-auto border border-slate-200 dark:border-slate-700 shadow-2xl modal-content">
|
||||
<div class="flex justify-between items-center p-6 border-b border-slate-200 dark:border-slate-700">
|
||||
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">{ title }</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="w-8 h-8 flex items-center justify-center rounded-lg text-gray-400 hover:text-white hover:bg-gray-700 transition-colors modal-close"
|
||||
class="w-10 h-10 flex items-center justify-center rounded-xl text-slate-400 hover:text-slate-600 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800 transition-all duration-200 modal-close"
|
||||
data-modal-id={ id }
|
||||
>
|
||||
<i class="fas fa-times"></i>
|
||||
@@ -155,27 +234,27 @@ templ Modal(id string, title string) {
|
||||
|
||||
// FormInput renders a form input field
|
||||
templ FormInput(id string, label string, inputType string, placeholder string, required bool) {
|
||||
<div class="mb-4">
|
||||
<label for={ id } class="block text-sm text-gray-400 mb-2">{ label }</label>
|
||||
<div class="mb-5">
|
||||
<label for={ id } class="block text-sm font-medium text-slate-600 dark:text-slate-300 mb-2">{ label }</label>
|
||||
<input
|
||||
type={ inputType }
|
||||
id={ id }
|
||||
name={ id }
|
||||
placeholder={ placeholder }
|
||||
if required { required }
|
||||
class="w-full px-4 py-3 bg-surface-dark border border-gray-600 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
|
||||
class="w-full px-4 py-3.5 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-600 rounded-xl text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
// FormSelect renders a form select field
|
||||
templ FormSelect(id string, label string, options []SelectOption) {
|
||||
<div class="mb-4">
|
||||
<label for={ id } class="block text-sm text-gray-400 mb-2">{ label }</label>
|
||||
<div class="mb-5">
|
||||
<label for={ id } class="block text-sm font-medium text-slate-600 dark:text-slate-300 mb-2">{ label }</label>
|
||||
<select
|
||||
id={ id }
|
||||
name={ id }
|
||||
class="w-full px-4 py-3 bg-surface-dark border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
|
||||
class="w-full px-4 py-3.5 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-600 rounded-xl text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-all duration-200 appearance-none cursor-pointer"
|
||||
>
|
||||
for _, opt := range options {
|
||||
<option value={ opt.Value } if opt.Selected { selected }>{ opt.Label }</option>
|
||||
@@ -193,22 +272,22 @@ type SelectOption struct {
|
||||
|
||||
// FormCheckbox renders a form checkbox
|
||||
templ FormCheckbox(id string, label string, checked bool) {
|
||||
<div class="mb-4 flex items-center gap-3">
|
||||
<div class="mb-5 flex items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={ id }
|
||||
name={ id }
|
||||
if checked { checked }
|
||||
class="w-5 h-5 bg-surface-dark border-gray-600 rounded text-primary focus:ring-primary focus:ring-2"
|
||||
class="w-5 h-5 bg-slate-100 dark:bg-slate-800 border-slate-300 dark:border-slate-600 rounded text-violet-600 focus:ring-violet-500 focus:ring-2 cursor-pointer"
|
||||
/>
|
||||
<label for={ id } class="text-sm text-gray-300">{ label }</label>
|
||||
<label for={ id } class="text-sm text-slate-600 dark:text-slate-300 font-medium cursor-pointer">{ label }</label>
|
||||
</div>
|
||||
}
|
||||
|
||||
// Button renders a styled button
|
||||
templ Button(label string, variant string) {
|
||||
<button
|
||||
class={ "px-4 py-2 rounded-lg font-medium transition-all flex items-center gap-2 " + buttonVariantClass(variant) }
|
||||
class={ "px-5 py-2.5 rounded-xl font-medium transition-all duration-300 flex items-center gap-2 " + buttonVariantClass(variant) }
|
||||
>
|
||||
{ label }
|
||||
</button>
|
||||
@@ -218,25 +297,25 @@ templ Button(label string, variant string) {
|
||||
func buttonVariantClass(variant string) string {
|
||||
switch variant {
|
||||
case "primary":
|
||||
return "bg-primary hover:bg-primary-dark text-white shadow-lg shadow-primary/25"
|
||||
return "bg-gradient-to-r from-violet-600 to-fuchsia-600 hover:from-violet-500 hover:to-fuchsia-500 text-white shadow-lg shadow-violet-500/25"
|
||||
case "danger":
|
||||
return "bg-red-500 hover:bg-red-600 text-white shadow-lg shadow-red-500/25"
|
||||
return "bg-gradient-to-r from-red-500 to-rose-600 hover:from-red-400 hover:to-rose-500 text-white shadow-lg shadow-red-500/25"
|
||||
case "success":
|
||||
return "bg-emerald-500 hover:bg-emerald-600 text-white shadow-lg shadow-emerald-500/25"
|
||||
return "bg-gradient-to-r from-emerald-500 to-green-600 hover:from-emerald-400 hover:to-green-500 text-white shadow-lg shadow-emerald-500/25"
|
||||
case "warning":
|
||||
return "bg-amber-500 hover:bg-amber-600 text-white shadow-lg shadow-amber-500/25"
|
||||
return "bg-gradient-to-r from-amber-500 to-orange-600 hover:from-amber-400 hover:to-orange-500 text-white shadow-lg shadow-amber-500/25"
|
||||
case "purple":
|
||||
return "bg-purple-500 hover:bg-purple-600 text-white shadow-lg shadow-purple-500/25"
|
||||
return "bg-gradient-to-r from-purple-500 to-violet-600 hover:from-purple-400 hover:to-violet-500 text-white shadow-lg shadow-purple-500/25"
|
||||
case "orange":
|
||||
return "bg-orange-500 hover:bg-orange-600 text-white shadow-lg shadow-orange-500/25"
|
||||
return "bg-gradient-to-r from-orange-500 to-amber-600 hover:from-orange-400 hover:to-amber-500 text-white shadow-lg shadow-orange-500/25"
|
||||
default:
|
||||
return "bg-gray-500 hover:bg-gray-600 text-white"
|
||||
return "bg-slate-500 hover:bg-slate-600 text-white"
|
||||
}
|
||||
}
|
||||
|
||||
// Badge renders a status badge
|
||||
templ Badge(text string, variant string) {
|
||||
<span class={ "px-3 py-1 rounded-full text-xs font-medium " + badgeVariantClass(variant) }>
|
||||
<span class={ "px-3 py-1.5 rounded-lg text-xs font-semibold " + badgeVariantClass(variant) }>
|
||||
{ text }
|
||||
</span>
|
||||
}
|
||||
@@ -245,21 +324,23 @@ templ Badge(text string, variant string) {
|
||||
func badgeVariantClass(variant string) string {
|
||||
switch variant {
|
||||
case "success":
|
||||
return "bg-emerald-500/20 text-emerald-400"
|
||||
return "bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/30"
|
||||
case "danger":
|
||||
return "bg-red-500/20 text-red-400"
|
||||
return "bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-400 border border-red-200 dark:border-red-500/30"
|
||||
case "info":
|
||||
return "bg-blue-500/20 text-blue-400"
|
||||
return "bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-400 border border-blue-200 dark:border-blue-500/30"
|
||||
case "warning":
|
||||
return "bg-amber-500/20 text-amber-400"
|
||||
return "bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-400 border border-amber-200 dark:border-amber-500/30"
|
||||
case "purple":
|
||||
return "bg-purple-100 text-purple-700 dark:bg-purple-500/20 dark:text-purple-400 border border-purple-200 dark:border-purple-500/30"
|
||||
default:
|
||||
return "bg-gray-500/20 text-gray-400"
|
||||
return "bg-slate-100 text-slate-700 dark:bg-slate-500/20 dark:text-slate-400 border border-slate-200 dark:border-slate-500/30"
|
||||
}
|
||||
}
|
||||
|
||||
// ProgressBar renders a progress bar
|
||||
templ ProgressBar(percent float64) {
|
||||
<div class="w-full h-2 bg-gray-700 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-gradient-to-r from-primary to-accent transition-all" style={ fmt.Sprintf("width: %.1f%%", percent) }></div>
|
||||
<div class="w-full h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-gradient-to-r from-violet-500 to-fuchsia-500 transition-all duration-500 rounded-full" style={ fmt.Sprintf("width: %.1f%%", percent) }></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user