feat(overlay): Anki maturity-based known-word highlighting

Color known-word subtitle highlights by Anki card maturity (new,
learning, young, mature) like asbplayer (#171). Notes are classified
server-side with Anki search filters (prop:ivl, is:learn) during the
known-word cache refresh, so no per-card data is fetched. A word's tier
is its most mature matching card/note, with the same reading-aware
matching as boolean known-word lookups.

- known-word cache v4 state persists per-note tiers; lifecycle key only
  gains the maturity field while enabled so existing caches survive
- ankiConnect.knownWords.maturityEnabled + matureThresholdDays (21)
- subtitleStyle.knownWordMaturityColors with catppuccin defaults
- word-maturity-<tier> class rides on word-known so hover/selection
  rules keep applying; falls back to knownWordColor without tier data
- runtime toggle subtitle.annotation.knownWords.maturityEnabled
This commit is contained in:
2026-07-17 23:41:40 -07:00
parent 08c6807cb1
commit bc8dce870b
31 changed files with 1238 additions and 47 deletions
+42
View File
@@ -1503,6 +1503,24 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] {
color: var(--subtitle-known-word-color, #a6da95);
}
/* Anki maturity tiers ride on word-known and only override its color, so
hover/selection rules keyed on word-known keep applying. */
#subtitleRoot .word.word-known.word-maturity-new {
color: var(--subtitle-maturity-new-color, #ee99a0);
}
#subtitleRoot .word.word-known.word-maturity-learning {
color: var(--subtitle-maturity-learning-color, #b7bdf8);
}
#subtitleRoot .word.word-known.word-maturity-young {
color: var(--subtitle-maturity-young-color, #91d7e3);
}
#subtitleRoot .word.word-known.word-maturity-mature {
color: var(--subtitle-maturity-mature-color, #a6da95);
}
#subtitleRoot .word.word-n-plus-one {
color: var(--subtitle-n-plus-one-color, #c6a0f6);
}
@@ -1680,6 +1698,30 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] {
-webkit-text-fill-color: var(--subtitle-known-word-color, #a6da95) !important;
}
#subtitleRoot .word.word-known.word-maturity-new::selection,
#subtitleRoot .word.word-known.word-maturity-new .c::selection {
color: var(--subtitle-maturity-new-color, #ee99a0) !important;
-webkit-text-fill-color: var(--subtitle-maturity-new-color, #ee99a0) !important;
}
#subtitleRoot .word.word-known.word-maturity-learning::selection,
#subtitleRoot .word.word-known.word-maturity-learning .c::selection {
color: var(--subtitle-maturity-learning-color, #b7bdf8) !important;
-webkit-text-fill-color: var(--subtitle-maturity-learning-color, #b7bdf8) !important;
}
#subtitleRoot .word.word-known.word-maturity-young::selection,
#subtitleRoot .word.word-known.word-maturity-young .c::selection {
color: var(--subtitle-maturity-young-color, #91d7e3) !important;
-webkit-text-fill-color: var(--subtitle-maturity-young-color, #91d7e3) !important;
}
#subtitleRoot .word.word-known.word-maturity-mature::selection,
#subtitleRoot .word.word-known.word-maturity-mature .c::selection {
color: var(--subtitle-maturity-mature-color, #a6da95) !important;
-webkit-text-fill-color: var(--subtitle-maturity-mature-color, #a6da95) !important;
}
#subtitleRoot .word.word-n-plus-one::selection,
#subtitleRoot .word.word-n-plus-one .c::selection {
color: var(--subtitle-n-plus-one-color, #c6a0f6) !important;