Yunit – companion language layer
+1
-1
| { | ||
| "manifest_version": 3, | ||
| "name": "Yunit \u2013 companion language layer", | ||
| "version": "0.2.0", | ||
| "version": "0.2.2", | ||
| "description": "Picks words from any page you read and quietly translates a few of them, so vocabulary travels with you.", | ||
@@ -6,0 +6,0 @@ "permissions": [ |
+23
-2
@@ -9,3 +9,9 @@ (() => { | ||
| // "lemma migration done" flag. Kept indefinitely as a safety net. | ||
| preLemmaSnapshot: "yunit.vocab.preLemma" | ||
| preLemmaSnapshot: "yunit.vocab.preLemma", | ||
| // Per-video lyrics state, both LRU-capped maps keyed by YT video id. | ||
| // Nudges: the manual sync offset the user dialed in, so a rewatch | ||
| // doesn't make them re-tune. Cache: the resolved LRCLIB payload, so a | ||
| // rewatch renders instantly without re-hitting the API. | ||
| lyricsNudges: "yunit.lyrics.nudges", | ||
| lyricsCache: "yunit.lyrics.cache" | ||
| }; | ||
@@ -167,3 +173,18 @@ var extDead = false; | ||
| // default surface rather than an Alt+Shift+L away. | ||
| lyricsAutoOpen: false | ||
| lyricsAutoOpen: false, | ||
| // On-video karaoke overlay appearance. Style picks the contrast strategy | ||
| // against the footage: 'shadow' (soft dark halo, the original), 'outline' | ||
| // (hard stroke, holds up on mid-brightness video), 'plate' (caption-style | ||
| // scrim behind the text — readable on anything). Color is a small curated | ||
| // palette of light inks; size multiplies the player-width-derived base | ||
| // font. nextLine shows the upcoming lyric dimmed under the current one. | ||
| // Controls live in the lyrics panel header, not the popup — appearance | ||
| // is tuned while watching. | ||
| lyricsOverlayStyle: "shadow", | ||
| // 'shadow' | 'outline' | 'plate' | ||
| lyricsOverlayColor: "white", | ||
| // 'white' | 'cream' | 'yellow' | 'cyan' | ||
| lyricsOverlaySize: "medium", | ||
| // 'small' | 'medium' | 'large' | ||
| lyricsOverlayNextLine: false | ||
| }; | ||
@@ -170,0 +191,0 @@ var settings = { ...DEFAULTS }; |
+243
-15
@@ -854,2 +854,4 @@ /* Yunit — companion language layer | ||
| gap: 6px; | ||
| /* Anchors the appearance menu's absolute positioning. */ | ||
| position: relative; | ||
| } | ||
@@ -888,3 +890,4 @@ .yunit-lyrics-header-row { | ||
| .yunit-lyrics-follow, | ||
| .yunit-lyrics-overlay { | ||
| .yunit-lyrics-overlay, | ||
| .yunit-lyrics-appearance { | ||
| flex-shrink: 0; | ||
@@ -907,6 +910,8 @@ background: transparent; | ||
| .yunit-lyrics-follow[hidden], | ||
| .yunit-lyrics-overlay[hidden] { display: none; } | ||
| .yunit-lyrics-overlay[hidden], | ||
| .yunit-lyrics-appearance[hidden] { display: none; } | ||
| .yunit-lyrics-gloss:hover, | ||
| .yunit-lyrics-follow:hover, | ||
| .yunit-lyrics-overlay:hover { | ||
| .yunit-lyrics-overlay:hover, | ||
| .yunit-lyrics-appearance:hover { | ||
| border-color: hsla(355, 45%, 32%, 0.45); | ||
@@ -918,3 +923,4 @@ color: hsl(355, 45%, 32%); | ||
| .yunit-lyrics-follow[data-on="true"], | ||
| .yunit-lyrics-overlay[data-on="true"] { | ||
| .yunit-lyrics-overlay[data-on="true"], | ||
| .yunit-lyrics-appearance[aria-expanded="true"] { | ||
| color: hsl(355, 50%, 26%); | ||
@@ -924,2 +930,76 @@ background: hsla(355, 45%, 32%, 0.08); | ||
| } | ||
| /* Appearance menu — a small settings card hanging off the header's action | ||
| row. Same paper-and-ink register as the rest of the panel. */ | ||
| .yunit-lyrics-appearance-menu { | ||
| position: absolute; | ||
| top: 100%; | ||
| right: 12px; | ||
| z-index: 40; | ||
| min-width: 230px; | ||
| background: hsl(40, 35%, 97%); | ||
| border: 1px solid hsl(28, 18%, 84%); | ||
| border-radius: 8px; | ||
| box-shadow: 0 6px 20px hsla(28, 30%, 15%, 0.14); | ||
| padding: 10px 12px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 9px; | ||
| } | ||
| .yunit-lyrics-appearance-menu[hidden] { display: none; } | ||
| .yunit-lyrics-appearance-row { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| gap: 10px; | ||
| } | ||
| .yunit-lyrics-appearance-label { | ||
| font-size: 11px; | ||
| font-weight: 600; | ||
| letter-spacing: 0.3px; | ||
| text-transform: uppercase; | ||
| color: hsl(28, 10%, 50%); | ||
| white-space: nowrap; | ||
| } | ||
| .yunit-lyrics-appearance-options { | ||
| display: inline-flex; | ||
| gap: 4px; | ||
| } | ||
| .yunit-lyrics-appearance-options button { | ||
| background: transparent; | ||
| border: 1px solid hsl(28, 18%, 82%); | ||
| border-radius: 5px; | ||
| font-family: inherit; | ||
| font-size: 11px; | ||
| color: hsl(28, 10%, 42%); | ||
| cursor: pointer; | ||
| padding: 0 7px; | ||
| height: 22px; | ||
| line-height: 20px; | ||
| transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease; | ||
| } | ||
| .yunit-lyrics-appearance-options button:hover { | ||
| border-color: hsla(355, 45%, 32%, 0.45); | ||
| color: hsl(355, 45%, 32%); | ||
| } | ||
| .yunit-lyrics-appearance-options button[data-active="true"], | ||
| .yunit-lyrics-appearance-options button[data-on="true"] { | ||
| color: hsl(355, 50%, 26%); | ||
| background: hsla(355, 45%, 32%, 0.08); | ||
| border-color: hsla(355, 45%, 32%, 0.4); | ||
| } | ||
| /* Color swatches — the ink itself is the label (set inline from JS). | ||
| A subtle dark inlay keeps the white swatch visible on cream paper. */ | ||
| .yunit-lyrics-appearance-swatches button { | ||
| width: 22px; | ||
| min-width: 22px; | ||
| padding: 0; | ||
| border-radius: 50%; | ||
| box-shadow: inset 0 0 0 1px hsla(28, 30%, 20%, 0.18); | ||
| } | ||
| .yunit-lyrics-appearance-swatches button[data-active="true"] { | ||
| border-color: hsl(355, 45%, 32%); | ||
| box-shadow: inset 0 0 0 1px hsla(28, 30%, 20%, 0.18), | ||
| 0 0 0 2px hsla(355, 45%, 32%, 0.25); | ||
| } | ||
| .yunit-lyrics-gloss[data-mode="off"], | ||
@@ -1115,2 +1195,11 @@ .yunit-lyrics-follow[data-on="false"] { color: hsl(28, 10%, 60%); } | ||
| /* On-video overlay. Appearance is driven by custom properties + data | ||
| attributes set from lyrics.js: | ||
| --yunit-lyrics-base px size derived from the player's width | ||
| --yunit-lyrics-scale the user's S/M/L multiplier | ||
| --yunit-lyrics-color / --yunit-lyrics-color-dim chosen ink | ||
| data-style contrast strategy: shadow | outline | plate | ||
| data-next upcoming-line preview: on | off | ||
| The lifted class moves us above YouTube's own caption band when both | ||
| are showing. */ | ||
| .yunit-video-overlay { | ||
@@ -1126,9 +1215,10 @@ position: absolute; | ||
| opacity: 0; | ||
| transition: opacity 0.3s ease; | ||
| transition: opacity 0.3s ease, bottom 0.25s ease; | ||
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; | ||
| } | ||
| .yunit-video-overlay.yunit-video-overlay-on { opacity: 1; } | ||
| .yunit-video-overlay.yunit-video-overlay-lifted { bottom: 170px; } | ||
| .yunit-video-source, | ||
| .yunit-video-target { | ||
| text-shadow: 0 0 6px rgba(0, 0, 0, 0.95), 0 2px 6px rgba(0, 0, 0, 0.7); | ||
| .yunit-video-target, | ||
| .yunit-video-next { | ||
| line-height: 1.3; | ||
@@ -1139,14 +1229,62 @@ word-wrap: break-word; | ||
| .yunit-video-source { | ||
| font-size: 24px; | ||
| font-size: calc(var(--yunit-lyrics-base, 24px) * var(--yunit-lyrics-scale, 1)); | ||
| font-weight: 500; | ||
| color: white; | ||
| color: var(--yunit-lyrics-color, #ffffff); | ||
| margin-bottom: 4px; | ||
| } | ||
| .yunit-video-target { | ||
| font-size: 18px; | ||
| color: rgba(255, 255, 255, 0.85); | ||
| font-size: calc(var(--yunit-lyrics-base, 24px) * var(--yunit-lyrics-scale, 1) * 0.75); | ||
| color: var(--yunit-lyrics-color-dim, rgba(255, 255, 255, 0.85)); | ||
| font-style: italic; | ||
| } | ||
| .yunit-video-next { | ||
| font-size: calc(var(--yunit-lyrics-base, 24px) * var(--yunit-lyrics-scale, 1) * 0.7); | ||
| color: var(--yunit-lyrics-color-dim, rgba(255, 255, 255, 0.85)); | ||
| opacity: 0.55; | ||
| margin-top: 6px; | ||
| } | ||
| .yunit-video-overlay:not([data-next="on"]) .yunit-video-next { display: none; } | ||
| .yunit-video-source:empty, | ||
| .yunit-video-target:empty { display: none; } | ||
| .yunit-video-target:empty, | ||
| .yunit-video-next:empty { display: none; } | ||
| /* Contrast strategies. Shadow is the default (also applies when the | ||
| data-style attribute is absent for any reason). Offsets are in em so | ||
| the halo / stroke scales with the font. */ | ||
| .yunit-video-overlay:not([data-style="outline"]):not([data-style="plate"]) .yunit-video-source, | ||
| .yunit-video-overlay:not([data-style="outline"]):not([data-style="plate"]) .yunit-video-target, | ||
| .yunit-video-overlay:not([data-style="outline"]):not([data-style="plate"]) .yunit-video-next { | ||
| text-shadow: 0 0 0.25em rgba(0, 0, 0, 0.95), 0 0.08em 0.25em rgba(0, 0, 0, 0.7); | ||
| } | ||
| /* Outline — a hard dark stroke built from layered shadows; survives | ||
| mid-brightness footage where a soft halo washes out. */ | ||
| .yunit-video-overlay[data-style="outline"] .yunit-video-source, | ||
| .yunit-video-overlay[data-style="outline"] .yunit-video-target, | ||
| .yunit-video-overlay[data-style="outline"] .yunit-video-next { | ||
| text-shadow: | ||
| -0.045em -0.045em 0 rgba(0, 0, 0, 0.9), | ||
| 0.045em -0.045em 0 rgba(0, 0, 0, 0.9), | ||
| -0.045em 0.045em 0 rgba(0, 0, 0, 0.9), | ||
| 0.045em 0.045em 0 rgba(0, 0, 0, 0.9), | ||
| 0 -0.06em 0 rgba(0, 0, 0, 0.9), | ||
| 0 0.06em 0 rgba(0, 0, 0, 0.9), | ||
| -0.06em 0 0 rgba(0, 0, 0, 0.9), | ||
| 0.06em 0 0 rgba(0, 0, 0, 0.9), | ||
| 0 0.1em 0.3em rgba(0, 0, 0, 0.45); | ||
| } | ||
| /* Plate — a caption-style scrim behind each line. Reads on any footage, | ||
| including white-on-white concert lighting. */ | ||
| .yunit-video-overlay[data-style="plate"] .yunit-video-source, | ||
| .yunit-video-overlay[data-style="plate"] .yunit-video-target, | ||
| .yunit-video-overlay[data-style="plate"] .yunit-video-next { | ||
| background: rgba(0, 0, 0, 0.62); | ||
| width: fit-content; | ||
| max-width: min(88%, 920px); | ||
| margin-left: auto; | ||
| margin-right: auto; | ||
| padding: 0.08em 0.55em 0.16em; | ||
| border-radius: 0.28em; | ||
| } | ||
| .yunit-video-overlay[data-style="plate"] .yunit-video-source { margin-bottom: 0.18em; } | ||
| .yunit-video-overlay[data-style="plate"] .yunit-video-next { margin-top: 0.3em; } | ||
| .yunit-lyrics-meta { flex: 1; min-width: 0; } | ||
@@ -1237,2 +1375,7 @@ .yunit-lyrics-title { | ||
| border-radius: 5px; | ||
| /* Lyric lines are content, not chrome — same editorial serif as the | ||
| panel title and the dual-view glosses, so the panel reads as a page | ||
| from the commonplace book rather than a widget. */ | ||
| font-family: 'Iowan Old Style', 'Charter', 'Cambria', Georgia, serif; | ||
| font-size: 15px; | ||
| color: hsl(28, 10%, 42%); | ||
@@ -1243,2 +1386,8 @@ transition: background 0.4s ease, color 0.3s ease; | ||
| } | ||
| /* Synced lyrics are seekable — click a line to replay it. The affordance | ||
| is a quiet brand-tint hover; the active line keeps its live purple. */ | ||
| .yunit-lyrics-body[data-synced="true"] .yunit-lyrics-line { cursor: pointer; } | ||
| .yunit-lyrics-body[data-synced="true"] .yunit-lyrics-line:hover:not(.yunit-lyrics-active) { | ||
| background: hsla(355, 45%, 32%, 0.05); | ||
| } | ||
| /* Active line during synced playback — uses the "live" purple, distinct | ||
@@ -1518,3 +1667,5 @@ from the burgundy brand accent so the user reads it as a transient | ||
| html[dark] .yunit-lyrics-follow, | ||
| html[dark] .yunit-lyrics-overlay { | ||
| html[dark] .yunit-lyrics-overlay, | ||
| html[dark] .yunit-lyrics-appearance, | ||
| html[dark] .yunit-lyrics-search-other { | ||
| border-color: #4a4550; | ||
@@ -1527,3 +1678,5 @@ color: #a89f8d; | ||
| html[dark] .yunit-lyrics-follow:hover, | ||
| html[dark] .yunit-lyrics-overlay:hover { | ||
| html[dark] .yunit-lyrics-overlay:hover, | ||
| html[dark] .yunit-lyrics-appearance:hover, | ||
| html[dark] .yunit-lyrics-search-other:hover { | ||
| color: hsl(355, 60%, 82%); | ||
@@ -1535,3 +1688,4 @@ border-color: hsla(355, 50%, 60%, 0.5); | ||
| html[dark] .yunit-lyrics-follow[data-on="true"], | ||
| html[dark] .yunit-lyrics-overlay[data-on="true"] { | ||
| html[dark] .yunit-lyrics-overlay[data-on="true"], | ||
| html[dark] .yunit-lyrics-appearance[aria-expanded="true"] { | ||
| color: hsl(355, 60%, 84%); | ||
@@ -1541,2 +1695,76 @@ background: hsla(355, 45%, 50%, 0.18); | ||
| } | ||
| html[dark] .yunit-lyrics-body[data-synced="true"] .yunit-lyrics-line:hover:not(.yunit-lyrics-active) { | ||
| background: hsla(355, 45%, 70%, 0.08); | ||
| } | ||
| html[dark] .yunit-lyrics-sharpen { | ||
| border-color: #4a4550; | ||
| color: #ddd9d2; | ||
| } | ||
| html[dark] .yunit-lyrics-sharpen:hover:not([disabled]) { | ||
| color: hsl(355, 60%, 84%); | ||
| border-color: hsla(355, 50%, 60%, 0.5); | ||
| background: hsla(355, 45%, 50%, 0.18); | ||
| } | ||
| html[dark] .yunit-lyrics-sharpen[disabled] { | ||
| color: #6e6760; | ||
| border-color: #3a363f; | ||
| } | ||
| html[dark] .yunit-lyrics-sharpen[data-state="done"][data-mode="sharp"] { | ||
| color: hsl(355, 60%, 80%); | ||
| border-color: hsla(355, 50%, 60%, 0.4); | ||
| background: hsla(355, 45%, 50%, 0.1); | ||
| } | ||
| html[dark] .yunit-lyrics-sharpen[data-state="done"][data-mode="nmt"] { | ||
| color: #8a8275; | ||
| border-color: #4a4550; | ||
| background: transparent; | ||
| } | ||
| html[dark] .yunit-lyrics-nudge { border-color: #4a4550; } | ||
| html[dark] .yunit-lyrics-nudge[data-active="true"] { | ||
| border-color: hsla(355, 50%, 60%, 0.5); | ||
| background: hsla(355, 45%, 50%, 0.12); | ||
| } | ||
| html[dark] .yunit-lyrics-nudge-back, | ||
| html[dark] .yunit-lyrics-nudge-fwd { color: #a89f8d; } | ||
| html[dark] .yunit-lyrics-nudge-back:hover, | ||
| html[dark] .yunit-lyrics-nudge-fwd:hover { | ||
| background: hsla(355, 45%, 50%, 0.18); | ||
| color: hsl(355, 60%, 84%); | ||
| } | ||
| html[dark] .yunit-lyrics-nudge-value { | ||
| color: #8a8275; | ||
| border-color: #3a363f; | ||
| } | ||
| html[dark] .yunit-lyrics-nudge[data-active="true"] .yunit-lyrics-nudge-value { | ||
| color: hsl(355, 60%, 84%); | ||
| border-color: hsla(355, 50%, 60%, 0.35); | ||
| } | ||
| html[dark] .yunit-lyrics-appearance-menu { | ||
| background: #26232a; | ||
| border-color: #3a363f; | ||
| box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5); | ||
| } | ||
| html[dark] .yunit-lyrics-appearance-label { color: #8a8275; } | ||
| html[dark] .yunit-lyrics-appearance-options button { | ||
| border-color: #4a4550; | ||
| color: #a89f8d; | ||
| } | ||
| html[dark] .yunit-lyrics-appearance-options button:hover { | ||
| color: hsl(355, 60%, 82%); | ||
| border-color: hsla(355, 50%, 60%, 0.5); | ||
| } | ||
| html[dark] .yunit-lyrics-appearance-options button[data-active="true"], | ||
| html[dark] .yunit-lyrics-appearance-options button[data-on="true"] { | ||
| color: hsl(355, 60%, 84%); | ||
| background: hsla(355, 45%, 50%, 0.18); | ||
| border-color: hsla(355, 50%, 60%, 0.5); | ||
| } | ||
| html[dark] .yunit-lyrics-appearance-swatches button { | ||
| box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12); | ||
| } | ||
| html[dark] .yunit-lyrics-appearance-swatches button[data-active="true"] { | ||
| border-color: hsl(355, 60%, 65%); | ||
| box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12), | ||
| 0 0 0 2px hsla(355, 60%, 65%, 0.3); | ||
| } | ||
| html[dark] .yunit-lyrics-picker-btn { | ||
@@ -1543,0 +1771,0 @@ color: #ddd9d2; |
+23
-2
@@ -9,3 +9,9 @@ (() => { | ||
| // "lemma migration done" flag. Kept indefinitely as a safety net. | ||
| preLemmaSnapshot: "yunit.vocab.preLemma" | ||
| preLemmaSnapshot: "yunit.vocab.preLemma", | ||
| // Per-video lyrics state, both LRU-capped maps keyed by YT video id. | ||
| // Nudges: the manual sync offset the user dialed in, so a rewatch | ||
| // doesn't make them re-tune. Cache: the resolved LRCLIB payload, so a | ||
| // rewatch renders instantly without re-hitting the API. | ||
| lyricsNudges: "yunit.lyrics.nudges", | ||
| lyricsCache: "yunit.lyrics.cache" | ||
| }; | ||
@@ -167,3 +173,18 @@ var extDead = false; | ||
| // default surface rather than an Alt+Shift+L away. | ||
| lyricsAutoOpen: false | ||
| lyricsAutoOpen: false, | ||
| // On-video karaoke overlay appearance. Style picks the contrast strategy | ||
| // against the footage: 'shadow' (soft dark halo, the original), 'outline' | ||
| // (hard stroke, holds up on mid-brightness video), 'plate' (caption-style | ||
| // scrim behind the text — readable on anything). Color is a small curated | ||
| // palette of light inks; size multiplies the player-width-derived base | ||
| // font. nextLine shows the upcoming lyric dimmed under the current one. | ||
| // Controls live in the lyrics panel header, not the popup — appearance | ||
| // is tuned while watching. | ||
| lyricsOverlayStyle: "shadow", | ||
| // 'shadow' | 'outline' | 'plate' | ||
| lyricsOverlayColor: "white", | ||
| // 'white' | 'cream' | 'yellow' | 'cyan' | ||
| lyricsOverlaySize: "medium", | ||
| // 'small' | 'medium' | 'large' | ||
| lyricsOverlayNextLine: false | ||
| }; | ||
@@ -170,0 +191,0 @@ var settings = { ...DEFAULTS }; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display