chore(app): update config, dependencies, storybook, and app shell

This commit is contained in:
Ilia Mashkov
2026-03-02 22:21:19 +03:00
parent 55e2efc222
commit 87bba388dc
11 changed files with 369 additions and 283 deletions

View File

@@ -2,9 +2,15 @@
Component: ThemeDecorator Component: ThemeDecorator
Storybook decorator that initializes ThemeManager for theme-related stories. Storybook decorator that initializes ThemeManager for theme-related stories.
Ensures theme management works correctly in Storybook's iframe environment. Ensures theme management works correctly in Storybook's iframe environment.
Includes a floating theme toggle for universal theme switching across all stories.
--> -->
<script lang="ts"> <script lang="ts">
import { themeManager } from '$features/ChangeAppTheme'; import { themeManager } from '$features/ChangeAppTheme';
import type { ResponsiveManager } from '$shared/lib';
import { IconButton } from '$shared/ui';
import MoonIcon from '@lucide/svelte/icons/moon';
import SunIcon from '@lucide/svelte/icons/sun';
import { getContext } from 'svelte';
import { import {
onDestroy, onDestroy,
onMount, onMount,
@@ -16,15 +22,58 @@ interface Props {
let { children }: Props = $props(); let { children }: Props = $props();
// Get responsive context (set by Decorator)
const responsive = getContext<ResponsiveManager>('responsive');
// Initialize themeManager on mount // Initialize themeManager on mount
onMount(() => { onMount(() => {
themeManager.init(); themeManager.init();
// Add keyboard shortcut for theme toggle (Cmd/Ctrl+Shift+D)
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'D') {
e.preventDefault();
themeManager.toggle();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}); });
// Clean up themeManager when story unmounts // Clean up themeManager when story unmounts
onDestroy(() => { onDestroy(() => {
themeManager.destroy(); themeManager.destroy();
}); });
const theme = $derived(themeManager.value);
const themeLabel = $derived(theme === 'light' ? 'Light' : 'Dark');
</script> </script>
<!-- Floating Theme Toggle -->
<div
class="fixed top-4 right-4 z-50 flex items-center gap-2 px-3 py-2 bg-card border border-border shadow-lg rounded-lg"
title="Toggle theme (Cmd/Ctrl+Shift+D)"
>
<span class="text-xs font-medium text-muted-foreground">Theme: {themeLabel}</span>
<IconButton
onclick={() => themeManager.toggle()}
size={responsive?.isMobile ? 'sm' : 'md'}
variant="ghost"
title="Toggle theme"
>
{#snippet icon()}
{#if theme === 'light'}
<MoonIcon class="size-4" />
{:else}
<SunIcon class="size-4" />
{/if}
{/snippet}
</IconButton>
</div>
<!-- Story Content -->
{@render children()} {@render children()}

View File

@@ -1,11 +1,74 @@
import type { Preview } from '@storybook/svelte-vite'; import type { Preview } from '@storybook/svelte-vite';
import Decorator from './Decorator.svelte'; import Decorator from './Decorator.svelte';
import StoryStage from './StoryStage.svelte'; import StoryStage from './StoryStage.svelte';
import ThemeDecorator from './ThemeDecorator.svelte';
import '../src/app/styles/app.css'; import '../src/app/styles/app.css';
const preview: Preview = { const preview: Preview = {
globalTypes: {
viewport: {
description: 'Viewport size for responsive design',
defaultValue: 'widgetWide',
toolbar: {
icon: 'view',
items: [
{
value: 'reset',
icon: 'refresh',
title: 'Reset viewport',
},
{
value: 'mobile1',
icon: 'mobile',
title: 'iPhone 5/SE',
},
{
value: 'mobile2',
icon: 'mobile',
title: 'iPhone 14 Pro Max',
},
{
value: 'tablet',
icon: 'tablet',
title: 'iPad (Portrait)',
},
{
value: 'desktop',
icon: 'desktop',
title: 'Desktop (Small)',
},
{
value: 'widgetMedium',
icon: 'view',
title: 'Widget Medium',
},
{
value: 'widgetWide',
icon: 'view',
title: 'Widget Wide',
},
{
value: 'widgetExtraWide',
icon: 'view',
title: 'Widget Extra Wide',
},
{
value: 'fullWidth',
icon: 'view',
title: 'Full Width',
},
{
value: 'fullScreen',
icon: 'expand',
title: 'Full Screen',
},
],
dynamicTitle: true,
},
},
},
parameters: { parameters: {
layout: 'fullscreen', layout: 'padded',
controls: { controls: {
matchers: { matchers: {
color: /(background|color)$/i, color: /(background|color)$/i,
@@ -23,7 +86,79 @@ const preview: Preview = {
docs: { docs: {
story: { story: {
// This sets the default height for the iframe in Autodocs // This sets the default height for the iframe in Autodocs
iframeHeight: '400px', iframeHeight: '600px',
},
},
viewport: {
viewports: {
// Mobile devices
mobile1: {
name: 'iPhone 5/SE',
styles: {
width: '320px',
height: '568px',
},
},
mobile2: {
name: 'iPhone 14 Pro Max',
styles: {
width: '414px',
height: '896px',
},
},
// Tablet
tablet: {
name: 'iPad (Portrait)',
styles: {
width: '834px',
height: '1112px',
},
},
desktop: {
name: 'Desktop (Small)',
styles: {
width: '1024px',
height: '1280px',
},
},
// Widget-specific viewports
widgetMedium: {
name: 'Widget Medium',
styles: {
width: '768px',
height: '800px',
},
},
widgetWide: {
name: 'Widget Wide',
styles: {
width: '1024px',
height: '800px',
},
},
widgetExtraWide: {
name: 'Widget Extra Wide',
styles: {
width: '1280px',
height: '800px',
},
},
// Full-width viewports
fullWidth: {
name: 'Full Width',
styles: {
width: '100%',
height: '800px',
},
},
fullScreen: {
name: 'Full Screen',
styles: {
width: '100%',
height: '100%',
},
},
}, },
}, },
@@ -45,6 +180,13 @@ const preview: Preview = {
}, },
decorators: [ decorators: [
// Outermost: initialize ThemeManager for all stories
story => ({
Component: ThemeDecorator,
props: {
children: story(),
},
}),
// Wrap with providers (TooltipProvider, ResponsiveManager) // Wrap with providers (TooltipProvider, ResponsiveManager)
story => ({ story => ({
Component: Decorator, Component: Decorator,

View File

@@ -61,7 +61,6 @@
"tailwindcss": "^4.1.18", "tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vaul-svelte": "^1.0.0-next.7",
"vite": "^7.2.6", "vite": "^7.2.6",
"vitest": "^4.0.16", "vitest": "^4.0.16",
"vitest-browser-svelte": "^2.0.1" "vitest-browser-svelte": "^2.0.1"

View File

@@ -1,3 +1,7 @@
<!--
Component: App
Application root with query provider and layout
-->
<script lang="ts"> <script lang="ts">
/** /**
* App Component * App Component

View File

@@ -11,6 +11,9 @@ import { QueryClientProvider } from '@tanstack/svelte-query';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
interface Props { interface Props {
/**
* Content snippet
*/
children?: Snippet; children?: Snippet;
} }

View File

@@ -306,81 +306,72 @@
animation: nudge 10s ease-in-out infinite; animation: nudge 10s ease-in-out infinite;
} }
* { /* ============================================
scrollbar-width: thin; SCROLLBAR STYLES
scrollbar-color: hsl(0 0% 70% / 0.4) transparent; ============================================ */
/* ---- Modern API: color + width (Chrome 121+, FF 64+) ---- */
@supports (scrollbar-width: auto) {
* {
scrollbar-width: thin;
scrollbar-color: hsl(0 0% 70% / 0.4) var(--color-surface);
}
.dark * {
scrollbar-color: hsl(0 0% 40% / 0.5) var(--color-surface);
}
} }
.dark * { /* ---- Webkit layer: runs ON TOP in Chrome, standalone in old Safari ---- */
scrollbar-color: hsl(0 0% 40% / 0.5) transparent; /* Handles things scrollbar-width can't: hiding buttons, exact sizing */
@supports selector(::-webkit-scrollbar) {
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-button {
display: none; /* kills arrows */
}
::-webkit-scrollbar-track {
background: var(--color-surface);
}
::-webkit-scrollbar-thumb {
background: hsl(0 0% 70% / 0.4);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: hsl(0 0% 50% / 0.6);
}
::-webkit-scrollbar-thumb:active {
background: hsl(0 0% 40% / 0.8);
}
::-webkit-scrollbar-corner {
background: var(--color-surface);
}
.dark ::-webkit-scrollbar-thumb { background: hsl(0 0% 40% / 0.5); }
.dark ::-webkit-scrollbar-thumb:hover { background: hsl(0 0% 55% / 0.6); }
.dark ::-webkit-scrollbar-thumb:active { background: hsl(0 0% 65% / 0.7); }
} }
/* ---- Webkit / Blink ---- */ html {
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: hsl(0 0% 70% / 0);
border-radius: 3px;
transition: background 0.2s ease;
}
/* Show thumb when container is hovered or actively scrolling */
:hover > ::-webkit-scrollbar-thumb,
::-webkit-scrollbar-thumb:hover,
*:hover::-webkit-scrollbar-thumb {
background: hsl(0 0% 70% / 0.4);
}
::-webkit-scrollbar-thumb:hover {
background: hsl(0 0% 50% / 0.6);
}
::-webkit-scrollbar-thumb:active {
background: hsl(0 0% 40% / 0.8);
}
::-webkit-scrollbar-corner {
background: transparent;
}
/* Dark mode */
.dark ::-webkit-scrollbar-thumb {
background: hsl(0 0% 40% / 0);
}
.dark :hover > ::-webkit-scrollbar-thumb,
.dark ::-webkit-scrollbar-thumb:hover,
.dark *:hover::-webkit-scrollbar-thumb {
background: hsl(0 0% 40% / 0.5);
}
.dark ::-webkit-scrollbar-thumb:hover {
background: hsl(0 0% 55% / 0.6);
}
.dark ::-webkit-scrollbar-thumb:active {
background: hsl(0 0% 65% / 0.7);
}
/* ---- Behavior ---- */
* {
scroll-behavior: smooth; scroll-behavior: smooth;
scrollbar-gutter: stable;
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
html { html { scroll-behavior: auto; }
scroll-behavior: auto;
}
} }
body { body {
overscroll-behavior-y: none; overscroll-behavior-y: none;
} }
.scroll-stable {
scrollbar-gutter: stable;
}

View File

@@ -1,3 +1,7 @@
<!--
Component: Layout
Application shell with providers and page wrapper
-->
<script lang="ts"> <script lang="ts">
/** /**
* Layout Component * Layout Component
@@ -11,13 +15,9 @@
* - Footer area (currently empty, reserved for future use) * - Footer area (currently empty, reserved for future use)
*/ */
import { BreadcrumbHeader } from '$entities/Breadcrumb'; import { BreadcrumbHeader } from '$entities/Breadcrumb';
import { import { themeManager } from '$features/ChangeAppTheme';
ThemeSwitch,
themeManager,
} from '$features/ChangeAppTheme';
import GD from '$shared/assets/GD.svg'; import GD from '$shared/assets/GD.svg';
import { ResponsiveProvider } from '$shared/lib'; import { ResponsiveProvider } from '$shared/lib';
import { ScrollArea } from '$shared/shadcn/ui/scroll-area';
import { Provider as TooltipProvider } from '$shared/shadcn/ui/tooltip'; import { Provider as TooltipProvider } from '$shared/shadcn/ui/tooltip';
import { cn } from '$shared/shadcn/utils/shadcn-utils'; import { cn } from '$shared/shadcn/utils/shadcn-utils';
import { import {
@@ -27,36 +27,16 @@ import {
} from 'svelte'; } from 'svelte';
interface Props { interface Props {
/**
* Content snippet
*/
children: Snippet; children: Snippet;
} }
let { children }: Props = $props(); let { children }: Props = $props();
let fontsReady = $state(false); let fontsReady = $state(true);
const theme = $derived(themeManager.value); const theme = $derived(themeManager.value);
/**
* Sets fontsReady flag to true when font for the page logo is loaded.
*/
onMount(async () => {
if (!('fonts' in document)) {
fontsReady = true;
return;
}
const required = ['100'];
const missing = required.filter(
w => !document.fonts.check(`${w} 1em Barlow`),
);
if (missing.length > 0) {
await Promise.all(
missing.map(w => document.fonts.load(`${w} 1em Barlow`)),
);
}
fontsReady = true;
});
onMount(() => themeManager.init()); onMount(() => themeManager.init());
onDestroy(() => themeManager.destroy()); onDestroy(() => themeManager.destroy());
</script> </script>
@@ -94,30 +74,29 @@ onDestroy(() => themeManager.destroy());
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Space+Grotesk:wght@300..700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&family=Syne:wght@800&display=swap" href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Space+Grotesk:wght@300..700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&family=Syne:wght@800&display=swap"
/> />
</noscript> </noscript>
<title>Compare Typography & Typefaces | GlyphDiff</title> <title>GlyphDiff | Typography & Typefaces</title>
</svelte:head> </svelte:head>
<ResponsiveProvider> <ResponsiveProvider>
<div <div
id="app-root" id="app-root"
class={cn( class={cn(
'min-h-screen flex flex-col bg-surface dark:bg-dark-bg', 'min-h-screen w-auto flex flex-col bg-surface dark:bg-dark-bg',
theme === 'dark' ? 'dark' : '', theme === 'dark' ? 'dark' : '',
)} )}
> >
<header> <header>
<BreadcrumbHeader /> <BreadcrumbHeader />
<ThemeSwitch />
</header> </header>
<!-- <ScrollArea class="h-screen w-screen"> --> <!-- <ScrollArea class="h-screen w-screen"> -->
<main class="flex-1 w-full mx-auto px-4 pt-0 pb-10 sm:px-6 sm:pt-8 sm:pb-12 md:px-8 md:pt-10 md:pb-16 lg:px-10 lg:pt-12 lg:pb-20 xl:px-16 relative"> <!-- <main class="flex-1 w-full mx-auto relative"> -->
<TooltipProvider> <TooltipProvider>
{#if fontsReady} {#if fontsReady}
{@render children?.()} {@render children?.()}
{/if} {/if}
</TooltipProvider> </TooltipProvider>
</main> <!-- </main> -->
<!-- </ScrollArea> --> <!-- </ScrollArea> -->
<footer></footer> <footer></footer>
</div> </div>

View File

@@ -4,149 +4,22 @@
--> -->
<script lang="ts"> <script lang="ts">
import { scrollBreadcrumbsStore } from '$entities/Breadcrumb'; import { scrollBreadcrumbsStore } from '$entities/Breadcrumb';
import type { ResponsiveManager } from '$shared/lib'; import { ComparisonView } from '$widgets/ComparisonView';
import { cn } from '$shared/shadcn/utils/shadcn-utils'; import { FontSearchSection } from '$widgets/FontSearch';
import { import { SampleListSection } from '$widgets/SampleList';
Logo,
Section,
} from '$shared/ui';
import ComparisonSlider from '$widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte';
import { FontSearch } from '$widgets/FontSearch';
import { SampleList } from '$widgets/SampleList';
import CodeIcon from '@lucide/svelte/icons/code';
import EyeIcon from '@lucide/svelte/icons/eye';
import LineSquiggleIcon from '@lucide/svelte/icons/line-squiggle';
import ScanSearchIcon from '@lucide/svelte/icons/search';
import {
type Snippet,
getContext,
} from 'svelte';
import { cubicIn } from 'svelte/easing'; import { cubicIn } from 'svelte/easing';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
let searchContainer: HTMLElement;
let isExpanded = $state(true);
const responsive = getContext<ResponsiveManager>('responsive');
function handleTitleStatusChanged(
index: number,
isPast: boolean,
title?: Snippet<[{ className?: string }]>,
id?: string,
) {
if (isPast && title) {
scrollBreadcrumbsStore.add({ index, title, id });
} else {
scrollBreadcrumbsStore.remove(index);
}
return () => {
scrollBreadcrumbsStore.remove(index);
};
}
</script> </script>
<!-- Font List -->
<div <div
class="p-2 sm:p-3 md:p-4 h-full grid gap-3 sm:gap-4 grid-cols-[max-content_1fr]" class="h-full flex flex-col gap-3 sm:gap-4"
in:fade={{ duration: 500, delay: 150, easing: cubicIn }} in:fade={{ duration: 500, delay: 150, easing: cubicIn }}
> >
<Section <section class="w-auto">
class="py-4 sm:py-10 md:py-12 gap-6 sm:gap-8" <ComparisonView />
onTitleStatusChange={handleTitleStatusChanged} </section>
> <main class="w-full pt-0 pb-10 sm:px-6 sm:pt-16 sm:pb-12 md:px-8 md:pt-32 md:pb-16 lg:px-10 lg:pt-48 lg:pb-20 xl:px-16">
{#snippet icon({ className })} <FontSearchSection />
<CodeIcon class={className} /> <SampleListSection index={1} />
{/snippet} </main>
{#snippet description({ className })}
<span class={className}> Project_Codename </span>
{/snippet}
{#snippet content({ className })}
<div class={cn(className, 'col-start-0 col-span-2')}>
<Logo />
</div>
{/snippet}
</Section>
<Section
class="py-4 sm:py-10 md:py-12 gap-6 sm:gap-x-12 sm:gap-y-8"
index={1}
id="optical_comparator"
onTitleStatusChange={handleTitleStatusChanged}
stickyTitle={responsive.isDesktopLarge}
stickyOffset="4rem"
>
{#snippet icon({ className })}
<EyeIcon class={className} />
{/snippet}
{#snippet title({ className })}
<h1 class={className}>
Optical<br />Comparator
</h1>
{/snippet}
{#snippet content({ className })}
<div class={cn(className, !responsive.isDesktopLarge && 'col-start-0 col-span-2')}>
<ComparisonSlider />
</div>
{/snippet}
</Section>
<Section
class="py-4 sm:py-10 md:py-12 gap-6 sm:gap-x-12 sm:gap-y-8"
index={2}
id="query_module"
onTitleStatusChange={handleTitleStatusChanged}
stickyTitle={responsive.isDesktopLarge}
stickyOffset="4rem"
>
{#snippet icon({ className })}
<ScanSearchIcon class={className} />
{/snippet}
{#snippet title({ className })}
<h2 class={className}>
Query<br />Module
</h2>
{/snippet}
{#snippet content({ className })}
<div class={cn(className, !responsive.isDesktopLarge && 'col-start-0 col-span-2')}>
<FontSearch bind:showFilters={isExpanded} />
</div>
{/snippet}
</Section>
<Section
class="py-4 sm:py-10 md:py-12 gap-6 sm:gap-x-12 sm:gap-y-8"
index={3}
id="sample_set"
onTitleStatusChange={handleTitleStatusChanged}
stickyTitle={responsive.isDesktopLarge}
stickyOffset="4rem"
>
{#snippet icon({ className })}
<LineSquiggleIcon class={className} />
{/snippet}
{#snippet title({ className })}
<h2 class={className}>
Sample<br />Set
</h2>
{/snippet}
{#snippet content({ className })}
<div class={cn(className, !responsive.isDesktopLarge && 'col-start-0 col-span-2')}>
<SampleList />
</div>
{/snippet}
</Section>
</div> </div>
<style>
.content {
/* Tells the browser to skip rendering off-screen content */
content-visibility: auto;
/* Helps the browser reserve space without calculating everything */
contain-intrinsic-size: 1px 1000px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>

View File

@@ -48,7 +48,7 @@ export default defineConfig({
statements: 70, statements: 70,
}, },
}, },
setupFiles: [], setupFiles: ['./vitest.setup.unit.ts'],
globals: false, globals: false,
}, },

83
vitest.setup.unit.ts Normal file
View File

@@ -0,0 +1,83 @@
/**
* Setup file for unit tests
*
* This file runs before all unit tests to set up global mocks
* that are needed before any module imports.
*
* IMPORTANT: This runs in Node environment BEFORE jsdom is initialized
* for test files that use @vitest-environment jsdom.
*/
import { vi } from 'vitest';
// Create a storage map that persists through the test session
// This is used for the localStorage mock
// We make it global so tests can clear it
(globalThis as any).__testStorageMap = new Map<string, string>();
// Mock ResizeObserver for tests that import modules using responsiveManager
// This must be done at setup time because the responsiveManager singleton
// is instantiated when the module is first imported
globalThis.ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}
} as any;
// Mock MediaQueryListEvent for tests that need to simulate system theme changes
// @ts-expect-error - Mocking a DOM API
globalThis.MediaQueryListEvent = class MediaQueryListEvent extends Event {
matches: boolean;
media: string;
constructor(type: string, eventInitDict: { matches: boolean; media: string }) {
super(type);
this.matches = eventInitDict.matches;
this.media = eventInitDict.media;
}
};
// Mock window.matchMedia for tests that import modules using media queries
// Some modules (like createPerspectiveManager) use matchMedia during import
Object.defineProperty(globalThis, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
// Mock localStorage for tests that use it during module import
// Some modules (like ThemeManager via createPersistentStore) access localStorage during initialization
// This MUST be a fully functional mock since it's used during module load
const getStorageMap = () => (globalThis as any).__testStorageMap;
Object.defineProperty(globalThis, 'localStorage', {
writable: true,
value: {
get length() {
return getStorageMap().size;
},
clear() {
getStorageMap().clear();
},
getItem(key: string) {
return getStorageMap().get(key) ?? null;
},
setItem(key: string, value: string) {
getStorageMap().set(key, value);
},
removeItem(key: string) {
getStorageMap().delete(key);
},
key(index: number) {
return Array.from(getStorageMap().keys())[index] ?? null;
},
},
});

View File

@@ -2470,7 +2470,6 @@ __metadata:
tailwindcss: "npm:^4.1.18" tailwindcss: "npm:^4.1.18"
tw-animate-css: "npm:^1.4.0" tw-animate-css: "npm:^1.4.0"
typescript: "npm:^5.9.3" typescript: "npm:^5.9.3"
vaul-svelte: "npm:^1.0.0-next.7"
vite: "npm:^7.2.6" vite: "npm:^7.2.6"
vitest: "npm:^4.0.16" vitest: "npm:^4.0.16"
vitest-browser-svelte: "npm:^2.0.1" vitest-browser-svelte: "npm:^2.0.1"
@@ -3626,17 +3625,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"runed@npm:^0.23.2":
version: 0.23.4
resolution: "runed@npm:0.23.4"
dependencies:
esm-env: "npm:^1.0.0"
peerDependencies:
svelte: ^5.7.0
checksum: 10c0/e27400af9e69b966dca449b851e82e09b3d2ddde4095ba72237599aa80fc248a23d0737c0286f751ca6c12721a5e09eb21b9d8cc872cbd70e7b161442818eece
languageName: node
linkType: hard
"runed@npm:^0.35.1": "runed@npm:^0.35.1":
version: 0.35.1 version: 0.35.1
resolution: "runed@npm:0.35.1" resolution: "runed@npm:0.35.1"
@@ -3920,19 +3908,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"svelte-toolbelt@npm:^0.7.1":
version: 0.7.1
resolution: "svelte-toolbelt@npm:0.7.1"
dependencies:
clsx: "npm:^2.1.1"
runed: "npm:^0.23.2"
style-to-object: "npm:^1.0.8"
peerDependencies:
svelte: ^5.0.0
checksum: 10c0/a50db97c851fa65af7fbf77007bd76730a179ac0239c0121301bd26682c1078a4ffea77835492550b133849a42d3dffee0714ae076154d86be8d0b3a84c9a9bf
languageName: node
linkType: hard
"svelte2tsx@npm:^0.7.44, svelte2tsx@npm:~0.7.46": "svelte2tsx@npm:^0.7.44, svelte2tsx@npm:~0.7.46":
version: 0.7.46 version: 0.7.46
resolution: "svelte2tsx@npm:0.7.46" resolution: "svelte2tsx@npm:0.7.46"
@@ -4257,18 +4232,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"vaul-svelte@npm:^1.0.0-next.7":
version: 1.0.0-next.7
resolution: "vaul-svelte@npm:1.0.0-next.7"
dependencies:
runed: "npm:^0.23.2"
svelte-toolbelt: "npm:^0.7.1"
peerDependencies:
svelte: ^5.0.0
checksum: 10c0/7a459122b39c9ef6bd830b525d5f6acbc07575491e05c758d9dfdb993cc98ab4dee4a9c022e475760faaf1d7bd8460a1434965431d36885a3ee48315ffa54eb3
languageName: node
linkType: hard
"vite@npm:^6.0.0 || ^7.0.0, vite@npm:^7.2.6": "vite@npm:^6.0.0 || ^7.0.0, vite@npm:^7.2.6":
version: 7.3.0 version: 7.3.0
resolution: "vite@npm:7.3.0" resolution: "vite@npm:7.3.0"