From 4652857512763e924dbd4b5629248eb5a39e2d97 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sun, 31 May 2026 13:25:13 +0300 Subject: [PATCH] fix(comparison): lay out lines to the content box, not a padding guess availableWidth was containerWidth minus a flat 48/96px constant, but the slider track's gutters are responsive CSS padding (px-4/8/12/lg:px-24, per side). At lg the real padding is 192px while only 96 was subtracted, so lines were broken ~96px too wide and overflowed the container. Measure the container's content box (ResizeObserver contentBoxSize) and use it directly as availableWidth; keep the border box for the slider and split math. The content box already excludes the gutters, so it tracks the breakpoints with no constant to maintain. --- .../ui/SliderArea/SliderArea.svelte | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) 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;