refactor: sweep call sites onto design-system utilities + bug fixes

Replace inline class clusters with the design-system utilities and
tokens established in the prior two commits. No behavior changes
intended beyond two real bug fixes.

Bug fixes:
- SampleList.svelte: 'border-border-subtle bg-background-40' was a
  silent no-op (both classes mis-spelled). Now 'border-subtle
  bg-background/40' applies as intended.
- FontList.svelte: 'h-[44px]' → 'h-11' (44px = 2.75rem = spacing-11,
  no need for arbitrary value).

Sweeps:
- TypographyMenu: popover + floating bar now use surface-popover /
  surface-floating + shadow-popover.
- FontList + FilterGroup: tertiary list buttons use the new
  Button layout="block-list-row" variant; skeleton fills use
  the skeleton-fill utility.
- Footer / BreadcrumbHeader: surface-floating absorbs the
  bg-surface/blur/border cluster. Footer bumped to z-20 with a
  comment explaining the stacking against SidebarContainer (z-40/50).
- FontSampler: surface-card + hover shadow-stamp-card token.
- SliderArea: surface-canvas, flex-center, shadow-floating-panel
  tokens (light + dark variants).
- Sidebar / Header / ButtonGroup / Layout / SidebarContainer:
  bg-surface dark:bg-dark-bg → surface-canvas (8 sites);
  SidebarContainer mobile panel uses shadow-overlay.
- Loader / Thumb: flex items-center justify-center → flex-center;
  Thumb durations → duration-fast.
- ComboControl: trigger uses surface-card-elevated when open,
  popover uses surface-card-elevated, label cluster → text-label-mono,
  flex-center for the trigger interior.
- Slider: shadow-sm → shadow-rest, duration-150 → duration-fast.
- text-secondary → text-subtle across Input, Slider, ComboControl
  (matches the rename in the styles commit).
- Link: reverted earlier surface-floating attempt — Link's original
  bg-surface/80 backdrop-blur pattern was thinner than surface-floating
  (no border, smaller blur), and the Footer was overlaying its own
  border-subtle on top, fighting the utility. Kept the original style.
