style: update subtitle text shadow, JLPT underlines, and frequency topX default (#96)

* style: update subtitle text shadow, JLPT underlines, and frequency topX

- Replace directional text-shadow with 4-corner outline shadow for sharper readability
- Increase JLPT level border-bottom from 2px to 3px; add drop-shadow filter
- Add margin-left 0.18em to character image token
- Raise frequencyDictionary topX default from 1000 to 10000

* docs: add subtitle style changelog fragment

* docs: update generated config examples

* style: wrap long textShadow strings and add blank line in CSS
This commit is contained in:
2026-05-28 00:18:39 -07:00
committed by GitHub
parent 8d0535f3ca
commit d33009d4a3
9 changed files with 45 additions and 25 deletions
+22 -10
View File
@@ -667,9 +667,13 @@ body.subtitle-sidebar-embedded-open #subtitleContainer {
--subtitle-frequency-band-3-color: #f9e2af;
--subtitle-frequency-band-4-color: #8bd5ca;
--subtitle-frequency-band-5-color: #8aadf4;
text-shadow:
2px 2px 4px rgba(0, 0, 0, 0.8),
-1px -1px 2px rgba(0, 0, 0, 0.5);
-1px -1px 2px rgba(0, 0, 0, 0.95),
1px -1px 2px rgba(0, 0, 0, 0.95),
-1px 1px 2px rgba(0, 0, 0, 0.95),
1px 1px 2px rgba(0, 0, 0, 0.95),
0 0 8px rgba(0, 0, 0, 0.5);
/* Enable text selection for Yomitan */
user-select: text;
cursor: text;
@@ -817,6 +821,7 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] {
display: inline-block;
position: relative;
padding-left: 1.08em;
margin-left: 0.18em;
vertical-align: baseline;
}
@@ -837,7 +842,8 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] {
#subtitleRoot .word.word-jlpt-n1 {
text-decoration-line: none;
border-bottom: 2px solid var(--subtitle-jlpt-n1-color, #ed8796);
border-bottom: 3px solid var(--subtitle-jlpt-n1-color, #ed8796);
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
}
#subtitleRoot .word.word-jlpt-n1[data-jlpt-level]::after {
@@ -846,7 +852,8 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] {
#subtitleRoot .word.word-jlpt-n2 {
text-decoration-line: none;
border-bottom: 2px solid var(--subtitle-jlpt-n2-color, #f5a97f);
border-bottom: 3px solid var(--subtitle-jlpt-n2-color, #f5a97f);
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
}
#subtitleRoot .word.word-jlpt-n2[data-jlpt-level]::after {
@@ -855,7 +862,8 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] {
#subtitleRoot .word.word-jlpt-n3 {
text-decoration-line: none;
border-bottom: 2px solid var(--subtitle-jlpt-n3-color, #f9e2af);
border-bottom: 3px solid var(--subtitle-jlpt-n3-color, #f9e2af);
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
}
#subtitleRoot .word.word-jlpt-n3[data-jlpt-level]::after {
@@ -864,7 +872,8 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] {
#subtitleRoot .word.word-jlpt-n4 {
text-decoration-line: none;
border-bottom: 2px solid var(--subtitle-jlpt-n4-color, #a6e3a1);
border-bottom: 3px solid var(--subtitle-jlpt-n4-color, #a6e3a1);
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
}
#subtitleRoot .word.word-jlpt-n4[data-jlpt-level]::after {
@@ -873,7 +882,8 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] {
#subtitleRoot .word.word-jlpt-n5 {
text-decoration-line: none;
border-bottom: 2px solid var(--subtitle-jlpt-n5-color, #8aadf4);
border-bottom: 3px solid var(--subtitle-jlpt-n5-color, #8aadf4);
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
}
#subtitleRoot .word.word-jlpt-n5[data-jlpt-level]::after {
@@ -1186,9 +1196,11 @@ body.layer-modal #overlay {
-webkit-text-stroke: 0.45px rgba(0, 0, 0, 0.7);
paint-order: stroke fill;
text-shadow:
0 2px 4px rgba(0, 0, 0, 0.95),
0 0 8px rgba(0, 0, 0, 0.8),
0 0 16px rgba(0, 0, 0, 0.55);
-1px -1px 2px rgba(0, 0, 0, 0.95),
1px -1px 2px rgba(0, 0, 0, 0.95),
-1px 1px 2px rgba(0, 0, 0, 0.95),
1px 1px 2px rgba(0, 0, 0, 0.95),
0 0 8px rgba(0, 0, 0, 0.5);
user-select: text;
cursor: text;
}
+4 -3
View File
@@ -909,8 +909,8 @@ test('subtitle annotation CSS underlines JLPT tokens without changing token colo
assert.doesNotMatch(plainJlptBlock, /text-decoration\s*:[^;]*\bunderline\b/i);
assert.match(
plainJlptBlock,
new RegExp(`border-bottom:\\s*2px\\s+solid\\s+var\\(--subtitle-jlpt-n${level}-color,`),
`JLPT level must paint a permanent 2px border-bottom in the level color`,
new RegExp(`border-bottom:\\s*3px\\s+solid\\s+var\\(--subtitle-jlpt-n${level}-color,`),
`JLPT level must paint a permanent 3px border-bottom in the level color`,
);
// JLPT tagging must communicate level *only* via the underline; it must
@@ -973,6 +973,7 @@ test('subtitle annotation CSS underlines JLPT tokens without changing token colo
assert.match(characterImageTokenBlock, /display:\s*inline-block;/);
assert.match(characterImageTokenBlock, /position:\s*relative;/);
assert.match(characterImageTokenBlock, /padding-left:\s*1\.08em;/);
assert.match(characterImageTokenBlock, /margin-left:\s*0\.18em;/);
const characterImageBlock = extractClassBlock(cssText, '#subtitleRoot .word-character-image');
assert.match(characterImageBlock, /position:\s*absolute;/);
@@ -1186,7 +1187,7 @@ test('subtitle annotation CSS underlines JLPT tokens without changing token colo
assert.match(secondaryRootBlock, /-webkit-text-stroke:\s*0\.45px rgba\(0,\s*0,\s*0,\s*0\.7\);/);
assert.match(
secondaryRootBlock,
/text-shadow:\s*0 2px 4px rgba\(0,\s*0,\s*0,\s*0\.95\),\s*0 0 8px rgba\(0,\s*0,\s*0,\s*0\.8\),\s*0 0 16px rgba\(0,\s*0,\s*0,\s*0\.55\);/,
/text-shadow:\s*-1px -1px 2px rgba\(0,\s*0,\s*0,\s*0\.95\),\s*1px -1px 2px rgba\(0,\s*0,\s*0,\s*0\.95\),\s*-1px 1px 2px rgba\(0,\s*0,\s*0,\s*0\.95\),\s*1px 1px 2px rgba\(0,\s*0,\s*0,\s*0\.95\),\s*0 0 8px rgba\(0,\s*0,\s*0,\s*0\.5\);/,
);
const secondaryHoverBaseBlock = extractClassBlock(
+1 -1
View File
@@ -89,7 +89,7 @@ function sanitizeSubtitleHoverTokenBackgroundColor(value: unknown): string {
const DEFAULT_FREQUENCY_RENDER_SETTINGS: FrequencyRenderSettings = {
enabled: false,
topX: 1000,
topX: 10000,
mode: 'single',
singleColor: '#f5a97f',
bandedColors: ['#ed8796', '#f5a97f', '#f9e2af', '#8bd5ca', '#8aadf4'],