From b791262860a3d620d73d6dab660764b109157a55 Mon Sep 17 00:00:00 2001 From: sudacode Date: Tue, 10 Mar 2026 01:37:32 -0700 Subject: [PATCH] fix: keep jlpt underline stable during Yomitan lookup --- changes/jlpt-underline-yomitan.md | 4 +++ src/renderer/style.css | 50 ++++++++++++++-------------- src/renderer/subtitle-render.test.ts | 26 +++++++++------ 3 files changed, 44 insertions(+), 36 deletions(-) create mode 100644 changes/jlpt-underline-yomitan.md diff --git a/changes/jlpt-underline-yomitan.md b/changes/jlpt-underline-yomitan.md new file mode 100644 index 0000000..96a1536 --- /dev/null +++ b/changes/jlpt-underline-yomitan.md @@ -0,0 +1,4 @@ +type: fixed +area: overlay + +- Kept JLPT underline colors stable during Yomitan hover and selection states, even when tokens also use known, N+1, name-match, or frequency styling. diff --git a/src/renderer/style.css b/src/renderer/style.css index 075372b..86244a1 100644 --- a/src/renderer/style.css +++ b/src/renderer/style.css @@ -423,11 +423,11 @@ body.settings-modal-open #subtitleContainer { } #subtitleRoot .word.word-jlpt-n1 { - text-decoration-line: underline; - text-decoration-thickness: 2px; - text-underline-offset: 4px; - text-decoration-color: var(--subtitle-jlpt-n1-color, #ed8796); - text-decoration-style: solid; + --subtitle-jlpt-underline-color: var(--subtitle-jlpt-n1-color, #ed8796); + border-bottom: 2px solid var(--subtitle-jlpt-underline-color); + padding-bottom: 1px; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; } #subtitleRoot .word.word-jlpt-n1[data-jlpt-level]::after { @@ -435,11 +435,11 @@ body.settings-modal-open #subtitleContainer { } #subtitleRoot .word.word-jlpt-n2 { - text-decoration-line: underline; - text-decoration-thickness: 2px; - text-underline-offset: 4px; - text-decoration-color: var(--subtitle-jlpt-n2-color, #f5a97f); - text-decoration-style: solid; + --subtitle-jlpt-underline-color: var(--subtitle-jlpt-n2-color, #f5a97f); + border-bottom: 2px solid var(--subtitle-jlpt-underline-color); + padding-bottom: 1px; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; } #subtitleRoot .word.word-jlpt-n2[data-jlpt-level]::after { @@ -447,11 +447,11 @@ body.settings-modal-open #subtitleContainer { } #subtitleRoot .word.word-jlpt-n3 { - text-decoration-line: underline; - text-decoration-thickness: 2px; - text-underline-offset: 4px; - text-decoration-color: var(--subtitle-jlpt-n3-color, #f9e2af); - text-decoration-style: solid; + --subtitle-jlpt-underline-color: var(--subtitle-jlpt-n3-color, #f9e2af); + border-bottom: 2px solid var(--subtitle-jlpt-underline-color); + padding-bottom: 1px; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; } #subtitleRoot .word.word-jlpt-n3[data-jlpt-level]::after { @@ -459,11 +459,11 @@ body.settings-modal-open #subtitleContainer { } #subtitleRoot .word.word-jlpt-n4 { - text-decoration-line: underline; - text-decoration-thickness: 2px; - text-underline-offset: 4px; - text-decoration-color: var(--subtitle-jlpt-n4-color, #a6e3a1); - text-decoration-style: solid; + --subtitle-jlpt-underline-color: var(--subtitle-jlpt-n4-color, #a6e3a1); + border-bottom: 2px solid var(--subtitle-jlpt-underline-color); + padding-bottom: 1px; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; } #subtitleRoot .word.word-jlpt-n4[data-jlpt-level]::after { @@ -471,11 +471,11 @@ body.settings-modal-open #subtitleContainer { } #subtitleRoot .word.word-jlpt-n5 { - text-decoration-line: underline; - text-decoration-thickness: 2px; - text-underline-offset: 4px; - text-decoration-color: var(--subtitle-jlpt-n5-color, #8aadf4); - text-decoration-style: solid; + --subtitle-jlpt-underline-color: var(--subtitle-jlpt-n5-color, #8aadf4); + border-bottom: 2px solid var(--subtitle-jlpt-underline-color); + padding-bottom: 1px; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; } #subtitleRoot .word.word-jlpt-n5[data-jlpt-level]::after { diff --git a/src/renderer/subtitle-render.test.ts b/src/renderer/subtitle-render.test.ts index 0ce9d43..3eb7517 100644 --- a/src/renderer/subtitle-render.test.ts +++ b/src/renderer/subtitle-render.test.ts @@ -686,9 +686,14 @@ test('JLPT CSS rules use underline-only styling in renderer stylesheet', () => { for (let level = 1; level <= 5; level += 1) { const block = extractClassBlock(cssText, `#subtitleRoot .word.word-jlpt-n${level}`); assert.ok(block.length > 0, `word-jlpt-n${level} class should exist`); - assert.match(block, /text-decoration-line:\s*underline;/); - assert.match(block, /text-decoration-thickness:\s*2px;/); - assert.match(block, /text-underline-offset:\s*4px;/); + assert.match( + block, + new RegExp(`--subtitle-jlpt-underline-color:\\s*var\\(--subtitle-jlpt-n${level}-color,`), + ); + assert.match(block, /border-bottom:\s*2px solid var\(--subtitle-jlpt-underline-color\);/); + assert.match(block, /padding-bottom:\s*1px;/); + assert.match(block, /box-decoration-break:\s*clone;/); + assert.match(block, /-webkit-box-decoration-break:\s*clone;/); assert.doesNotMatch(block, /(?:^|\n)\s*color\s*:/m); } @@ -828,18 +833,17 @@ test('JLPT CSS rules use underline-only styling in renderer stylesheet', () => { jlptOnlyHoverBlock, /-webkit-text-fill-color:\s*var\(--subtitle-hover-token-color,\s*#f4dbd6\)\s*!important;/, ); + + const jlptOnlySelectionBlock = extractClassBlock( + cssText, + '#subtitleRoot .word:is(.word-jlpt-n1, .word-jlpt-n2, .word-jlpt-n3, .word-jlpt-n4, .word-jlpt-n5):not(.word-known):not(.word-n-plus-one):not(.word-name-match):not(.word-frequency-single):not(.word-frequency-band-1):not(.word-frequency-band-2):not(.word-frequency-band-3):not(.word-frequency-band-4):not(.word-frequency-band-5)::selection', + ); assert.match( - extractClassBlock( - cssText, - '#subtitleRoot .word:is(.word-jlpt-n1, .word-jlpt-n2, .word-jlpt-n3, .word-jlpt-n4, .word-jlpt-n5):not(.word-known):not(.word-n-plus-one):not(.word-name-match):not(.word-frequency-single):not(.word-frequency-band-1):not(.word-frequency-band-2):not(.word-frequency-band-3):not(.word-frequency-band-4):not(.word-frequency-band-5)::selection', - ), + jlptOnlySelectionBlock, /color:\s*var\(--subtitle-hover-token-color,\s*#f4dbd6\)\s*!important;/, ); assert.match( - extractClassBlock( - cssText, - '#subtitleRoot .word:is(.word-jlpt-n1, .word-jlpt-n2, .word-jlpt-n3, .word-jlpt-n4, .word-jlpt-n5):not(.word-known):not(.word-n-plus-one):not(.word-name-match):not(.word-frequency-single):not(.word-frequency-band-1):not(.word-frequency-band-2):not(.word-frequency-band-3):not(.word-frequency-band-4):not(.word-frequency-band-5)::selection', - ), + jlptOnlySelectionBlock, /-webkit-text-fill-color:\s*var\(--subtitle-hover-token-color,\s*#f4dbd6\)\s*!important;/, );