This commit is contained in:
Ilia Mashkov
2026-05-25 10:20:40 +03:00
parent 15bb961ccc
commit 5b7ec03973
18 changed files with 56 additions and 58 deletions
+1 -1
View File
@@ -74,7 +74,7 @@ onDestroy(() => themeManager.destroy());
<div <div
id="app-root" id="app-root"
class={cn( class={cn(
'min-h-dvh w-auto flex flex-col bg-surface dark:bg-dark-bg relative', 'min-h-dvh w-auto flex flex-col surface-canvas relative',
theme === 'dark' ? 'dark' : '', theme === 'dark' ? 'dark' : '',
)} )}
> >
@@ -43,8 +43,8 @@ function createButtonText(item: BreadcrumbItem) {
md:h-16 px-4 md:px-6 lg:px-8 md:h-16 px-4 md:px-6 lg:px-8
flex items-center justify-between flex items-center justify-between
z-40 z-40
bg-surface/90 dark:bg-dark-bg/90 backdrop-blur-md surface-floating bg-surface/90 dark:bg-dark-bg/90
border-b border-subtle border-x-0 border-t-0
" "
> >
<div class="max-w-8xl px-4 sm:px-6 h-full w-full flex items-center justify-between gap-2 sm:gap-4"> <div class="max-w-8xl px-4 sm:px-6 h-full w-full flex items-center justify-between gap-2 sm:gap-4">
@@ -90,11 +90,8 @@ $effect(() => {
align="end" align="end"
sideOffset={8} sideOffset={8}
class={cn( class={cn(
'z-50 w-72', 'z-50 w-72 p-4 rounded-none',
'bg-surface dark:bg-dark-card', 'surface-popover',
'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=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', '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-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
@@ -118,7 +115,7 @@ $effect(() => {
{#snippet child({ props })} {#snippet child({ props })}
<button <button
{...props} {...props}
class="inline-flex items-center justify-center size-6 rounded-none hover:bg-black/5 dark:hover:bg-white/5 transition-colors" class="flex-center size-6 rounded-none hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
aria-label="Close controls" aria-label="Close controls"
> >
<XIcon class="size-3.5 text-neutral-500" /> <XIcon class="size-3.5 text-neutral-500" />
@@ -150,10 +147,9 @@ $effect(() => {
<div <div
class={cn( class={cn(
'flex items-center gap-1 md:gap-2 p-1.5 md:p-2', 'flex items-center gap-1 md:gap-2 p-1.5 md:p-2',
'bg-surface/95 dark:bg-dark-bg/95 backdrop-blur-xl', 'surface-floating bg-surface/95 dark:bg-dark-bg/95 backdrop-blur-xl',
'border border-subtle', 'shadow-popover rounded-none',
'shadow-[0_20px_40px_-10px_rgba(0,0,0,0.1)]', 'ring-1 ring-black/5 dark:ring-white/5',
'rounded-none ring-1 ring-black/5 dark:ring-white/5',
)} )}
> >
<!-- Header: icon + label --> <!-- Header: icon + label -->
@@ -58,12 +58,10 @@ const stats = $derived([
class=" class="
group relative group relative
w-full h-full w-full h-full
bg-paper dark:bg-dark-card surface-card
border border-subtle
hover:border-brand dark:hover:border-brand hover:border-brand dark:hover:border-brand
hover:shadow-brand/10 hover:shadow-stamp-card
hover:shadow-[5px_5px_0px_0px] transition-all duration-normal
transition-all duration-200
overflow-hidden overflow-hidden
flex flex-col flex flex-col
min-h-60 min-h-60
+1 -1
View File
@@ -25,7 +25,7 @@ let { children, class: className, ...rest }: Props = $props();
<div <div
class={cn( class={cn(
'flex items-center gap-1 p-1', 'flex items-center gap-1 p-1',
'bg-surface dark:bg-dark-bg', 'surface-canvas',
'border border-subtle', 'border border-subtle',
'rounded-none', 'rounded-none',
'transition-colors duration-500', 'transition-colors duration-500',
@@ -91,7 +91,7 @@ const displayLabel = $derived(label ?? controlLabel ?? '');
step={control.step} step={control.step}
orientation="horizontal" orientation="horizontal"
/> />
<span class="font-mono text-xs text-secondary tabular-nums w-10 text-right shrink-0"> <span class="font-mono text-xs text-subtle tabular-nums w-10 text-right shrink-0">
{formattedValue()} {formattedValue()}
</span> </span>
</div> </div>
@@ -120,12 +120,12 @@ const displayLabel = $derived(label ?? controlLabel ?? '');
<button <button
{...props} {...props}
class={cn( class={cn(
'flex flex-col items-center justify-center w-14 py-1', 'flex flex-col flex-center w-14 py-1',
'select-none rounded-none transition-all duration-150', 'select-none rounded-none transition-all duration-fast',
'border border-transparent', 'border border-transparent',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/30', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/30',
open open
? 'bg-paper dark:bg-dark-card shadow-sm border-subtle' ? 'surface-card-elevated'
: 'hover:bg-paper/50 dark:hover:bg-dark-card/50', : 'hover:bg-paper/50 dark:hover:bg-dark-card/50',
)} )}
aria-label={controlLabel ? `${controlLabel}: ${formattedValue()}` : undefined} aria-label={controlLabel ? `${controlLabel}: ${formattedValue()}` : undefined}
@@ -134,7 +134,7 @@ const displayLabel = $derived(label ?? controlLabel ?? '');
{#if displayLabel} {#if displayLabel}
<span <span
class=" class="
text-3xs font-primary font-bold tracking-tight uppercase text-3xs text-label-mono
text-neutral-900 dark:text-neutral-100 text-neutral-900 dark:text-neutral-100
mb-0.5 leading-none mb-0.5 leading-none
" "
@@ -153,7 +153,7 @@ const displayLabel = $derived(label ?? controlLabel ?? '');
<!-- Vertical slider popover --> <!-- Vertical slider popover -->
<Popover.Content <Popover.Content
class="w-auto py-4 px-3 h-64 flex items-center justify-center rounded-none border border-subtle shadow-sm bg-paper dark:bg-dark-card" class="w-auto py-4 px-3 h-64 flex-center rounded-none surface-card-elevated"
align="center" align="center"
side="top" side="top"
> >
+2 -2
View File
@@ -83,9 +83,9 @@ $effect(() => {
<div transition:fly={{ y: 20, duration: 200, easing: cubicOut }}> <div transition:fly={{ y: 20, duration: 200, easing: cubicOut }}>
<Button <Button
variant="tertiary" variant="tertiary"
layout="block-list-row"
active={property.selected} active={property.selected}
onclick={() => (property.selected = !property.selected)} onclick={() => (property.selected = !property.selected)}
class="w-full px-3 md:px-4 py-2.5 md:py-3 justify-between text-left text-sm flex"
iconPosition="right" iconPosition="right"
icon={property.selected ? icon : undefined} icon={property.selected ? icon : undefined}
> >
@@ -96,8 +96,8 @@ $effect(() => {
{#if hasMore} {#if hasMore}
<Button <Button
variant="icon" variant="icon"
layout="block-list-row"
onclick={() => (showMore = !showMore)} onclick={() => (showMore = !showMore)}
class="w-full px-3 md:px-4 py-2.5 md:py-3 justify-between text-left text-sm flex"
iconPosition="left" iconPosition="left"
> >
{#snippet icon()} {#snippet icon()}
+1 -1
View File
@@ -149,7 +149,7 @@ const inputClasses = $derived(cn(
<span <span
class={cn( class={cn(
'text-2xs font-mono tracking-wide px-1', 'text-2xs font-mono tracking-wide px-1',
error ? 'text-brand ' : 'text-secondary', error ? 'text-brand ' : 'text-subtle',
)} )}
> >
{helperText} {helperText}
+1 -1
View File
@@ -26,7 +26,7 @@ let { size = 20, class: className = '', message = 'analyzing_data' }: Props = $p
</script> </script>
<div <div
class="absolute inset-x-0 inset-y-0 flex items-center justify-center gap-4 {className}" class="absolute inset-x-0 inset-y-0 flex-center gap-4 {className}"
in:fade={{ duration: 300 }} in:fade={{ duration: 300 }}
out:fade={{ duration: 300 }} out:fade={{ duration: 300 }}
> >
@@ -59,7 +59,7 @@ function close() {
<!-- Panel --> <!-- Panel -->
<div <div
class="fixed left-0 top-0 bottom-0 w-80 z-50 shadow-2xl" class="fixed left-0 top-0 bottom-0 w-80 z-50 shadow-overlay"
in:fly={{ x: -320, duration: 300, easing: cubicOut }} in:fly={{ x: -320, duration: 300, easing: cubicOut }}
out:fly={{ x: -320, duration: 250, easing: cubicOut }} out:fly={{ x: -320, duration: 250, easing: cubicOut }}
> >
@@ -83,11 +83,10 @@ function close() {
'shrink-0 z-30 h-full relative', 'shrink-0 z-30 h-full relative',
'overflow-hidden', 'overflow-hidden',
'will-change-[width]', 'will-change-[width]',
'transition-[width] duration-300 ease-out',
'border-r border-subtle', 'border-r border-subtle',
'bg-surface dark:bg-dark-bg', 'surface-canvas',
isOpen ? 'w-80 opacity-100' : 'w-0 opacity-0', isOpen ? 'w-80 opacity-100' : 'w-0 opacity-0',
'transition-[width,opacity] duration-300 ease-out', 'transition-[width,opacity] duration-slow ease-out',
className, className,
)} )}
> >
+3 -3
View File
@@ -70,17 +70,17 @@ let {
const isVertical = $derived(orientation === 'vertical'); const isVertical = $derived(orientation === 'vertical');
const labelClasses = `font-mono text-2xs tabular-nums shrink-0 const labelClasses = `font-mono text-2xs tabular-nums shrink-0
text-secondary text-subtle
group-hover:text-neutral-700 dark:group-hover:text-neutral-300 group-hover:text-neutral-700 dark:group-hover:text-neutral-300
transition-colors`; transition-colors`;
const thumbClasses = `block w-2.5 h-2.5 bg-brand const thumbClasses = `block w-2.5 h-2.5 bg-brand
rotate-45 shadow-sm rotate-45 shadow-rest
hover:scale-125 hover:scale-125
focus-visible:outline-none focus-visible:outline-none
focus-visible:ring-2 focus-visible:ring-brand/20 focus-visible:ring-2 focus-visible:ring-brand/20
data-active:scale-90 data-active:scale-90
transition-transform duration-150 transition-transform duration-fast
disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none disabled:opacity-50
cursor-grab active:cursor-grabbing`; cursor-grab active:cursor-grabbing`;
</script> </script>
@@ -110,28 +110,28 @@ function isFontReady(font: UnifiedFont): boolean {
<div class="w-full px-3 py-3 flex items-center justify-between"> <div class="w-full px-3 py-3 flex items-center justify-between">
<div class="flex-1 flex items-center gap-3"> <div class="flex-1 flex items-center gap-3">
<Skeleton <Skeleton
class="h-4 w-32 bg-neutral-200/70 dark:bg-neutral-800/70" class="h-4 w-32 skeleton-fill"
style="width: {getSkeletonWidth(index)}" style="width: {getSkeletonWidth(index)}"
/> />
</div> </div>
<Skeleton class="w-1.5 h-1.5 rounded-full bg-neutral-200/70 dark:bg-neutral-800/70" /> <Skeleton class="w-1.5 h-1.5 rounded-full skeleton-fill" />
</div> </div>
{/each} {/each}
</div> </div>
{/snippet} {/snippet}
{#snippet children({ item: font, index })} {#snippet children({ item: font, index })}
<div class="relative h-[44px] w-full"> <div class="relative h-11 w-full">
{#if !isFontReady(font)} {#if !isFontReady(font)}
<div <div
class="absolute inset-0 px-3 md:px-4 flex items-center justify-between border border-transparent" class="absolute inset-0 px-3 md:px-4 flex items-center justify-between border border-transparent"
transition:fade={{ duration: 300 }} transition:fade={{ duration: 300 }}
> >
<Skeleton <Skeleton
class="h-4 bg-neutral-200/70 dark:bg-neutral-800/70" class="h-4 skeleton-fill"
style="width: {getSkeletonWidth(index)}" style="width: {getSkeletonWidth(index)}"
/> />
<Skeleton class="w-1.5 h-1.5 rounded-full bg-neutral-200/70 dark:bg-neutral-800/70" /> <Skeleton class="w-1.5 h-1.5 rounded-full skeleton-fill" />
</div> </div>
{:else} {:else}
{@const isSelectedA = font.id === comparisonStore.fontA?.id} {@const isSelectedA = font.id === comparisonStore.fontA?.id}
@@ -141,9 +141,10 @@ function isFontReady(font: UnifiedFont): boolean {
<div transition:fade={{ duration: 300 }} class="h-full"> <div transition:fade={{ duration: 300 }} class="h-full">
<Button <Button
variant="tertiary" variant="tertiary"
layout="block-list-row"
{active} {active}
onclick={() => handleSelect(font)} onclick={() => handleSelect(font)}
class="w-full h-full px-3 md:px-4 py-2.5 md:py-3 flex !justify-between text-left text-sm" class="h-full"
iconPosition="right" iconPosition="right"
> >
<FontApplicator {font}> <FontApplicator {font}>
@@ -53,7 +53,7 @@ const fontBName = $derived(comparisonStore.fontB?.name ?? '');
'px-4 md:px-8 py-4 md:py-6', 'px-4 md:px-8 py-4 md:py-6',
'h-16 md:h-20 z-20', 'h-16 md:h-20 z-20',
'border-b border-subtle', 'border-b border-subtle',
'bg-surface dark:bg-dark-bg', 'surface-canvas',
className, className,
)} )}
> >
@@ -43,7 +43,7 @@ let {
class={cn( class={cn(
'flex flex-col h-full', 'flex flex-col h-full',
'w-80', 'w-80',
'bg-surface dark:bg-dark-bg', 'surface-canvas',
'border-r border-subtle', 'border-r border-subtle',
'transition-colors duration-500', 'transition-colors duration-500',
className, className,
@@ -54,7 +54,7 @@ let {
class=" class="
p-6 shrink-0 p-6 shrink-0
border-b border-subtle border-b border-subtle
bg-surface dark:bg-dark-bg surface-canvas
" "
> >
<!-- Title --> <!-- Title -->
@@ -90,7 +90,7 @@ let {
</div> </div>
<!-- ── Main: content area (no scroll - VirtualList handles scrolling) ─────────────────────────────── --> <!-- ── Main: content area (no scroll - VirtualList handles scrolling) ─────────────────────────────── -->
<div class="flex-1 min-h-0 bg-surface dark:bg-dark-bg"> <div class="flex-1 min-h-0 surface-canvas">
{#if main} {#if main}
{@render main()} {@render main()}
{/if} {/if}
@@ -101,7 +101,7 @@ let {
<div <div
class=" class="
shrink-0 p-6 shrink-0 p-6
bg-surface dark:bg-dark-bg surface-canvas
border-t border-subtle border-t border-subtle
z-10 z-10
" "
@@ -233,14 +233,14 @@ const scaleClass = $derived(
Outer flex container — fills parent. Outer flex container — fills parent.
The paper div inside scales down when the sidebar opens on desktop. The paper div inside scales down when the sidebar opens on desktop.
--> -->
<div class={cn('flex-1 relative flex items-center justify-center p-0 overflow-hidden bg-surface dark:bg-dark-bg', className)}> <div class={cn('flex-1 relative flex-center p-0 overflow-hidden surface-canvas', className)}>
<!-- Paper surface --> <!-- Paper surface -->
<div <div
class={cn( class={cn(
'w-full h-full flex flex-col items-center justify-center relative', 'w-full h-full flex flex-col flex-center relative',
'bg-paper dark:bg-dark-card', 'bg-paper dark:bg-dark-card',
'shadow-2xl shadow-black/5 dark:shadow-black/20', 'shadow-floating-panel dark:shadow-floating-panel-dark',
'transition-transform duration-300 ease-out', 'transition-transform duration-slow ease-out',
scaleClass, scaleClass,
)} )}
> >
@@ -36,9 +36,9 @@ let { sliderPos, isDragging }: Props = $props();
'-ml-2.5 md:-ml-3', '-ml-2.5 md:-ml-3',
'mt-2 md:mt-4', 'mt-2 md:mt-4',
'bg-brand text-white', 'bg-brand text-white',
'flex items-center justify-center', 'flex-center',
'rounded-none shadow-md', 'rounded-none shadow-md',
'transition-transform duration-150', 'transition-transform duration-fast',
isDragging ? 'scale-110' : 'scale-100', isDragging ? 'scale-110' : 'scale-100',
)} )}
> >
@@ -52,9 +52,9 @@ let { sliderPos, isDragging }: Props = $props();
'-ml-2.5 md:-ml-3', '-ml-2.5 md:-ml-3',
'mb-2 md:mb-4', 'mb-2 md:mb-4',
'bg-brand text-white', 'bg-brand text-white',
'flex items-center justify-center', 'flex-center',
'rounded-none shadow-md', 'rounded-none shadow-md',
'transition-transform duration-150', 'transition-transform duration-fast',
isDragging ? 'scale-110' : 'scale-100', isDragging ? 'scale-110' : 'scale-100',
)} )}
> >
+6 -2
View File
@@ -14,15 +14,19 @@ const isVertical = $derived(responsive?.isDesktop || responsive?.isDesktopLarge)
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
</script> </script>
<!--
z-20 keeps the footer below the sidebar's mobile/tablet overlay
(z-40) and panel (z-50) so an open sidebar fully covers the link.
-->
<footer <footer
class={cn( class={cn(
'fixed z-10 flex flex-row items-end gap-1 pointer-events-none', 'fixed z-20 flex flex-row items-end gap-1 pointer-events-none',
isVertical ? 'bottom-2.5 right-2.5 [writing-mode:vertical-rl] rotate-180' : 'bottom-4 left-4', isVertical ? 'bottom-2.5 right-2.5 [writing-mode:vertical-rl] rotate-180' : 'bottom-4 left-4',
)} )}
> >
<!-- Project Name (Horizontal) --> <!-- Project Name (Horizontal) -->
{#if isVertical} {#if isVertical}
<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="flex flex-row pointer-events-auto items-center gap-2 surface-floating backdrop-blur-sm px-2 py-1">
<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}
@@ -84,7 +84,7 @@ const fontRowHeight = $derived.by(() =>
{#snippet skeleton()} {#snippet skeleton()}
<div class="flex flex-col gap-3 sm:gap-4 p-3 sm:p-4"> <div class="flex flex-col gap-3 sm:gap-4 p-3 sm:p-4">
{#each Array(5) as _, i} {#each Array(5) as _, i}
<div class="flex flex-col gap-1.5 sm:gap-2 p-3 sm:p-4 border rounded-lg sm:rounded-xl border-border-subtle bg-background-40"> <div class="flex flex-col gap-1.5 sm:gap-2 p-3 sm:p-4 border rounded-lg sm:rounded-xl border-subtle bg-background/40">
<div class="flex items-center justify-between mb-3 sm:mb-4"> <div class="flex items-center justify-between mb-3 sm:mb-4">
<Skeleton class="h-6 sm:h-8 w-1/3" /> <Skeleton class="h-6 sm:h-8 w-1/3" />
<Skeleton class="h-6 sm:h-8 w-6 sm:w-8 rounded-full" /> <Skeleton class="h-6 sm:h-8 w-6 sm:w-8 rounded-full" />