diff --git a/src/widgets/ComparisonView/ui/SliderArea/SliderArea.svelte b/src/widgets/ComparisonView/ui/SliderArea/SliderArea.svelte index 892919c..d5af8ea 100644 --- a/src/widgets/ComparisonView/ui/SliderArea/SliderArea.svelte +++ b/src/widgets/ComparisonView/ui/SliderArea/SliderArea.svelte @@ -61,14 +61,6 @@ const SLIDER_SPRING_CONFIG = { stiffness: 0.2, damping: 0.7 } as const; */ 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; - /** * Position bounds (percent of container width). */ @@ -94,7 +86,18 @@ const isMobile = $derived(responsive?.isMobile ?? false); let isDragging = $state(false); let isTypographyMenuOpen = $state(false); +/** + * Border-box width of the slider track. Drives the slider→position mapping and + * the per-char split math (pointer events are measured against this same box). + */ let containerWidth = $state(0); +/** + * Content-box width of the slider track (border box minus the responsive CSS + * gutters). The width the text lays out into, and what the line-breaker fits + * within. Measured rather than derived from a padding constant so it tracks the + * breakpoint gutters. + */ +let contentWidth = $state(0); const layout = new DualFontLayout(); @@ -114,10 +117,14 @@ $effect(() => { const observer = new ResizeObserver(entries => { for (const entry of entries) { - // Use borderBoxSize if available, fallback to contentRect - const width = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width; - if (width > 0) { - containerWidth = width; + // Border box for slider/pointer math, content box for text width. + const border = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width; + const content = entry.contentBoxSize?.[0]?.inlineSize ?? entry.contentRect.width; + if (border > 0) { + containerWidth = border; + } + if (content > 0) { + contentWidth = content; } } }); @@ -226,7 +233,7 @@ $effect(() => { } }); -// Layout effect — depends on content, settings AND containerWidth. +// Layout effect — depends on content, settings, and contentWidth. // Awaits font loading into the canvas measurement context before invoking // the engine; otherwise pretext caches fallback-font widths globally per // font string, and the morph boundary drifts from the thumb visually. @@ -236,18 +243,15 @@ $effect(() => { const _size = typography.renderedSize; const _height = typography.height; const _spacing = typography.spacing; - const _width = containerWidth; - const _isMobile = isMobile; + const availableWidth = contentWidth; - if (!container || !fontA || !fontB || _width <= 0) { + if (!container || !fontA || !fontB || availableWidth <= 0) { return; } const fontAStr = getPretextFontString(_weight, _size, fontA.name); const fontBStr = getPretextFontString(_weight, _size, fontB.name); - const padding = _isMobile ? SLIDER_PADDING_MOBILE_PX : SLIDER_PADDING_DESKTOP_PX; - const availableWidth = Math.max(0, _width - padding); const lineHeight = _size * _height; let cancelled = false;