Fixes/minor tweaks #39
@@ -10,6 +10,9 @@ node_modules
|
|||||||
/build
|
/build
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
|
# IDE settings
|
||||||
|
.vscode
|
||||||
|
|
||||||
# Git worktrees (isolated development branches)
|
# Git worktrees (isolated development branches)
|
||||||
.worktrees
|
.worktrees
|
||||||
|
|
||||||
|
|||||||
+4
-5
@@ -13,7 +13,7 @@
|
|||||||
"https://plugins.dprint.dev/typescript-0.93.0.wasm",
|
"https://plugins.dprint.dev/typescript-0.93.0.wasm",
|
||||||
"https://plugins.dprint.dev/json-0.19.3.wasm",
|
"https://plugins.dprint.dev/json-0.19.3.wasm",
|
||||||
"https://plugins.dprint.dev/markdown-0.17.8.wasm",
|
"https://plugins.dprint.dev/markdown-0.17.8.wasm",
|
||||||
"https://plugins.dprint.dev/g-plane/markup_fmt-v0.25.3.wasm"
|
"https://plugins.dprint.dev/g-plane/markup_fmt-v0.27.0.wasm"
|
||||||
],
|
],
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"lineWidth": 120,
|
"lineWidth": 120,
|
||||||
@@ -57,9 +57,8 @@
|
|||||||
"quotes": "double",
|
"quotes": "double",
|
||||||
"scriptIndent": false,
|
"scriptIndent": false,
|
||||||
"styleIndent": false,
|
"styleIndent": false,
|
||||||
|
"formatComments": true,
|
||||||
"vBindStyle": "short",
|
"svelteAttrShorthand": true,
|
||||||
"vOnStyle": "short",
|
"svelteDirectiveShorthand": true
|
||||||
"formatComments": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-33
@@ -27,45 +27,45 @@
|
|||||||
"build-storybook": "storybook build"
|
"build-storybook": "storybook build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@chromatic-com/storybook": "^4.1.3",
|
"@chromatic-com/storybook": "5.1.2",
|
||||||
"@internationalized/date": "^3.10.0",
|
"@internationalized/date": "3.12.1",
|
||||||
"@lucide/svelte": "^0.561.0",
|
"@lucide/svelte": "^1.14.0",
|
||||||
"@playwright/test": "^1.57.0",
|
"@playwright/test": "1.59.1",
|
||||||
"@storybook/addon-a11y": "^10.1.11",
|
"@storybook/addon-a11y": "10.3.6",
|
||||||
"@storybook/addon-docs": "^10.1.11",
|
"@storybook/addon-docs": "10.3.6",
|
||||||
"@storybook/addon-svelte-csf": "^5.0.10",
|
"@storybook/addon-svelte-csf": "5.1.2",
|
||||||
"@storybook/addon-vitest": "^10.1.11",
|
"@storybook/addon-vitest": "10.3.6",
|
||||||
"@storybook/svelte-vite": "^10.1.11",
|
"@storybook/svelte-vite": "10.3.6",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
"@sveltejs/vite-plugin-svelte": "7.1.0",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "4.2.4",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/svelte": "^5.3.1",
|
"@testing-library/svelte": "^5.3.1",
|
||||||
"@tsconfig/svelte": "^5.0.6",
|
"@tsconfig/svelte": "5.0.8",
|
||||||
"@types/jsdom": "^27",
|
"@types/jsdom": "28.0.1",
|
||||||
"@vitest/browser-playwright": "^4.0.16",
|
"@vitest/browser-playwright": "4.1.5",
|
||||||
"@vitest/coverage-v8": "^4.0.16",
|
"@vitest/coverage-v8": "4.1.5",
|
||||||
"bits-ui": "^2.14.4",
|
"bits-ui": "2.18.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dprint": "^0.50.2",
|
"dprint": "0.54.0",
|
||||||
"jsdom": "^27.4.0",
|
"jsdom": "29.1.1",
|
||||||
"lefthook": "^2.0.13",
|
"lefthook": "2.1.6",
|
||||||
"oxlint": "^1.35.0",
|
"oxlint": "1.62.0",
|
||||||
"playwright": "^1.57.0",
|
"playwright": "1.59.1",
|
||||||
"storybook": "^10.1.11",
|
"storybook": "10.3.6",
|
||||||
"svelte": "^5.45.6",
|
"svelte": "5.55.5",
|
||||||
"svelte-check": "^4.3.4",
|
"svelte-check": "4.4.8",
|
||||||
"svelte-language-server": "^0.17.23",
|
"svelte-language-server": "0.18.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "3.5.0",
|
||||||
"tailwind-variants": "^3.2.2",
|
"tailwind-variants": "^3.2.2",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "4.2.4",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "6.0.3",
|
||||||
"vite": "^7.2.6",
|
"vite": "8.0.10",
|
||||||
"vitest": "^4.0.16",
|
"vitest": "4.1.5",
|
||||||
"vitest-browser-svelte": "^2.0.1"
|
"vitest-browser-svelte": "2.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chenglou/pretext": "^0.0.5",
|
"@chenglou/pretext": "0.0.6",
|
||||||
"@tanstack/svelte-query": "^6.0.14"
|
"@tanstack/svelte-query": "6.1.28"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
@@ -36,6 +36,8 @@ declare module '*.jpg' {
|
|||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '*.css';
|
||||||
|
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ let mockObserverInstances: MockIntersectionObserver[] = [];
|
|||||||
class MockIntersectionObserver implements IntersectionObserver {
|
class MockIntersectionObserver implements IntersectionObserver {
|
||||||
root = null;
|
root = null;
|
||||||
rootMargin = '';
|
rootMargin = '';
|
||||||
|
scrollMargin = '';
|
||||||
thresholds: number[] = [];
|
thresholds: number[] = [];
|
||||||
readonly callbacks: Array<(entries: IntersectionObserverEntry[], observer: IntersectionObserver) => void> = [];
|
readonly callbacks: Array<(entries: IntersectionObserverEntry[], observer: IntersectionObserver) => void> = [];
|
||||||
readonly observedElements = new Set<Element>();
|
readonly observedElements = new Set<Element>();
|
||||||
|
|||||||
-2
@@ -2,9 +2,7 @@
|
|||||||
* Yields to main thread during CPU-intensive parsing. Uses scheduler.yield() where available or MessageChannel fallback.
|
* Yields to main thread during CPU-intensive parsing. Uses scheduler.yield() where available or MessageChannel fallback.
|
||||||
*/
|
*/
|
||||||
export async function yieldToMainThread(): Promise<void> {
|
export async function yieldToMainThread(): Promise<void> {
|
||||||
// @ts-expect-error - scheduler not in TypeScript lib yet
|
|
||||||
if (typeof scheduler !== 'undefined' && 'yield' in scheduler) {
|
if (typeof scheduler !== 'undefined' && 'yield' in scheduler) {
|
||||||
// @ts-expect-error - scheduler.yield not in TypeScript lib yet
|
|
||||||
await scheduler.yield();
|
await scheduler.yield();
|
||||||
} else {
|
} else {
|
||||||
await new Promise<void>(resolve => {
|
await new Promise<void>(resolve => {
|
||||||
|
|||||||
@@ -72,74 +72,76 @@ $effect(() => {
|
|||||||
|
|
||||||
{#if !hidden}
|
{#if !hidden}
|
||||||
{#if responsive.isMobileOrTablet}
|
{#if responsive.isMobileOrTablet}
|
||||||
<Popover.Root bind:open>
|
<div class={className}>
|
||||||
<Popover.Trigger>
|
<Popover.Root bind:open>
|
||||||
{#snippet child({ props })}
|
<Popover.Trigger>
|
||||||
<Button class={className} variant="primary" {...props}>
|
{#snippet child({ props })}
|
||||||
{#snippet icon()}
|
<Button variant="primary" {...props}>
|
||||||
<Settings2Icon class="size-4" />
|
{#snippet icon()}
|
||||||
{/snippet}
|
<Settings2Icon class="size-4" />
|
||||||
</Button>
|
|
||||||
{/snippet}
|
|
||||||
</Popover.Trigger>
|
|
||||||
|
|
||||||
<Popover.Portal>
|
|
||||||
<Popover.Content
|
|
||||||
side="top"
|
|
||||||
align="end"
|
|
||||||
sideOffset={8}
|
|
||||||
class={cn(
|
|
||||||
'z-50 w-72',
|
|
||||||
'bg-surface dark:bg-dark-card',
|
|
||||||
'border border-subtle',
|
|
||||||
'shadow-[0_20px_40px_-10px_rgba(0,0,0,0.15)]',
|
|
||||||
'rounded-none p-4',
|
|
||||||
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
||||||
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
||||||
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
||||||
'data-[side=top]:slide-in-from-bottom-2',
|
|
||||||
'data-[side=bottom]:slide-in-from-top-2',
|
|
||||||
)}
|
|
||||||
interactOutsideBehavior="close"
|
|
||||||
escapeKeydownBehavior="close"
|
|
||||||
>
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="flex items-center justify-between mb-3 pb-3 border-b border-subtle">
|
|
||||||
<div class="flex items-center gap-1.5">
|
|
||||||
<Settings2Icon size={12} class="text-swiss-red" />
|
|
||||||
<span
|
|
||||||
class="text-3xs font-mono uppercase tracking-widest font-bold text-swiss-black dark:text-neutral-200"
|
|
||||||
>
|
|
||||||
CONTROLS
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Popover.Close>
|
|
||||||
{#snippet child({ props })}
|
|
||||||
<button
|
|
||||||
{...props}
|
|
||||||
class="inline-flex items-center justify-center size-6 rounded-none hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
|
|
||||||
aria-label="Close controls"
|
|
||||||
>
|
|
||||||
<XIcon class="size-3.5 text-neutral-500" />
|
|
||||||
</button>
|
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Popover.Close>
|
</Button>
|
||||||
</div>
|
{/snippet}
|
||||||
|
</Popover.Trigger>
|
||||||
|
|
||||||
<!-- Controls -->
|
<Popover.Portal>
|
||||||
{#each typographySettingsStore.controls as control (control.id)}
|
<Popover.Content
|
||||||
<ControlGroup label={control.controlLabel ?? ''}>
|
side="top"
|
||||||
<Slider
|
align="end"
|
||||||
bind:value={control.instance.value}
|
sideOffset={8}
|
||||||
min={control.instance.min}
|
class={cn(
|
||||||
max={control.instance.max}
|
'z-50 w-72',
|
||||||
step={control.instance.step}
|
'bg-surface dark:bg-dark-card',
|
||||||
/>
|
'border border-subtle',
|
||||||
</ControlGroup>
|
'shadow-[0_20px_40px_-10px_rgba(0,0,0,0.15)]',
|
||||||
{/each}
|
'rounded-none p-4',
|
||||||
</Popover.Content>
|
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
||||||
</Popover.Portal>
|
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||||
</Popover.Root>
|
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
||||||
|
'data-[side=top]:slide-in-from-bottom-2',
|
||||||
|
'data-[side=bottom]:slide-in-from-top-2',
|
||||||
|
)}
|
||||||
|
interactOutsideBehavior="close"
|
||||||
|
escapeKeydownBehavior="close"
|
||||||
|
>
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex items-center justify-between mb-3 pb-3 border-b border-subtle">
|
||||||
|
<div class="flex items-center gap-1.5">
|
||||||
|
<Settings2Icon size={12} class="text-swiss-red" />
|
||||||
|
<span
|
||||||
|
class="text-3xs font-mono uppercase tracking-widest font-bold text-swiss-black dark:text-neutral-200"
|
||||||
|
>
|
||||||
|
CONTROLS
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Popover.Close>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<button
|
||||||
|
{...props}
|
||||||
|
class="inline-flex items-center justify-center size-6 rounded-none hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
|
||||||
|
aria-label="Close controls"
|
||||||
|
>
|
||||||
|
<XIcon class="size-3.5 text-neutral-500" />
|
||||||
|
</button>
|
||||||
|
{/snippet}
|
||||||
|
</Popover.Close>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controls -->
|
||||||
|
{#each typographySettingsStore.controls as control (control.id)}
|
||||||
|
<ControlGroup label={control.controlLabel ?? ''}>
|
||||||
|
<Slider
|
||||||
|
bind:value={control.instance.value}
|
||||||
|
min={control.instance.min}
|
||||||
|
max={control.instance.max}
|
||||||
|
step={control.instance.step}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
{/each}
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover.Portal>
|
||||||
|
</Popover.Root>
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class={cn('w-full md:w-auto', className)}
|
class={cn('w-full md:w-auto', className)}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ let selected = $state(false);
|
|||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
{...args}
|
{...args}
|
||||||
selected={selected}
|
{selected}
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
selected = !selected;
|
selected = !selected;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ const { children, class: className, render }: Props = $props();
|
|||||||
|
|
||||||
{#if render}
|
{#if render}
|
||||||
{@render render({
|
{@render render({
|
||||||
class: cn(
|
class: cn(
|
||||||
'font-mono text-3xs sm:text-2xs lowercase tracking-wider-mono text-text-soft',
|
'font-mono text-3xs sm:text-2xs lowercase tracking-wider-mono text-text-soft',
|
||||||
className,
|
className,
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
{:else if children}
|
{:else if children}
|
||||||
<span
|
<span
|
||||||
class={cn(
|
class={cn(
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const flyParams: FlyParams = {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{#if headerTitle}
|
{#if headerTitle}
|
||||||
<SectionHeader title={headerTitle} subtitle={headerSubtitle} index={index} />
|
<SectionHeader title={headerTitle} subtitle={headerSubtitle} {index} />
|
||||||
{/if}
|
{/if}
|
||||||
<SectionTitle text={title} />
|
<SectionTitle text={title} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -288,24 +288,24 @@ $effect(() => {
|
|||||||
>
|
>
|
||||||
{#if itemIndex < items.length}
|
{#if itemIndex < items.length}
|
||||||
{@render children({
|
{@render children({
|
||||||
item: items[itemIndex],
|
item: items[itemIndex],
|
||||||
index: itemIndex,
|
index: itemIndex,
|
||||||
isFullyVisible: row.isFullyVisible,
|
isFullyVisible: row.isFullyVisible,
|
||||||
isPartiallyVisible: row.isPartiallyVisible,
|
isPartiallyVisible: row.isPartiallyVisible,
|
||||||
proximity: row.proximity,
|
proximity: row.proximity,
|
||||||
})}
|
})}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="min-h-0">
|
<div class="min-h-0">
|
||||||
{#if itemIndex < items.length}
|
{#if itemIndex < items.length}
|
||||||
{@render children({
|
{@render children({
|
||||||
item: items[itemIndex],
|
item: items[itemIndex],
|
||||||
index: itemIndex,
|
index: itemIndex,
|
||||||
isFullyVisible: row.isFullyVisible,
|
isFullyVisible: row.isFullyVisible,
|
||||||
isPartiallyVisible: row.isPartiallyVisible,
|
isPartiallyVisible: row.isPartiallyVisible,
|
||||||
proximity: row.proximity,
|
proximity: row.proximity,
|
||||||
})}
|
})}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ function isFontReady(font: UnifiedFont): boolean {
|
|||||||
data-font-list
|
data-font-list
|
||||||
weight={DEFAULT_FONT_WEIGHT}
|
weight={DEFAULT_FONT_WEIGHT}
|
||||||
itemHeight={44}
|
itemHeight={44}
|
||||||
|
gap={2}
|
||||||
class="bg-transparent min-h-0 h-full scroll-stable py-2 pl-6 pr-4"
|
class="bg-transparent min-h-0 h-full scroll-stable py-2 pl-6 pr-4"
|
||||||
>
|
>
|
||||||
{#snippet skeleton()}
|
{#snippet skeleton()}
|
||||||
|
|||||||
@@ -4,8 +4,18 @@
|
|||||||
Updates the global filterManager query to filter the font list.
|
Updates the global filterManager query to filter the font list.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { filterManager } from '$features/GetFonts';
|
import { fontStore } from '$entities/Font';
|
||||||
|
import {
|
||||||
|
filterManager,
|
||||||
|
mapManagerToParams,
|
||||||
|
} from '$features/GetFonts';
|
||||||
import { SearchBar } from '$shared/ui';
|
import { SearchBar } from '$shared/ui';
|
||||||
|
import { untrack } from 'svelte';
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const params = mapManagerToParams(filterManager);
|
||||||
|
untrack(() => fontStore.setParams(params));
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-6 border-b border-black/5">
|
<div class="p-6 border-b border-black/5">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const currentYear = new Date().getFullYear();
|
|||||||
>
|
>
|
||||||
<!-- Project Name (Horizontal) -->
|
<!-- Project Name (Horizontal) -->
|
||||||
{#if isVertical}
|
{#if isVertical}
|
||||||
<div class="pointer-events-auto items-center gap-2 bg-surface/80 dark:bg-dark-bg/80 backdrop-blur-sm px-3 py-1 border border-subtle">
|
<div class="flex flex-row pointer-events-auto items-center gap-2 bg-surface/80 dark:bg-dark-bg/80 backdrop-blur-sm px-2 py-1 border border-subtle">
|
||||||
<div class="w-1.5 h-1.5 bg-brand"></div>
|
<div class="w-1.5 h-1.5 bg-brand"></div>
|
||||||
<span class="text-2xs font-mono uppercase tracking-wider-mono text-neutral-500 dark:text-neutral-400">
|
<span class="text-2xs font-mono uppercase tracking-wider-mono text-neutral-500 dark:text-neutral-400">
|
||||||
GlyphDiff © 2025 — {currentYear}
|
GlyphDiff © 2025 — {currentYear}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"rootDir": ".",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
@@ -22,7 +23,6 @@
|
|||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
|
|
||||||
/* Path Aliases */
|
/* Path Aliases */
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"$lib/*": ["./src/lib/*"],
|
"$lib/*": ["./src/lib/*"],
|
||||||
"$app/*": ["./src/app/*"],
|
"$app/*": ["./src/app/*"],
|
||||||
|
|||||||
Reference in New Issue
Block a user