initial commit

This commit is contained in:
2026-02-09 19:04:19 -08:00
commit 272d92169d
531 changed files with 196294 additions and 0 deletions

265
src/renderer/index.html Normal file
View File

@@ -0,0 +1,265 @@
<!--
SubMiner - All-in-one sentence mining overlay
Copyright (C) 2024 sudacode
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self' 'unsafe-inline' chrome-extension:; script-src 'self' 'unsafe-inline' chrome-extension:; style-src 'self' 'unsafe-inline' chrome-extension:;"
/>
<title>SubMiner</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="overlay">
<div id="secondarySubContainer" class="secondary-sub-hidden">
<div id="secondarySubRoot"></div>
</div>
<div id="subtitleContainer">
<div id="subtitleRoot"></div>
</div>
<div id="jimakuModal" class="modal hidden" aria-hidden="true">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">Jimaku Subtitles</div>
<button id="jimakuClose" class="modal-close" type="button">
Close
</button>
</div>
<div class="modal-body">
<div class="jimaku-form">
<label class="jimaku-field">
<span>Title</span>
<input id="jimakuTitle" type="text" placeholder="Anime title" />
</label>
<label class="jimaku-field">
<span>Season</span>
<input
id="jimakuSeason"
type="number"
min="1"
placeholder="1"
/>
</label>
<label class="jimaku-field">
<span>Episode</span>
<input
id="jimakuEpisode"
type="number"
min="1"
placeholder="1"
/>
</label>
<button id="jimakuSearch" class="jimaku-button" type="button">
Search
</button>
</div>
<div id="jimakuStatus" class="jimaku-status"></div>
<div id="jimakuEntriesSection" class="jimaku-section hidden">
<div class="jimaku-section-title">Entries</div>
<ul id="jimakuEntries" class="jimaku-list"></ul>
</div>
<div id="jimakuFilesSection" class="jimaku-section hidden">
<div class="jimaku-section-title">Files</div>
<ul id="jimakuFiles" class="jimaku-list"></ul>
<button
id="jimakuBroaden"
class="jimaku-link hidden"
type="button"
>
Broaden search (all files)
</button>
</div>
</div>
</div>
</div>
<div id="kikuFieldGroupingModal" class="modal hidden" aria-hidden="true">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">Duplicate Card Detected</div>
</div>
<div class="modal-body">
<div id="kikuSelectionStep">
<div class="kiku-info-text">
A card with the same expression already exists. Select which
card to keep. The other card's content will be merged using Kiku
field grouping. You can choose whether to delete the duplicate.
</div>
<div class="kiku-cards-container">
<div id="kikuCard1" class="kiku-card active" tabindex="0">
<div class="kiku-card-label">1 &mdash; Original Card</div>
<div
class="kiku-card-expression"
id="kikuCard1Expression"
></div>
<div class="kiku-card-sentence" id="kikuCard1Sentence"></div>
<div class="kiku-card-meta" id="kikuCard1Meta"></div>
</div>
<div id="kikuCard2" class="kiku-card" tabindex="0">
<div class="kiku-card-label">2 &mdash; New Card</div>
<div
class="kiku-card-expression"
id="kikuCard2Expression"
></div>
<div class="kiku-card-sentence" id="kikuCard2Sentence"></div>
<div class="kiku-card-meta" id="kikuCard2Meta"></div>
</div>
</div>
<div class="kiku-footer">
<label class="kiku-delete-toggle">
<input id="kikuDeleteDuplicate" type="checkbox" checked />
Delete duplicate card after merge
</label>
<button
id="kikuConfirmButton"
class="kiku-confirm-button"
type="button"
>
Continue
</button>
<button
id="kikuCancelButton"
class="kiku-cancel-button"
type="button"
>
Cancel
</button>
</div>
</div>
<div id="kikuPreviewStep" class="hidden">
<div class="kiku-preview-header">
<div class="kiku-preview-title">Final Merge Preview</div>
<div class="kiku-preview-toggle">
<button id="kikuPreviewCompact" type="button">Compact</button>
<button id="kikuPreviewFull" type="button">Full</button>
</div>
</div>
<div
id="kikuPreviewError"
class="kiku-preview-error hidden"
></div>
<pre id="kikuPreviewJson" class="kiku-preview-json"></pre>
<div class="kiku-footer">
<button
id="kikuBackButton"
class="kiku-cancel-button"
type="button"
>
Back
</button>
<button
id="kikuFinalConfirmButton"
class="kiku-confirm-button"
type="button"
>
Confirm Merge
</button>
<button
id="kikuFinalCancelButton"
class="kiku-cancel-button"
type="button"
>
Cancel
</button>
</div>
</div>
<div id="kikuHint" class="kiku-hint">
Press 1 or 2 to select &middot; Enter to confirm &middot; Esc to
cancel
</div>
</div>
</div>
</div>
<div id="runtimeOptionsModal" class="modal hidden" aria-hidden="true">
<div class="modal-content runtime-modal-content">
<div class="modal-header">
<div class="modal-title">Runtime Options</div>
<button
id="runtimeOptionsClose"
class="modal-close"
type="button"
>
Close
</button>
</div>
<div class="modal-body">
<div id="runtimeOptionsHint" class="runtime-options-hint">
Arrow keys: select/change · Enter or double-click: apply · Esc:
close
</div>
<ul id="runtimeOptionsList" class="runtime-options-list"></ul>
<div id="runtimeOptionsStatus" class="runtime-options-status"></div>
</div>
</div>
</div>
<div id="subsyncModal" class="modal hidden" aria-hidden="true">
<div class="modal-content subsync-modal-content">
<div class="modal-header">
<div class="modal-title">Auto Subtitle Sync</div>
<button id="subsyncClose" class="modal-close" type="button">
Close
</button>
</div>
<div class="modal-body">
<div class="subsync-form">
<div class="subsync-field">
<span>Engine</span>
<label class="subsync-radio">
<input
id="subsyncEngineAlass"
type="radio"
name="subsyncEngine"
checked
/>
alass
</label>
<label class="subsync-radio">
<input
id="subsyncEngineFfsubsync"
type="radio"
name="subsyncEngine"
/>
ffsubsync
</label>
</div>
<label id="subsyncSourceLabel" class="subsync-field">
<span>Source Subtitle (for alass)</span>
<select id="subsyncSourceSelect"></select>
</label>
</div>
<div id="subsyncStatus" class="runtime-options-status"></div>
<div class="subsync-footer">
<button
id="subsyncRun"
class="kiku-confirm-button"
type="button"
>
Run Sync
</button>
</div>
</div>
</div>
</div>
</div>
<script src="renderer.js"></script>
</body>
</html>

