Compare commits

..
Author SHA1 Message Date
rasmusq a3c4067a4c Merge pull request 'feature/builtin-themes' (#4) from feature/builtin-themes into master
Reviewed-on: #4
2025-12-14 19:49:13 +00:00
41 changed files with 103 additions and 7528 deletions
-4
View File
@@ -77,10 +77,6 @@ export const user = pgTable("user", {
password: text(),
username: text(),
dashboardTheme: text("dashboard_theme").default('none'),
dashboardColor: text("dashboard_color"),
lastLogin: timestamp("last_login", { mode: 'string' }),
createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
}, (table) => [
unique("user_email_unique").on(table.email),
unique("user_username_unique").on(table.username),
-2
View File
@@ -13,7 +13,6 @@
}
:root {
color-scheme: light;
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.129 0.042 264.695);
@@ -49,7 +48,6 @@
}
.dark {
color-scheme: dark;
--background: oklch(0.129 0.042 264.695);
--foreground: oklch(0.984 0.003 247.858);
--card: oklch(0.208 0.042 265.755);
-8
View File
@@ -103,14 +103,6 @@ const authConfig: SvelteKitAuthConfig = {
signIn: '/signin'
},
callbacks: {
async signIn({ user }) {
if (user?.id) {
await db.update(users)
.set({ lastLogin: new Date() })
.where(eq(users.id, user.id));
}
return true;
},
async jwt({ token, user }) {
if (user) {
token.id = user.id;
@@ -7,13 +7,9 @@
import { onMount } from 'svelte';
let {
isAuthenticated = false,
fallbackColor = null,
fallbackTheme = null
isAuthenticated = false
}: {
isAuthenticated?: boolean;
fallbackColor?: string | null;
fallbackTheme?: string | null;
} = $props();
const t = $derived(languageStore.t);
@@ -118,8 +114,6 @@
emptyActionLabel={t.dashboard.createLocalWishlist || "Create local wishlist"}
emptyActionHref="/"
showCreateButton={true}
fallbackColor={fallbackColor}
fallbackTheme={fallbackTheme}
>
{#snippet actions(wishlist, unlocked)}
<div class="flex gap-2 flex-wrap">
@@ -11,8 +11,6 @@
itemCount,
color = null,
theme = null,
fallbackColor = null,
fallbackTheme = null,
children
}: {
title: string;
@@ -20,18 +18,14 @@
itemCount: number;
color?: string | null;
theme?: string | null;
fallbackColor?: string | null;
fallbackTheme?: string | null;
children?: Snippet;
} = $props();
const finalColor = $derived(color || fallbackColor);
const finalTheme = $derived(theme || fallbackTheme);
const cardStyle = $derived(getCardStyle(color, fallbackColor));
const cardStyle = $derived(getCardStyle(color));
</script>
<Card style={cardStyle} class="h-full flex flex-col relative overflow-hidden">
<ThemeCard themeName={finalTheme} color={finalColor} />
<ThemeCard themeName={theme} color={color} />
<CardHeader class="flex-shrink-0 relative z-10">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1 sm:gap-2">
<CardTitle class="text-lg flex items-center gap-2 flex-1 min-w-0">
@@ -4,8 +4,6 @@
import EmptyState from '$lib/components/layout/EmptyState.svelte';
import type { Snippet } from 'svelte';
import { flip } from 'svelte/animate';
import { getCardStyle } from '$lib/utils/colors';
import ThemeCard from '$lib/components/themes/ThemeCard.svelte';
let {
title,
@@ -15,8 +13,6 @@
emptyDescription,
emptyActionLabel,
emptyActionHref,
fallbackColor = null,
fallbackTheme = null,
headerAction,
searchBar,
children
@@ -28,15 +24,11 @@
emptyDescription?: string;
emptyActionLabel?: string;
emptyActionHref?: string;
fallbackColor?: string | null;
fallbackTheme?: string | null;
headerAction?: Snippet;
searchBar?: Snippet;
children: Snippet<[any]>;
} = $props();
const cardStyle = $derived(getCardStyle(fallbackColor, null));
let scrollContainer: HTMLElement | null = null;
function handleWheel(event: WheelEvent) {
@@ -52,9 +44,8 @@
}
</script>
<Card style={cardStyle} class="relative overflow-hidden">
<ThemeCard themeName={fallbackTheme} color={fallbackColor} showPattern={false} />
<CardHeader class="relative z-10">
<Card>
<CardHeader>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div class="flex-1 min-w-0">
<CardTitle>{title}</CardTitle>
@@ -72,7 +63,7 @@
</div>
{/if}
</CardHeader>
<CardContent class="relative z-10">
<CardContent>
{#if items && items.length > 0}
<div
bind:this={scrollContainer}
@@ -21,8 +21,6 @@
emptyActionHref,
showCreateButton = false,
hideIfEmpty = false,
fallbackColor = null,
fallbackTheme = null,
actions
}: {
title: string;
@@ -34,8 +32,6 @@
emptyActionHref?: string;
showCreateButton?: boolean;
hideIfEmpty?: boolean;
fallbackColor?: string | null;
fallbackTheme?: string | null;
actions: Snippet<[WishlistItem, boolean]>; // item, unlocked
} = $props();
@@ -130,8 +126,6 @@
{emptyDescription}
{emptyActionLabel}
{emptyActionHref}
{fallbackColor}
{fallbackTheme}
>
{#snippet headerAction()}
<div class="flex flex-col sm:flex-row gap-2">
@@ -156,8 +150,6 @@
itemCount={wishlist.items?.length || 0}
color={wishlist.color}
theme={wishlist.theme}
fallbackColor={fallbackColor}
fallbackTheme={fallbackTheme}
>
{@render actions(item, unlocked)}
</WishlistCard>
@@ -3,7 +3,6 @@
import { ThemeToggle } from '$lib/components/ui/theme-toggle';
import { LanguageToggle } from '$lib/components/ui/language-toggle';
import ThemePicker from '$lib/components/ui/theme-picker.svelte';
import ColorPicker from '$lib/components/ui/ColorPicker.svelte';
import { signOut } from '@auth/sveltekit/client';
import { languageStore } from '$lib/stores/language.svelte';
import { enhance } from '$app/forms';
@@ -12,27 +11,25 @@
userName,
userEmail,
dashboardTheme = 'none',
dashboardColor = null,
isAuthenticated = false,
onThemeUpdate,
onColorUpdate
onThemeUpdate
}: {
userName?: string | null;
userEmail?: string | null;
dashboardTheme?: string;
dashboardColor?: string | null;
isAuthenticated?: boolean;
onThemeUpdate?: (theme: string | null) => void;
onColorUpdate?: (color: string | null) => void;
} = $props();
const t = $derived(languageStore.t);
async function handleThemeChange(theme: string) {
// Update theme immediately for instant visual feedback
if (onThemeUpdate) {
onThemeUpdate(theme);
}
// Only submit to database for authenticated users
if (isAuthenticated) {
const formData = new FormData();
formData.append('theme', theme);
@@ -43,30 +40,6 @@
});
}
}
let localColor = $state(dashboardColor);
$effect(() => {
localColor = dashboardColor;
});
async function handleColorChange() {
if (onColorUpdate) {
onColorUpdate(localColor);
}
if (isAuthenticated) {
const formData = new FormData();
if (localColor) {
formData.append('color', localColor);
}
await fetch('?/updateDashboardColor', {
method: 'POST',
body: formData
});
}
}
</script>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
@@ -79,7 +52,6 @@
{/if}
</div>
<div class="flex items-center gap-1 sm:gap-2 flex-shrink-0">
<ColorPicker bind:color={localColor} onchange={handleColorChange} size="sm" />
<ThemePicker value={dashboardTheme} onValueChange={handleThemeChange} />
<LanguageToggle />
<ThemeToggle />
+1 -14
View File
@@ -1,8 +1,6 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import ThemeBackground from '$lib/components/themes/ThemeBackground.svelte';
import { hexToRgba } from '$lib/utils/colors';
import { themeStore } from '$lib/stores/theme.svelte';
let {
children,
@@ -15,20 +13,9 @@
theme?: string | null;
themeColor?: string | null;
} = $props();
const backgroundStyle = $derived.by(() => {
if (!themeColor) return '';
const isDark = themeStore.getResolvedTheme() === 'dark';
const tintedColor = hexToRgba(themeColor, 0.15);
return isDark
? `background: linear-gradient(${tintedColor}, ${tintedColor}), #000000;`
: `background-color: ${tintedColor};`;
});
</script>
<div class="min-h-screen p-4 md:p-8 relative overflow-hidden" style={backgroundStyle}>
<div class="min-h-screen p-4 md:p-8 relative overflow-hidden">
<ThemeBackground themeName={theme} color={themeColor} />
<div class="max-w-{maxWidth} mx-auto space-y-6 relative z-10">
{@render children()}
@@ -1,8 +1,7 @@
<script lang="ts">
import TopPattern from './svgs/TopPattern.svelte';
import BottomPattern from './svgs/BottomPattern.svelte';
import { getTheme, PATTERN_OPACITY } from '$lib/utils/themes';
import { themeStore } from '$lib/stores/theme.svelte';
import { getTheme, getPatternColor, PATTERN_OPACITY } from '$lib/utils/themes';
let {
themeName,
@@ -17,10 +16,7 @@
} = $props();
const theme = $derived(getTheme(themeName));
const patternColor = $derived.by(() => {
const isDark = themeStore.getResolvedTheme() === 'dark';
return isDark ? '#FFFFFF' : '#000000';
});
const patternColor = $derived(getPatternColor(color));
</script>
{#if theme.pattern !== 'none'}
+6 -12
View File
@@ -1,25 +1,19 @@
<script lang="ts">
import CardPattern from './svgs/CardPattern.svelte';
import { getTheme, PATTERN_OPACITY } from '$lib/utils/themes';
import { themeStore } from '$lib/stores/theme.svelte';
import { getTheme, getPatternColor, PATTERN_OPACITY } from '$lib/utils/themes';
let {
themeName,
color,
showPattern = true
color
}: {
themeName?: string | null;
color?: string | null;
showPattern?: boolean;
themeName?: string;
color?: string;
} = $props();
const theme = $derived(getTheme(themeName));
const patternColor = $derived.by(() => {
const isDark = themeStore.getResolvedTheme() === 'dark';
return isDark ? '#FFFFFF' : '#000000';
});
const patternColor = $derived(getPatternColor(color));
</script>
{#if showPattern && theme.pattern !== 'none'}
{#if theme.pattern !== 'none'}
<CardPattern pattern={theme.pattern} color={patternColor} opacity={PATTERN_OPACITY} />
{/if}
@@ -16,15 +16,17 @@
{#if pattern !== 'none'}
<div
class="fixed bottom-0 left-0 right-0 pointer-events-none overflow-hidden z-0"
class="absolute bottom-0 left-0 right-0 pointer-events-none overflow-hidden"
style="
mask-image: url({patternPath});
-webkit-mask-image: url({patternPath});
mask-size: cover;
mask-repeat: no-repeat;
mask-position: left bottom;
-webkit-mask-size: cover;
mask-repeat: repeat;
-webkit-mask-repeat: repeat;
background-color: {color};
opacity: {opacity};
height: 100vh;
height: 200px;
"
/>
{/if}
@@ -16,15 +16,18 @@
{#if pattern !== 'none'}
<div
class="absolute bottom-0 top-0 right-0 pointer-events-none overflow-hidden rounded-b-lg"
class="absolute bottom-0 right-0 pointer-events-none overflow-hidden rounded-b-lg"
style="
mask-image: url({patternPath});
-webkit-mask-image: url({patternPath});
mask-size: cover;
mask-repeat: no-repeat;
mask-position: right bottom;
-webkit-mask-size: cover;
mask-repeat: repeat;
-webkit-mask-repeat: repeat;
background-color: {color};
opacity: {opacity};
width: 100%;
width: 200px;
height: 200px;
"
/>
{/if}
@@ -16,16 +16,17 @@
{#if pattern !== 'none'}
<div
class="fixed top-0 right-0 left-0 pointer-events-none z-0"
class="absolute top-0 left-0 right-0 pointer-events-none overflow-hidden"
style="
mask-image: url({patternPath});
-webkit-mask-image: url({patternPath});
mask-size: cover;
mask-repeat: no-repeat;
mask-position: right top;
-webkit-mask-size: cover;
mask-repeat: repeat;
-webkit-mask-repeat: repeat;
background-color: {color};
opacity: {opacity};
height: 100vh;
height: 200px;
"
/>
{/if}
+1 -1
View File
@@ -18,7 +18,7 @@
};
const iconSizeClasses = {
sm: 'w-4 h-4',
sm: 'w-3 h-3',
md: 'w-4 h-4',
lg: 'w-5 h-5'
};
+4 -11
View File
@@ -8,18 +8,12 @@
import ColorPicker from '$lib/components/ui/ColorPicker.svelte';
import { enhance } from '$app/forms';
import { languageStore } from '$lib/stores/language.svelte';
import ThemeCard from '$lib/components/themes/ThemeCard.svelte';
import { getCardStyle } from '$lib/utils/colors';
interface Props {
onSuccess?: () => void;
wishlistColor?: string | null;
wishlistTheme?: string | null;
}
let { onSuccess, wishlistColor = null, wishlistTheme = null }: Props = $props();
const cardStyle = $derived(getCardStyle(wishlistColor, null));
let { onSuccess }: Props = $props();
const t = $derived(languageStore.t);
@@ -59,12 +53,11 @@
}
</script>
<Card style={cardStyle} class="relative overflow-hidden">
<ThemeCard themeName={wishlistTheme} color={wishlistColor} showPattern={false} />
<CardHeader class="relative z-10">
<Card>
<CardHeader>
<CardTitle>{t.form.addNewWish}</CardTitle>
</CardHeader>
<CardContent class="relative z-10">
<CardContent>
<form
method="POST"
action="?/addItem"
@@ -9,8 +9,6 @@
import { enhance } from '$app/forms';
import type { Item } from '$lib/server/schema';
import { languageStore } from '$lib/stores/language.svelte';
import ThemeCard from '$lib/components/themes/ThemeCard.svelte';
import { getCardStyle } from '$lib/utils/colors';
interface Props {
item: Item;
@@ -20,13 +18,9 @@
currentPosition?: number;
totalItems?: number;
onPositionChange?: (newPosition: number) => void;
wishlistColor?: string | null;
wishlistTheme?: string | null;
}
let { item, onSuccess, onCancel, onColorChange, currentPosition = 1, totalItems = 1, onPositionChange, wishlistColor = null, wishlistTheme = null }: Props = $props();
const cardStyle = $derived(getCardStyle(wishlistColor, null));
let { item, onSuccess, onCancel, onColorChange, currentPosition = 1, totalItems = 1, onPositionChange }: Props = $props();
const t = $derived(languageStore.t);
@@ -66,12 +60,11 @@
}
</script>
<Card style={cardStyle} class="relative overflow-hidden">
<ThemeCard themeName={wishlistTheme} color={wishlistColor} showPattern={false} />
<CardHeader class="relative z-10">
<Card>
<CardHeader>
<CardTitle>{t.wishlist.editWish}</CardTitle>
</CardHeader>
<CardContent class="relative z-10">
<CardContent>
<form
method="POST"
action="?/updateItem"
@@ -7,27 +7,22 @@
import { enhance } from "$app/forms";
import { flip } from "svelte/animate";
import { languageStore } from '$lib/stores/language.svelte';
import ThemeCard from "$lib/components/themes/ThemeCard.svelte";
import { getCardStyle } from "$lib/utils/colors";
let {
items = $bindable([]),
rearranging,
onStartEditing,
onReorder,
theme = null,
wishlistColor = null
theme = null
}: {
items: Item[];
rearranging: boolean;
onStartEditing: (item: Item) => void;
onReorder: (items: Item[]) => Promise<void>;
theme?: string | null;
wishlistColor?: string | null;
} = $props();
const t = $derived(languageStore.t);
const cardStyle = $derived(getCardStyle(wishlistColor));
</script>
<div class="space-y-4">
@@ -35,7 +30,7 @@
<div class="space-y-4">
{#each items as item (item.id)}
<div animate:flip={{ duration: 300 }}>
<WishlistItem {item} {theme} {wishlistColor} showDragHandle={false}>
<WishlistItem {item} {theme} showDragHandle={false}>
<div class="flex gap-2">
<Button
type="button"
@@ -71,9 +66,8 @@
{/each}
</div>
{:else}
<Card style={cardStyle} class="relative overflow-hidden">
<ThemeCard themeName={theme} color={wishlistColor} />
<CardContent class="p-12 relative z-10">
<Card>
<CardContent class="p-12">
<EmptyState
message={t.wishlist.noWishes + ". " + t.wishlist.addFirstWish + "!"}
/>
@@ -4,18 +4,15 @@
import { Label } from '$lib/components/ui/label';
import { Card, CardContent } from '$lib/components/ui/card';
import { languageStore } from '$lib/stores/language.svelte';
import { getCardStyle } from '$lib/utils/colors';
interface Props {
publicUrl: string;
ownerUrl?: string;
wishlistColor?: string | null;
}
let { publicUrl, ownerUrl, wishlistColor = null }: Props = $props();
let { publicUrl, ownerUrl }: Props = $props();
const t = $derived(languageStore.t);
const cardStyle = $derived(getCardStyle(null, wishlistColor));
let copiedPublic = $state(false);
let copiedOwner = $state(false);
@@ -37,7 +34,7 @@
}
</script>
<Card style={cardStyle}>
<Card>
<CardContent class="space-y-4 pt-6">
<div class="space-y-2">
<Label>{t.wishlist.shareViewOnly}</Label>
@@ -8,7 +8,6 @@
import ThemePicker from "$lib/components/ui/theme-picker.svelte";
import type { Wishlist } from "$lib/server/schema";
import { languageStore } from '$lib/stores/language.svelte';
import { getCardStyle } from '$lib/utils/colors';
let {
wishlist,
@@ -40,8 +39,6 @@
: null,
);
const cardStyle = $derived(getCardStyle(null, wishlistColor));
async function saveTitle() {
if (!wishlistTitle.trim()) {
wishlistTitle = wishlist.title;
@@ -132,13 +129,12 @@
<ColorPicker
bind:color={wishlistColor}
onchange={() => onColorUpdate(wishlistColor)}
size="sm"
/>
</div>
</div>
<!-- Settings Card -->
<Card style={cardStyle}>
<Card>
<CardContent class="pt-6 space-y-4">
<!-- Description -->
<div class="space-y-2">
@@ -13,7 +13,6 @@
children?: any;
showDragHandle?: boolean;
theme?: string | null;
wishlistColor?: string | null;
}
let {
@@ -21,8 +20,7 @@
showImage = true,
children,
showDragHandle = false,
theme = null,
wishlistColor = null
theme = null
}: Props = $props();
const t = $derived(languageStore.t);
@@ -53,7 +51,7 @@
return `${symbol}${amount}`;
}
const cardStyle = $derived(getCardStyle(item.color, wishlistColor));
const cardStyle = $derived(getCardStyle(item.color));
</script>
<Card style={cardStyle} class="relative overflow-hidden">
+1 -5
View File
@@ -13,11 +13,7 @@ export const users = pgTable('user', {
image: text('image'),
password: text('password'),
username: text('username').unique(),
dashboardTheme: text('dashboard_theme').default('none'),
dashboardColor: text('dashboard_color'),
lastLogin: timestamp('last_login', { mode: 'date' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull()
dashboardTheme: text('dashboard_theme').default('none')
});
export const accounts = pgTable(
+5 -4
View File
@@ -5,7 +5,6 @@ type ResolvedTheme = 'light' | 'dark';
class ThemeStore {
current = $state<Theme>('system');
resolved = $state<ResolvedTheme>('light');
constructor() {
if (browser) {
@@ -28,8 +27,6 @@ class ThemeStore {
const isDark = this.current === 'dark' ||
(this.current === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
this.resolved = isDark ? 'dark' : 'light';
if (isDark) {
document.documentElement.classList.add('dark');
} else {
@@ -38,7 +35,11 @@ class ThemeStore {
}
getResolvedTheme(): ResolvedTheme {
return this.resolved;
if (!browser) return 'light';
const isDark = this.current === 'dark' ||
(this.current === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
return isDark ? 'dark' : 'light';
}
toggle() {
+3 -5
View File
@@ -11,10 +11,8 @@ export function hexToRgba(hex: string, alpha: number): string {
/**
* Generate card style string with color, transparency, and blur
*/
export function getCardStyle(color: string | null, fallbackColor?: string | null): string {
const activeColor = color || fallbackColor;
if (!activeColor) return '';
export function getCardStyle(color: string | null): string {
if (!color) return '';
const opacity = color ? 0.2 : 0.15;
return `background-color: ${hexToRgba(activeColor, opacity)} !important; backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important;`;
return `background-color: ${hexToRgba(color, 0.2)} !important; backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important;`;
}
+11 -3
View File
@@ -1,6 +1,6 @@
import { themeStore } from '$lib/stores/theme.svelte';
export type ThemePattern = 'snow' | 'none';
export type ThemePattern = 'waves' | 'geometric' | 'dots' | 'none';
export interface Theme {
name: string;
@@ -13,8 +13,16 @@ export const AVAILABLE_THEMES: Record<string, Theme> = {
pattern: 'none'
},
waves: {
name: 'Snow',
pattern: 'snow'
name: 'Waves',
pattern: 'waves'
},
geometric: {
name: 'Geometric',
pattern: 'geometric'
},
dots: {
name: 'Dots',
pattern: 'dots'
}
};
+2 -18
View File
@@ -85,7 +85,7 @@ export const actions: Actions = {
}
await db.update(wishlists)
.set({ isFavorite: !isFavorite, updatedAt: new Date() })
.set({ isFavorite: !isFavorite })
.where(eq(wishlists.id, wishlistId));
return { success: true };
@@ -171,23 +171,7 @@ export const actions: Actions = {
}
await db.update(users)
.set({ dashboardTheme: theme, updatedAt: new Date() })
.where(eq(users.id, session.user.id));
return { success: true };
},
updateDashboardColor: async ({ request, locals }) => {
const session = await locals.auth();
if (!session?.user?.id) {
throw redirect(303, '/signin');
}
const formData = await request.formData();
const color = formData.get('color') as string | null;
await db.update(users)
.set({ dashboardColor: color, updatedAt: new Date() })
.set({ dashboardTheme: theme })
.where(eq(users.id, session.user.id));
return { success: true };
+2 -41
View File
@@ -24,21 +24,7 @@
}
}
// For anonymous users, get color from localStorage
function getInitialColor() {
if (data.isAuthenticated) {
return data.user?.dashboardColor || null;
} else {
// Anonymous user - get from localStorage
if (typeof window !== 'undefined') {
return localStorage.getItem('dashboardColor') || null;
}
return null;
}
}
let currentTheme = $state(getInitialTheme());
let currentColor = $state(getInitialColor());
// Save to localStorage when theme changes for anonymous users
function handleThemeUpdate(theme: string | null) {
@@ -49,19 +35,6 @@
}
}
// Save to localStorage when color changes for anonymous users
function handleColorUpdate(color: string | null) {
currentColor = color;
if (!data.isAuthenticated && typeof window !== 'undefined') {
if (color) {
localStorage.setItem('dashboardColor', color);
} else {
localStorage.removeItem('dashboardColor');
}
}
}
const t = $derived(languageStore.t);
// Only owned wishlists for "My Wishlists"
@@ -85,23 +58,17 @@
});
</script>
<PageContainer theme={currentTheme} themeColor={currentColor}>
<PageContainer theme={currentTheme} themeColor={null}>
<DashboardHeader
userName={data.user?.name}
userEmail={data.user?.email}
dashboardTheme={currentTheme}
dashboardColor={currentColor}
isAuthenticated={data.isAuthenticated}
onThemeUpdate={handleThemeUpdate}
onColorUpdate={handleColorUpdate}
/>
<!-- Local Wishlists Section (for anonymous and authenticated users) -->
<LocalWishlistsSection
isAuthenticated={data.isAuthenticated}
fallbackColor={currentColor}
fallbackTheme={currentTheme}
/>
<LocalWishlistsSection isAuthenticated={data.isAuthenticated} />
{#if data.isAuthenticated}
<!-- My Wishlists Section -->
@@ -113,8 +80,6 @@
emptyActionLabel={t.dashboard.emptyWishlistsAction}
emptyActionHref="/"
showCreateButton={true}
fallbackColor={currentColor}
fallbackTheme={currentTheme}
>
{#snippet actions(wishlist, unlocked)}
<div class="flex gap-2 flex-wrap">
@@ -170,8 +135,6 @@
emptyMessage={t.dashboard.emptyClaimedWishlists}
emptyDescription={t.dashboard.emptyClaimedWishlistsDescription}
hideIfEmpty={true}
fallbackColor={currentColor}
fallbackTheme={currentTheme}
>
{#snippet actions(wishlist, unlocked)}
<div class="flex gap-2 flex-wrap">
@@ -226,8 +189,6 @@
items={savedWishlists()}
emptyMessage={t.dashboard.emptySavedWishlists}
emptyDescription={t.dashboard.emptySavedWishlistsDescription}
fallbackColor={currentColor}
fallbackTheme={currentTheme}
>
{#snippet actions(saved, unlocked)}
<div class="flex gap-2 flex-wrap">
+13 -8
View File
@@ -7,6 +7,8 @@
CardTitle,
} from "$lib/components/ui/card";
import { Button } from "$lib/components/ui/button";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import type { PageData } from "./$types";
import WishlistItem from "$lib/components/wishlist/WishlistItem.svelte";
import ReservationButton from "$lib/components/wishlist/ReservationButton.svelte";
@@ -17,7 +19,6 @@
import { getCardStyle } from "$lib/utils/colors";
import { languageStore } from '$lib/stores/language.svelte';
import SearchBar from "$lib/components/ui/SearchBar.svelte";
import ThemeCard from "$lib/components/themes/ThemeCard.svelte";
let { data }: { data: PageData } = $props();
@@ -40,6 +41,7 @@
showDashboardLink={true}
/>
<!-- Header -->
<Card style={headerCardStyle}>
<CardContent class="pt-6">
<div class="flex flex-wrap items-start justify-between gap-4">
@@ -53,6 +55,7 @@
</div>
{#if data.isAuthenticated}
{#if data.isClaimed}
<!-- User has claimed this wishlist - show claimed status -->
<Button
variant="outline"
size="sm"
@@ -61,6 +64,7 @@
{t.wishlist.youClaimedThis}
</Button>
{:else if data.isSaved}
<!-- User has saved but not claimed - show unsave button -->
<form method="POST" action="?/unsaveWishlist" use:enhance>
<input
type="hidden"
@@ -72,6 +76,7 @@
</Button>
</form>
{:else}
<!-- Not saved - show save button -->
<form method="POST" action="?/saveWishlist" use:enhance={() => {
return async ({ update }) => {
await update({ reset: false });
@@ -96,14 +101,16 @@
</CardContent>
</Card>
<!-- Search Bar -->
{#if data.wishlist.items && data.wishlist.items.length > 0}
<SearchBar bind:value={searchQuery} />
{/if}
<!-- Items List -->
<div class="space-y-4">
{#if filteredItems.length > 0}
{#each filteredItems as item}
<WishlistItem {item} theme={data.wishlist.theme} wishlistColor={data.wishlist.color}>
<WishlistItem {item} theme={data.wishlist.theme}>
<ReservationButton
itemId={item.id}
isReserved={item.isReserved}
@@ -114,18 +121,16 @@
</WishlistItem>
{/each}
{:else if data.wishlist.items && data.wishlist.items.length > 0}
<Card style={headerCardStyle} class="relative overflow-hidden">
<ThemeCard themeName={data.wishlist.theme} color={data.wishlist.color} />
<CardContent class="p-12 relative z-10">
<Card>
<CardContent class="p-12">
<EmptyState
message="No wishes match your search."
/>
</CardContent>
</Card>
{:else}
<Card style={headerCardStyle} class="relative overflow-hidden">
<ThemeCard themeName={data.wishlist.theme} color={data.wishlist.color} />
<CardContent class="p-12 relative z-10">
<Card>
<CardContent class="p-12">
<EmptyState
message={t.wishlist.emptyWishes}
/>
+3 -13
View File
@@ -23,7 +23,6 @@
let editFormElement = $state<HTMLElement | null>(null);
let searchQuery = $state("");
let currentTheme = $state(data.wishlist.theme || 'none');
let currentColor = $state(data.wishlist.color);
let items = $state<Item[]>([]);
@@ -112,14 +111,9 @@
currentTheme = theme || 'none';
await wishlistUpdates.updateTheme(theme);
}
async function handleColorUpdate(color: string | null) {
currentColor = color;
await wishlistUpdates.updateColor(color);
}
</script>
<PageContainer maxWidth="4xl" theme={currentTheme} themeColor={currentColor}>
<PageContainer maxWidth="4xl" theme={currentTheme} themeColor={data.wishlist.color}>
<Navigation
isAuthenticated={data.isAuthenticated}
showDashboardLink={true}
@@ -129,7 +123,7 @@
wishlist={data.wishlist}
onTitleUpdate={wishlistUpdates.updateTitle}
onDescriptionUpdate={wishlistUpdates.updateDescription}
onColorUpdate={handleColorUpdate}
onColorUpdate={wishlistUpdates.updateColor}
onEndDateUpdate={wishlistUpdates.updateEndDate}
onThemeUpdate={handleThemeUpdate}
/>
@@ -137,7 +131,6 @@
<ShareLinks
publicUrl={data.publicUrl}
ownerUrl="/wishlist/{data.wishlist.ownerToken}/edit"
wishlistColor={currentColor}
/>
<ClaimWishlistSection
@@ -155,7 +148,7 @@
{#if showAddForm}
<div bind:this={addFormElement}>
<AddItemForm onSuccess={handleItemAdded} wishlistColor={currentColor} wishlistTheme={currentTheme} />
<AddItemForm onSuccess={handleItemAdded} />
</div>
{/if}
@@ -169,8 +162,6 @@
currentPosition={items.findIndex(item => item.id === editingItem.id) + 1}
totalItems={items.length}
onPositionChange={handlePositionChange}
wishlistColor={currentColor}
wishlistTheme={currentTheme}
/>
</div>
{/if}
@@ -185,7 +176,6 @@
onStartEditing={startEditing}
onReorder={handleReorder}
theme={currentTheme}
wishlistColor={currentColor}
/>
<DangerZone bind:unlocked={rearranging} />
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52"><path fill="#000000" d="M0 17.83V0h17.83a3 3 0 0 1-5.66 2H5.9A5 5 0 0 1 2 5.9v6.27a3 3 0 0 1-2 5.66zm0 18.34a3 3 0 0 1 2 5.66v6.27A5 5 0 0 1 5.9 52h6.27a3 3 0 0 1 5.66 0H0V36.17zM36.17 52a3 3 0 0 1 5.66 0h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 0 1 0-5.66V52H36.17zM0 31.93v-9.78a5 5 0 0 1 3.8.72l4.43-4.43a3 3 0 1 1 1.42 1.41L5.2 24.28a5 5 0 0 1 0 5.52l4.44 4.43a3 3 0 1 1-1.42 1.42L3.8 31.2a5 5 0 0 1-3.8.72zm52-14.1a3 3 0 0 1 0-5.66V5.9A5 5 0 0 1 48.1 2h-6.27a3 3 0 0 1-5.66-2H52v17.83zm0 14.1a4.97 4.97 0 0 1-1.72-.72l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1 0-5.52l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43c.53-.35 1.12-.6 1.72-.72v9.78zM22.15 0h9.78a5 5 0 0 1-.72 3.8l4.44 4.43a3 3 0 1 1-1.42 1.42L29.8 5.2a5 5 0 0 1-5.52 0l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1-.72-3.8zm0 52c.13-.6.37-1.19.72-1.72l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43a5 5 0 0 1 5.52 0l4.43-4.43a3 3 0 1 1 1.42 1.41l-4.44 4.43c.36.53.6 1.12.72 1.72h-9.78zm9.75-24a5 5 0 0 1-3.9 3.9v6.27a3 3 0 1 1-2 0V31.9a5 5 0 0 1-3.9-3.9h-6.27a3 3 0 1 1 0-2h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 1 1 2 0v6.27a5 5 0 0 1 3.9 3.9h6.27a3 3 0 1 1 0 2H31.9z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52"><path fill="#000000" d="M0 17.83V0h17.83a3 3 0 0 1-5.66 2H5.9A5 5 0 0 1 2 5.9v6.27a3 3 0 0 1-2 5.66zm0 18.34a3 3 0 0 1 2 5.66v6.27A5 5 0 0 1 5.9 52h6.27a3 3 0 0 1 5.66 0H0V36.17zM36.17 52a3 3 0 0 1 5.66 0h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 0 1 0-5.66V52H36.17zM0 31.93v-9.78a5 5 0 0 1 3.8.72l4.43-4.43a3 3 0 1 1 1.42 1.41L5.2 24.28a5 5 0 0 1 0 5.52l4.44 4.43a3 3 0 1 1-1.42 1.42L3.8 31.2a5 5 0 0 1-3.8.72zm52-14.1a3 3 0 0 1 0-5.66V5.9A5 5 0 0 1 48.1 2h-6.27a3 3 0 0 1-5.66-2H52v17.83zm0 14.1a4.97 4.97 0 0 1-1.72-.72l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1 0-5.52l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43c.53-.35 1.12-.6 1.72-.72v9.78zM22.15 0h9.78a5 5 0 0 1-.72 3.8l4.44 4.43a3 3 0 1 1-1.42 1.42L29.8 5.2a5 5 0 0 1-5.52 0l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1-.72-3.8zm0 52c.13-.6.37-1.19.72-1.72l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43a5 5 0 0 1 5.52 0l4.43-4.43a3 3 0 1 1 1.42 1.41l-4.44 4.43c.36.53.6 1.12.72 1.72h-9.78zm9.75-24a5 5 0 0 1-3.9 3.9v6.27a3 3 0 1 1-2 0V31.9a5 5 0 0 1-3.9-3.9h-6.27a3 3 0 1 1 0-2h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 1 1 2 0v6.27a5 5 0 0 1 3.9 3.9h6.27a3 3 0 1 1 0 2H31.9z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52"><path fill="#000000" d="M0 17.83V0h17.83a3 3 0 0 1-5.66 2H5.9A5 5 0 0 1 2 5.9v6.27a3 3 0 0 1-2 5.66zm0 18.34a3 3 0 0 1 2 5.66v6.27A5 5 0 0 1 5.9 52h6.27a3 3 0 0 1 5.66 0H0V36.17zM36.17 52a3 3 0 0 1 5.66 0h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 0 1 0-5.66V52H36.17zM0 31.93v-9.78a5 5 0 0 1 3.8.72l4.43-4.43a3 3 0 1 1 1.42 1.41L5.2 24.28a5 5 0 0 1 0 5.52l4.44 4.43a3 3 0 1 1-1.42 1.42L3.8 31.2a5 5 0 0 1-3.8.72zm52-14.1a3 3 0 0 1 0-5.66V5.9A5 5 0 0 1 48.1 2h-6.27a3 3 0 0 1-5.66-2H52v17.83zm0 14.1a4.97 4.97 0 0 1-1.72-.72l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1 0-5.52l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43c.53-.35 1.12-.6 1.72-.72v9.78zM22.15 0h9.78a5 5 0 0 1-.72 3.8l4.44 4.43a3 3 0 1 1-1.42 1.42L29.8 5.2a5 5 0 0 1-5.52 0l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1-.72-3.8zm0 52c.13-.6.37-1.19.72-1.72l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43a5 5 0 0 1 5.52 0l4.43-4.43a3 3 0 1 1 1.42 1.41l-4.44 4.43c.36.53.6 1.12.72 1.72h-9.78zm9.75-24a5 5 0 0 1-3.9 3.9v6.27a3 3 0 1 1-2 0V31.9a5 5 0 0 1-3.9-3.9h-6.27a3 3 0 1 1 0-2h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 1 1 2 0v6.27a5 5 0 0 1 3.9 3.9h6.27a3 3 0 1 1 0 2H31.9z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52"><path fill="#000000" d="M0 17.83V0h17.83a3 3 0 0 1-5.66 2H5.9A5 5 0 0 1 2 5.9v6.27a3 3 0 0 1-2 5.66zm0 18.34a3 3 0 0 1 2 5.66v6.27A5 5 0 0 1 5.9 52h6.27a3 3 0 0 1 5.66 0H0V36.17zM36.17 52a3 3 0 0 1 5.66 0h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 0 1 0-5.66V52H36.17zM0 31.93v-9.78a5 5 0 0 1 3.8.72l4.43-4.43a3 3 0 1 1 1.42 1.41L5.2 24.28a5 5 0 0 1 0 5.52l4.44 4.43a3 3 0 1 1-1.42 1.42L3.8 31.2a5 5 0 0 1-3.8.72zm52-14.1a3 3 0 0 1 0-5.66V5.9A5 5 0 0 1 48.1 2h-6.27a3 3 0 0 1-5.66-2H52v17.83zm0 14.1a4.97 4.97 0 0 1-1.72-.72l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1 0-5.52l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43c.53-.35 1.12-.6 1.72-.72v9.78zM22.15 0h9.78a5 5 0 0 1-.72 3.8l4.44 4.43a3 3 0 1 1-1.42 1.42L29.8 5.2a5 5 0 0 1-5.52 0l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1-.72-3.8zm0 52c.13-.6.37-1.19.72-1.72l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43a5 5 0 0 1 5.52 0l4.43-4.43a3 3 0 1 1 1.42 1.41l-4.44 4.43c.36.53.6 1.12.72 1.72h-9.78zm9.75-24a5 5 0 0 1-3.9 3.9v6.27a3 3 0 1 1-2 0V31.9a5 5 0 0 1-3.9-3.9h-6.27a3 3 0 1 1 0-2h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 1 1 2 0v6.27a5 5 0 0 1 3.9 3.9h6.27a3 3 0 1 1 0 2H31.9z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52"><path fill="#000000" d="M0 17.83V0h17.83a3 3 0 0 1-5.66 2H5.9A5 5 0 0 1 2 5.9v6.27a3 3 0 0 1-2 5.66zm0 18.34a3 3 0 0 1 2 5.66v6.27A5 5 0 0 1 5.9 52h6.27a3 3 0 0 1 5.66 0H0V36.17zM36.17 52a3 3 0 0 1 5.66 0h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 0 1 0-5.66V52H36.17zM0 31.93v-9.78a5 5 0 0 1 3.8.72l4.43-4.43a3 3 0 1 1 1.42 1.41L5.2 24.28a5 5 0 0 1 0 5.52l4.44 4.43a3 3 0 1 1-1.42 1.42L3.8 31.2a5 5 0 0 1-3.8.72zm52-14.1a3 3 0 0 1 0-5.66V5.9A5 5 0 0 1 48.1 2h-6.27a3 3 0 0 1-5.66-2H52v17.83zm0 14.1a4.97 4.97 0 0 1-1.72-.72l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1 0-5.52l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43c.53-.35 1.12-.6 1.72-.72v9.78zM22.15 0h9.78a5 5 0 0 1-.72 3.8l4.44 4.43a3 3 0 1 1-1.42 1.42L29.8 5.2a5 5 0 0 1-5.52 0l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1-.72-3.8zm0 52c.13-.6.37-1.19.72-1.72l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43a5 5 0 0 1 5.52 0l4.43-4.43a3 3 0 1 1 1.42 1.41l-4.44 4.43c.36.53.6 1.12.72 1.72h-9.78zm9.75-24a5 5 0 0 1-3.9 3.9v6.27a3 3 0 1 1-2 0V31.9a5 5 0 0 1-3.9-3.9h-6.27a3 3 0 1 1 0-2h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 1 1 2 0v6.27a5 5 0 0 1 3.9 3.9h6.27a3 3 0 1 1 0 2H31.9z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52"><path fill="#000000" d="M0 17.83V0h17.83a3 3 0 0 1-5.66 2H5.9A5 5 0 0 1 2 5.9v6.27a3 3 0 0 1-2 5.66zm0 18.34a3 3 0 0 1 2 5.66v6.27A5 5 0 0 1 5.9 52h6.27a3 3 0 0 1 5.66 0H0V36.17zM36.17 52a3 3 0 0 1 5.66 0h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 0 1 0-5.66V52H36.17zM0 31.93v-9.78a5 5 0 0 1 3.8.72l4.43-4.43a3 3 0 1 1 1.42 1.41L5.2 24.28a5 5 0 0 1 0 5.52l4.44 4.43a3 3 0 1 1-1.42 1.42L3.8 31.2a5 5 0 0 1-3.8.72zm52-14.1a3 3 0 0 1 0-5.66V5.9A5 5 0 0 1 48.1 2h-6.27a3 3 0 0 1-5.66-2H52v17.83zm0 14.1a4.97 4.97 0 0 1-1.72-.72l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1 0-5.52l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43c.53-.35 1.12-.6 1.72-.72v9.78zM22.15 0h9.78a5 5 0 0 1-.72 3.8l4.44 4.43a3 3 0 1 1-1.42 1.42L29.8 5.2a5 5 0 0 1-5.52 0l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1-.72-3.8zm0 52c.13-.6.37-1.19.72-1.72l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43a5 5 0 0 1 5.52 0l4.43-4.43a3 3 0 1 1 1.42 1.41l-4.44 4.43c.36.53.6 1.12.72 1.72h-9.78zm9.75-24a5 5 0 0 1-3.9 3.9v6.27a3 3 0 1 1-2 0V31.9a5 5 0 0 1-3.9-3.9h-6.27a3 3 0 1 1 0-2h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 1 1 2 0v6.27a5 5 0 0 1 3.9 3.9h6.27a3 3 0 1 1 0 2H31.9z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 130 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 130 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 78 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52"><path fill="#000000" d="M0 17.83V0h17.83a3 3 0 0 1-5.66 2H5.9A5 5 0 0 1 2 5.9v6.27a3 3 0 0 1-2 5.66zm0 18.34a3 3 0 0 1 2 5.66v6.27A5 5 0 0 1 5.9 52h6.27a3 3 0 0 1 5.66 0H0V36.17zM36.17 52a3 3 0 0 1 5.66 0h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 0 1 0-5.66V52H36.17zM0 31.93v-9.78a5 5 0 0 1 3.8.72l4.43-4.43a3 3 0 1 1 1.42 1.41L5.2 24.28a5 5 0 0 1 0 5.52l4.44 4.43a3 3 0 1 1-1.42 1.42L3.8 31.2a5 5 0 0 1-3.8.72zm52-14.1a3 3 0 0 1 0-5.66V5.9A5 5 0 0 1 48.1 2h-6.27a3 3 0 0 1-5.66-2H52v17.83zm0 14.1a4.97 4.97 0 0 1-1.72-.72l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1 0-5.52l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43c.53-.35 1.12-.6 1.72-.72v9.78zM22.15 0h9.78a5 5 0 0 1-.72 3.8l4.44 4.43a3 3 0 1 1-1.42 1.42L29.8 5.2a5 5 0 0 1-5.52 0l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1-.72-3.8zm0 52c.13-.6.37-1.19.72-1.72l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43a5 5 0 0 1 5.52 0l4.43-4.43a3 3 0 1 1 1.42 1.41l-4.44 4.43c.36.53.6 1.12.72 1.72h-9.78zm9.75-24a5 5 0 0 1-3.9 3.9v6.27a3 3 0 1 1-2 0V31.9a5 5 0 0 1-3.9-3.9h-6.27a3 3 0 1 1 0-2h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 1 1 2 0v6.27a5 5 0 0 1 3.9 3.9h6.27a3 3 0 1 1 0 2H31.9z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52"><path fill="#000000" d="M0 17.83V0h17.83a3 3 0 0 1-5.66 2H5.9A5 5 0 0 1 2 5.9v6.27a3 3 0 0 1-2 5.66zm0 18.34a3 3 0 0 1 2 5.66v6.27A5 5 0 0 1 5.9 52h6.27a3 3 0 0 1 5.66 0H0V36.17zM36.17 52a3 3 0 0 1 5.66 0h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 0 1 0-5.66V52H36.17zM0 31.93v-9.78a5 5 0 0 1 3.8.72l4.43-4.43a3 3 0 1 1 1.42 1.41L5.2 24.28a5 5 0 0 1 0 5.52l4.44 4.43a3 3 0 1 1-1.42 1.42L3.8 31.2a5 5 0 0 1-3.8.72zm52-14.1a3 3 0 0 1 0-5.66V5.9A5 5 0 0 1 48.1 2h-6.27a3 3 0 0 1-5.66-2H52v17.83zm0 14.1a4.97 4.97 0 0 1-1.72-.72l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1 0-5.52l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43c.53-.35 1.12-.6 1.72-.72v9.78zM22.15 0h9.78a5 5 0 0 1-.72 3.8l4.44 4.43a3 3 0 1 1-1.42 1.42L29.8 5.2a5 5 0 0 1-5.52 0l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1-.72-3.8zm0 52c.13-.6.37-1.19.72-1.72l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43a5 5 0 0 1 5.52 0l4.43-4.43a3 3 0 1 1 1.42 1.41l-4.44 4.43c.36.53.6 1.12.72 1.72h-9.78zm9.75-24a5 5 0 0 1-3.9 3.9v6.27a3 3 0 1 1-2 0V31.9a5 5 0 0 1-3.9-3.9h-6.27a3 3 0 1 1 0-2h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 1 1 2 0v6.27a5 5 0 0 1 3.9 3.9h6.27a3 3 0 1 1 0 2H31.9z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52"><path fill="#000000" d="M0 17.83V0h17.83a3 3 0 0 1-5.66 2H5.9A5 5 0 0 1 2 5.9v6.27a3 3 0 0 1-2 5.66zm0 18.34a3 3 0 0 1 2 5.66v6.27A5 5 0 0 1 5.9 52h6.27a3 3 0 0 1 5.66 0H0V36.17zM36.17 52a3 3 0 0 1 5.66 0h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 0 1 0-5.66V52H36.17zM0 31.93v-9.78a5 5 0 0 1 3.8.72l4.43-4.43a3 3 0 1 1 1.42 1.41L5.2 24.28a5 5 0 0 1 0 5.52l4.44 4.43a3 3 0 1 1-1.42 1.42L3.8 31.2a5 5 0 0 1-3.8.72zm52-14.1a3 3 0 0 1 0-5.66V5.9A5 5 0 0 1 48.1 2h-6.27a3 3 0 0 1-5.66-2H52v17.83zm0 14.1a4.97 4.97 0 0 1-1.72-.72l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1 0-5.52l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43c.53-.35 1.12-.6 1.72-.72v9.78zM22.15 0h9.78a5 5 0 0 1-.72 3.8l4.44 4.43a3 3 0 1 1-1.42 1.42L29.8 5.2a5 5 0 0 1-5.52 0l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1-.72-3.8zm0 52c.13-.6.37-1.19.72-1.72l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43a5 5 0 0 1 5.52 0l4.43-4.43a3 3 0 1 1 1.42 1.41l-4.44 4.43c.36.53.6 1.12.72 1.72h-9.78zm9.75-24a5 5 0 0 1-3.9 3.9v6.27a3 3 0 1 1-2 0V31.9a5 5 0 0 1-3.9-3.9h-6.27a3 3 0 1 1 0-2h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 1 1 2 0v6.27a5 5 0 0 1 3.9 3.9h6.27a3 3 0 1 1 0 2H31.9z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB