update docs
@@ -5,6 +5,12 @@ export default {
|
|||||||
title: 'SubMiner Docs',
|
title: 'SubMiner Docs',
|
||||||
description: 'All-in-one sentence mining overlay for MPV with AnkiConnect and dictionary integration',
|
description: 'All-in-one sentence mining overlay for MPV with AnkiConnect and dictionary integration',
|
||||||
base,
|
base,
|
||||||
|
head: [
|
||||||
|
['link', { rel: 'icon', href: '/favicon.ico', sizes: 'any' }],
|
||||||
|
['link', { rel: 'icon', type: 'image/png', href: '/favicon-32x32.png', sizes: '32x32' }],
|
||||||
|
['link', { rel: 'icon', type: 'image/png', href: '/favicon-16x16.png', sizes: '16x16' }],
|
||||||
|
['link', { rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' }],
|
||||||
|
],
|
||||||
appearance: 'dark',
|
appearance: 'dark',
|
||||||
cleanUrls: true,
|
cleanUrls: true,
|
||||||
lastUpdated: true,
|
lastUpdated: true,
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ To mine multiple subtitle lines as one sentence card, use `Ctrl/Cmd+Shift+S` fol
|
|||||||
|
|
||||||
## Field Grouping (Kiku)
|
## Field Grouping (Kiku)
|
||||||
|
|
||||||
When you mine the same word multiple times, SubMiner can merge the cards instead of creating duplicates. This is designed for note types like [Kiku](https://github.com/donkuri/Kiku) that support grouped sentence/audio/image fields.
|
When you mine the same word multiple times, SubMiner can merge the cards instead of creating duplicates. This is designed for note types like [Kiku](https://github.com/youyoumu/kiku) that support grouped sentence/audio/image fields.
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
"ankiConnect": {
|
"ankiConnect": {
|
||||||
|
|||||||
@@ -86,71 +86,51 @@ src/renderer/
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
classDef root fill:#c6a0f6,stroke:#181926,color:#181926,stroke-width:2px;
|
classDef root fill:#c6a0f6,stroke:#24273a,color:#24273a,stroke-width:2px
|
||||||
classDef orchestration fill:#494d64,stroke:#b7bdf8,color:#cad3f5,stroke-width:2px;
|
classDef comp fill:#b7bdf8,stroke:#24273a,color:#24273a,stroke-width:1.5px
|
||||||
classDef domain fill:#494d64,stroke:#8aadf4,color:#cad3f5,stroke-width:2px;
|
classDef svc fill:#8aadf4,stroke:#24273a,color:#24273a,stroke-width:1.5px
|
||||||
classDef boundary fill:#494d64,stroke:#a6da95,color:#cad3f5,stroke-width:2px;
|
classDef ext fill:#a6da95,stroke:#24273a,color:#24273a,stroke-width:1.5px
|
||||||
|
|
||||||
subgraph Entry["Entrypoint"]
|
Main["src/main.ts"]:::root
|
||||||
Main["src/main.ts\nentry point"]
|
|
||||||
|
subgraph Composition["Composition Modules"]
|
||||||
|
Startup["Startup & Lifecycle"]:::comp
|
||||||
|
IpcCli["IPC & CLI Wiring"]:::comp
|
||||||
|
Overlay["Overlay & Shortcuts"]:::comp
|
||||||
|
Subsync["Subsync"]:::comp
|
||||||
end
|
end
|
||||||
class Main root;
|
|
||||||
|
|
||||||
subgraph MainModules["src/main/ Composition Modules"]
|
subgraph Services["Domain Services"]
|
||||||
Startup["startup.ts\nbootstrap flow"]
|
OverlaySvc["Overlay Services"]:::svc
|
||||||
AppLifecycle["app-lifecycle.ts\nlifecycle events"]
|
MpvSvc["MPV Stack"]:::svc
|
||||||
StartupLifecycle["startup-lifecycle.ts\napp-ready sequence"]
|
MiningSvc["Mining & Subtitles"]:::svc
|
||||||
State["state.ts\nruntime state container"]
|
ShortcutIpc["Shortcuts & IPC"]:::svc
|
||||||
IpcRuntime["ipc-runtime.ts\nIPC handlers"]
|
|
||||||
CliRuntime["cli-runtime.ts\nCLI dispatch"]
|
|
||||||
OverlayRuntime["overlay-runtime.ts\nwindow/modal"]
|
|
||||||
SubsyncRuntime["subsync-runtime.ts\nsubsync orchestration"]
|
|
||||||
end
|
end
|
||||||
class Startup,AppLifecycle,StartupLifecycle,State,IpcRuntime,CliRuntime,OverlayRuntime,SubsyncRuntime orchestration;
|
|
||||||
|
|
||||||
subgraph RuntimeServices["Runtime Domain Services"]
|
subgraph External["External Boundaries"]
|
||||||
OverlayMgr["overlay-manager-service"]
|
Config["Config & CLI"]:::ext
|
||||||
OverlayWindow["overlay-window-service"]
|
Trackers["Window Trackers"]:::ext
|
||||||
OverlayVisibility["overlay-visibility-service"]
|
Integrations["Jimaku & Subsync"]:::ext
|
||||||
Ipc["ipc-service\nipc-command-service"]
|
|
||||||
RuntimeOpts["runtime-options-ipc-service"]
|
|
||||||
Mpv["mpv-service\nmpv-control-service"]
|
|
||||||
MpvTransport["mpv-transport\nmpv-protocol"]
|
|
||||||
Subtitle["subtitle-ws-service\nsecondary-subtitle-service"]
|
|
||||||
Shortcuts["shortcut-service\noverlay-shortcut-service"]
|
|
||||||
end
|
end
|
||||||
class OverlayMgr,OverlayWindow,OverlayVisibility,Ipc,RuntimeOpts,Mpv,MpvTransport,Subtitle,Shortcuts domain;
|
|
||||||
|
|
||||||
subgraph Adapters["External Boundaries"]
|
Main --> Startup
|
||||||
Config["src/config/*"]
|
Main --> IpcCli
|
||||||
Cli["src/cli/*"]
|
Main --> Overlay
|
||||||
Trackers["src/window-trackers/*"]
|
Main --> Subsync
|
||||||
Integrations["src/jimaku/*\nsrc/subsync/*"]
|
|
||||||
end
|
|
||||||
class Config,Cli,Trackers,Integrations boundary;
|
|
||||||
|
|
||||||
Main -->|delegates to| Startup
|
Startup --> OverlaySvc
|
||||||
Main -->|registers| AppLifecycle
|
IpcCli --> ShortcutIpc
|
||||||
AppLifecycle -->|triggers| StartupLifecycle
|
Overlay --> OverlaySvc
|
||||||
StartupLifecycle -->|initializes| State
|
Overlay --> MpvSvc
|
||||||
|
Subsync --> Integrations
|
||||||
|
|
||||||
Main -->|builds deps| IpcRuntime
|
OverlaySvc --> Trackers
|
||||||
Main -->|builds deps| CliRuntime
|
MpvSvc --> MiningSvc
|
||||||
Main -->|builds deps| OverlayRuntime
|
ShortcutIpc --> Config
|
||||||
Main -->|builds deps| SubsyncRuntime
|
|
||||||
|
|
||||||
IpcRuntime -->|registers| Ipc
|
style Composition fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||||
IpcRuntime -->|registers| RuntimeOpts
|
style Services fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||||
CliRuntime -->|registers| Cli
|
style External fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||||
OverlayRuntime -->|manages| OverlayMgr
|
|
||||||
OverlayRuntime -->|manages| OverlayWindow
|
|
||||||
OverlayRuntime -->|manages| OverlayVisibility
|
|
||||||
|
|
||||||
Main -->|loads| Config
|
|
||||||
|
|
||||||
Ipc -->|updates| RuntimeOpts
|
|
||||||
Mpv -->|feeds| Subtitle
|
|
||||||
Shortcuts -->|drives| OverlayMgr
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Composition Pattern
|
## Composition Pattern
|
||||||
@@ -189,35 +169,28 @@ This keeps side effects explicit and makes behavior easy to unit-test with fakes
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
classDef phase fill:#494d64,stroke:#b7bdf8,color:#cad3f5,stroke-width:2px;
|
classDef phase fill:#b7bdf8,stroke:#24273a,color:#24273a,stroke-width:1.5px
|
||||||
classDef decision fill:#494d64,stroke:#f5a97f,color:#cad3f5,stroke-width:2px;
|
classDef decision fill:#f5a97f,stroke:#24273a,color:#24273a,stroke-width:1.5px
|
||||||
classDef runtime fill:#494d64,stroke:#8aadf4,color:#cad3f5,stroke-width:2px;
|
classDef runtime fill:#8aadf4,stroke:#24273a,color:#24273a,stroke-width:1.5px
|
||||||
classDef shutdown fill:#494d64,stroke:#a6da95,color:#cad3f5,stroke-width:2px;
|
classDef shutdown fill:#a6da95,stroke:#24273a,color:#24273a,stroke-width:1.5px
|
||||||
|
|
||||||
Args["CLI args / env"] --> Startup["src/main/startup.ts\nstartup-service"]
|
Args["CLI args / env"]:::phase --> Startup["src/main/startup.ts"]:::phase
|
||||||
class Args,Startup phase;
|
|
||||||
|
|
||||||
Startup --> Decision{"generate-config?"}
|
Startup --> Decision{"generate-config?"}:::decision
|
||||||
class Decision decision;
|
|
||||||
|
|
||||||
Decision -->|yes| WriteConfig["write config + exit"]
|
Decision -->|yes| WriteConfig["Write config + exit"]:::phase
|
||||||
Decision -->|no| AppLifecycle["src/main/app-lifecycle.ts\napp-lifecycle-service"]
|
Decision -->|no| AppLifecycle["src/main/app-lifecycle.ts"]:::phase
|
||||||
class WriteConfig,AppLifecycle phase;
|
|
||||||
|
|
||||||
AppLifecycle --> Ready["src/main/startup-lifecycle.ts\napp-ready flow\n(config + websocket policy + tracker/tokenizer init + state init)"]
|
AppLifecycle --> Ready["src/main/startup-lifecycle.ts\nConfig · WebSocket · Tracker · Tokenizer · State"]:::phase
|
||||||
class Ready phase;
|
|
||||||
|
|
||||||
Ready --> Runtime["src/main/* runtime modules:\nipc-runtime, cli-runtime, overlay-runtime, subsync-runtime"]
|
Ready --> Runtime["Runtime Modules\nipc · cli · overlay · subsync"]:::runtime
|
||||||
class Runtime runtime;
|
|
||||||
|
|
||||||
Runtime --> Overlay["overlay visibility + mining actions"]
|
Runtime --> Overlay["Overlay & Mining"]:::runtime
|
||||||
Runtime --> Subtitle["subtitle + secondary-subtitle processing"]
|
Runtime --> Subtitle["Subtitle Processing"]:::runtime
|
||||||
Runtime --> Subsync["subsync / jimaku integration actions"]
|
Runtime --> SubsyncInt["Subsync & Jimaku"]:::runtime
|
||||||
class Overlay,Subtitle,Subsync runtime;
|
|
||||||
|
|
||||||
Runtime --> WillQuit["Electron will-quit"]
|
Runtime --> WillQuit["Electron will-quit"]:::shutdown
|
||||||
WillQuit --> Cleanup["service-level teardown\n(unregister hooks, close resources)"]
|
WillQuit --> Cleanup["Service Teardown"]:::shutdown
|
||||||
class WillQuit,Cleanup shutdown;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Why This Design
|
## Why This Design
|
||||||
|
|||||||
@@ -2,6 +2,26 @@
|
|||||||
|
|
||||||
Settings are stored in `$XDG_CONFIG_HOME/SubMiner/config.jsonc` (or `~/.config/SubMiner/config.jsonc` when `XDG_CONFIG_HOME` is unset). For backward compatibility, SubMiner also reads existing configs from lowercase `subminer` directories.
|
Settings are stored in `$XDG_CONFIG_HOME/SubMiner/config.jsonc` (or `~/.config/SubMiner/config.jsonc` when `XDG_CONFIG_HOME` is unset). For backward compatibility, SubMiner also reads existing configs from lowercase `subminer` directories.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
For most users, start with this minimal configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ankiConnect": {
|
||||||
|
"enabled": true,
|
||||||
|
"deck": "YourDeckName",
|
||||||
|
"fields": {
|
||||||
|
"sentence": "Sentence",
|
||||||
|
"audio": "Audio",
|
||||||
|
"image": "Image"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then customize as needed using the sections below.
|
||||||
|
|
||||||
## Configuration File
|
## Configuration File
|
||||||
|
|
||||||
See [config.example.jsonc](/config.example.jsonc) for a comprehensive example configuration file with all available options, default values, and detailed comments. Only include the options you want to customize in your config file.
|
See [config.example.jsonc](/config.example.jsonc) for a comprehensive example configuration file with all available options, default values, and detailed comments. Only include the options you want to customize in your config file.
|
||||||
@@ -196,12 +216,8 @@ To refresh roughly once per day, set:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<video controls playsinline preload="metadata" poster="/assets/kiku-integration-poster.jpg" style="width: 100%; max-width: 960px;">
|
### Field Grouping Modes
|
||||||
<source :src="'/assets/kiku-integration.webm'" type="video/webm" />
|
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
|
|
||||||
<a :href="'/assets/kiku-integration.webm'" target="_blank" rel="noreferrer">Open demo in a new tab</a>
|
|
||||||
| Mode | Behavior |
|
| Mode | Behavior |
|
||||||
| ---------- | -------------------------------------------------------------------------------------------------------------------------- |
|
| ---------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `auto` | Automatically merges the new card's content into the original; duplicate deletion is controlled by `deleteDuplicateInAuto` |
|
| `auto` | Automatically merges the new card's content into the original; duplicate deletion is controlled by `deleteDuplicateInAuto` |
|
||||||
@@ -210,15 +226,22 @@ To refresh roughly once per day, set:
|
|||||||
|
|
||||||
`deleteDuplicateInAuto` controls whether `auto` mode deletes the duplicate after merge (default: `true`). In `manual` mode, the popup asks each time whether to delete the duplicate.
|
`deleteDuplicateInAuto` controls whether `auto` mode deletes the duplicate after merge (default: `true`). In `manual` mode, the popup asks each time whether to delete the duplicate.
|
||||||
|
|
||||||
|
<video controls playsinline preload="metadata" poster="/assets/kiku-integration-poster.jpg" style="width: 100%; max-width: 960px;">
|
||||||
|
<source :src="'/assets/kiku-integration.webm'" type="video/webm" />
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
<a :href="'/assets/kiku-integration.webm'" target="_blank" rel="noreferrer">Open demo in a new tab</a>
|
||||||
|
|
||||||
**Image Quality Notes:**
|
**Image Quality Notes:**
|
||||||
|
|
||||||
- `imageQuality` affects JPG and WebP only; PNG is lossless and ignores this setting
|
- `imageQuality` affects JPG and WebP only; PNG is lossless and ignores this setting
|
||||||
- JPG quality is mapped to FFmpeg's scale (2-31, lower = better)
|
- JPG quality is mapped to FFmpeg's scale (2-31, lower = better)
|
||||||
- WebP quality uses FFmpeg's native 0-100 scale
|
- WebP quality uses FFmpeg's native 0-100 scale
|
||||||
|
|
||||||
**Manual Card Update:**
|
### Manual Card Update Shortcuts
|
||||||
|
|
||||||
When `behavior.autoUpdateNewCards` is set to `false`, new cards are detected but not automatically updated. Instead, you can manually update cards using keyboard shortcuts:
|
When `behavior.autoUpdateNewCards` is set to `false`, new cards are detected but not automatically updated. Use these keyboard shortcuts for manual control:
|
||||||
|
|
||||||
| Shortcut | Action |
|
| Shortcut | Action |
|
||||||
| -------------- | ------------------------------------------------------------------------------------------------------------ |
|
| -------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||||
@@ -232,14 +255,14 @@ When `behavior.autoUpdateNewCards` is set to `false`, new cards are detected but
|
|||||||
| `Ctrl+Shift+A` | Mark the last added Anki card as an audio card (sets IsAudioCard, SentenceAudio, Sentence, Picture) |
|
| `Ctrl+Shift+A` | Mark the last added Anki card as an audio card (sets IsAudioCard, SentenceAudio, Sentence, Picture) |
|
||||||
| `Ctrl+Shift+O` | Open runtime options palette (session-only live toggles) |
|
| `Ctrl+Shift+O` | Open runtime options palette (session-only live toggles) |
|
||||||
|
|
||||||
To copy multiple lines (current + previous):
|
**Multi-line copy workflow:**
|
||||||
|
|
||||||
1. Press `Ctrl+Shift+C`
|
1. Press `Ctrl+Shift+C`
|
||||||
2. Press a number key (`1-9`) within 3 seconds
|
2. Press a number key (`1-9`) within 3 seconds
|
||||||
3. The specified number of most recent subtitle lines are copied
|
3. The specified number of most recent subtitle lines are copied
|
||||||
4. Press `Ctrl+V` to update the last added card with the copied lines
|
4. Press `Ctrl+V` to update the last added card with the copied lines
|
||||||
|
|
||||||
These shortcuts are only active when the overlay window is visible. They are automatically disabled when the overlay is hidden to avoid interfering with normal system clipboard operations.
|
These shortcuts are only active when the overlay window is visible and automatically disabled when hidden.
|
||||||
|
|
||||||
### Auto-Start Overlay
|
### Auto-Start Overlay
|
||||||
|
|
||||||
|
|||||||
@@ -23,22 +23,44 @@ hero:
|
|||||||
link: /configuration
|
link: /configuration
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- icon: "🎯"
|
- icon:
|
||||||
title: Click-to-Lookup Overlay
|
src: /assets/mpv.svg
|
||||||
details: Subtitles are tokenized into clickable words. Click any word to open a Yomitan dictionary popup — right on top of the video.
|
alt: mpv icon
|
||||||
- icon: "📇"
|
title: Built for mpv
|
||||||
title: Automatic Anki Cards
|
details: Connects directly to mpv over IPC — tracks subtitles in real time, observes playback properties, and renders a self-contained overlay with everything bundled in a single application.
|
||||||
details: Add a word from Yomitan and SubMiner fills in the sentence, audio clip, screenshot, and translation automatically.
|
- icon:
|
||||||
- icon: "🪟"
|
src: /assets/yomitan-icon.svg
|
||||||
|
alt: Yomitan logo
|
||||||
|
title: Yomitan Integration
|
||||||
|
details: Hover over any word in the subtitles to trigger Yomitan dictionary lookups — get instant definitions without leaving the video player.
|
||||||
|
- icon:
|
||||||
|
src: /assets/anki-card.svg
|
||||||
|
alt: Anki card icon
|
||||||
|
title: Anki Card Enrichment
|
||||||
|
details: Add a word from Yomitan and SubMiner automatically updates the card with the sentence, audio clip, screenshot, and translation — no extra steps needed.
|
||||||
|
- icon:
|
||||||
|
src: /assets/dual-layer.svg
|
||||||
|
alt: Dual layer icon
|
||||||
title: Dual-Layer Subtitle System
|
title: Dual-Layer Subtitle System
|
||||||
details: Visible overlay with styled, interactive subtitles — plus an invisible layer that aligns with mpv's own subtitle rendering for seamless click-through lookup.
|
details: Visible overlay with styled, interactive subtitles — plus an invisible layer that aligns with mpv's own subtitle rendering for seamless click-through lookup.
|
||||||
- icon: "🎬"
|
- icon:
|
||||||
title: YouTube & Subtitle Sync
|
src: /assets/highlight.svg
|
||||||
details: Play YouTube videos with auto-generated subtitles. Sync external subtitle files with alass or ffsubsync. Search and download anime subtitles from Jimaku.
|
alt: Highlight icon
|
||||||
- icon: "🔠"
|
title: N+1 Word Highlighting
|
||||||
title: Smart Tokenization
|
details: Highlights words you already know from your Anki deck, making it easy to spot new vocabulary and identify true N+1 sentences during immersion.
|
||||||
details: Japanese text is segmented using Yomitan's internal parser with MeCab fallback, enabling accurate word boundary detection for dictionary lookups.
|
- icon:
|
||||||
- icon: "⌨️"
|
src: /assets/texthooker.svg
|
||||||
|
alt: Texthooker icon
|
||||||
|
title: Texthooker & WebSocket
|
||||||
|
details: Built-in texthooker page that receives subtitles over WebSocket — use it as a clipboard inserter for Yomitan or connect external tools for real-time subtitle streaming.
|
||||||
|
- icon:
|
||||||
|
src: /assets/subtitle-download.svg
|
||||||
|
alt: Subtitle download icon
|
||||||
|
title: Subtitle Download & Sync
|
||||||
|
details: Search and download Japanese subtitles from Jimaku, then sync them to the audio with alass or ffsubsync — all from within the player.
|
||||||
|
- icon:
|
||||||
|
src: /assets/keyboard.svg
|
||||||
|
alt: Keyboard icon
|
||||||
title: Keyboard-Driven Workflow
|
title: Keyboard-Driven Workflow
|
||||||
details: Mine sentences, copy subtitles, cycle display modes, and trigger field grouping — all from configurable keyboard shortcuts without touching the mouse.
|
details: Mine sentences, copy subtitles, cycle display modes, and trigger field grouping — all from configurable keyboard shortcuts without touching the mouse.
|
||||||
---
|
---
|
||||||
@@ -136,7 +158,7 @@ SubMiner sits as a transparent overlay on top of mpv. Subtitles appear as intera
|
|||||||
<div class="workflow-step">
|
<div class="workflow-step">
|
||||||
<div class="step-number">02</div>
|
<div class="step-number">02</div>
|
||||||
<div class="step-title">Look Up</div>
|
<div class="step-title">Look Up</div>
|
||||||
<div class="step-desc">Click any word in the subtitle overlay. Yomitan opens its dictionary popup instantly.</div>
|
<div class="step-desc">Hover over a word in the subtitle overlay and hold Shift to trigger a Yomitan dictionary lookup.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="workflow-step">
|
<div class="workflow-step">
|
||||||
<div class="step-number">03</div>
|
<div class="step-number">03</div>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ The visible overlay renders subtitles as tokenized, clickable word spans. Each w
|
|||||||
- Right-click to pause/resume
|
- Right-click to pause/resume
|
||||||
- Right-click + drag to reposition subtitles
|
- Right-click + drag to reposition subtitles
|
||||||
- Modal dialogs for Jimaku search, field grouping, subsync, and runtime options
|
- Modal dialogs for Jimaku search, field grouping, subsync, and runtime options
|
||||||
|
- **N+1 highlighting** — known words from your Anki deck are visually highlighted
|
||||||
|
|
||||||
Toggle with `Alt+Shift+O` (global) or `y-t` (mpv plugin).
|
Toggle with `Alt+Shift+O` (global) or `y-t` (mpv plugin).
|
||||||
|
|
||||||
@@ -106,7 +107,7 @@ When a card is created, SubMiner uses the secondary subtitle text as the transla
|
|||||||
|
|
||||||
## Field Grouping (Kiku)
|
## Field Grouping (Kiku)
|
||||||
|
|
||||||
If you mine the same word from different sentences, SubMiner can merge the cards instead of creating duplicates. This feature is designed for use with [Kiku](https://github.com/donkuri/Kiku) and similar note types that support grouped fields.
|
If you mine the same word from different sentences, SubMiner can merge the cards instead of creating duplicates. This feature is designed for use with [Kiku](https://github.com/youyoumu/kiku) and similar note types that support grouped fields.
|
||||||
|
|
||||||
### How It Works
|
### How It Works
|
||||||
|
|
||||||
@@ -151,3 +152,41 @@ If your subtitle file is out of sync with the audio, SubMiner can resynchronize
|
|||||||
4. SubMiner runs the sync and reloads the corrected subtitle.
|
4. SubMiner runs the sync and reloads the corrected subtitle.
|
||||||
|
|
||||||
Install the sync tools separately — see [Troubleshooting](/troubleshooting#subtitle-sync-subsync) if the tools are not found.
|
Install the sync tools separately — see [Troubleshooting](/troubleshooting#subtitle-sync-subsync) if the tools are not found.
|
||||||
|
|
||||||
|
## N+1 Word Highlighting
|
||||||
|
|
||||||
|
When enabled, SubMiner highlights words you already know in your Anki deck, making it easier to spot new (N+1) vocabulary during immersion.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. SubMiner periodically syncs with Anki to build a local cache of known words (expressions/headwords from your configured decks)
|
||||||
|
2. As subtitles appear, known words are visually highlighted in the visible overlay
|
||||||
|
3. Unknown words remain unhighlighted — these are your potential mining targets
|
||||||
|
|
||||||
|
### Enabling N+1 Mode
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ankiConnect": {
|
||||||
|
"nPlusOne": {
|
||||||
|
"highlightEnabled": true,
|
||||||
|
"refreshMinutes": 1440,
|
||||||
|
"matchMode": "headword",
|
||||||
|
"decks": ["Learning::Japanese"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `highlightEnabled` | Turn on/off the highlighting feature |
|
||||||
|
| `refreshMinutes` | How often to refresh the known-word cache (default: 1440 = daily) |
|
||||||
|
| `matchMode` | `"headword"` (dictionary form) or `"surface"` (exact text match) |
|
||||||
|
| `decks` | Which Anki decks to consider "known" (empty = uses `ankiConnect.deck`) |
|
||||||
|
|
||||||
|
### Use Cases
|
||||||
|
|
||||||
|
- **Immersion tracking**: Quickly identify which sentences contain only known words vs. those with new vocabulary
|
||||||
|
- **Mining focus**: Target sentences with exactly one unknown word (true N+1)
|
||||||
|
- **Progress visualization**: See your growing vocabulary visually represented in real content
|
||||||
|
|||||||
BIN
docs/public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
15
docs/public/assets/anki-card.svg
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ac" x1="6" y1="6" x2="36" y2="42" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#34d399"/>
|
||||||
|
<stop offset="1" stop-color="#059669"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="12" y="5" width="24" height="34" rx="3" fill="#059669" opacity="0.18"/>
|
||||||
|
<rect x="8" y="9" width="24" height="34" rx="3" fill="url(#ac)"/>
|
||||||
|
<rect x="13" y="18" width="14" height="2.5" rx="1.25" fill="white" opacity="0.85"/>
|
||||||
|
<rect x="13" y="24" width="10" height="2.5" rx="1.25" fill="white" opacity="0.4"/>
|
||||||
|
<rect x="13" y="30" width="12" height="2.5" rx="1.25" fill="white" opacity="0.4"/>
|
||||||
|
<path d="M39.5 8l1.8 4.2 4.2 1.8-4.2 1.8L39.5 20l-1.8-4.2L33.5 14l4.2-1.8z" fill="#34d399"/>
|
||||||
|
<path d="M36 27l1 2.3 2.3 1-2.3 1L36 33.5l-1-2.2-2.3-1 2.3-1z" fill="#34d399" opacity="0.45"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 914 B |
15
docs/public/assets/dual-layer.svg
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="dl" x1="4" y1="24" x2="44" y2="24" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#818cf8"/>
|
||||||
|
<stop offset="1" stop-color="#6366f1"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="4" y="6" width="40" height="14" rx="4" fill="#818cf8" opacity="0.12"/>
|
||||||
|
<rect x="4" y="6" width="40" height="14" rx="4" stroke="#818cf8" stroke-width="1.5" stroke-dasharray="4 3" fill="none" opacity="0.55"/>
|
||||||
|
<rect x="10" y="11" width="20" height="3" rx="1.5" fill="#818cf8" opacity="0.35"/>
|
||||||
|
<line x1="24" y1="22" x2="24" y2="26" stroke="#a5b4fc" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
|
||||||
|
<path d="M21.5 24.5L24 27l2.5-2.5" stroke="#a5b4fc" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.5"/>
|
||||||
|
<rect x="4" y="28" width="40" height="14" rx="4" fill="url(#dl)"/>
|
||||||
|
<rect x="10" y="33" width="20" height="3" rx="1.5" fill="white" opacity="0.85"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
13
docs/public/assets/highlight.svg
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="hl" x1="20" y1="14" x2="38" y2="34" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#fbbf24"/>
|
||||||
|
<stop offset="1" stop-color="#f59e0b"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="2" y="17" width="10" height="14" rx="3" fill="#fbbf24" opacity="0.3"/>
|
||||||
|
<rect x="14" y="17" width="7" height="14" rx="3" fill="#fbbf24" opacity="0.3"/>
|
||||||
|
<rect x="23" y="13" width="13" height="22" rx="3.5" fill="url(#hl)"/>
|
||||||
|
<rect x="38" y="17" width="8" height="14" rx="3" fill="#fbbf24" opacity="0.3"/>
|
||||||
|
<path d="M28.2 4l1 2.4 2.4 1-2.4 1-1 2.4-1-2.4-2.4-1 2.4-1z" fill="#fbbf24" opacity="0.7"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 729 B |
21
docs/public/assets/keyboard.svg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="kb" x1="2" y1="10" x2="46" y2="42" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#c084fc"/>
|
||||||
|
<stop offset="1" stop-color="#7c3aed"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="2" y="12" width="44" height="30" rx="5" fill="url(#kb)" opacity="0.12"/>
|
||||||
|
<rect x="2" y="12" width="44" height="30" rx="5" stroke="url(#kb)" stroke-width="1.5" fill="none"/>
|
||||||
|
<rect x="6" y="16" width="8" height="6" rx="2" fill="url(#kb)"/>
|
||||||
|
<rect x="16" y="16" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||||
|
<rect x="26" y="16" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||||
|
<rect x="36" y="16" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||||
|
<rect x="6" y="24" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||||
|
<rect x="16" y="24" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||||
|
<rect x="26" y="24" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||||
|
<rect x="36" y="24" width="8" height="6" rx="2" fill="url(#kb)"/>
|
||||||
|
<rect x="6" y="32" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||||
|
<rect x="16" y="32" width="16" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||||
|
<rect x="34" y="32" width="10" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
86
docs/public/assets/mpv.svg
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 63.999999 63.999999"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="mpv.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="5.3710484"
|
||||||
|
inkscape:cx="10.112865"
|
||||||
|
inkscape:cy="18.643164"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1016"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-988.3622)">
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#e5e5e5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10161044;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||||
|
id="path4380"
|
||||||
|
cx="32"
|
||||||
|
cy="1020.3622"
|
||||||
|
r="27.949194" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#672168;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0988237;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||||
|
id="path4390"
|
||||||
|
cx="32.727058"
|
||||||
|
cy="1019.5079"
|
||||||
|
r="25.950588" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#420143;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||||
|
id="path4400"
|
||||||
|
cx="34.224396"
|
||||||
|
cy="1017.7957"
|
||||||
|
r="20" />
|
||||||
|
<path
|
||||||
|
style="fill:#dddbdd;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 44.481446,1020.4807 a 12.848894,12.848894 0 0 1 -12.84889,12.8489 12.848894,12.848894 0 0 1 -12.8489,-12.8489 12.848894,12.848894 0 0 1 12.8489,-12.8489 12.848894,12.848894 0 0 1 12.84889,12.8489 z"
|
||||||
|
id="path4412"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="fill:#691f69;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 28.374316,1014.709 0,11.4502 9.21608,-5.8647 z"
|
||||||
|
id="path4426"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.4 KiB |
16
docs/public/assets/subtitle-download.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="sd" x1="4" y1="4" x2="44" y2="44" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#22d3ee"/>
|
||||||
|
<stop offset="1" stop-color="#0891b2"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="8" y="4" width="24" height="32" rx="3" fill="url(#sd)" opacity="0.15"/>
|
||||||
|
<rect x="8" y="4" width="24" height="32" rx="3" stroke="url(#sd)" stroke-width="1.5" fill="none"/>
|
||||||
|
<rect x="13" y="12" width="14" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.5"/>
|
||||||
|
<rect x="13" y="18" width="10" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.35"/>
|
||||||
|
<rect x="13" y="24" width="12" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.35"/>
|
||||||
|
<line x1="38" y1="16" x2="38" y2="32" stroke="url(#sd)" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<path d="M33 28l5 5 5-5" stroke="url(#sd)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
|
<line x1="33" y1="40" x2="43" y2="40" stroke="url(#sd)" stroke-width="2" stroke-linecap="round" opacity="0.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
19
docs/public/assets/texthooker.svg
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="th" x1="4" y1="6" x2="44" y2="42" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#f97316"/>
|
||||||
|
<stop offset="1" stop-color="#c2410c"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="4" y="6" width="30" height="36" rx="4" fill="url(#th)" opacity="0.12"/>
|
||||||
|
<rect x="4" y="6" width="30" height="36" rx="4" stroke="url(#th)" stroke-width="1.5" fill="none"/>
|
||||||
|
<rect x="9" y="14" width="14" height="2.5" rx="1.25" fill="#f97316" opacity="0.6"/>
|
||||||
|
<rect x="9" y="20" width="18" height="2.5" rx="1.25" fill="#f97316" opacity="0.4"/>
|
||||||
|
<rect x="9" y="26" width="12" height="2.5" rx="1.25" fill="#f97316" opacity="0.4"/>
|
||||||
|
<rect x="9" y="32" width="16" height="2.5" rx="1.25" fill="#f97316" opacity="0.4"/>
|
||||||
|
<circle cx="40" cy="18" r="3.5" fill="url(#th)" opacity="0.8"/>
|
||||||
|
<circle cx="40" cy="30" r="3.5" fill="url(#th)" opacity="0.8"/>
|
||||||
|
<line x1="36" y1="18" x2="34" y2="18" stroke="url(#th)" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>
|
||||||
|
<line x1="36" y1="30" x2="34" y2="30" stroke="url(#th)" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>
|
||||||
|
<line x1="40" y1="21.5" x2="40" y2="26.5" stroke="url(#th)" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
16
docs/public/assets/tokenization.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="tk" x1="0" y1="14" x2="48" y2="34" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#22d3ee"/>
|
||||||
|
<stop offset="1" stop-color="#0891b2"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="2" y="12" width="12" height="24" rx="3.5" fill="url(#tk)"/>
|
||||||
|
<rect x="18" y="12" width="12" height="24" rx="3.5" fill="url(#tk)"/>
|
||||||
|
<rect x="34" y="12" width="12" height="24" rx="3.5" fill="url(#tk)"/>
|
||||||
|
<line x1="15.5" y1="10" x2="15.5" y2="38" stroke="#22d3ee" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="3 3" opacity="0.45"/>
|
||||||
|
<line x1="32.5" y1="10" x2="32.5" y2="38" stroke="#22d3ee" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="3 3" opacity="0.45"/>
|
||||||
|
<rect x="5" y="22" width="6" height="2.5" rx="1.25" fill="white" opacity="0.7"/>
|
||||||
|
<rect x="21" y="22" width="6" height="2.5" rx="1.25" fill="white" opacity="0.7"/>
|
||||||
|
<rect x="37" y="22" width="6" height="2.5" rx="1.25" fill="white" opacity="0.7"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
12
docs/public/assets/video.svg
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="vd" x1="4" y1="10" x2="44" y2="38" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#fb7185"/>
|
||||||
|
<stop offset="1" stop-color="#e11d48"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="4" y="10" width="40" height="28" rx="4" fill="url(#vd)" opacity="0.15"/>
|
||||||
|
<rect x="4" y="10" width="40" height="28" rx="4" stroke="url(#vd)" stroke-width="1.5" fill="none"/>
|
||||||
|
<path d="M20 18l12 6-12 6z" fill="url(#vd)"/>
|
||||||
|
<rect x="10" y="32" width="22" height="2.5" rx="1.25" fill="white" opacity="0.4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 635 B |
1
docs/public/assets/yomitan-icon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><defs><linearGradient id="a" x1="11.876" x2="4.014" y1="4.073" y2="11.935" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#bc00ff" stop-opacity=".941" style="stop-color:#bc00ff;stop-opacity:1"/><stop offset="1" stop-color="#00b9fe"/></linearGradient></defs><rect width="16" height="16" fill="url(#a)" rx="1.625" ry="1.625"/><path d="M2 2v2h3v3H2v2h3v3H2v2h5V2Zm7 0v2h5V2Zm0 5v2h5V7Zm0 5v2h5v-2z" shape-rendering="crispEdges" style="fill:#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 527 B |
BIN
docs/public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
docs/public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
docs/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |