feat(theme): migrate theme emojis to Phosphor icons and integrate theme system into Zitare

- Replace emoji with Phosphor icon components in ThemeCard
- Add icon property to ThemeVariantDefinition type
- Add theme icon SVG paths to PillDropdown (sparkle, leaf, hexagon, waves)
- Update all app layouts to use icon instead of emoji for theme variants
- Integrate shared-theme system into Zitare web app
- Migrate Zitare from Tailwind v3 to v4
- Add themes page to Zitare
- Update Zitare settings page with icon-based theme display
- Add missing shared-icons dependency to Presi and Zitare

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-11-29 09:24:32 +01:00
parent b9608bd5d2
commit f436fbb99d
18 changed files with 300 additions and 328 deletions

View file

@ -27,23 +27,23 @@
// Theme variants
...theme.variants.map((variant) => ({
id: variant,
label: `${THEME_DEFINITIONS[variant].emoji} ${THEME_DEFINITIONS[variant].label}`,
label: THEME_DEFINITIONS[variant].label,
icon: THEME_DEFINITIONS[variant].icon,
onClick: () => theme.setVariant(variant),
active: theme.variant === variant,
})),
// Separator and link to full themes page
{
id: 'all-themes',
label: '🎨 Alle Themes',
label: 'Alle Themes',
icon: 'palette',
onClick: () => goto('/themes'),
active: false,
},
]);
// Current theme variant label
let currentThemeVariantLabel = $derived(
`${THEME_DEFINITIONS[theme.variant].emoji} ${THEME_DEFINITIONS[theme.variant].label}`
);
let currentThemeVariantLabel = $derived(THEME_DEFINITIONS[theme.variant].label);
// Navigation items for Chat
const navItems: PillNavItem[] = [

View file

@ -33,22 +33,22 @@
let themeVariantItems = $derived<PillDropdownItem[]>([
...theme.variants.map((variant) => ({
id: variant,
label: `${THEME_DEFINITIONS[variant].emoji} ${THEME_DEFINITIONS[variant].label}`,
label: THEME_DEFINITIONS[variant].label,
icon: THEME_DEFINITIONS[variant].icon,
onClick: () => theme.setVariant(variant),
active: theme.variant === variant,
})),
{
id: 'all-themes',
label: '🎨 Alle Themes',
label: 'Alle Themes',
icon: 'palette',
onClick: () => goto('/themes'),
active: false,
},
]);
// Current theme variant label
let currentThemeVariantLabel = $derived(
`${THEME_DEFINITIONS[theme.variant].emoji} ${THEME_DEFINITIONS[theme.variant].label}`
);
let currentThemeVariantLabel = $derived(THEME_DEFINITIONS[theme.variant].label);
// Navigation shortcuts (Ctrl+1-5)
const navRoutes = navItems.map((item) => item.href);

View file

@ -76,22 +76,22 @@
let themeVariantItems = $derived<PillDropdownItem[]>([
...theme.variants.map((variant) => ({
id: variant,
label: `${THEME_DEFINITIONS[variant].emoji} ${THEME_DEFINITIONS[variant].label}`,
label: THEME_DEFINITIONS[variant].label,
icon: THEME_DEFINITIONS[variant].icon,
onClick: () => theme.setVariant(variant),
active: theme.variant === variant,
})),
{
id: 'all-themes',
label: '🎨 Alle Themes',
label: 'Alle Themes',
icon: 'palette',
onClick: () => goto('/app/themes'),
active: false,
},
]);
// Current theme variant label
let currentThemeVariantLabel = $derived(
`${THEME_DEFINITIONS[theme.variant].emoji} ${THEME_DEFINITIONS[theme.variant].label}`
);
let currentThemeVariantLabel = $derived(THEME_DEFINITIONS[theme.variant].label);
// Elements (divider + view mode tabs)
let elements: PillNavElement[] = $derived([

View file

@ -33,8 +33,10 @@
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-icons": "workspace:*",
"@manacore/shared-tailwind": "workspace:*",
"@manacore/shared-theme": "workspace:*",
"@manacore/shared-theme-ui": "workspace:*",
"@manacore/shared-ui": "workspace:*",
"lucide-svelte": "^0.460.0"
},

View file

@ -23,22 +23,22 @@
let themeVariantItems = $derived<PillDropdownItem[]>([
...theme.variants.map((variant) => ({
id: variant,
label: `${THEME_DEFINITIONS[variant].emoji} ${THEME_DEFINITIONS[variant].label}`,
label: THEME_DEFINITIONS[variant].label,
icon: THEME_DEFINITIONS[variant].icon,
onClick: () => theme.setVariant(variant),
active: theme.variant === variant,
})),
{
id: 'all-themes',
label: '🎨 Alle Themes',
label: 'Alle Themes',
icon: 'palette',
onClick: () => goto('/themes'),
active: false,
},
]);
// Current theme variant label
let currentThemeVariantLabel = $derived(
`${THEME_DEFINITIONS[theme.variant].emoji} ${THEME_DEFINITIONS[theme.variant].label}`
);
let currentThemeVariantLabel = $derived(THEME_DEFINITIONS[theme.variant].label);
// Navigation items for Presi
const navItems: PillNavItem[] = [

View file

@ -21,16 +21,20 @@ export default defineConfig({
},
ssr: {
noExternal: [
'@manacore/shared-icons',
'@manacore/shared-ui',
'@manacore/shared-theme',
'@manacore/shared-theme-ui',
'@manacore/shared-auth-ui',
'@manacore/shared-branding',
],
},
optimizeDeps: {
exclude: [
'@manacore/shared-icons',
'@manacore/shared-ui',
'@manacore/shared-theme',
'@manacore/shared-theme-ui',
'@manacore/shared-auth-ui',
'@manacore/shared-branding',
],

View file

@ -15,19 +15,22 @@
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.1.7",
"@types/node": "^20.0.0",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^3.4.0",
"tailwindcss": "^4.1.7",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^6.0.0"
},
"dependencies": {
"@manacore/shared-icons": "workspace:*",
"@manacore/shared-tailwind": "workspace:*",
"@manacore/shared-theme": "workspace:*",
"@manacore/shared-theme-ui": "workspace:*",
"@manacore/shared-ui": "workspace:*",
"@zitare/shared": "workspace:*",
"@zitare/web-ui": "workspace:*"

View file

@ -1,39 +1,14 @@
/* Base Styles & Theme Variables */
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
@import "@manacore/shared-tailwind/themes.css";
/* Scan shared packages for Tailwind classes */
@source "../../../packages/shared-ui/src";
@source "../../../packages/shared-theme-ui/src";
@source "../../../packages/web-ui/src";
/* Zitare-specific CSS Variables */
@layer base {
:root {
/* Color Palette */
--color-primary: 102 126 234; /* #667eea */
--color-primary-dark: 118 75 162; /* #764ba2 */
--color-secondary: 236 72 153; /* #ec4899 */
/* Semantic Colors - Light Mode */
--color-background: 255 255 255;
--color-surface: 245 245 245;
--color-surface-elevated: 255 255 255;
--color-text-primary: 51 51 51;
--color-text-secondary: 102 102 102;
--color-text-tertiary: 153 153 153;
--color-border: 224 224 224;
--color-border-hover: 189 189 189;
/* Status Colors */
--color-success: 34 197 94;
--color-warning: 234 179 8;
--color-error: 239 68 68;
--color-info: 59 130 246;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
/* Spacing */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
@ -54,45 +29,13 @@
--transition-base: 200ms ease;
--transition-slow: 300ms ease;
}
/* Dark Mode */
[data-theme="dark"] {
--color-background: 17 24 39; /* gray-900 */
--color-surface: 31 41 55; /* gray-800 */
--color-surface-elevated: 55 65 81; /* gray-700 */
--color-text-primary: 243 244 246; /* gray-100 */
--color-text-secondary: 209 213 219; /* gray-300 */
--color-text-tertiary: 156 163 175; /* gray-400 */
--color-border: 55 65 81; /* gray-700 */
--color-border-hover: 75 85 99; /* gray-600 */
/* Adjust shadows for dark mode */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
}
/* Apply theme colors to base elements */
body {
background-color: rgb(var(--color-background));
color: rgb(var(--color-text-primary));
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
transition: background-color var(--transition-base), color var(--transition-base);
}
* {
box-sizing: border-box;
}
}
/* Utility Classes */
@layer components {
/* Gradient backgrounds */
.gradient-primary {
background: linear-gradient(135deg, rgb(var(--color-primary)) 0%, rgb(var(--color-primary-dark)) 100%);
background: linear-gradient(135deg, hsl(var(--primary)) 0%, hsl(var(--primary) / 0.8) 100%);
}
/* Glass effect */
@ -102,23 +45,23 @@
border: 1px solid rgba(255, 255, 255, 0.2);
}
[data-theme="dark"] .glass {
.dark .glass {
background: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* Card styles */
.card {
background-color: rgb(var(--color-surface-elevated));
background-color: hsl(var(--card));
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
border: 1px solid rgb(var(--color-border));
border: 1px solid hsl(var(--border));
transition: transform var(--transition-base), box-shadow var(--transition-base);
}
.card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
/* Button styles */
@ -132,26 +75,58 @@
}
.btn-primary {
background: rgb(var(--color-primary));
color: white;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
.btn-primary:hover {
background: rgb(var(--color-primary-dark));
background: hsl(var(--primary) / 0.9);
}
/* Input styles */
.input {
padding: var(--spacing-sm) var(--spacing-md);
border: 2px solid rgb(var(--color-border));
border: 2px solid hsl(var(--border));
border-radius: var(--radius-md);
background-color: rgb(var(--color-background));
color: rgb(var(--color-text-primary));
background-color: hsl(var(--background));
color: hsl(var(--foreground));
transition: border-color var(--transition-fast);
}
.input:focus {
outline: none;
border-color: rgb(var(--color-primary));
border-color: hsl(var(--primary));
}
}
/* Legacy color variable compatibility */
@layer base {
:root {
--color-primary: var(--primary);
--color-background: var(--background);
--color-surface: var(--card);
--color-surface-elevated: var(--card);
--color-text-primary: var(--foreground);
--color-text-secondary: var(--muted-foreground);
--color-text-tertiary: var(--muted-foreground);
--color-border: var(--border);
--color-border-hover: var(--border);
--color-success: 142 76% 36%;
--color-warning: 38 92% 50%;
--color-error: 0 84% 60%;
--color-info: 217 91% 60%;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}
.dark {
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
}
}

View file

@ -1,35 +1,7 @@
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
import { createThemeStore } from '@manacore/shared-theme';
export type Theme = 'light' | 'dark';
function createThemeStore() {
const { subscribe, set, update } = writable<Theme>('light');
return {
subscribe,
set,
toggle: () => {
update((current) => {
const newTheme = current === 'light' ? 'dark' : 'light';
if (browser) {
localStorage.setItem('theme', newTheme);
document.documentElement.setAttribute('data-theme', newTheme);
}
return newTheme;
});
},
init: () => {
if (browser) {
const savedTheme = localStorage.getItem('theme') as Theme | null;
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const initialTheme = savedTheme || (prefersDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', initialTheme);
set(initialTheme);
}
},
};
}
export const theme = createThemeStore();
// Create theme store with Zitare's primary color (amber)
export const theme = createThemeStore({
defaultVariant: 'lume',
storagePrefix: 'zitare',
});

View file

@ -3,8 +3,9 @@
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { PillNavigation } from '@manacore/shared-ui';
import type { PillNavItem } from '@manacore/shared-ui';
import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui';
import { theme } from '$lib/stores/theme';
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
import {
isSidebarMode as sidebarModeStore,
isNavCollapsed as collapsedStore,
@ -18,8 +19,31 @@
let isSidebarMode = $state(false);
let isCollapsed = $state(false);
// Get theme state
let isDark = $state(false);
// Use theme store's isDark directly
let isDark = $derived(theme.isDark);
// Theme variant dropdown items
let themeVariantItems = $derived<PillDropdownItem[]>([
// Theme variants
...theme.variants.map((variant) => ({
id: variant,
label: THEME_DEFINITIONS[variant].label,
icon: THEME_DEFINITIONS[variant].icon,
onClick: () => theme.setVariant(variant),
active: theme.variant === variant,
})),
// Separator and link to full themes page
{
id: 'all-themes',
label: 'Alle Themes',
icon: 'palette',
onClick: () => goto('/themes'),
active: false,
},
]);
// Current theme variant label
let currentThemeVariantLabel = $derived(THEME_DEFINITIONS[theme.variant].label);
// Navigation items for Zitare
const navItems: PillNavItem[] = [
@ -77,14 +101,12 @@
}
function handleToggleTheme() {
theme.toggle();
isDark = !isDark;
theme.toggleMode();
}
onMount(() => {
// Initialize theme
theme.init();
isDark = document.documentElement.classList.contains('dark');
theme.initialize();
// Initialize sidebar mode from localStorage
const savedSidebar = localStorage.getItem('zitare-nav-sidebar');
@ -109,12 +131,12 @@
<ToastContainer />
{#if loading}
<div class="flex min-h-screen items-center justify-center bg-gray-50 dark:bg-gray-900">
<div class="flex min-h-screen items-center justify-center bg-background">
<div class="text-center">
<div
class="mb-4 inline-block h-12 w-12 animate-spin rounded-full border-4 border-solid border-amber-500 border-r-transparent"
class="mb-4 inline-block h-12 w-12 animate-spin rounded-full border-4 border-solid border-primary border-r-transparent"
></div>
<p class="text-gray-500 dark:text-gray-400">Laden...</p>
<p class="text-muted-foreground">Laden...</p>
</div>
</div>
{:else}
@ -133,6 +155,9 @@
{isCollapsed}
onCollapsedChange={handleCollapsedChange}
showThemeToggle={true}
showThemeVariants={true}
{themeVariantItems}
{currentThemeVariantLabel}
showLanguageSwitcher={false}
showLogout={false}
primaryColor="#f59e0b"
@ -140,7 +165,7 @@
<!-- Main Content with dynamic padding based on nav mode -->
<main
class="main-content"
class="main-content bg-background"
class:sidebar-mode={isSidebarMode && !isCollapsed}
class:floating-mode={!isSidebarMode && !isCollapsed}
>

View file

@ -1,6 +1,18 @@
<script lang="ts">
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { theme } from '$lib/stores/theme';
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
import { ThemeColorPreview } from '@manacore/shared-theme-ui';
import { Sparkle, Leaf, Hexagon, Waves } from '@manacore/shared-icons';
// Theme icon mapping
const themeIcons = {
sparkle: Sparkle,
leaf: Leaf,
hexagon: Hexagon,
waves: Waves,
} as const;
// Settings state
let language = $state<'de' | 'en'>('de');
@ -8,8 +20,6 @@
// Load settings from localStorage on mount
onMount(() => {
theme.init();
const savedLanguage = localStorage.getItem('language');
const savedUserName = localStorage.getItem('userName');
@ -23,7 +33,7 @@
}
function toggleDarkMode() {
theme.toggle();
theme.toggleMode();
}
function setLanguageSetting(lang: 'de' | 'en') {
@ -43,37 +53,10 @@
window.location.href = '/';
}
}
const themes = [
{
id: 'default' as const,
name: 'Standard',
desc: 'Klassisch & elegant',
gradientDark: ['#1e293b', '#334155'],
gradientLight: ['#94a3b8', '#cbd5e1'],
color: '#64748b',
},
{
id: 'colorful' as const,
name: 'Farbenfroh',
desc: 'Lebendig & energetisch',
gradientDark: ['#be185d', '#e11d48'],
gradientLight: ['#fb7185', '#fbbf24'],
color: '#e11d48',
},
{
id: 'nature' as const,
name: 'Natur',
desc: 'Beruhigend & harmonisch',
gradientDark: ['#16a34a', '#22c55e'],
gradientLight: ['#86efac', '#34d399'],
color: '#16a34a',
},
];
</script>
<svelte:head>
<title>Einstellungen - Quotes Web App</title>
<title>Einstellungen - Zitare</title>
</svelte:head>
<div class="settings-page">
@ -112,12 +95,35 @@
<p class="setting-description">Dunkles Farbschema verwenden</p>
</div>
<label class="toggle">
<input type="checkbox" checked={$theme === 'dark'} onchange={toggleDarkMode} />
<input type="checkbox" checked={theme.isDark} onchange={toggleDarkMode} />
<span class="toggle-slider"></span>
</label>
</div>
</div>
<!-- Current Theme -->
<div class="setting-card">
<div class="setting-row">
<div class="setting-content">
<h3>Aktuelles Theme</h3>
<p class="setting-description theme-label">
{#if THEME_DEFINITIONS[theme.variant].icon && themeIcons[THEME_DEFINITIONS[theme.variant].icon as keyof typeof themeIcons]}
<svelte:component
this={themeIcons[THEME_DEFINITIONS[theme.variant].icon as keyof typeof themeIcons]}
size={16}
weight="duotone"
class="theme-icon"
/>
{/if}
{THEME_DEFINITIONS[theme.variant].label}
</p>
</div>
<button class="theme-btn" onclick={() => goto('/themes')}>
Themes wählen
</button>
</div>
</div>
<!-- Theme Preview -->
<div class="setting-card">
<div class="setting-header">
@ -126,17 +132,11 @@
</div>
<div class="theme-preview">
<div class="preview-colors">
<div class="color-swatch primary">
<span>Primary</span>
</div>
<div class="color-swatch surface">
<span>Surface</span>
</div>
<div class="color-swatch text">
<span>Text</span>
</div>
</div>
<ThemeColorPreview
variant={theme.variant}
mode={theme.isDark ? 'dark' : 'light'}
size="lg"
/>
</div>
</div>
</section>
@ -214,7 +214,7 @@
h1 {
font-size: 2rem;
margin: 0;
color: rgb(var(--color-text-primary));
color: hsl(var(--foreground));
}
.settings-section {
@ -227,13 +227,13 @@
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: rgb(var(--color-text-secondary));
color: hsl(var(--muted-foreground));
margin-bottom: var(--spacing-md);
}
.setting-card {
background: rgb(var(--color-surface));
border: 1px solid rgb(var(--color-border));
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-md);
@ -265,18 +265,28 @@
h3 {
font-size: 1rem;
font-weight: 600;
color: rgb(var(--color-text-primary));
color: hsl(var(--foreground));
margin: 0 0 var(--spacing-xs) 0;
}
.setting-description {
font-size: 0.875rem;
color: rgb(var(--color-text-secondary));
color: hsl(var(--muted-foreground));
margin: 0;
}
.setting-description.theme-label {
display: flex;
align-items: center;
gap: 0.375rem;
}
.setting-description.theme-label :global(.theme-icon) {
color: hsl(var(--primary));
}
.setting-value {
color: rgb(var(--color-text-secondary));
color: hsl(var(--muted-foreground));
font-size: 0.95rem;
}
@ -285,9 +295,9 @@
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-md);
border: 2px solid rgb(var(--color-border));
background: rgb(var(--color-background));
color: rgb(var(--color-text-primary));
border: 2px solid hsl(var(--border));
background: hsl(var(--background));
color: hsl(var(--foreground));
font-size: 1rem;
margin-bottom: var(--spacing-sm);
transition: border-color var(--transition-fast);
@ -295,7 +305,7 @@
.text-input:focus {
outline: none;
border-color: rgb(var(--color-primary));
border-color: hsl(var(--primary));
}
/* Toggle Switch */
@ -320,7 +330,7 @@
left: 0;
right: 0;
bottom: 0;
background-color: rgb(var(--color-border));
background-color: hsl(var(--border));
transition: var(--transition-base);
border-radius: 31px;
}
@ -338,50 +348,34 @@
}
.toggle input:checked + .toggle-slider {
background-color: rgb(var(--color-primary));
background-color: hsl(var(--primary));
}
.toggle input:checked + .toggle-slider:before {
transform: translateX(20px);
}
/* Theme Button */
.theme-btn {
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-md);
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
font-weight: 500;
border: none;
cursor: pointer;
transition: background var(--transition-fast);
}
.theme-btn:hover {
background: hsl(var(--primary) / 0.9);
}
/* Theme Preview */
.theme-preview {
margin-top: var(--spacing-md);
}
.preview-colors {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-md);
}
.color-swatch {
padding: var(--spacing-lg);
border-radius: var(--radius-md);
text-align: center;
font-weight: 500;
font-size: 0.875rem;
display: flex;
align-items: center;
justify-content: center;
min-height: 80px;
}
.color-swatch.primary {
background: rgb(var(--color-primary));
color: white;
}
.color-swatch.surface {
background: rgb(var(--color-surface));
color: rgb(var(--color-text-primary));
border: 1px solid rgb(var(--color-border));
}
.color-swatch.text {
background: rgb(var(--color-text-primary));
color: rgb(var(--color-background));
}
/* Language Toggle */
@ -389,29 +383,29 @@
display: flex;
border-radius: var(--radius-full);
overflow: hidden;
background: rgb(var(--color-surface));
border: 1px solid rgb(var(--color-border));
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
}
.lang-btn {
padding: var(--spacing-sm) var(--spacing-md);
border: none;
background: transparent;
color: rgb(var(--color-text-primary));
color: hsl(var(--foreground));
font-weight: 500;
cursor: pointer;
transition: background var(--transition-fast);
}
.lang-btn.active {
background: rgb(var(--color-primary));
color: white;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
/* Danger Zone */
.setting-card.danger {
background: rgba(var(--color-error), 0.1);
border-color: rgba(var(--color-error), 0.2);
background: hsl(var(--destructive) / 0.1);
border-color: hsl(var(--destructive) / 0.2);
}
.danger-btn {
@ -427,7 +421,7 @@
}
.danger-title {
color: rgb(var(--color-error));
color: hsl(var(--destructive));
}
.danger-icon {
@ -448,15 +442,5 @@
.section-title {
font-size: 0.7rem;
}
.preview-colors {
grid-template-columns: 1fr;
gap: var(--spacing-sm);
}
.color-swatch {
min-height: 60px;
padding: var(--spacing-md);
}
}
</style>

View file

@ -0,0 +1,19 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { ThemePage } from '@manacore/shared-theme-ui';
import { theme } from '$lib/stores/theme';
</script>
<svelte:head>
<title>Themes | Zitare</title>
</svelte:head>
<ThemePage
currentVariant={theme.variant}
onSelectTheme={(v) => theme.setVariant(v)}
showModeSelector={true}
currentMode={theme.mode}
onModeChange={(m) => theme.setMode(m)}
showBackButton={true}
onBack={() => goto('/')}
/>

View file

@ -1,65 +0,0 @@
import type { Config } from 'tailwindcss';
export default {
content: [
'./src/**/*.{html,js,svelte,ts}',
'../../../packages/shared-ui/src/**/*.{html,js,svelte,ts}',
],
darkMode: ['class', '[data-theme="dark"]'],
theme: {
extend: {
colors: {
primary: {
DEFAULT: 'rgb(var(--color-primary) / <alpha-value>)',
dark: 'rgb(var(--color-primary-dark) / <alpha-value>)',
},
secondary: 'rgb(var(--color-secondary) / <alpha-value>)',
background: 'rgb(var(--color-background) / <alpha-value>)',
surface: {
DEFAULT: 'rgb(var(--color-surface) / <alpha-value>)',
elevated: 'rgb(var(--color-surface-elevated) / <alpha-value>)',
},
text: {
primary: 'rgb(var(--color-text-primary) / <alpha-value>)',
secondary: 'rgb(var(--color-text-secondary) / <alpha-value>)',
tertiary: 'rgb(var(--color-text-tertiary) / <alpha-value>)',
},
border: {
DEFAULT: 'rgb(var(--color-border) / <alpha-value>)',
hover: 'rgb(var(--color-border-hover) / <alpha-value>)',
},
success: 'rgb(var(--color-success) / <alpha-value>)',
warning: 'rgb(var(--color-warning) / <alpha-value>)',
error: 'rgb(var(--color-error) / <alpha-value>)',
info: 'rgb(var(--color-info) / <alpha-value>)',
},
spacing: {
xs: 'var(--spacing-xs)',
sm: 'var(--spacing-sm)',
md: 'var(--spacing-md)',
lg: 'var(--spacing-lg)',
xl: 'var(--spacing-xl)',
'2xl': 'var(--spacing-2xl)',
},
borderRadius: {
sm: 'var(--radius-sm)',
md: 'var(--radius-md)',
lg: 'var(--radius-lg)',
xl: 'var(--radius-xl)',
full: 'var(--radius-full)',
},
boxShadow: {
sm: 'var(--shadow-sm)',
md: 'var(--shadow-md)',
lg: 'var(--shadow-lg)',
xl: 'var(--shadow-xl)',
},
transitionDuration: {
fast: '150ms',
base: '200ms',
slow: '300ms',
},
},
},
plugins: [],
} satisfies Config;

View file

@ -1,8 +1,9 @@
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
plugins: [tailwindcss(), sveltekit()],
server: {
port: 5177,
strictPort: true,
@ -12,7 +13,11 @@ export default defineConfig({
'@zitare/shared',
'@zitare/web-ui',
'@zitare/content',
'@manacore/shared-icons',
'@manacore/shared-ui',
'@manacore/shared-tailwind',
'@manacore/shared-theme',
'@manacore/shared-theme-ui',
],
},
optimizeDeps: {
@ -20,7 +25,11 @@ export default defineConfig({
'@zitare/shared',
'@zitare/web-ui',
'@zitare/content',
'@manacore/shared-icons',
'@manacore/shared-ui',
'@manacore/shared-tailwind',
'@manacore/shared-theme',
'@manacore/shared-theme-ui',
],
},
});

View file

@ -1,5 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",

View file

@ -1,10 +1,18 @@
<script lang="ts">
import type { ThemeVariant } from '@manacore/shared-theme';
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
import { Check, Lock, Clock, Star } from '@manacore/shared-icons';
import { Check, Lock, Clock, Star, Sparkle, Leaf, Hexagon, Waves } from '@manacore/shared-icons';
import type { ThemeStatus } from '../types';
import ThemeColorPreview from './ThemeColorPreview.svelte';
// Theme icon components map
const themeIcons = {
sparkle: Sparkle,
leaf: Leaf,
hexagon: Hexagon,
waves: Waves,
} as const;
interface Props {
variant: ThemeVariant;
isActive: boolean;
@ -93,7 +101,14 @@
<!-- Header -->
<div class="flex items-center gap-2 mb-3">
<span class="text-xl">{definition.emoji}</span>
{#if definition.icon && themeIcons[definition.icon as keyof typeof themeIcons]}
<svelte:component
this={themeIcons[definition.icon as keyof typeof themeIcons]}
size={20}
weight="duotone"
class="text-primary"
/>
{/if}
<span class="font-semibold text-foreground">{definition.label}</span>
</div>

View file

@ -63,6 +63,14 @@
'M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9',
palette:
'M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01',
// Theme icons (Phosphor-style)
sparkle:
'M12 2L13.09 8.26L18 6L15.74 10.91L22 12L15.74 13.09L18 18L13.09 15.74L12 22L10.91 15.74L6 18L8.26 13.09L2 12L8.26 10.91L6 6L10.91 8.26L12 2Z',
leaf: 'M6.5 21.5C3.5 18.5 3.5 12.5 6.5 8.5C9.5 4.5 15 3 20 3C20 8 18.5 13.5 14.5 16.5C10.5 19.5 4.5 19.5 4.5 19.5M6.5 21.5L4.5 19.5M6.5 21.5C6.5 21.5 12 18 14.5 16.5',
hexagon:
'M12 2L21.5 7.5V16.5L12 22L2.5 16.5V7.5L12 2Z',
waves:
'M2 12C2 12 5 8 9 8C13 8 15 12 15 12C15 12 17 16 21 16M2 17C2 17 5 13 9 13C13 13 15 17 15 17C15 17 17 21 21 21M2 7C2 7 5 3 9 3C13 3 15 7 15 7C15 7 17 11 21 11',
};
function getIcon(iconName: string) {

38
pnpm-lock.yaml generated
View file

@ -1728,12 +1728,18 @@ importers:
'@manacore/shared-branding':
specifier: workspace:*
version: link:../../../../packages/shared-branding
'@manacore/shared-icons':
specifier: workspace:*
version: link:../../../../packages/shared-icons
'@manacore/shared-tailwind':
specifier: workspace:*
version: link:../../../../packages/shared-tailwind
'@manacore/shared-theme':
specifier: workspace:*
version: link:../../../../packages/shared-theme
'@manacore/shared-theme-ui':
specifier: workspace:*
version: link:../../../../packages/shared-theme-ui
'@manacore/shared-ui':
specifier: workspace:*
version: link:../../../../packages/shared-ui
@ -2089,6 +2095,18 @@ importers:
apps/zitare/apps/web:
dependencies:
'@manacore/shared-icons':
specifier: workspace:*
version: link:../../../../packages/shared-icons
'@manacore/shared-tailwind':
specifier: workspace:*
version: link:../../../../packages/shared-tailwind
'@manacore/shared-theme':
specifier: workspace:*
version: link:../../../../packages/shared-theme
'@manacore/shared-theme-ui':
specifier: workspace:*
version: link:../../../../packages/shared-theme-ui
'@manacore/shared-ui':
specifier: workspace:*
version: link:../../../../packages/shared-ui
@ -2108,15 +2126,12 @@ importers:
'@sveltejs/vite-plugin-svelte':
specifier: ^5.0.0
version: 5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
'@tailwindcss/vite':
specifier: ^4.1.7
version: 4.1.17(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
'@types/node':
specifier: ^20.0.0
version: 20.19.25
autoprefixer:
specifier: ^10.4.16
version: 10.4.22(postcss@8.5.6)
postcss:
specifier: ^8.4.32
version: 8.5.6
prettier:
specifier: ^3.1.1
version: 3.6.2
@ -2130,8 +2145,8 @@ importers:
specifier: ^4.0.0
version: 4.3.4(picomatch@4.0.3)(svelte@5.44.0)(typescript@5.9.3)
tailwindcss:
specifier: ^3.4.0
version: 3.4.18(tsx@4.20.6)(yaml@2.8.1)
specifier: ^4.1.7
version: 4.1.17
tslib:
specifier: ^2.4.1
version: 2.8.1
@ -23337,6 +23352,13 @@ snapshots:
postcss-selector-parser: 6.0.10
tailwindcss: 4.1.17
'@tailwindcss/vite@4.1.17(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))':
dependencies:
'@tailwindcss/node': 4.1.17
'@tailwindcss/oxide': 4.1.17
tailwindcss: 4.1.17
vite: 6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
'@tailwindcss/vite@4.1.17(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))':
dependencies:
'@tailwindcss/node': 4.1.17