diff --git a/src/entities/Font/ui/FontVirtualList/FontVirtualList.svelte b/src/entities/Font/ui/FontVirtualList/FontVirtualList.svelte index 9cb6bed..957c86d 100644 --- a/src/entities/Font/ui/FontVirtualList/FontVirtualList.svelte +++ b/src/entities/Font/ui/FontVirtualList/FontVirtualList.svelte @@ -83,9 +83,15 @@ async function handleJump(targetIndex: number) { } } +/** + * Debounce wait before asking the font lifecycle manager to load fonts + * for the current visible window. Coalesces rapid scroll into one batch. + */ +const TOUCH_DEBOUNCE_MS = 150; + const debouncedTouch = debounce((configs: FontLoadRequestConfig[]) => { fontLifecycleManager.touch(configs); -}, 150); +}, TOUCH_DEBOUNCE_MS); // Re-touch whenever visible set or weight changes — fixes weight-change gap $effect(() => { diff --git a/src/shared/lib/helpers/createPerspectiveManager/createPerspectiveManager.svelte.ts b/src/shared/lib/helpers/createPerspectiveManager/createPerspectiveManager.svelte.ts index 5c15c7a..4a55458 100644 --- a/src/shared/lib/helpers/createPerspectiveManager/createPerspectiveManager.svelte.ts +++ b/src/shared/lib/helpers/createPerspectiveManager/createPerspectiveManager.svelte.ts @@ -28,6 +28,12 @@ import { Spring } from 'svelte/motion'; +/** + * Spring tuning for the perspective animation. Lower stiffness = slower + * easing into back/front state; higher damping = less overshoot. + */ +const PERSPECTIVE_SPRING_CONFIG = { stiffness: 0.2, damping: 0.8 } as const; + /** * Configuration options for perspective effects */ @@ -93,10 +99,7 @@ export class PerspectiveManager { * Spring animation state * Animates between 0 (front) and 1 (back) with configurable physics */ - spring = new Spring(0, { - stiffness: 0.2, - damping: 0.8, - }); + spring = new Spring(0, PERSPECTIVE_SPRING_CONFIG); /** * Reactive state: true when in back position diff --git a/src/shared/ui/VirtualList/VirtualList.svelte b/src/shared/ui/VirtualList/VirtualList.svelte index c4af3d1..0bba6f3 100644 --- a/src/shared/ui/VirtualList/VirtualList.svelte +++ b/src/shared/ui/VirtualList/VirtualList.svelte @@ -167,17 +167,33 @@ $effect(() => { } }); +/** + * Throttle for visible-items change callbacks. Lower = more responsive + * downstream UI; higher = fewer recomputes during scroll. + */ +const VISIBLE_CHANGE_THROTTLE_MS = 150; + +/** + * Throttle for near-bottom callbacks (typically used to prefetch next page). + */ +const NEAR_BOTTOM_THROTTLE_MS = 200; + +/** + * Throttle for jump callbacks (programmatic scroll-to-index). + */ +const JUMP_THROTTLE_MS = 200; + const throttledVisibleChange = throttle((visibleItems: T[]) => { onVisibleItemsChange?.(visibleItems); -}, 150); // 150ms throttle +}, VISIBLE_CHANGE_THROTTLE_MS); const throttledNearBottom = throttle((lastVisibleIndex: number) => { onNearBottom?.(lastVisibleIndex); -}, 200); // 200ms throttle +}, NEAR_BOTTOM_THROTTLE_MS); const throttledOnJump = throttle((targetIndex: number) => { onJump?.(targetIndex); -}, 200); +}, JUMP_THROTTLE_MS); // Calculate top/bottom padding for spacer elements // In CSS Grid, gap creates space BETWEEN elements. diff --git a/src/widgets/ComparisonView/ui/SliderArea/SliderArea.svelte b/src/widgets/ComparisonView/ui/SliderArea/SliderArea.svelte index 59142e8..555a77c 100644 --- a/src/widgets/ComparisonView/ui/SliderArea/SliderArea.svelte +++ b/src/widgets/ComparisonView/ui/SliderArea/SliderArea.svelte @@ -50,6 +50,26 @@ interface Props { let { isSidebarOpen = false, class: className }: Props = $props(); +/** + * Spring tuning for the comparison slider thumb. Lower stiffness = slower + * follow; higher damping = less overshoot. + */ +const SLIDER_SPRING_CONFIG = { stiffness: 0.2, damping: 0.7 } as const; + +/** + * Debounce wait before persisting the slider position to the store. + * High frequency during drag → batched writes. + */ +const SLIDER_PERSIST_DEBOUNCE_MS = 100; + +/** + * Horizontal layout padding subtracted from container width before laying + * out the comparison text. Different per breakpoint to match the gutters + * around the slider track. + */ +const SLIDER_PADDING_MOBILE_PX = 48; +const SLIDER_PADDING_DESKTOP_PX = 96; + const fontA = $derived(comparisonStore.fontA); const fontB = $derived(comparisonStore.fontB); const isLoading = $derived(comparisonStore.isLoading || !comparisonStore.isReady); @@ -89,10 +109,7 @@ $effect(() => { return () => observer.disconnect(); }); -const sliderSpring = new Spring(50, { - stiffness: 0.2, - damping: 0.7, -}); +const sliderSpring = new Spring(50, SLIDER_SPRING_CONFIG); const sliderPos = $derived(sliderSpring.current); function handleMove(e: PointerEvent) { @@ -115,7 +132,7 @@ function startDragging(e: PointerEvent) { const storeSliderPosition = debounce((value: number) => { comparisonStore.sliderPosition = value; -}, 100); +}, SLIDER_PERSIST_DEBOUNCE_MS); $effect(() => { storeSliderPosition(sliderPos); @@ -172,7 +189,7 @@ $effect(() => { const fontAStr = getPretextFontString(_weight, _size, fontA.name); const fontBStr = getPretextFontString(_weight, _size, fontB.name); - const padding = _isMobile ? 48 : 96; + const padding = _isMobile ? SLIDER_PADDING_MOBILE_PX : SLIDER_PADDING_DESKTOP_PX; const availableWidth = Math.max(0, _width - padding); const lineHeight = _size * _height; diff --git a/src/widgets/SampleList/ui/SampleList/SampleList.svelte b/src/widgets/SampleList/ui/SampleList/SampleList.svelte index 48b8076..09d5258 100644 --- a/src/widgets/SampleList/ui/SampleList/SampleList.svelte +++ b/src/widgets/SampleList/ui/SampleList/SampleList.svelte @@ -36,6 +36,12 @@ const SAMPLER_CONTENT_PADDING_X = 32; // Matches the previous hardcoded itemHeight={220} value to avoid regressions. const SAMPLER_FALLBACK_HEIGHT = 220; +/** + * Throttle for the `checkPosition` scroll observer. Trades responsiveness + * of the perspective tilt against scroll-handler cost. + */ +const CHECK_POSITION_THROTTLE_MS = 100; + let text = $state('The quick brown fox jumps over the lazy dog...'); let wrapper = $state(null); // Binds to the actual window height @@ -54,7 +60,7 @@ const checkPosition = throttle(() => { const viewportMiddle = innerHeight / 2; isAboveMiddle = rect.top < viewportMiddle; -}, 100); +}, CHECK_POSITION_THROTTLE_MS); // Resolver recreated when typography values change. The returned closure reads // fontLifecycleManager.statuses (a SvelteMap) on every call, so any font status