mirror of
https://github.com/Xevion/xevion.dev.git
synced 2026-01-31 12:26:39 -06:00
feat: add light/dark theme toggle with system preference detection
- Implement theme store with localStorage persistence - Add ThemeToggle component with animated icon transitions - Update color system with semantic tokens for light/dark modes - Add blocking script in app.html to prevent FOUC - Apply theme-aware styling across all public and admin pages
This commit is contained in:
+71
-13
@@ -1,9 +1,23 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
/* Dark variant definition */
|
||||||
|
@variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
/* Custom colors */
|
/* Custom colors */
|
||||||
--color-zinc-850: #1d1d20;
|
--color-zinc-850: #1d1d20;
|
||||||
|
|
||||||
|
/* Semantic color tokens - Light mode defaults */
|
||||||
|
--color-bg-primary: #ffffff;
|
||||||
|
--color-bg-secondary: #f4f4f5;
|
||||||
|
--color-surface: #ffffff;
|
||||||
|
--color-surface-secondary: #fafafa;
|
||||||
|
--color-border: #e4e4e7;
|
||||||
|
--color-border-subtle: #f4f4f5;
|
||||||
|
--color-text-primary: #18181b;
|
||||||
|
--color-text-secondary: #52525b;
|
||||||
|
--color-text-tertiary: #71717a;
|
||||||
|
|
||||||
/* Custom font sizes */
|
/* Custom font sizes */
|
||||||
--font-size-10xl: 10rem;
|
--font-size-10xl: 10rem;
|
||||||
|
|
||||||
@@ -29,22 +43,22 @@
|
|||||||
--animate-fade-left: fade-left 3s ease-in-out forwards;
|
--animate-fade-left: fade-left 3s ease-in-out forwards;
|
||||||
--animate-fade-right: fade-right 3s ease-in-out forwards;
|
--animate-fade-right: fade-right 3s ease-in-out forwards;
|
||||||
|
|
||||||
/* Admin colors - Geist-inspired semantic scale */
|
/* Admin colors - Light mode defaults */
|
||||||
--color-admin-bg: #0a0a0b;
|
--color-admin-bg: #f9fafb;
|
||||||
--color-admin-bg-secondary: #18181b;
|
--color-admin-bg-secondary: #ffffff;
|
||||||
--color-admin-surface: #27272a;
|
--color-admin-surface: #ffffff;
|
||||||
--color-admin-surface-hover: #3f3f46;
|
--color-admin-surface-hover: #f3f4f6;
|
||||||
--color-admin-border: #27272a;
|
--color-admin-border: #e5e7eb;
|
||||||
--color-admin-border-hover: #3f3f46;
|
--color-admin-border-hover: #d1d5db;
|
||||||
--color-admin-text: #fafafa;
|
--color-admin-text: #111827;
|
||||||
--color-admin-text-secondary: #a1a1aa;
|
--color-admin-text-secondary: #4b5563;
|
||||||
--color-admin-text-muted: #71717a;
|
--color-admin-text-muted: #6b7280;
|
||||||
--color-admin-accent: #6366f1;
|
--color-admin-accent: #6366f1;
|
||||||
--color-admin-accent-hover: #818cf8;
|
--color-admin-accent-hover: #818cf8;
|
||||||
|
|
||||||
/* Legacy aliases for backward compatibility */
|
/* Legacy aliases for backward compatibility */
|
||||||
--color-admin-panel: #18181b;
|
--color-admin-panel: #ffffff;
|
||||||
--color-admin-hover: #3f3f46;
|
--color-admin-hover: #f3f4f6;
|
||||||
|
|
||||||
/* Status colors */
|
/* Status colors */
|
||||||
--color-status-active: #22c55e;
|
--color-status-active: #22c55e;
|
||||||
@@ -56,6 +70,34 @@
|
|||||||
--color-status-info: #06b6d4;
|
--color-status-info: #06b6d4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark mode overrides */
|
||||||
|
.dark {
|
||||||
|
--color-bg-primary: #000000;
|
||||||
|
--color-bg-secondary: #09090b;
|
||||||
|
--color-surface: #18181b;
|
||||||
|
--color-surface-secondary: #27272a;
|
||||||
|
--color-border: #27272a;
|
||||||
|
--color-border-subtle: #18181b;
|
||||||
|
--color-text-primary: #fafafa;
|
||||||
|
--color-text-secondary: #d4d4d8;
|
||||||
|
--color-text-tertiary: #a1a1aa;
|
||||||
|
|
||||||
|
/* Admin colors - Dark mode overrides */
|
||||||
|
--color-admin-bg: #0a0a0b;
|
||||||
|
--color-admin-bg-secondary: #18181b;
|
||||||
|
--color-admin-surface: #27272a;
|
||||||
|
--color-admin-surface-hover: #3f3f46;
|
||||||
|
--color-admin-border: #27272a;
|
||||||
|
--color-admin-border-hover: #3f3f46;
|
||||||
|
--color-admin-text: #fafafa;
|
||||||
|
--color-admin-text-secondary: #a1a1aa;
|
||||||
|
--color-admin-text-muted: #71717a;
|
||||||
|
|
||||||
|
/* Legacy aliases */
|
||||||
|
--color-admin-panel: #18181b;
|
||||||
|
--color-admin-hover: #3f3f46;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fade {
|
@keyframes fade {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0%;
|
opacity: 0%;
|
||||||
@@ -126,11 +168,27 @@
|
|||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
@apply font-inter overflow-x-hidden text-white;
|
@apply font-inter overflow-x-hidden;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply h-full;
|
@apply h-full;
|
||||||
|
background-color: var(--color-bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth theme transitions for all elements */
|
||||||
|
*:not(canvas):not([class*="animate-"]) {
|
||||||
|
transition-property: background-color, border-color, color, fill, stroke;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Elements with explicit transition classes should extend, not replace */
|
||||||
|
[class*="transition-colors"],
|
||||||
|
[class*="transition-all"] {
|
||||||
|
transition-duration: 0.3s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* OverlayScrollbars theme customization */
|
/* OverlayScrollbars theme customization */
|
||||||
|
|||||||
@@ -3,6 +3,15 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
const stored = localStorage.getItem('theme');
|
||||||
|
const isDark = stored === 'dark' || (stored !== 'light' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
|
if (isDark) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
|||||||
@@ -2,23 +2,31 @@
|
|||||||
import { cn } from "$lib/utils";
|
import { cn } from "$lib/utils";
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
import Dots from "./Dots.svelte";
|
import Dots from "./Dots.svelte";
|
||||||
|
import ThemeToggle from "./ThemeToggle.svelte";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
class: className = "",
|
class: className = "",
|
||||||
backgroundClass = "",
|
backgroundClass = "",
|
||||||
bgColor = "bg-black",
|
bgColor = "",
|
||||||
|
showThemeToggle = true,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
class?: string;
|
class?: string;
|
||||||
backgroundClass?: string;
|
backgroundClass?: string;
|
||||||
bgColor?: string;
|
bgColor?: string;
|
||||||
|
showThemeToggle?: boolean;
|
||||||
children?: Snippet;
|
children?: Snippet;
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={cn("pointer-events-none fixed inset-0 -z-20", bgColor)}></div>
|
<div class={cn("pointer-events-none fixed inset-0 -z-20 bg-white dark:bg-black transition-colors duration-300", bgColor)}></div>
|
||||||
<Dots class={[backgroundClass]} />
|
<Dots class={[backgroundClass]} />
|
||||||
<main class={cn("relative min-h-screen text-zinc-50", className)}>
|
{#if showThemeToggle}
|
||||||
|
<div class="fixed top-5 right-6 z-50">
|
||||||
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<main class={cn("relative min-h-screen text-zinc-900 dark:text-zinc-50 transition-colors duration-300", className)}>
|
||||||
{#if children}
|
{#if children}
|
||||||
{@render children()}
|
{@render children()}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -39,22 +39,22 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class={cn(
|
class={cn(
|
||||||
"group flex h-44 flex-col gap-2.5 rounded-lg border border-zinc-800 bg-zinc-900/50 p-3 transition-all hover:border-zinc-700 hover:bg-zinc-800/70",
|
"group flex h-44 flex-col gap-2.5 rounded-lg border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/50 p-3 transition-all hover:border-zinc-300 dark:hover:border-zinc-700 hover:bg-zinc-100 dark:hover:bg-zinc-800/70",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<div class="flex items-start justify-between gap-2">
|
<div class="flex items-start justify-between gap-2">
|
||||||
<h3
|
<h3
|
||||||
class="truncate font-medium text-lg sm:text-base text-zinc-100 transition-colors group-hover:text-white"
|
class="truncate font-medium text-lg sm:text-base text-zinc-900 dark:text-zinc-100 transition-colors group-hover:text-zinc-950 dark:group-hover:text-white"
|
||||||
>
|
>
|
||||||
{project.name}
|
{project.name}
|
||||||
</h3>
|
</h3>
|
||||||
<span class="shrink-0 sm:text-[0.83rem] text-zinc-300">
|
<span class="shrink-0 sm:text-[0.83rem] text-zinc-600 dark:text-zinc-300">
|
||||||
{formatDate(project.updatedAt)}
|
{formatDate(project.updatedAt)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="line-clamp-3 sm:text-sm leading-relaxed text-zinc-400">
|
<p class="line-clamp-3 sm:text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
|
||||||
{project.shortDescription}
|
{project.shortDescription}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
{#each project.tags as tag (tag.name)}
|
{#each project.tags as tag (tag.name)}
|
||||||
<!-- TODO: Add link to project search with tag filtering -->
|
<!-- TODO: Add link to project search with tag filtering -->
|
||||||
<span
|
<span
|
||||||
class="inline-flex items-center gap-1.25 rounded-r-sm rounded-l-xs bg-zinc-700/50 px-2 sm:px-1.5 py-1 sm:py-0.75 text-sm sm:text-xs text-zinc-300 border-l-3"
|
class="inline-flex items-center gap-1.25 rounded-r-sm rounded-l-xs bg-zinc-200/80 dark:bg-zinc-700/50 px-2 sm:px-1.5 py-1 sm:py-0.75 text-sm sm:text-xs text-zinc-700 dark:text-zinc-300 border-l-3"
|
||||||
style="border-left-color: #{tag.color || '06b6d4'}"
|
style="border-left-color: #{tag.color || '06b6d4'}"
|
||||||
>
|
>
|
||||||
{#if tag.iconSvg}
|
{#if tag.iconSvg}
|
||||||
@@ -80,22 +80,22 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class={cn(
|
class={cn(
|
||||||
"flex h-44 flex-col gap-2.5 rounded-lg border border-zinc-800 bg-zinc-900/50 p-3",
|
"flex h-44 flex-col gap-2.5 rounded-lg border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/50 p-3",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<div class="flex items-start justify-between gap-2">
|
<div class="flex items-start justify-between gap-2">
|
||||||
<h3
|
<h3
|
||||||
class="truncate font-medium text-lg sm:text-base text-zinc-100"
|
class="truncate font-medium text-lg sm:text-base text-zinc-900 dark:text-zinc-100"
|
||||||
>
|
>
|
||||||
{project.name}
|
{project.name}
|
||||||
</h3>
|
</h3>
|
||||||
<span class="shrink-0 sm:text-[0.83rem] text-zinc-300">
|
<span class="shrink-0 sm:text-[0.83rem] text-zinc-600 dark:text-zinc-300">
|
||||||
{formatDate(project.updatedAt)}
|
{formatDate(project.updatedAt)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="line-clamp-3 sm:text-sm leading-relaxed text-zinc-400">
|
<p class="line-clamp-3 sm:text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
|
||||||
{project.shortDescription}
|
{project.shortDescription}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
<div class="mt-auto flex flex-wrap gap-1">
|
<div class="mt-auto flex flex-wrap gap-1">
|
||||||
{#each project.tags as tag (tag.name)}
|
{#each project.tags as tag (tag.name)}
|
||||||
<span
|
<span
|
||||||
class="inline-flex items-center gap-1.25 rounded-r-sm rounded-l-xs bg-zinc-700/50 px-2 sm:px-1.5 py-1 sm:py-0.75 text-sm sm:text-xs text-zinc-300 border-l-3"
|
class="inline-flex items-center gap-1.25 rounded-r-sm rounded-l-xs bg-zinc-200/80 dark:bg-zinc-700/50 px-2 sm:px-1.5 py-1 sm:py-0.75 text-sm sm:text-xs text-zinc-700 dark:text-zinc-300 border-l-3"
|
||||||
style="border-left-color: #{tag.color || '06b6d4'}"
|
style="border-left-color: #{tag.color || '06b6d4'}"
|
||||||
>
|
>
|
||||||
{#if tag.iconSvg}
|
{#if tag.iconSvg}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { themeStore } from "$lib/stores/theme.svelte";
|
||||||
|
import IconSun from "~icons/lucide/sun";
|
||||||
|
import IconMoon from "~icons/lucide/moon";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => themeStore.toggle()}
|
||||||
|
aria-label={themeStore.isDark ? "Switch to light mode" : "Switch to dark mode"}
|
||||||
|
class="relative size-9 rounded-md border border-zinc-300 dark:border-zinc-700 bg-zinc-100 dark:bg-zinc-900/50 hover:bg-zinc-200 dark:hover:bg-zinc-800/70 transition-all duration-200"
|
||||||
|
>
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center">
|
||||||
|
<IconSun
|
||||||
|
class="size-5 text-zinc-600 dark:text-zinc-400 transition-all duration-300 {themeStore.isDark
|
||||||
|
? 'rotate-90 scale-0 opacity-0'
|
||||||
|
: 'rotate-0 scale-100 opacity-100'}"
|
||||||
|
/>
|
||||||
|
<IconMoon
|
||||||
|
class="absolute size-5 text-zinc-600 dark:text-zinc-400 transition-all duration-300 {themeStore.isDark
|
||||||
|
? 'rotate-0 scale-100 opacity-100'
|
||||||
|
: '-rotate-90 scale-0 opacity-0'}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
@@ -28,12 +28,12 @@
|
|||||||
|
|
||||||
const variantStyles = {
|
const variantStyles = {
|
||||||
primary:
|
primary:
|
||||||
"bg-indigo-600 text-white hover:bg-indigo-500 focus-visible:ring-indigo-500 shadow-sm hover:shadow",
|
"bg-admin-accent text-white hover:bg-admin-accent-hover focus-visible:ring-admin-accent shadow-sm hover:shadow",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-transparent text-admin-text border border-zinc-700 hover:border-zinc-600 hover:bg-zinc-800/50 focus-visible:ring-zinc-500",
|
"bg-transparent text-admin-text border border-admin-border hover:border-admin-border-hover hover:bg-admin-surface-hover/50 focus-visible:ring-admin-accent",
|
||||||
danger:
|
danger:
|
||||||
"bg-red-600 text-white hover:bg-red-500 focus-visible:ring-red-500 shadow-sm hover:shadow",
|
"bg-red-600 text-white hover:bg-red-500 focus-visible:ring-red-500 shadow-sm hover:shadow",
|
||||||
ghost: "text-admin-text hover:bg-zinc-800/50 focus-visible:ring-zinc-500",
|
ghost: "text-admin-text hover:bg-admin-surface-hover focus-visible:ring-admin-accent",
|
||||||
};
|
};
|
||||||
|
|
||||||
const sizeStyles = {
|
const sizeStyles = {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
<div class={cn("space-y-3", className)}>
|
<div class={cn("space-y-3", className)}>
|
||||||
{#if label}
|
{#if label}
|
||||||
<label class="block text-sm font-medium text-zinc-300">{label}</label>
|
<label class="block text-sm font-medium text-admin-text">{label}</label>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Preset Palette -->
|
<!-- Preset Palette -->
|
||||||
@@ -88,8 +88,8 @@
|
|||||||
class={cn(
|
class={cn(
|
||||||
"size-8 rounded border-2 transition-all hover:scale-110",
|
"size-8 rounded border-2 transition-all hover:scale-110",
|
||||||
selectedColor === preset.value
|
selectedColor === preset.value
|
||||||
? "border-white ring-2 ring-white/20"
|
? "border-admin-accent ring-2 ring-admin-accent/20"
|
||||||
: "border-zinc-700 hover:border-zinc-500",
|
: "border-admin-border hover:border-admin-border-hover",
|
||||||
)}
|
)}
|
||||||
style="background-color: #{preset.value}"
|
style="background-color: #{preset.value}"
|
||||||
title={preset.name}
|
title={preset.name}
|
||||||
@@ -103,13 +103,13 @@
|
|||||||
class={cn(
|
class={cn(
|
||||||
"size-8 rounded border-2 transition-all hover:scale-110 flex items-center justify-center",
|
"size-8 rounded border-2 transition-all hover:scale-110 flex items-center justify-center",
|
||||||
!selectedColor
|
!selectedColor
|
||||||
? "border-white ring-2 ring-white/20 bg-zinc-800"
|
? "border-admin-accent ring-2 ring-admin-accent/20 bg-admin-surface-hover"
|
||||||
: "border-zinc-700 hover:border-zinc-500 bg-zinc-900",
|
: "border-admin-border hover:border-admin-border-hover bg-admin-surface",
|
||||||
)}
|
)}
|
||||||
title="No color"
|
title="No color"
|
||||||
onclick={clearColor}
|
onclick={clearColor}
|
||||||
>
|
>
|
||||||
<span class="text-zinc-500 text-xs">✕</span>
|
<span class="text-admin-text-muted text-xs">✕</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
<div class="flex items-start gap-2">
|
<div class="flex items-start gap-2">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500"
|
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-admin-text-muted"
|
||||||
>#</span
|
>#</span
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -127,11 +127,11 @@
|
|||||||
placeholder="3b82f6"
|
placeholder="3b82f6"
|
||||||
maxlength="6"
|
maxlength="6"
|
||||||
class={cn(
|
class={cn(
|
||||||
"w-full rounded-md border bg-zinc-900 px-3 py-2 pl-7 text-sm text-zinc-100",
|
"w-full rounded-md border bg-admin-bg-secondary px-3 py-2 pl-7 text-sm text-admin-text",
|
||||||
"placeholder:text-zinc-600 focus:outline-none focus:ring-2",
|
"placeholder:text-admin-text-muted focus:outline-none focus:ring-2",
|
||||||
validationError
|
validationError
|
||||||
? "border-red-500 focus:ring-red-500/20"
|
? "border-red-500 focus:ring-red-500/20"
|
||||||
: "border-zinc-700 focus:border-zinc-600 focus:ring-zinc-500/20",
|
: "border-admin-border focus:border-admin-border-hover focus:ring-admin-accent/20",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
<!-- Color Preview -->
|
<!-- Color Preview -->
|
||||||
{#if selectedColor && validateHexColor(selectedColor)}
|
{#if selectedColor && validateHexColor(selectedColor)}
|
||||||
<div
|
<div
|
||||||
class="size-10 shrink-0 rounded-md border-2 border-zinc-700"
|
class="size-10 shrink-0 rounded-md border-2 border-admin-border"
|
||||||
style="background-color: #{selectedColor}"
|
style="background-color: #{selectedColor}"
|
||||||
title="#{selectedColor}"
|
title="#{selectedColor}"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
defer
|
defer
|
||||||
style="max-height: {maxHeight}"
|
style="max-height: {maxHeight}"
|
||||||
>
|
>
|
||||||
<div class="divide-y divide-zinc-800/50 bg-zinc-950">
|
<div class="divide-y divide-admin-border/50 bg-admin-bg">
|
||||||
{#each events as event (event.id)}
|
{#each events as event (event.id)}
|
||||||
{@const levelColors = {
|
{@const levelColors = {
|
||||||
info: "text-cyan-500/60",
|
info: "text-cyan-500/60",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
warning: "WARN",
|
warning: "WARN",
|
||||||
error: "ERR",
|
error: "ERR",
|
||||||
}}
|
}}
|
||||||
<div class="hover:bg-zinc-900/50 transition-colors">
|
<div class="hover:bg-admin-surface-hover/50 transition-colors">
|
||||||
<div class="px-4 py-1.5">
|
<div class="px-4 py-1.5">
|
||||||
<div class="flex items-center justify-between gap-4 text-xs">
|
<div class="flex items-center justify-between gap-4 text-xs">
|
||||||
<div class="flex items-center gap-2.5 flex-1 min-w-0">
|
<div class="flex items-center gap-2.5 flex-1 min-w-0">
|
||||||
@@ -60,23 +60,23 @@
|
|||||||
>
|
>
|
||||||
{levelLabels[event.level]}
|
{levelLabels[event.level]}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-zinc-300 truncate">
|
<span class="text-admin-text truncate">
|
||||||
{event.message}
|
{event.message}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-zinc-500 shrink-0">
|
<span class="text-admin-text-muted shrink-0">
|
||||||
target=<span class="text-zinc-400">{event.target}</span>
|
target=<span class="text-admin-text-secondary">{event.target}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3 shrink-0">
|
<div class="flex items-center gap-3 shrink-0">
|
||||||
{#if showMetadata && event.metadata}
|
{#if showMetadata && event.metadata}
|
||||||
<button
|
<button
|
||||||
class="text-[11px] text-indigo-400 hover:text-indigo-300 transition-colors"
|
class="text-[11px] text-admin-accent hover:text-admin-accent-hover transition-colors"
|
||||||
onclick={() => toggleMetadata(event.id)}
|
onclick={() => toggleMetadata(event.id)}
|
||||||
>
|
>
|
||||||
{expandedEventId === event.id ? "hide" : "show"}
|
{expandedEventId === event.id ? "hide" : "show"}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="text-zinc-600 text-[11px] tabular-nums">
|
<span class="text-admin-text-muted text-[11px] tabular-nums">
|
||||||
{formatTimestamp(event.timestamp)}
|
{formatTimestamp(event.timestamp)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,10 +85,10 @@
|
|||||||
{#if showMetadata && expandedEventId === event.id && event.metadata}
|
{#if showMetadata && expandedEventId === event.id && event.metadata}
|
||||||
<div class="px-4 pb-2">
|
<div class="px-4 pb-2">
|
||||||
<div
|
<div
|
||||||
class="bg-zinc-900 border border-zinc-800 rounded p-3 text-[11px]"
|
class="bg-admin-surface border border-admin-border rounded p-3 text-[11px]"
|
||||||
>
|
>
|
||||||
<p class="text-zinc-500 mb-2 font-medium">Metadata:</p>
|
<p class="text-admin-text-muted mb-2 font-medium">Metadata:</p>
|
||||||
<pre class="text-zinc-400 overflow-x-auto">{JSON.stringify(
|
<pre class="text-admin-text-secondary overflow-x-auto">{JSON.stringify(
|
||||||
event.metadata,
|
event.metadata,
|
||||||
null,
|
null,
|
||||||
2,
|
2,
|
||||||
|
|||||||
@@ -211,7 +211,7 @@
|
|||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html selectedIconSvg}
|
{@html selectedIconSvg}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="size-6 animate-pulse rounded bg-zinc-700"></div>
|
<div class="size-6 animate-pulse rounded bg-admin-surface-hover"></div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
@@ -234,8 +234,8 @@
|
|||||||
class={cn(
|
class={cn(
|
||||||
"rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
|
"rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
|
||||||
selectedCollection === "all"
|
selectedCollection === "all"
|
||||||
? "bg-indigo-600 text-white"
|
? "bg-admin-accent text-white"
|
||||||
: "bg-admin-panel text-admin-text-muted hover:bg-admin-hover hover:text-admin-text",
|
: "bg-admin-surface text-admin-text-muted hover:bg-admin-surface-hover hover:text-admin-text",
|
||||||
)}
|
)}
|
||||||
onclick={() => (selectedCollection = "all")}
|
onclick={() => (selectedCollection = "all")}
|
||||||
>
|
>
|
||||||
@@ -247,8 +247,8 @@
|
|||||||
class={cn(
|
class={cn(
|
||||||
"rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
|
"rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
|
||||||
selectedCollection === collection.id
|
selectedCollection === collection.id
|
||||||
? "bg-indigo-600 text-white"
|
? "bg-admin-accent text-white"
|
||||||
: "bg-admin-panel text-admin-text-muted hover:bg-admin-hover hover:text-admin-text",
|
: "bg-admin-surface text-admin-text-muted hover:bg-admin-surface-hover hover:text-admin-text",
|
||||||
)}
|
)}
|
||||||
onclick={() => (selectedCollection = collection.id)}
|
onclick={() => (selectedCollection = collection.id)}
|
||||||
>
|
>
|
||||||
@@ -265,7 +265,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
bind:value={searchQuery}
|
bind:value={searchQuery}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
class="w-full rounded-md border border-admin-border bg-admin-panel px-3 py-2 text-sm text-admin-text placeholder:text-admin-text-muted focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
class="w-full rounded-md border border-admin-border bg-admin-bg-secondary px-3 py-2 text-sm text-admin-text placeholder:text-admin-text-muted focus:border-admin-accent focus:outline-none focus:ring-1 focus:ring-admin-accent"
|
||||||
onfocus={handleInputFocus}
|
onfocus={handleInputFocus}
|
||||||
onblur={handleInputBlur}
|
onblur={handleInputBlur}
|
||||||
/>
|
/>
|
||||||
@@ -273,7 +273,7 @@
|
|||||||
<!-- Search results dropdown -->
|
<!-- Search results dropdown -->
|
||||||
{#if showDropdown && searchResults.length > 0}
|
{#if showDropdown && searchResults.length > 0}
|
||||||
<div
|
<div
|
||||||
class="absolute z-10 mt-1 max-h-96 w-full overflow-auto rounded-md border border-admin-border bg-admin-panel shadow-lg"
|
class="absolute z-10 mt-1 max-h-96 w-full overflow-auto rounded-md border border-admin-border bg-admin-surface shadow-lg"
|
||||||
>
|
>
|
||||||
<!-- Grid layout for icons -->
|
<!-- Grid layout for icons -->
|
||||||
<div class="grid grid-cols-8 gap-1 p-2">
|
<div class="grid grid-cols-8 gap-1 p-2">
|
||||||
@@ -293,14 +293,14 @@
|
|||||||
{@html cachedSvg}
|
{@html cachedSvg}
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="size-full animate-pulse rounded bg-zinc-700"
|
class="size-full animate-pulse rounded bg-admin-surface-hover"
|
||||||
></div>
|
></div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tooltip on hover -->
|
<!-- Tooltip on hover -->
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute -top-8 left-1/2 z-20 hidden -translate-x-1/2 whitespace-nowrap rounded bg-zinc-900 px-2 py-1 text-xs text-white group-hover:block"
|
class="pointer-events-none absolute -top-8 left-1/2 z-20 hidden -translate-x-1/2 whitespace-nowrap rounded bg-admin-surface border border-admin-border px-2 py-1 text-xs text-admin-text group-hover:block"
|
||||||
>
|
>
|
||||||
{result.name}
|
{result.name}
|
||||||
</div>
|
</div>
|
||||||
@@ -318,7 +318,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if showDropdown && searchQuery && !isLoading}
|
{:else if showDropdown && searchQuery && !isLoading}
|
||||||
<div
|
<div
|
||||||
class="absolute z-10 mt-1 w-full rounded-md border border-admin-border bg-admin-panel p-3 text-center text-sm text-admin-text-muted shadow-lg"
|
class="absolute z-10 mt-1 w-full rounded-md border border-admin-border bg-admin-surface p-3 text-center text-sm text-admin-text-muted shadow-lg"
|
||||||
>
|
>
|
||||||
No icons found for "{searchQuery}"
|
No icons found for "{searchQuery}"
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
const inputId = `input-${Math.random().toString(36).substring(2, 11)}`;
|
const inputId = `input-${Math.random().toString(36).substring(2, 11)}`;
|
||||||
|
|
||||||
const inputStyles =
|
const inputStyles =
|
||||||
"block w-full rounded-md border border-zinc-700 bg-zinc-900 px-3 py-2 text-sm text-zinc-200 placeholder:text-zinc-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 disabled:cursor-not-allowed disabled:opacity-50 transition-colors";
|
"block w-full rounded-md border border-admin-border bg-admin-bg-secondary px-3 py-2 text-sm text-admin-text placeholder:text-admin-text-muted focus:border-admin-accent focus:outline-none focus:ring-1 focus:ring-admin-accent disabled:cursor-not-allowed disabled:opacity-50 transition-colors";
|
||||||
|
|
||||||
const errorStyles = $derived(
|
const errorStyles = $derived(
|
||||||
error ? "border-red-500 focus:border-red-500 focus:ring-red-500" : "",
|
error ? "border-red-500 focus:border-red-500 focus:ring-red-500" : "",
|
||||||
|
|||||||
@@ -51,18 +51,18 @@
|
|||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="relative w-full max-w-md rounded-xl bg-zinc-900 border border-zinc-800 p-8 shadow-xl shadow-black/50"
|
class="relative w-full max-w-md rounded-xl bg-admin-surface border border-admin-border p-8 shadow-xl shadow-black/20 dark:shadow-black/50"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
>
|
>
|
||||||
{#if title}
|
{#if title}
|
||||||
<h2 class="text-lg font-semibold text-zinc-50 mb-2">
|
<h2 class="text-lg font-semibold text-admin-text mb-2">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if description}
|
{#if description}
|
||||||
<p class="text-sm text-zinc-400 mb-4">
|
<p class="text-sm text-admin-text-secondary mb-4">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
import { cn } from "$lib/utils";
|
import { cn } from "$lib/utils";
|
||||||
|
import ThemeToggle from "$lib/components/ThemeToggle.svelte";
|
||||||
import IconLayoutDashboard from "~icons/lucide/layout-dashboard";
|
import IconLayoutDashboard from "~icons/lucide/layout-dashboard";
|
||||||
import IconFolder from "~icons/lucide/folder";
|
import IconFolder from "~icons/lucide/folder";
|
||||||
import IconTags from "~icons/lucide/tags";
|
import IconTags from "~icons/lucide/tags";
|
||||||
@@ -57,7 +58,7 @@
|
|||||||
|
|
||||||
<!-- Mobile menu button -->
|
<!-- Mobile menu button -->
|
||||||
<button
|
<button
|
||||||
class="fixed top-4 right-4 z-50 lg:hidden rounded-md bg-zinc-900 p-2 text-zinc-200 border border-zinc-800"
|
class="fixed top-4 right-4 z-50 lg:hidden rounded-md bg-admin-surface p-2 text-admin-text border border-admin-border"
|
||||||
onclick={() => (mobileMenuOpen = !mobileMenuOpen)}
|
onclick={() => (mobileMenuOpen = !mobileMenuOpen)}
|
||||||
aria-label="Toggle menu"
|
aria-label="Toggle menu"
|
||||||
>
|
>
|
||||||
@@ -71,17 +72,18 @@
|
|||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside
|
<aside
|
||||||
class={cn(
|
class={cn(
|
||||||
"fixed left-0 top-0 z-40 h-screen w-64 border-r border-zinc-800 bg-admin-bg transition-transform lg:translate-x-0",
|
"fixed left-0 top-0 z-40 h-screen w-64 border-r border-admin-border bg-admin-bg transition-transform lg:translate-x-0",
|
||||||
mobileMenuOpen ? "translate-x-0" : "-translate-x-full",
|
mobileMenuOpen ? "translate-x-0" : "-translate-x-full",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="flex h-full flex-col">
|
<div class="flex h-full flex-col">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<div class="border-b border-zinc-800 px-4 py-5">
|
<div class="border-b border-admin-border px-4 py-5 flex items-center justify-between">
|
||||||
<h1 class="text-base font-semibold text-zinc-50">
|
<h1 class="text-base font-semibold text-admin-text">
|
||||||
xevion.dev
|
xevion.dev
|
||||||
<span class="text-xs font-normal text-zinc-500 ml-1.5">Admin</span>
|
<span class="text-xs font-normal text-admin-text-muted ml-1.5">Admin</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
<ThemeToggle />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
@@ -92,14 +94,14 @@
|
|||||||
class={cn(
|
class={cn(
|
||||||
"flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-all relative",
|
"flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-all relative",
|
||||||
isActive(item.href)
|
isActive(item.href)
|
||||||
? "bg-zinc-800/50 text-zinc-50 before:absolute before:left-0 before:top-1 before:bottom-1 before:w-0.5 before:bg-indigo-500 before:rounded-r"
|
? "bg-admin-surface-hover text-admin-text before:absolute before:left-0 before:top-1 before:bottom-1 before:w-0.5 before:bg-admin-accent before:rounded-r"
|
||||||
: "text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800/30",
|
: "text-admin-text-muted hover:text-admin-text hover:bg-admin-surface-hover/50",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<item.icon class="w-4 h-4 flex-shrink-0" />
|
<item.icon class="w-4 h-4 flex-shrink-0" />
|
||||||
<span class="flex-1">{item.label}</span>
|
<span class="flex-1">{item.label}</span>
|
||||||
{#if item.badge}
|
{#if item.badge}
|
||||||
<span class="text-xs text-zinc-500">
|
<span class="text-xs text-admin-text-muted">
|
||||||
{item.badge}
|
{item.badge}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -108,17 +110,17 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Bottom actions -->
|
<!-- Bottom actions -->
|
||||||
<div class="space-y-0.5 border-t border-zinc-800 bg-zinc-900/50 p-3">
|
<div class="space-y-0.5 border-t border-admin-border bg-admin-surface/50 p-3">
|
||||||
<a
|
<a
|
||||||
href="/"
|
href="/"
|
||||||
class="flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-zinc-400 transition-all hover:text-zinc-200 hover:bg-zinc-800/30"
|
class="flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-admin-text-muted transition-all hover:text-admin-text hover:bg-admin-surface-hover/50"
|
||||||
>
|
>
|
||||||
<IconArrowLeft class="w-4 h-4" />
|
<IconArrowLeft class="w-4 h-4" />
|
||||||
<span>Back to Site</span>
|
<span>Back to Site</span>
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
onclick={handleLogout}
|
onclick={handleLogout}
|
||||||
class="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-zinc-400 transition-all hover:text-zinc-200 hover:bg-zinc-800/30"
|
class="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-admin-text-muted transition-all hover:text-admin-text hover:bg-admin-surface-hover/50"
|
||||||
>
|
>
|
||||||
<IconLogOut class="w-4 h-4" />
|
<IconLogOut class="w-4 h-4" />
|
||||||
<span>Logout</span>
|
<span>Logout</span>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
<!-- Selected tags display -->
|
<!-- Selected tags display -->
|
||||||
<div
|
<div
|
||||||
class="min-h-[42px] w-full rounded-md border border-admin-border bg-admin-panel px-3 py-2"
|
class="min-h-[42px] w-full rounded-md border border-admin-border bg-admin-bg-secondary px-3 py-2"
|
||||||
>
|
>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
{#each selectedTags as tag (tag.id)}
|
{#each selectedTags as tag (tag.id)}
|
||||||
@@ -106,12 +106,12 @@
|
|||||||
<!-- Dropdown -->
|
<!-- Dropdown -->
|
||||||
{#if dropdownOpen && filteredTags.length > 0}
|
{#if dropdownOpen && filteredTags.length > 0}
|
||||||
<div
|
<div
|
||||||
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md border border-admin-border bg-admin-panel py-1 shadow-lg"
|
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md border border-admin-border bg-admin-surface py-1 shadow-lg"
|
||||||
>
|
>
|
||||||
{#each filteredTags as tag (tag.id)}
|
{#each filteredTags as tag (tag.id)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="w-full px-3 py-2 text-left text-sm text-admin-text hover:bg-admin-hover transition-colors"
|
class="w-full px-3 py-2 text-left text-sm text-admin-text hover:bg-admin-surface-hover transition-colors"
|
||||||
onclick={() => addTag(tag.id)}
|
onclick={() => addTag(tag.id)}
|
||||||
>
|
>
|
||||||
{tag.name}
|
{tag.name}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
class ThemeStore {
|
||||||
|
isDark = $state<boolean>(true);
|
||||||
|
private initialized = false;
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (this.initialized || typeof window === "undefined") return;
|
||||||
|
this.initialized = true;
|
||||||
|
|
||||||
|
const stored = localStorage.getItem("theme");
|
||||||
|
if (stored === "light" || stored === "dark") {
|
||||||
|
this.isDark = stored === "dark";
|
||||||
|
} else {
|
||||||
|
this.isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateDOMClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.isDark = !this.isDark;
|
||||||
|
localStorage.setItem("theme", this.isDark ? "dark" : "light");
|
||||||
|
this.updateDOMClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
setTheme(isDark: boolean) {
|
||||||
|
this.isDark = isDark;
|
||||||
|
localStorage.setItem("theme", isDark ? "dark" : "light");
|
||||||
|
this.updateDOMClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateDOMClass() {
|
||||||
|
if (typeof document === "undefined") return;
|
||||||
|
|
||||||
|
if (this.isDark) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const themeStore = new ThemeStore();
|
||||||
@@ -24,12 +24,12 @@
|
|||||||
<AppWrapper>
|
<AppWrapper>
|
||||||
<div class="flex min-h-screen items-center justify-center">
|
<div class="flex min-h-screen items-center justify-center">
|
||||||
<div class="mx-4 max-w-2xl text-center">
|
<div class="mx-4 max-w-2xl text-center">
|
||||||
<h1 class="mb-4 font-hanken text-8xl text-zinc-200">{status}</h1>
|
<h1 class="mb-4 font-hanken text-8xl text-text-secondary">{status}</h1>
|
||||||
<p class="mb-8 text-2xl text-zinc-400">{message}</p>
|
<p class="mb-8 text-2xl text-text-tertiary">{message}</p>
|
||||||
{#if showHomeLink}
|
{#if showHomeLink}
|
||||||
<a
|
<a
|
||||||
href={resolve("/")}
|
href={resolve("/")}
|
||||||
class="inline-block rounded-sm bg-zinc-900 px-4 py-2 text-zinc-100 transition-colors hover:bg-zinc-800"
|
class="inline-block rounded-sm bg-surface px-4 py-2 text-text-primary transition-colors hover:bg-surface-hover"
|
||||||
>
|
>
|
||||||
Return home
|
Return home
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import "../app.css";
|
import "../app.css";
|
||||||
import { OverlayScrollbars } from "overlayscrollbars";
|
import { OverlayScrollbars } from "overlayscrollbars";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import { themeStore } from "$lib/stores/theme.svelte";
|
||||||
|
|
||||||
let { children, data } = $props();
|
let { children, data } = $props();
|
||||||
|
|
||||||
@@ -20,6 +21,9 @@
|
|||||||
const metadata = $derived(data?.metadata ?? defaultMetadata);
|
const metadata = $derived(data?.metadata ?? defaultMetadata);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
// Initialize theme store
|
||||||
|
themeStore.init();
|
||||||
|
|
||||||
// Initialize overlay scrollbars on the body element
|
// Initialize overlay scrollbars on the body element
|
||||||
const osInstance = OverlayScrollbars(document.body, {
|
const osInstance = OverlayScrollbars(document.body, {
|
||||||
scrollbars: {
|
scrollbars: {
|
||||||
|
|||||||
+21
-23
@@ -13,22 +13,20 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AppWrapper class="overflow-x-hidden font-schibsted">
|
<AppWrapper class="overflow-x-hidden font-schibsted">
|
||||||
<div class="flex w-full justify-end items-center pt-5 px-6 pb-9"></div>
|
<div class="flex items-center flex-col pt-14">
|
||||||
|
|
||||||
<div class="flex items-center flex-col">
|
|
||||||
<div
|
<div
|
||||||
class="max-w-2xl mx-4 border-b border-zinc-700 divide-y divide-zinc-700 sm:mx-6"
|
class="max-w-2xl mx-4 border-b border-zinc-200 dark:border-zinc-700 divide-y divide-zinc-200 dark:divide-zinc-700 sm:mx-6"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col pb-4">
|
<div class="flex flex-col pb-4">
|
||||||
<span class="text-2xl font-bold text-white sm:text-3xl"
|
<span class="text-2xl font-bold text-zinc-900 dark:text-white sm:text-3xl"
|
||||||
>Ryan Walters,</span
|
>Ryan Walters,</span
|
||||||
>
|
>
|
||||||
<span class="text-xl font-normal text-zinc-400 sm:text-2xl">
|
<span class="text-xl font-normal text-zinc-600 dark:text-zinc-400 sm:text-2xl">
|
||||||
Full-Stack Software Engineer
|
Full-Stack Software Engineer
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="py-4 text-zinc-200">
|
<div class="py-4 text-zinc-700 dark:text-zinc-200">
|
||||||
<p class="sm:text-[0.95em]">
|
<p class="sm:text-[0.95em]">
|
||||||
A fanatical software engineer with expertise and passion for sound,
|
A fanatical software engineer with expertise and passion for sound,
|
||||||
scalable and high-performance applications. I'm always working on
|
scalable and high-performance applications. I'm always working on
|
||||||
@@ -38,43 +36,43 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="py-3">
|
<div class="py-3">
|
||||||
<span class="text-zinc-200">Connect with me</span>
|
<span class="text-zinc-700 dark:text-zinc-200">Connect with me</span>
|
||||||
<div class="flex flex-wrap gap-2 pl-3 pt-3 pb-2">
|
<div class="flex flex-wrap gap-2 pl-3 pt-3 pb-2">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/Xevion"
|
href="https://github.com/Xevion"
|
||||||
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
|
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-100 dark:bg-zinc-900 shadow-sm hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors"
|
||||||
>
|
>
|
||||||
<IconSimpleIconsGithub class="size-4 text-zinc-300" />
|
<IconSimpleIconsGithub class="size-4 text-zinc-600 dark:text-zinc-300" />
|
||||||
<span class="whitespace-nowrap text-sm text-zinc-100">GitHub</span>
|
<span class="whitespace-nowrap text-sm text-zinc-800 dark:text-zinc-100">GitHub</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://linkedin.com/in/ryancwalters"
|
href="https://linkedin.com/in/ryancwalters"
|
||||||
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
|
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-100 dark:bg-zinc-900 shadow-sm hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors"
|
||||||
>
|
>
|
||||||
<IconSimpleIconsLinkedin class="size-4 text-zinc-300" />
|
<IconSimpleIconsLinkedin class="size-4 text-zinc-600 dark:text-zinc-300" />
|
||||||
<span class="whitespace-nowrap text-sm text-zinc-100">LinkedIn</span
|
<span class="whitespace-nowrap text-sm text-zinc-800 dark:text-zinc-100">LinkedIn</span
|
||||||
>
|
>
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
|
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-100 dark:bg-zinc-900 shadow-sm hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors"
|
||||||
>
|
>
|
||||||
<IconSimpleIconsDiscord class="size-4 text-zinc-300" />
|
<IconSimpleIconsDiscord class="size-4 text-zinc-600 dark:text-zinc-300" />
|
||||||
<span class="whitespace-nowrap text-sm text-zinc-100">Discord</span>
|
<span class="whitespace-nowrap text-sm text-zinc-800 dark:text-zinc-100">Discord</span>
|
||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
href="mailto:your.email@example.com"
|
href="mailto:your.email@example.com"
|
||||||
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
|
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-100 dark:bg-zinc-900 shadow-sm hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors"
|
||||||
>
|
>
|
||||||
<MaterialSymbolsMailRounded class="size-4.5 text-zinc-300" />
|
<MaterialSymbolsMailRounded class="size-4.5 text-zinc-600 dark:text-zinc-300" />
|
||||||
<span class="whitespace-nowrap text-sm text-zinc-100">Email</span>
|
<span class="whitespace-nowrap text-sm text-zinc-800 dark:text-zinc-100">Email</span>
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
|
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-100 dark:bg-zinc-900 shadow-sm hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors"
|
||||||
>
|
>
|
||||||
<MaterialSymbolsVpnKey class="size-4.5 text-zinc-300" />
|
<MaterialSymbolsVpnKey class="size-4.5 text-zinc-600 dark:text-zinc-300" />
|
||||||
<span class="whitespace-nowrap text-sm text-zinc-100">PGP Key</span>
|
<span class="whitespace-nowrap text-sm text-zinc-800 dark:text-zinc-100">PGP Key</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
{@render children()}
|
{@render children()}
|
||||||
{:else}
|
{:else}
|
||||||
<!-- Admin layout with sidebar and dots shader -->
|
<!-- Admin layout with sidebar and dots shader -->
|
||||||
<AppWrapper bgColor="bg-admin-bg">
|
<AppWrapper bgColor="bg-admin-bg" showThemeToggle={false}>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
projectCount={stats?.totalProjects ?? 0}
|
projectCount={stats?.totalProjects ?? 0}
|
||||||
tagCount={stats?.totalTags ?? 0}
|
tagCount={stats?.totalTags ?? 0}
|
||||||
|
|||||||
@@ -32,8 +32,8 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-xl font-semibold text-zinc-50">Dashboard</h1>
|
<h1 class="text-xl font-semibold text-admin-text">Dashboard</h1>
|
||||||
<p class="mt-1 text-sm text-zinc-500">
|
<p class="mt-1 text-sm text-admin-text-muted">
|
||||||
Overview of your portfolio and recent activity
|
Overview of your portfolio and recent activity
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,22 +58,22 @@
|
|||||||
|
|
||||||
<!-- Recent Events -->
|
<!-- Recent Events -->
|
||||||
<div
|
<div
|
||||||
class="rounded-xl border border-zinc-800 bg-zinc-900/50 overflow-hidden shadow-sm shadow-black/20"
|
class="rounded-xl border border-admin-border bg-admin-surface/50 overflow-hidden shadow-sm shadow-black/10 dark:shadow-black/20"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-between px-6 py-3.5 bg-zinc-800/30 border-b border-zinc-800"
|
class="flex items-center justify-between px-6 py-3.5 bg-admin-surface-hover/30 border-b border-admin-border"
|
||||||
>
|
>
|
||||||
<h2 class="text-sm font-medium text-zinc-300">Recent Events</h2>
|
<h2 class="text-sm font-medium text-admin-text-secondary">Recent Events</h2>
|
||||||
<a
|
<a
|
||||||
href={resolve("/admin/events")}
|
href={resolve("/admin/events")}
|
||||||
class="text-sm text-indigo-400 hover:text-indigo-300 transition-colors"
|
class="text-sm text-admin-accent hover:text-admin-accent-hover transition-colors"
|
||||||
>
|
>
|
||||||
View all →
|
View all →
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if recentEvents.length === 0}
|
{#if recentEvents.length === 0}
|
||||||
<p class="text-sm text-zinc-500 text-center py-8">No events yet</p>
|
<p class="text-sm text-admin-text-muted text-center py-8">No events yet</p>
|
||||||
{:else}
|
{:else}
|
||||||
<EventLog events={recentEvents} maxHeight="400px" />
|
<EventLog events={recentEvents} maxHeight="400px" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -46,17 +46,17 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-xl font-semibold text-zinc-50">Event Log</h1>
|
<h1 class="text-xl font-semibold text-admin-text">Event Log</h1>
|
||||||
<p class="mt-1 text-sm text-zinc-500">
|
<p class="mt-1 text-sm text-admin-text-muted">
|
||||||
System activity, errors, and sync operations
|
System activity, errors, and sync operations
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div
|
<div
|
||||||
class="rounded-xl border border-zinc-800 bg-zinc-900 p-6 shadow-sm shadow-black/20"
|
class="rounded-xl border border-admin-border bg-admin-surface p-6 shadow-sm shadow-black/10 dark:shadow-black/20"
|
||||||
>
|
>
|
||||||
<h3 class="text-sm font-medium text-zinc-400 mb-4">Filters</h3>
|
<h3 class="text-sm font-medium text-admin-text-secondary mb-4">Filters</h3>
|
||||||
<div class="grid gap-4 md:grid-cols-2">
|
<div class="grid gap-4 md:grid-cols-2">
|
||||||
<Input
|
<Input
|
||||||
label="Level"
|
label="Level"
|
||||||
@@ -75,19 +75,19 @@
|
|||||||
|
|
||||||
<!-- Events Log -->
|
<!-- Events Log -->
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="text-center py-12 text-zinc-500">Loading events...</div>
|
<div class="text-center py-12 text-admin-text-muted">Loading events...</div>
|
||||||
{:else if events.length === 0}
|
{:else if events.length === 0}
|
||||||
<div class="text-center py-12">
|
<div class="text-center py-12">
|
||||||
<p class="text-zinc-500">No events found</p>
|
<p class="text-admin-text-muted">No events found</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="rounded-xl border border-zinc-800 bg-zinc-900/50 overflow-hidden shadow-sm shadow-black/20"
|
class="rounded-xl border border-admin-border bg-admin-surface/50 overflow-hidden shadow-sm shadow-black/10 dark:shadow-black/20"
|
||||||
>
|
>
|
||||||
<div class="px-6 py-3.5 bg-zinc-800/30 border-b border-zinc-800">
|
<div class="px-6 py-3.5 bg-admin-surface-hover/30 border-b border-admin-border">
|
||||||
<h2 class="text-sm font-medium text-zinc-300">
|
<h2 class="text-sm font-medium text-admin-text-secondary">
|
||||||
Event Log
|
Event Log
|
||||||
<span class="text-zinc-500 font-normal ml-2">
|
<span class="text-admin-text-muted font-normal ml-2">
|
||||||
({events.length} event{events.length === 1 ? "" : "s"})
|
({events.length} event{events.length === 1 ? "" : "s"})
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
<div class="flex min-h-screen items-center justify-center px-4">
|
<div class="flex min-h-screen items-center justify-center px-4">
|
||||||
<div class="w-full max-w-md space-y-4">
|
<div class="w-full max-w-md space-y-4">
|
||||||
<!-- Login Form -->
|
<!-- Login Form -->
|
||||||
<div class="rounded-lg bg-admin-panel p-8 shadow-2xl shadow-zinc-500/20">
|
<div class="rounded-lg bg-admin-surface border border-admin-border p-8 shadow-2xl shadow-black/10 dark:shadow-zinc-500/20">
|
||||||
<form onsubmit={handleSubmit} class="space-y-6">
|
<form onsubmit={handleSubmit} class="space-y-6">
|
||||||
<Input
|
<Input
|
||||||
label="Username"
|
label="Username"
|
||||||
|
|||||||
@@ -83,8 +83,8 @@
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-xl font-semibold text-zinc-50">Projects</h1>
|
<h1 class="text-xl font-semibold text-admin-text">Projects</h1>
|
||||||
<p class="mt-1 text-sm text-zinc-500">Manage your project portfolio</p>
|
<p class="mt-1 text-sm text-admin-text-muted">Manage your project portfolio</p>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="primary" href="/admin/projects/new">
|
<Button variant="primary" href="/admin/projects/new">
|
||||||
<IconPlus class="w-4 h-4 mr-2" />
|
<IconPlus class="w-4 h-4 mr-2" />
|
||||||
@@ -94,45 +94,45 @@
|
|||||||
|
|
||||||
<!-- Projects Table -->
|
<!-- Projects Table -->
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="text-center py-12 text-zinc-500">Loading projects...</div>
|
<div class="text-center py-12 text-admin-text-muted">Loading projects...</div>
|
||||||
{:else if projects.length === 0}
|
{:else if projects.length === 0}
|
||||||
<div class="text-center py-12">
|
<div class="text-center py-12">
|
||||||
<p class="text-zinc-500 mb-4">No projects yet</p>
|
<p class="text-admin-text-muted mb-4">No projects yet</p>
|
||||||
<Button variant="primary" href="/admin/projects/new"
|
<Button variant="primary" href="/admin/projects/new"
|
||||||
>Create your first project</Button
|
>Create your first project</Button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Table>
|
<Table>
|
||||||
<thead class="bg-zinc-900/50">
|
<thead class="bg-admin-surface/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-zinc-500">
|
<th class="px-4 py-3 text-left text-xs font-medium text-admin-text-muted">
|
||||||
Name
|
Name
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-zinc-500">
|
<th class="px-4 py-3 text-left text-xs font-medium text-admin-text-muted">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-zinc-500">
|
<th class="px-4 py-3 text-left text-xs font-medium text-admin-text-muted">
|
||||||
Tags
|
Tags
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-zinc-500">
|
<th class="px-4 py-3 text-left text-xs font-medium text-admin-text-muted">
|
||||||
Updated
|
Updated
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-right text-xs font-medium text-zinc-500">
|
<th class="px-4 py-3 text-right text-xs font-medium text-admin-text-muted">
|
||||||
Actions
|
Actions
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-zinc-800/50">
|
<tbody class="divide-y divide-admin-border/50">
|
||||||
{#each projects as project (project.id)}
|
{#each projects as project (project.id)}
|
||||||
<tr class="hover:bg-zinc-800/30 transition-colors">
|
<tr class="hover:bg-admin-surface-hover/30 transition-colors">
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div>
|
<div>
|
||||||
<div class="font-medium text-zinc-200">
|
<div class="font-medium text-admin-text">
|
||||||
{project.name}
|
{project.name}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-zinc-500">
|
<div class="text-xs text-admin-text-muted">
|
||||||
{project.slug}
|
{project.slug}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-zinc-500 text-sm">
|
<td class="px-4 py-3 text-admin-text-secondary text-sm">
|
||||||
{formatDate(project.updatedAt)}
|
{formatDate(project.updatedAt)}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-right">
|
<td class="px-4 py-3 text-right">
|
||||||
@@ -192,9 +192,9 @@
|
|||||||
oncancel={cancelDelete}
|
oncancel={cancelDelete}
|
||||||
>
|
>
|
||||||
{#if deleteTarget}
|
{#if deleteTarget}
|
||||||
<div class="rounded-md bg-zinc-800/50 border border-zinc-700 p-3">
|
<div class="rounded-md bg-admin-surface-hover/50 border border-admin-border p-3">
|
||||||
<p class="font-medium text-zinc-200">{deleteTarget.name}</p>
|
<p class="font-medium text-admin-text">{deleteTarget.name}</p>
|
||||||
<p class="text-sm text-zinc-500">{deleteTarget.slug}</p>
|
<p class="text-sm text-admin-text-secondary">{deleteTarget.slug}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -78,13 +78,13 @@
|
|||||||
<p class="text-admin-text-muted mb-4">Project not found</p>
|
<p class="text-admin-text-muted mb-4">Project not found</p>
|
||||||
<a
|
<a
|
||||||
href={resolve("/admin/projects")}
|
href={resolve("/admin/projects")}
|
||||||
class="text-blue-400 hover:text-blue-300"
|
class="text-admin-accent hover:text-admin-accent-hover"
|
||||||
>
|
>
|
||||||
← Back to projects
|
← Back to projects
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="rounded-lg border border-admin-border bg-admin-panel p-6">
|
<div class="rounded-lg border border-admin-border bg-admin-surface p-6">
|
||||||
<ProjectForm
|
<ProjectForm
|
||||||
{project}
|
{project}
|
||||||
availableTags={tags}
|
availableTags={tags}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="text-center py-12 text-admin-text-muted">Loading...</div>
|
<div class="text-center py-12 text-admin-text-muted">Loading...</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="rounded-lg border border-admin-border bg-admin-panel p-6">
|
<div class="rounded-lg border border-admin-border bg-admin-surface p-6">
|
||||||
<ProjectForm
|
<ProjectForm
|
||||||
availableTags={tags}
|
availableTags={tags}
|
||||||
onsubmit={handleSubmit}
|
onsubmit={handleSubmit}
|
||||||
|
|||||||
@@ -111,25 +111,25 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-xl font-semibold text-zinc-50">Settings</h1>
|
<h1 class="text-xl font-semibold text-admin-text">Settings</h1>
|
||||||
<p class="mt-1 text-sm text-zinc-500">
|
<p class="mt-1 text-sm text-admin-text-muted">
|
||||||
Configure your site identity, social links, and admin preferences
|
Configure your site identity, social links, and admin preferences
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="text-center py-12 text-zinc-500">Loading settings...</div>
|
<div class="text-center py-12 text-admin-text-muted">Loading settings...</div>
|
||||||
{:else if formData}
|
{:else if formData}
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<div class="border-b border-zinc-800">
|
<div class="border-b border-admin-border">
|
||||||
<nav class="flex gap-6" aria-label="Settings tabs">
|
<nav class="flex gap-6" aria-label="Settings tabs">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={cn(
|
class={cn(
|
||||||
"pb-3 px-1 text-sm font-medium border-b-2 transition-colors",
|
"pb-3 px-1 text-sm font-medium border-b-2 transition-colors",
|
||||||
activeTab === "identity"
|
activeTab === "identity"
|
||||||
? "border-indigo-500 text-zinc-50"
|
? "border-admin-accent text-admin-text"
|
||||||
: "border-transparent text-zinc-400 hover:text-zinc-300 hover:border-zinc-700",
|
: "border-transparent text-admin-text-muted hover:text-admin-text hover:border-admin-border-hover",
|
||||||
)}
|
)}
|
||||||
onclick={() => navigateToTab("identity")}
|
onclick={() => navigateToTab("identity")}
|
||||||
>
|
>
|
||||||
@@ -140,8 +140,8 @@
|
|||||||
class={cn(
|
class={cn(
|
||||||
"pb-3 px-1 text-sm font-medium border-b-2 transition-colors",
|
"pb-3 px-1 text-sm font-medium border-b-2 transition-colors",
|
||||||
activeTab === "social"
|
activeTab === "social"
|
||||||
? "border-indigo-500 text-zinc-50"
|
? "border-admin-accent text-admin-text"
|
||||||
: "border-transparent text-zinc-400 hover:text-zinc-300 hover:border-zinc-700",
|
: "border-transparent text-admin-text-muted hover:text-admin-text hover:border-admin-border-hover",
|
||||||
)}
|
)}
|
||||||
onclick={() => navigateToTab("social")}
|
onclick={() => navigateToTab("social")}
|
||||||
>
|
>
|
||||||
@@ -152,8 +152,8 @@
|
|||||||
class={cn(
|
class={cn(
|
||||||
"pb-3 px-1 text-sm font-medium border-b-2 transition-colors",
|
"pb-3 px-1 text-sm font-medium border-b-2 transition-colors",
|
||||||
activeTab === "admin"
|
activeTab === "admin"
|
||||||
? "border-indigo-500 text-zinc-50"
|
? "border-admin-accent text-admin-text"
|
||||||
: "border-transparent text-zinc-400 hover:text-zinc-300 hover:border-zinc-700",
|
: "border-transparent text-admin-text-muted hover:text-admin-text hover:border-admin-border-hover",
|
||||||
)}
|
)}
|
||||||
onclick={() => navigateToTab("admin")}
|
onclick={() => navigateToTab("admin")}
|
||||||
>
|
>
|
||||||
@@ -164,11 +164,11 @@
|
|||||||
|
|
||||||
<!-- Tab Content -->
|
<!-- Tab Content -->
|
||||||
<div
|
<div
|
||||||
class="rounded-xl border border-zinc-800 bg-zinc-900 p-6 shadow-sm shadow-black/20"
|
class="rounded-xl border border-admin-border bg-admin-surface p-6 shadow-sm shadow-black/10 dark:shadow-black/20"
|
||||||
>
|
>
|
||||||
{#if activeTab === "identity"}
|
{#if activeTab === "identity"}
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h3 class="text-base font-medium text-zinc-200 mb-4">
|
<h3 class="text-base font-medium text-admin-text mb-4">
|
||||||
Site Identity
|
Site Identity
|
||||||
</h3>
|
</h3>
|
||||||
<Input
|
<Input
|
||||||
@@ -204,8 +204,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if activeTab === "social"}
|
{:else if activeTab === "social"}
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h3 class="text-base font-medium text-zinc-200 mb-4">Social Links</h3>
|
<h3 class="text-base font-medium text-admin-text mb-4">Social Links</h3>
|
||||||
<p class="text-sm text-zinc-500 mb-4">
|
<p class="text-sm text-admin-text-muted mb-4">
|
||||||
Configure your social media presence on the index page
|
Configure your social media presence on the index page
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -213,23 +213,23 @@
|
|||||||
{#each formData.socialLinks as link (link.id)}
|
{#each formData.socialLinks as link (link.id)}
|
||||||
{@const Icon = getSocialIcon(link.platform)}
|
{@const Icon = getSocialIcon(link.platform)}
|
||||||
<div
|
<div
|
||||||
class="rounded-lg border border-zinc-800 bg-zinc-900/50 p-4 hover:border-zinc-700 transition-colors"
|
class="rounded-lg border border-admin-border bg-admin-surface-hover/50 p-4 hover:border-admin-border-hover transition-colors"
|
||||||
>
|
>
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<Icon class="w-5 h-5 text-zinc-400" />
|
<Icon class="w-5 h-5 text-admin-text-muted" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 space-y-3">
|
<div class="flex-1 space-y-3">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-sm font-medium text-zinc-200"
|
<span class="text-sm font-medium text-admin-text"
|
||||||
>{link.label}</span
|
>{link.label}</span
|
||||||
>
|
>
|
||||||
<label class="flex items-center gap-2 cursor-pointer">
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
<span class="text-xs text-zinc-500">Visible</span>
|
<span class="text-xs text-admin-text-muted">Visible</span>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
bind:checked={link.visible}
|
bind:checked={link.visible}
|
||||||
class="w-4 h-4 rounded border-zinc-700 bg-zinc-800 text-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-0 cursor-pointer"
|
class="w-4 h-4 rounded border-admin-border bg-admin-bg-secondary text-admin-accent focus:ring-2 focus:ring-admin-accent focus:ring-offset-0 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -246,7 +246,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if activeTab === "admin"}
|
{:else if activeTab === "admin"}
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h3 class="text-base font-medium text-zinc-200 mb-4">
|
<h3 class="text-base font-medium text-admin-text mb-4">
|
||||||
Admin Preferences
|
Admin Preferences
|
||||||
</h3>
|
</h3>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -160,8 +160,8 @@
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-xl font-semibold text-zinc-50">Tags</h1>
|
<h1 class="text-xl font-semibold text-admin-text">Tags</h1>
|
||||||
<p class="mt-1 text-sm text-zinc-500">
|
<p class="mt-1 text-sm text-admin-text-muted">
|
||||||
Manage project tags and categories
|
Manage project tags and categories
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -181,9 +181,9 @@
|
|||||||
<!-- Create Form -->
|
<!-- Create Form -->
|
||||||
{#if showCreateForm}
|
{#if showCreateForm}
|
||||||
<div
|
<div
|
||||||
class="rounded-xl border border-zinc-800 bg-zinc-900 p-6 shadow-sm shadow-black/20"
|
class="rounded-xl border border-admin-border bg-admin-surface p-6 shadow-sm shadow-black/10 dark:shadow-black/20"
|
||||||
>
|
>
|
||||||
<h3 class="text-base font-medium text-zinc-200 mb-4">Create New Tag</h3>
|
<h3 class="text-base font-medium text-admin-text mb-4">Create New Tag</h3>
|
||||||
<div class="grid gap-4 md:grid-cols-2">
|
<div class="grid gap-4 md:grid-cols-2">
|
||||||
<Input
|
<Input
|
||||||
label="Name"
|
label="Name"
|
||||||
@@ -219,38 +219,38 @@
|
|||||||
|
|
||||||
<!-- Tags Table -->
|
<!-- Tags Table -->
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="text-center py-12 text-zinc-500">Loading tags...</div>
|
<div class="text-center py-12 text-admin-text-muted">Loading tags...</div>
|
||||||
{:else if tags.length === 0}
|
{:else if tags.length === 0}
|
||||||
<div class="text-center py-12">
|
<div class="text-center py-12">
|
||||||
<p class="text-zinc-500 mb-4">No tags yet</p>
|
<p class="text-admin-text-muted mb-4">No tags yet</p>
|
||||||
<Button variant="primary" onclick={() => (showCreateForm = true)}>
|
<Button variant="primary" onclick={() => (showCreateForm = true)}>
|
||||||
Create your first tag
|
Create your first tag
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Table>
|
<Table>
|
||||||
<thead class="bg-zinc-900/50">
|
<thead class="bg-admin-surface/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-zinc-500">
|
<th class="px-4 py-3 text-left text-xs font-medium text-admin-text-muted">
|
||||||
Name
|
Name
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-zinc-500">
|
<th class="px-4 py-3 text-left text-xs font-medium text-admin-text-muted">
|
||||||
Slug
|
Slug
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-zinc-500">
|
<th class="px-4 py-3 text-left text-xs font-medium text-admin-text-muted">
|
||||||
Color
|
Color
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-zinc-500">
|
<th class="px-4 py-3 text-left text-xs font-medium text-admin-text-muted">
|
||||||
Projects
|
Projects
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-right text-xs font-medium text-zinc-500">
|
<th class="px-4 py-3 text-right text-xs font-medium text-admin-text-muted">
|
||||||
Actions
|
Actions
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-zinc-800/50">
|
<tbody class="divide-y divide-admin-border/50">
|
||||||
{#each tags as tag (tag.id)}
|
{#each tags as tag (tag.id)}
|
||||||
<tr class="hover:bg-zinc-800/30 transition-colors">
|
<tr class="hover:bg-admin-surface-hover/30 transition-colors">
|
||||||
{#if editingId === tag.id}
|
{#if editingId === tag.id}
|
||||||
<!-- Edit mode -->
|
<!-- Edit mode -->
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
@@ -271,13 +271,13 @@
|
|||||||
{#if editColor}
|
{#if editColor}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
class="size-6 rounded border border-zinc-700"
|
class="size-6 rounded border border-admin-border"
|
||||||
style="background-color: #{editColor}"
|
style="background-color: #{editColor}"
|
||||||
/>
|
/>
|
||||||
<span class="text-xs text-zinc-500">#{editColor}</span>
|
<span class="text-xs text-admin-text-muted">#{editColor}</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text-xs text-zinc-500">No color</span>
|
<span class="text-xs text-admin-text-muted">No color</span>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-admin-text">
|
<td class="px-4 py-3 text-admin-text">
|
||||||
@@ -305,26 +305,26 @@
|
|||||||
</td>
|
</td>
|
||||||
{:else}
|
{:else}
|
||||||
<!-- View mode -->
|
<!-- View mode -->
|
||||||
<td class="px-4 py-3 font-medium text-zinc-200">
|
<td class="px-4 py-3 font-medium text-admin-text">
|
||||||
{tag.name}
|
{tag.name}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-zinc-500">
|
<td class="px-4 py-3 text-admin-text-secondary">
|
||||||
{tag.slug}
|
{tag.slug}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
{#if tag.color}
|
{#if tag.color}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
class="size-6 rounded border border-zinc-700"
|
class="size-6 rounded border border-admin-border"
|
||||||
style="background-color: #{tag.color}"
|
style="background-color: #{tag.color}"
|
||||||
/>
|
/>
|
||||||
<span class="text-xs text-zinc-500">#{tag.color}</span>
|
<span class="text-xs text-admin-text-muted">#{tag.color}</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text-xs text-zinc-500">No color</span>
|
<span class="text-xs text-admin-text-muted">No color</span>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-zinc-300">
|
<td class="px-4 py-3 text-admin-text">
|
||||||
{tag.projectCount}
|
{tag.projectCount}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-right">
|
<td class="px-4 py-3 text-right">
|
||||||
@@ -364,9 +364,9 @@
|
|||||||
oncancel={cancelDelete}
|
oncancel={cancelDelete}
|
||||||
>
|
>
|
||||||
{#if deleteTarget}
|
{#if deleteTarget}
|
||||||
<div class="rounded-md bg-zinc-800/50 border border-zinc-700 p-3">
|
<div class="rounded-md bg-admin-surface-hover/50 border border-admin-border p-3">
|
||||||
<p class="font-medium text-zinc-200">{deleteTarget.name}</p>
|
<p class="font-medium text-admin-text">{deleteTarget.name}</p>
|
||||||
<p class="text-sm text-zinc-500">
|
<p class="text-sm text-admin-text-secondary">
|
||||||
Used in {deleteTarget.projectCount} project{deleteTarget.projectCount ===
|
Used in {deleteTarget.projectCount} project{deleteTarget.projectCount ===
|
||||||
1
|
1
|
||||||
? ""
|
? ""
|
||||||
|
|||||||
Reference in New Issue
Block a user