diff --git a/src/widgets/ComparisonView/lib/index.ts b/src/widgets/ComparisonView/lib/index.ts
index f84d781..942760e 100644
--- a/src/widgets/ComparisonView/lib/index.ts
+++ b/src/widgets/ComparisonView/lib/index.ts
@@ -1,3 +1,4 @@
+export { computeStrutHeight } from './utils/computeStrutHeight/computeStrutHeight';
export {
createDotCrossfade,
getDotTransitionParams,
diff --git a/src/widgets/ComparisonView/lib/utils/computeStrutHeight/computeStrutHeight.test.ts b/src/widgets/ComparisonView/lib/utils/computeStrutHeight/computeStrutHeight.test.ts
new file mode 100644
index 0000000..b6a2dfa
--- /dev/null
+++ b/src/widgets/ComparisonView/lib/utils/computeStrutHeight/computeStrutHeight.test.ts
@@ -0,0 +1,28 @@
+import {
+ describe,
+ expect,
+ it,
+} from 'vitest';
+import { computeStrutHeight } from './computeStrutHeight';
+
+describe('computeStrutHeight', () => {
+ it('uses the centering height when the line-height is generous', () => {
+ // centering = 40/2 + 16*0.34 = 25.44; floor = 16*1.1 = 17.6 → centering wins.
+ expect(computeStrutHeight(40, 16)).toBeCloseTo(25.44, 5);
+ });
+
+ it('falls back to the ascent floor when the line-height is tight', () => {
+ // centering = 16/2 + 16*0.34 = 13.44; floor = 16*1.1 = 17.6 → floor wins.
+ expect(computeStrutHeight(16, 16)).toBeCloseTo(17.6, 5);
+ });
+
+ it('treats the floor and centering height as equal at the crossover line-height', () => {
+ // centering == floor when lineHeight = 1.52 * fontSize → 24.32 for 16px.
+ expect(computeStrutHeight(24.32, 16)).toBeCloseTo(17.6, 5);
+ });
+
+ it('scales with font size', () => {
+ // centering = 60/2 + 32*0.34 = 40.88; floor = 32*1.1 = 35.2 → centering wins.
+ expect(computeStrutHeight(60, 32)).toBeCloseTo(40.88, 5);
+ });
+});
diff --git a/src/widgets/ComparisonView/lib/utils/computeStrutHeight/computeStrutHeight.ts b/src/widgets/ComparisonView/lib/utils/computeStrutHeight/computeStrutHeight.ts
new file mode 100644
index 0000000..cafe65b
--- /dev/null
+++ b/src/widgets/ComparisonView/lib/utils/computeStrutHeight/computeStrutHeight.ts
@@ -0,0 +1,45 @@
+/**
+ * Fraction of the font size added to half the line height to drop the strut's
+ * baseline from the line box's vertical middle down to the text's optical
+ * center. Empirical: ~0.34em approximates a Latin font's midline-to-baseline
+ * offset, so glyphs sit centered rather than riding high in the line.
+ */
+const BASELINE_OFFSET_RATIO = 0.34;
+
+/**
+ * Minimum strut height as a multiple of the font size. Floors the strut above
+ * the fonts' ascent (~1em) so that at tight line-heights it stays the tallest
+ * inline box and keeps ownership of the line baseline. Empirical: 1.1 clears the
+ * tallest ascenders in the catalog's Latin fonts.
+ */
+const MIN_HEIGHT_RATIO = 1.1;
+
+/**
+ * Pixel height for a slider line's invisible baseline strut.
+ *
+ * The slider renders each line with a zero-width strut span whose box is
+ * deliberately the tallest inline box on the line. The browser pins a line box's
+ * baseline to its tallest inline box; fixing the strut's height independent of
+ * which bulk runs or window chars are currently mounted keeps the baseline (and
+ * every glyph) from jumping as the slider sweeps runs in and out. With
+ * `overflow: hidden` the strut's baseline sits at its bottom edge, so this height
+ * also sets the text's vertical position within the line box.
+ *
+ * The result is `max(centeringHeight, ascentFloor)`:
+ * - `centeringHeight = lineHeightPx / 2 + fontSizePx * BASELINE_OFFSET_RATIO`
+ * centers the text — half the line box places the strut's bottom edge at the
+ * vertical middle, and the offset term nudges the baseline down to the glyphs'
+ * optical center.
+ * - `ascentFloor = fontSizePx * MIN_HEIGHT_RATIO` keeps the strut taller than the
+ * fonts' ascent when the line-height is tight (where `centeringHeight` would
+ * shrink below a real glyph box and let another box steal the baseline).
+ *
+ * @param lineHeightPx Line height in pixels (typography line-height × font size).
+ * @param fontSizePx Rendered font size in pixels.
+ * @returns Strut height in pixels.
+ */
+export function computeStrutHeight(lineHeightPx: number, fontSizePx: number): number {
+ const centeringHeight = lineHeightPx / 2 + fontSizePx * BASELINE_OFFSET_RATIO;
+ const ascentFloor = fontSizePx * MIN_HEIGHT_RATIO;
+ return Math.max(centeringHeight, ascentFloor);
+}
diff --git a/src/widgets/ComparisonView/ui/Line/Line.svelte b/src/widgets/ComparisonView/ui/Line/Line.svelte
index 92350b2..df864c5 100644
--- a/src/widgets/ComparisonView/ui/Line/Line.svelte
+++ b/src/widgets/ComparisonView/ui/Line/Line.svelte
@@ -14,8 +14,10 @@ import {
windowSizeForLine,
} from '$entities/Font';
import { getTypographySettingsStore } from '$features/AdjustTypography';
+import { computeStrutHeight } from '../../lib';
import { getComparisonStore } from '../../model';
import Character from '../Character/Character.svelte';
+import SettledText from '../SettledText/SettledText.svelte';
interface Props {
/**
@@ -44,36 +46,9 @@ const fontSizePx = $derived(typography.renderedSize);
const lineHeightPx = $derived(typography.height * typography.renderedSize);
const letterSpacingPx = $derived(typography.spacing * typography.renderedSize);
-/**
- * Class and style are single short bindings so the formatter keeps
- * `{text}` on one line. A wrapped text expression would leak
- * its indentation into the span content under `white-space: pre`.
- */
-const BULK_LEFT_CLASS =
- 'inline-block align-baseline leading-none text-swiss-black/75 dark:text-brand/75 transition-colors duration-300';
-const BULK_RIGHT_CLASS =
- 'inline-block align-baseline leading-none text-neutral-950 dark:text-white transition-colors duration-300';
-
-const leftStyle = $derived(`font-family:${fontA?.name ?? ''};font-size:${fontSizePx}px`);
-const rightStyle = $derived(`font-family:${fontB?.name ?? ''};font-size:${fontSizePx}px`);
-
-/**
- * Stops the whole line from jumping up or down as the slider moves. The browser
- * pins a line box's baseline to its tallest inline box, so without a fixed
- * reference the baseline (and every glyph) shifts the moment a bulk run appears
- * or disappears, or the last window char morphs to a font with a taller ascent.
- * This invisible strut is always the tallest box — `overflow: hidden` puts its
- * baseline at its bottom edge — so it owns the line baseline and holds it still.
- * Its height also sets the text's vertical position (the container is block, so
- * nothing else centers it).
- *
- * Height factors are empirical: the first term centers the text, the `* 1.1`
- * floor keeps the strut above the fonts' ascent at tight line-heights.
- */
-const strutHeightPx = $derived(Math.max(lineHeightPx / 2 + fontSizePx * 0.34, fontSizePx * 1.1));
-const strutStyle = $derived(
- `display:inline-block;width:0;overflow:hidden;vertical-align:baseline;height:${strutHeightPx}px`,
-);
+// Invisible strut that pins the line baseline so glyphs don't jump as the
+// slider moves; `computeStrutHeight` explains the why and the formula.
+const strutHeightPx = $derived(computeStrutHeight(lineHeightPx, fontSizePx));
+
+
+{text}