From c1bdd6c0e75d47a5381d70866adf55d7fb963b83 Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 13:57:04 +0200 Subject: [PATCH 01/15] impr: enable dots typed effect for ligature languages (@byseif21) --- frontend/src/styles/test.scss | 68 +++++++++++++++---------- frontend/src/ts/test/break-ligatures.ts | 9 ++++ frontend/src/ts/test/test-ui.ts | 2 + 3 files changed, 53 insertions(+), 26 deletions(-) create mode 100644 frontend/src/ts/test/break-ligatures.ts diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index c935deffed67..668bdec2802b 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -533,45 +533,61 @@ } } - &.typed-effect-dots:not(.withLigatures) { + &.typed-effect-dots { /* transform already typed letters into appropriately colored dots */ - .word letter { - position: relative; - &::after { - content: ""; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 1em; - aspect-ratio: 1; - border-radius: 50%; - opacity: 0; + &:not(.withLigatures) .word, + &.withLigatures .word.broken-ligatures { + letter { + position: relative; + display: inline-block; + &::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 1em; + aspect-ratio: 1; + border-radius: 50%; + opacity: 0; + } } } - .typed letter { - color: var(--bg-color); - animation: typedEffectToDust 200ms ease-out 0ms 1 forwards !important; - &::after { - animation: typedEffectFadeIn 100ms ease-in 100ms 1 forwards; - background: var(--c-dot); + + &:not(.withLigatures) .typed, + &.withLigatures .word.broken-ligatures.typed { + letter { + color: var(--bg-color); + animation: typedEffectToDust 200ms ease-out 0ms 1 forwards !important; + &::after { + animation: typedEffectFadeIn 100ms ease-in 100ms 1 forwards; + background: var(--c-dot); + } } } - &:not(.blind) { + + &:not(.withLigatures):not(.blind) { .word letter.incorrect::after { background: var(--c-dot--error); } } + // Handle error dots for broken ligatures too? + &.withLigatures:not(.blind) .word.broken-ligatures letter.incorrect::after { + background: var(--c-dot--error); + } @media (prefers-reduced-motion) { - .typed letter { - animation: none !important; - transform: scale(0.4); - color: transparent; - &::after { + &:not(.withLigatures) .typed, + &.withLigatures .word.broken-ligatures.typed { + letter { animation: none !important; - opacity: 1; + transform: scale(0.4); + color: transparent; + &::after { + animation: none !important; + opacity: 1; + } } } } diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts new file mode 100644 index 000000000000..6582beb3fde0 --- /dev/null +++ b/frontend/src/ts/test/break-ligatures.ts @@ -0,0 +1,9 @@ +import Config from "../config"; +import { ElementWithUtils, qsr } from "../utils/dom"; + +export function breakLigaturesForWord(wordEl: ElementWithUtils): void { + const wordsEl = qsr("#words"); + if (Config.typedEffect === "dots" && wordsEl.hasClass("withLigatures")) { + wordEl.addClass("broken-ligatures"); + } +} diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 36b0b88279a6..a1a363016c4a 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -48,6 +48,7 @@ import * as SlowTimer from "../states/slow-timer"; import * as TestConfig from "./test-config"; import * as CompositionDisplay from "../elements/composition-display"; import * as AdController from "../controllers/ad-controller"; +import * as BreakLigatures from "./break-ligatures"; import * as LayoutfluidFunboxTimer from "../test/funbox/layoutfluid-funbox-timer"; import * as Keymap from "../elements/keymap"; import * as ThemeController from "../controllers/theme-controller"; @@ -141,6 +142,7 @@ export function updateActiveElement( if (previousActiveWord !== null) { if (direction === "forward") { previousActiveWord.addClass("typed"); + BreakLigatures.breakLigaturesForWord(previousActiveWord); } else if (direction === "back") { // } From a6cddd2000215d51cdfc6a12e6169fa0731b8723 Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 13:59:38 +0200 Subject: [PATCH 02/15] readd if active --- frontend/src/ts/test/test-ui.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index a1a363016c4a..53beb714b5f2 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -159,6 +159,7 @@ export function updateActiveElement( newActiveWord.addClass("active"); newActiveWord.removeClass("error"); newActiveWord.removeClass("typed"); + newActiveWord.removeClass("broken-ligatures"); activeWordTop = newActiveWord.getOffsetTop(); activeWordHeight = newActiveWord.getOffsetHeight(); From e830566235861f14b8891639e2e46c4cf9f6e857 Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 14:45:46 +0200 Subject: [PATCH 03/15] refactor abit for readability --- frontend/src/ts/test/break-ligatures.ts | 10 +++++++++- frontend/src/ts/test/test-ui.ts | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts index 6582beb3fde0..54b448cca29f 100644 --- a/frontend/src/ts/test/break-ligatures.ts +++ b/frontend/src/ts/test/break-ligatures.ts @@ -1,7 +1,15 @@ import Config from "../config"; import { ElementWithUtils, qsr } from "../utils/dom"; -export function breakLigaturesForWord(wordEl: ElementWithUtils): void { +export function set( + wordEl: ElementWithUtils, + state: "broken" | "normal", +): void { + if (state === "normal") { + wordEl.removeClass("broken-ligatures"); + return; + } + const wordsEl = qsr("#words"); if (Config.typedEffect === "dots" && wordsEl.hasClass("withLigatures")) { wordEl.addClass("broken-ligatures"); diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 53beb714b5f2..87d2bf81aa44 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -48,7 +48,7 @@ import * as SlowTimer from "../states/slow-timer"; import * as TestConfig from "./test-config"; import * as CompositionDisplay from "../elements/composition-display"; import * as AdController from "../controllers/ad-controller"; -import * as BreakLigatures from "./break-ligatures"; +import * as Ligatures from "./break-ligatures"; import * as LayoutfluidFunboxTimer from "../test/funbox/layoutfluid-funbox-timer"; import * as Keymap from "../elements/keymap"; import * as ThemeController from "../controllers/theme-controller"; @@ -142,7 +142,7 @@ export function updateActiveElement( if (previousActiveWord !== null) { if (direction === "forward") { previousActiveWord.addClass("typed"); - BreakLigatures.breakLigaturesForWord(previousActiveWord); + Ligatures.set(previousActiveWord, "broken"); } else if (direction === "back") { // } @@ -159,7 +159,7 @@ export function updateActiveElement( newActiveWord.addClass("active"); newActiveWord.removeClass("error"); newActiveWord.removeClass("typed"); - newActiveWord.removeClass("broken-ligatures"); + Ligatures.set(newActiveWord, "normal"); activeWordTop = newActiveWord.getOffsetTop(); activeWordHeight = newActiveWord.getOffsetHeight(); From 709adbba17294a2440b904c22cedaa38ac3c1bce Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 14:47:14 +0200 Subject: [PATCH 04/15] yap --- frontend/src/styles/test.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index 668bdec2802b..f89c605ab2d0 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -572,7 +572,6 @@ background: var(--c-dot--error); } } - // Handle error dots for broken ligatures too? &.withLigatures:not(.blind) .word.broken-ligatures letter.incorrect::after { background: var(--c-dot--error); } From b539944a0394a31636b29e36bc98b52b60568bc9 Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 15:22:47 +0200 Subject: [PATCH 05/15] fix moving words --- frontend/src/styles/test.scss | 8 ++++++++ frontend/src/ts/test/break-ligatures.ts | 3 +++ 2 files changed, 11 insertions(+) diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index f89c605ab2d0..73f4578f26bd 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -554,6 +554,14 @@ } } } + // unify dote spaceing + &.withLigatures .word.broken-ligatures { + display: inline-flex; + justify-content: center; + letter { + width: 0.5em; + } + } &:not(.withLigatures) .typed, &.withLigatures .word.broken-ligatures.typed { diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts index 54b448cca29f..7de24c773371 100644 --- a/frontend/src/ts/test/break-ligatures.ts +++ b/frontend/src/ts/test/break-ligatures.ts @@ -7,11 +7,14 @@ export function set( ): void { if (state === "normal") { wordEl.removeClass("broken-ligatures"); + wordEl.setStyle({ width: "" }); return; } const wordsEl = qsr("#words"); if (Config.typedEffect === "dots" && wordsEl.hasClass("withLigatures")) { + const width = wordEl.native.getBoundingClientRect().width; + wordEl.setStyle({ width: `${width}px` }); wordEl.addClass("broken-ligatures"); } } From 8caa0192771e4b9a2d91d7697bb86cef213e212c Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 15:39:23 +0200 Subject: [PATCH 06/15] fix for blind --- frontend/src/styles/test.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index 73f4578f26bd..8ece63f2dbef 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -351,7 +351,7 @@ &.blind { .word { & letter.extra { - display: none; + display: none !important; } & letter.incorrect { color: var(--correct-letter-color); From eed55cb1e1ee163df114f4f6b1f69c79bd6b7f69 Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 15:51:02 +0200 Subject: [PATCH 07/15] dont center --- frontend/src/styles/test.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index 8ece63f2dbef..5bc770fe1775 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -557,7 +557,6 @@ // unify dote spaceing &.withLigatures .word.broken-ligatures { display: inline-flex; - justify-content: center; letter { width: 0.5em; } From e051784466c840caab60c794806132dabc5d8b2b Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 16:15:32 +0200 Subject: [PATCH 08/15] optmize --- frontend/src/ts/test/break-ligatures.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts index 7de24c773371..0023c43c0cf4 100644 --- a/frontend/src/ts/test/break-ligatures.ts +++ b/frontend/src/ts/test/break-ligatures.ts @@ -1,5 +1,5 @@ import Config from "../config"; -import { ElementWithUtils, qsr } from "../utils/dom"; +import { ElementWithUtils } from "../utils/dom"; export function set( wordEl: ElementWithUtils, @@ -11,8 +11,11 @@ export function set( return; } - const wordsEl = qsr("#words"); - if (Config.typedEffect === "dots" && wordsEl.hasClass("withLigatures")) { + if (Config.typedEffect !== "dots") return; + if (wordEl.hasClass("broken-ligatures")) return; + + const parent = wordEl.native.parentElement; + if (parent?.classList.contains("withLigatures")) { const width = wordEl.native.getBoundingClientRect().width; wordEl.setStyle({ width: `${width}px` }); wordEl.addClass("broken-ligatures"); From 32694a12c5c67054061bef8059acb2ae6f117b15 Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 17:05:23 +0200 Subject: [PATCH 09/15] fix changing effect mid test after refresh --- frontend/src/ts/test/break-ligatures.ts | 41 +++++++++++++++++-------- frontend/src/ts/test/test-ui.ts | 1 + 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts index 0023c43c0cf4..950613a2aee3 100644 --- a/frontend/src/ts/test/break-ligatures.ts +++ b/frontend/src/ts/test/break-ligatures.ts @@ -1,23 +1,38 @@ import Config from "../config"; import { ElementWithUtils } from "../utils/dom"; +function canBreak(wordEl: ElementWithUtils): boolean { + if (Config.typedEffect !== "dots") return false; + if (wordEl.hasClass("broken-ligatures")) return false; + + const parent = wordEl.native.parentElement; + return !!parent?.classList.contains("withLigatures"); +} + +function applyIfNeeded(wordEl: ElementWithUtils): void { + if (!canBreak(wordEl)) return; + + const { width } = wordEl.native.getBoundingClientRect(); + wordEl.setStyle({ width: `${width}px` }); + wordEl.addClass("broken-ligatures"); +} + +function reset(wordEl: ElementWithUtils): void { + if (!wordEl.hasClass("broken-ligatures")) return; + wordEl.removeClass("broken-ligatures"); + wordEl.setStyle({ width: "" }); +} + export function set( wordEl: ElementWithUtils, state: "broken" | "normal", ): void { - if (state === "normal") { - wordEl.removeClass("broken-ligatures"); - wordEl.setStyle({ width: "" }); - return; - } + state === "normal" ? reset(wordEl) : applyIfNeeded(wordEl); +} - if (Config.typedEffect !== "dots") return; - if (wordEl.hasClass("broken-ligatures")) return; +export function update(key: string, wordsEl: ElementWithUtils): void { + if (key !== "typedEffect") return; + if (!wordsEl.hasClass("withLigatures")) return; - const parent = wordEl.native.parentElement; - if (parent?.classList.contains("withLigatures")) { - const width = wordEl.native.getBoundingClientRect().width; - wordEl.setStyle({ width: `${width}px` }); - wordEl.addClass("broken-ligatures"); - } + wordsEl.qsa(".word.typed").forEach(applyIfNeeded); } diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 87d2bf81aa44..e64f8faef0c1 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -2080,6 +2080,7 @@ ConfigEvent.subscribe(({ key, newValue }) => { ].includes(key) ) { updateWordWrapperClasses(); + Ligatures.update(key, wordsEl); } if (["tapeMode", "tapeMargin"].includes(key)) { updateLiveStatsMargin(); From aae08c92257eacd0dc4ba4b796570ed6bb5ae360 Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 18:24:11 +0200 Subject: [PATCH 10/15] fix resizing mid test --- frontend/src/ts/test/break-ligatures.ts | 19 ++++++++++++++----- frontend/src/ts/test/test-ui.ts | 1 + 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts index 950613a2aee3..a2ca82d97689 100644 --- a/frontend/src/ts/test/break-ligatures.ts +++ b/frontend/src/ts/test/break-ligatures.ts @@ -5,8 +5,7 @@ function canBreak(wordEl: ElementWithUtils): boolean { if (Config.typedEffect !== "dots") return false; if (wordEl.hasClass("broken-ligatures")) return false; - const parent = wordEl.native.parentElement; - return !!parent?.classList.contains("withLigatures"); + return !!wordEl.native.closest(".withLigatures"); } function applyIfNeeded(wordEl: ElementWithUtils): void { @@ -31,8 +30,18 @@ export function set( } export function update(key: string, wordsEl: ElementWithUtils): void { - if (key !== "typedEffect") return; - if (!wordsEl.hasClass("withLigatures")) return; + if (!["typedEffect", "fontFamily", "fontSize"].includes(key)) return; - wordsEl.qsa(".word.typed").forEach(applyIfNeeded); + const words = wordsEl.qsa(".word.typed"); + + const shouldReset = + !wordsEl.hasClass("withLigatures") || + Config.typedEffect !== "dots" || + key === "fontFamily" || + key === "fontSize"; + + if (shouldReset) { + words.forEach(reset); + } + words.forEach(applyIfNeeded); } diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index e64f8faef0c1..9321f5578f1f 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -2075,6 +2075,7 @@ ConfigEvent.subscribe(({ key, newValue }) => { "colorfulMode", "showAllLines", "fontSize", + "fontFamily", "maxLineWidth", "tapeMargin", ].includes(key) From 6760d2bc04749c97daf0ed156c775531a438d720 Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 18:29:09 +0200 Subject: [PATCH 11/15] use screenBounds --- frontend/src/ts/test/break-ligatures.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts index a2ca82d97689..32722799c973 100644 --- a/frontend/src/ts/test/break-ligatures.ts +++ b/frontend/src/ts/test/break-ligatures.ts @@ -11,7 +11,7 @@ function canBreak(wordEl: ElementWithUtils): boolean { function applyIfNeeded(wordEl: ElementWithUtils): void { if (!canBreak(wordEl)) return; - const { width } = wordEl.native.getBoundingClientRect(); + const { width } = wordEl.screenBounds(); wordEl.setStyle({ width: `${width}px` }); wordEl.addClass("broken-ligatures"); } From b2d8d2c06da56ba6665bad85d720af57b27c4b04 Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 18:57:37 +0200 Subject: [PATCH 12/15] . --- frontend/src/ts/test/break-ligatures.ts | 4 ++-- frontend/src/ts/test/test-ui.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts index 32722799c973..1582bd4b08e8 100644 --- a/frontend/src/ts/test/break-ligatures.ts +++ b/frontend/src/ts/test/break-ligatures.ts @@ -24,9 +24,9 @@ function reset(wordEl: ElementWithUtils): void { export function set( wordEl: ElementWithUtils, - state: "broken" | "normal", + areLigaturesBroken: boolean, ): void { - state === "normal" ? reset(wordEl) : applyIfNeeded(wordEl); + areLigaturesBroken ? applyIfNeeded(wordEl) : reset(wordEl); } export function update(key: string, wordsEl: ElementWithUtils): void { diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 9321f5578f1f..a2e5e0d4e743 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -142,7 +142,7 @@ export function updateActiveElement( if (previousActiveWord !== null) { if (direction === "forward") { previousActiveWord.addClass("typed"); - Ligatures.set(previousActiveWord, "broken"); + Ligatures.set(previousActiveWord, true); } else if (direction === "back") { // } @@ -159,7 +159,7 @@ export function updateActiveElement( newActiveWord.addClass("active"); newActiveWord.removeClass("error"); newActiveWord.removeClass("typed"); - Ligatures.set(newActiveWord, "normal"); + Ligatures.set(newActiveWord, false); activeWordTop = newActiveWord.getOffsetTop(); activeWordHeight = newActiveWord.getOffsetHeight(); From 5b72288b29150e3ec13a8bc2b2988c9ea3f43d79 Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 20:42:50 +0200 Subject: [PATCH 13/15] limit to ligature update only --- frontend/src/ts/test/test-ui.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index a2e5e0d4e743..49dc79a9b3d8 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -2080,7 +2080,7 @@ ConfigEvent.subscribe(({ key, newValue }) => { "tapeMargin", ].includes(key) ) { - updateWordWrapperClasses(); + if (key !== "fontFamily") updateWordWrapperClasses(); Ligatures.update(key, wordsEl); } if (["tapeMode", "tapeMargin"].includes(key)) { From e6cf966cf46e8d7697b7d7c7d70931e730fb2e64 Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 28 Jan 2026 21:33:18 +0200 Subject: [PATCH 14/15] move --- frontend/src/ts/test/break-ligatures.ts | 2 -- frontend/src/ts/test/test-ui.ts | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts index 1582bd4b08e8..6af798017914 100644 --- a/frontend/src/ts/test/break-ligatures.ts +++ b/frontend/src/ts/test/break-ligatures.ts @@ -30,8 +30,6 @@ export function set( } export function update(key: string, wordsEl: ElementWithUtils): void { - if (!["typedEffect", "fontFamily", "fontSize"].includes(key)) return; - const words = wordsEl.qsa(".word.typed"); const shouldReset = diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 49dc79a9b3d8..f0d9541122c7 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -2081,7 +2081,9 @@ ConfigEvent.subscribe(({ key, newValue }) => { ].includes(key) ) { if (key !== "fontFamily") updateWordWrapperClasses(); - Ligatures.update(key, wordsEl); + if (["typedEffect", "fontFamily", "fontSize"].includes(key)) { + Ligatures.update(key, wordsEl); + } } if (["tapeMode", "tapeMargin"].includes(key)) { updateLiveStatsMargin(); From d249901e0eef0e5c18730f57e9ad9196e686b49d Mon Sep 17 00:00:00 2001 From: byseif21 Date: Wed, 11 Feb 2026 17:51:44 +0200 Subject: [PATCH 15/15] fix: replace inline-flex with width-lock + wrap detection inline-flex/forced nowrap caused overflow when dots appeared, e.g very long words in Custom mode - so small hack for that, we detect actual multi-line wrapping by comparing per-letter top positions; lock width only for single-line words to stabilize dot spacing, and needs-wrap for multi-line words to preserve natural wrapping --- frontend/src/styles/test.scss | 6 ++++-- frontend/src/ts/test/break-ligatures.ts | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index 5bc770fe1775..671f5373d80b 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -556,11 +556,13 @@ } // unify dote spaceing &.withLigatures .word.broken-ligatures { - display: inline-flex; letter { - width: 0.5em; + width: 0.4em; } } + .word.broken-ligatures:not(.needs-wrap) { + white-space: nowrap; + } &:not(.withLigatures) .typed, &.withLigatures .word.broken-ligatures.typed { diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts index 6af798017914..655971c983a7 100644 --- a/frontend/src/ts/test/break-ligatures.ts +++ b/frontend/src/ts/test/break-ligatures.ts @@ -11,14 +11,27 @@ function canBreak(wordEl: ElementWithUtils): boolean { function applyIfNeeded(wordEl: ElementWithUtils): void { if (!canBreak(wordEl)) return; - const { width } = wordEl.screenBounds(); - wordEl.setStyle({ width: `${width}px` }); + const letters = wordEl.qsa("letter"); + const firstTop = Math.floor(letters[0]?.getOffsetTop() ?? 0); + const isWrapped = letters.some( + (l) => Math.floor(l.getOffsetTop()) !== firstTop, + ); + + if (!isWrapped) { + const { width } = wordEl.screenBounds(); + wordEl.setStyle({ width: `${width}px` }); + wordEl.removeClass("needs-wrap"); + } else { + wordEl.setStyle({ width: "" }); + wordEl.addClass("needs-wrap"); + } wordEl.addClass("broken-ligatures"); } function reset(wordEl: ElementWithUtils): void { if (!wordEl.hasClass("broken-ligatures")) return; wordEl.removeClass("broken-ligatures"); + wordEl.removeClass("needs-wrap"); wordEl.setStyle({ width: "" }); }