2443
src/renderer/renderer.ts Normal file

File diff suppressed because it is too large Load Diff

703
src/renderer/style.css Normal file
View File

@@ -0,0 +1,703 @@
/*
* SubMiner - All-in-one sentence mining overlay
* Copyright (C) 2024 sudacode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
width: 100%;
height: 100%;
overflow: hidden;
background: transparent;
font-family:
"Noto Sans CJK JP Regular", "Noto Sans CJK JP", "Arial Unicode MS", Arial,
sans-serif;
}
#overlay {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
pointer-events: none;
}
#overlay.interactive {
pointer-events: auto;
}
.modal {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.55);
pointer-events: auto;
z-index: 1000;
}
.modal.hidden {
display: none;
}
.hidden {
display: none !important;
}
.modal-content {
width: min(720px, 92%);
max-height: 80%;
background: rgba(20, 20, 20, 0.95);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 12px;
padding: 16px;
color: #fff;
display: flex;
flex-direction: column;
gap: 12px;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.modal-title {
font-size: 18px;
font-weight: 600;
}
.modal-close {
background: rgba(255, 255, 255, 0.1);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
padding: 6px 10px;
cursor: pointer;
}
.modal-close:hover {
background: rgba(255, 255, 255, 0.2);
}
.modal-body {
display: flex;
flex-direction: column;
gap: 12px;
overflow: hidden;
}
.jimaku-form {
display: grid;
grid-template-columns: 1fr 120px 120px auto;
gap: 10px;
align-items: end;
}
.jimaku-field {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
}
.jimaku-field input {
background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
color: #fff;
padding: 6px 8px;
}
.jimaku-button {
height: 36px;
padding: 0 14px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.25);
background: rgba(255, 255, 255, 0.15);
color: #fff;
cursor: pointer;
}
.jimaku-button:hover {
background: rgba(255, 255, 255, 0.25);
}
.jimaku-status {
min-height: 20px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
}
.jimaku-section {
display: flex;
flex-direction: column;
gap: 6px;
max-height: 220px;
overflow: hidden;
}
.jimaku-section.hidden {
display: none;
}
.jimaku-section-title {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: rgba(255, 255, 255, 0.6);
}
.jimaku-list {
list-style: none;
padding: 0;
margin: 0;
overflow-y: auto;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
max-height: 180px;
}
.jimaku-list li {
padding: 8px 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
cursor: pointer;
display: flex;
flex-direction: column;
gap: 2px;
}
.jimaku-list li:last-child {
border-bottom: none;
}
.jimaku-list li.active {
background: rgba(255, 255, 255, 0.15);
}
.jimaku-list .jimaku-subtext {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
}
.jimaku-link {
align-self: flex-start;
background: transparent;
color: rgba(255, 255, 255, 0.8);
border: none;
padding: 0;
cursor: pointer;
text-decoration: underline;
}
.jimaku-link.hidden {
display: none;
}
@media (max-width: 640px) {
.jimaku-form {
grid-template-columns: 1fr 1fr;
}
.jimaku-button {
grid-column: span 2;
}
}
#subtitleContainer {
max-width: 80%;
margin-bottom: 60px;
padding: 12px 20px;
background: rgba(54, 58, 79, 0.5);
border-radius: 8px;
pointer-events: auto;
}
#subtitleRoot {
text-align: center;
font-size: 35px;
line-height: 1.5;
color: #cad3f5;
text-shadow:
2px 2px 4px rgba(0, 0, 0, 0.8),
-1px -1px 2px rgba(0, 0, 0, 0.5);
/* Enable text selection for Yomitan */
user-select: text;
cursor: text;
}
#subtitleRoot:empty {
display: none;
}
#subtitleContainer:has(#subtitleRoot:empty) {
display: none;
}
#subtitleRoot .c {
display: inline;
position: relative;
}
#subtitleRoot .c:hover {
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
}
#subtitleRoot .word {
display: inline;
position: relative;
}
#subtitleRoot .word:hover {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
}
#subtitleRoot br {
display: block;
content: "";
margin-bottom: 0.3em;
}
#subtitleRoot.has-selection .word:hover,
#subtitleRoot.has-selection .c:hover {
background: transparent;
}
body.layer-invisible #subtitleContainer {
background: transparent !important;
border: 0 !important;
padding: 0 !important;
border-radius: 0 !important;
}
body.layer-invisible #subtitleRoot,
body.layer-invisible #subtitleRoot .word,
body.layer-invisible #subtitleRoot .c {
color: transparent !important;
text-shadow: none !important;
-webkit-text-stroke: 0 !important;
-webkit-text-fill-color: transparent !important;
background: transparent !important;
caret-color: transparent !important;
line-height: normal !important;
font-kerning: auto;
letter-spacing: normal;
font-variant-ligatures: normal;
font-feature-settings: normal;
text-rendering: auto;
}
body.layer-invisible #subtitleRoot br {
margin-bottom: 0 !important;
}
body.layer-invisible #subtitleRoot .word:hover,
body.layer-invisible #subtitleRoot .c:hover,
body.layer-invisible #subtitleRoot.has-selection .word:hover,
body.layer-invisible #subtitleRoot.has-selection .c:hover {
background: transparent !important;
}
body.layer-invisible #subtitleRoot::selection,
body.layer-invisible #subtitleRoot .word::selection,
body.layer-invisible #subtitleRoot .c::selection {
background: transparent !important;
color: transparent !important;
}
body.layer-invisible.debug-invisible-visualization #subtitleRoot,
body.layer-invisible.debug-invisible-visualization #subtitleRoot .word,
body.layer-invisible.debug-invisible-visualization #subtitleRoot .c {
color: #ed8796 !important;
-webkit-text-fill-color: #ed8796 !important;
-webkit-text-stroke: 0.55px rgba(0, 0, 0, 0.95) !important;
text-shadow:
0 0 8px rgba(237, 135, 150, 0.9),
0 2px 6px rgba(0, 0, 0, 0.95) !important;
}
#secondarySubContainer {
position: absolute;
top: 40px;
left: 50%;
transform: translateX(-50%);
max-width: 80%;
padding: 10px 18px;
background: transparent;
border-radius: 8px;
pointer-events: auto;
}
#secondarySubRoot {
text-align: center;
font-size: 24px;
line-height: 1.5;
color: #ffffff;
-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);
user-select: text;
cursor: text;
}
#secondarySubRoot:empty {
display: none;
}
#secondarySubContainer:has(#secondarySubRoot:empty) {
display: none;
}
.secondary-sub-hidden {
display: none !important;
}
#secondarySubContainer.secondary-sub-hover {
opacity: 0;
transition: opacity 0.2s ease;
pointer-events: auto;
top: 0;
left: 0;
right: 0;
transform: none;
max-width: 100%;
background: transparent;
padding: 40px 0 0 0;
border-radius: 0;
display: flex;
justify-content: center;
}
#secondarySubContainer.secondary-sub-hover #secondarySubRoot {
background: transparent;
border-radius: 8px;
padding: 10px 18px;
}
#secondarySubContainer.secondary-sub-hover:hover {
opacity: 1;
}
iframe[id^="yomitan-popup"] {
pointer-events: auto !important;
z-index: 2147483647 !important;
}
.kiku-info-text {
font-size: 14px;
color: rgba(255, 255, 255, 0.8);
line-height: 1.5;
}
.kiku-cards-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.kiku-card {
background: rgba(40, 40, 40, 0.8);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 14px;
display: flex;
flex-direction: column;
gap: 8px;
cursor: pointer;
transition: border-color 0.15s;
outline: none;
}
.kiku-card:hover {
border-color: rgba(255, 255, 255, 0.3);
}
.kiku-card.active {
border-color: rgba(100, 180, 255, 0.8);
background: rgba(40, 60, 90, 0.5);
}
.kiku-card-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: rgba(255, 255, 255, 0.5);
font-weight: 600;
}
.kiku-card.active .kiku-card-label {
color: rgba(100, 180, 255, 0.9);
}
.kiku-card-expression {
font-size: 22px;
font-weight: 600;
color: #fff;
}
.kiku-card-sentence {
font-size: 14px;
color: rgba(255, 255, 255, 0.75);
max-height: 52px;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.4;
}
.kiku-card-meta {
font-size: 12px;
color: rgba(255, 255, 255, 0.45);
display: flex;
gap: 10px;
}
.kiku-footer {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 10px;
padding-top: 8px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.kiku-preview-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.kiku-preview-title {
font-size: 13px;
font-weight: 600;
color: rgba(255, 255, 255, 0.88);
}
.kiku-preview-toggle {
display: inline-flex;
gap: 6px;
}
.kiku-preview-toggle button {
padding: 5px 10px;
border-radius: 5px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.04);
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 12px;
}
.kiku-preview-toggle button.active {
border-color: rgba(100, 180, 255, 0.45);
background: rgba(100, 180, 255, 0.16);
color: rgba(100, 180, 255, 0.95);
}
.kiku-preview-json {
margin: 0;
min-height: 220px;
max-height: 320px;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 8px;
background: rgba(0, 0, 0, 0.34);
padding: 10px;
font-size: 11px;
line-height: 1.45;
color: rgba(255, 255, 255, 0.88);
}
.kiku-preview-error {
margin: 0 0 10px;
font-size: 12px;
color: #ff8f8f;
}
.kiku-delete-toggle {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
user-select: none;
}
.kiku-delete-toggle input {
accent-color: rgba(100, 180, 255, 0.9);
}
.kiku-confirm-button {
padding: 8px 20px;
border-radius: 6px;
border: 1px solid rgba(100, 180, 255, 0.4);
background: rgba(100, 180, 255, 0.15);
color: rgba(100, 180, 255, 0.95);
font-weight: 600;
cursor: pointer;
}
.kiku-confirm-button:hover {
background: rgba(100, 180, 255, 0.25);
}
.subsync-modal-content {
width: min(560px, 92%);
}
.subsync-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.subsync-field {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
}
.subsync-radio {
display: inline-flex;
align-items: center;
gap: 8px;
margin-right: 14px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
}
.subsync-field select {
background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
color: #fff;
padding: 8px 10px;
}
.subsync-footer {
display: flex;
justify-content: flex-end;
}
.kiku-cancel-button {
padding: 8px 16px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: transparent;
color: rgba(255, 255, 255, 0.6);
cursor: pointer;
}
.kiku-cancel-button:hover {
background: rgba(255, 255, 255, 0.08);
color: #fff;
}
.kiku-hint {
text-align: center;
font-size: 11px;
color: rgba(255, 255, 255, 0.35);
}
.runtime-modal-content {
width: min(560px, 92%);
}
.runtime-options-hint {
font-size: 12px;
color: rgba(255, 255, 255, 0.65);
}
.runtime-options-list {
list-style: none;
margin: 0;
padding: 0;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 8px;
max-height: 320px;
overflow-y: auto;
}
.runtime-options-item {
display: flex;
flex-direction: column;
gap: 4px;
padding: 10px 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
cursor: pointer;
}
.runtime-options-item:last-child {
border-bottom: none;
}
.runtime-options-item.active {
background: rgba(100, 180, 255, 0.15);
}
.runtime-options-label {
font-size: 14px;
color: #fff;
}
.runtime-options-value {
font-size: 13px;
color: rgba(100, 180, 255, 0.9);
}
.runtime-options-allowed {
font-size: 11px;
color: rgba(255, 255, 255, 0.55);
}
.runtime-options-status {
min-height: 18px;
font-size: 12px;
color: rgba(255, 255, 255, 0.75);
}
.runtime-options-status.error {
color: #ff8f8f;
}
@media (max-width: 640px) {
.kiku-cards-container {
grid-template-columns: 1fr;
}
}