update docs
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 141 KiB |
BIN
assets/kiku-integration.gif
Normal file
|
After Width: | Height: | Size: 4.3 MiB |
BIN
assets/kiku-integration.mkv
Normal file
BIN
assets/kiku-integration.mp4
Normal file
BIN
assets/minecard-poster.jpg
Normal file
|
After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 13 MiB After Width: | Height: | Size: 12 MiB |
BIN
assets/minecard.jpg
Normal file
|
After Width: | Height: | Size: 303 KiB |
@@ -4,7 +4,7 @@ SubMiner is split into three cooperating runtimes:
|
|||||||
|
|
||||||
- Electron desktop app (`src/`) for overlay/UI/runtime orchestration.
|
- Electron desktop app (`src/`) for overlay/UI/runtime orchestration.
|
||||||
- Launcher CLI (`launcher/`) for mpv/app command workflows.
|
- Launcher CLI (`launcher/`) for mpv/app command workflows.
|
||||||
- mpv Lua plugin (`plugin/subminer/main.lua` + module files) for player-side controls and IPC handoff.
|
- mpv Lua plugin (`plugin/subminer/init.lua` + module files) for player-side controls and IPC handoff.
|
||||||
|
|
||||||
Within the desktop app, `src/main.ts` is a composition root that wires small runtime/domain modules plus core services.
|
Within the desktop app, `src/main.ts` is a composition root that wires small runtime/domain modules plus core services.
|
||||||
|
|
||||||
@@ -26,7 +26,9 @@ launcher/ # Standalone CLI launcher wrapper and mpv helpers
|
|||||||
config/ # Launcher config parsers + CLI parser builder
|
config/ # Launcher config parsers + CLI parser builder
|
||||||
main.ts # Launcher entrypoint and command dispatch
|
main.ts # Launcher entrypoint and command dispatch
|
||||||
plugin/
|
plugin/
|
||||||
subminer/ # mpv plugin modules + main entrypoint
|
subminer/ # Modular mpv plugin (init · main · bootstrap · lifecycle · process
|
||||||
|
# state · messages · hover · ui · options · environment · log
|
||||||
|
# binary · aniskip · aniskip_match)
|
||||||
src/
|
src/
|
||||||
main-entry.ts # Background-mode bootstrap wrapper before loading main.js
|
main-entry.ts # Background-mode bootstrap wrapper before loading main.js
|
||||||
main.ts # Entry point — delegates to runtime composers/domain modules
|
main.ts # Entry point — delegates to runtime composers/domain modules
|
||||||
@@ -66,24 +68,26 @@ src/
|
|||||||
renderer/ # Overlay renderer (modularized UI/runtime)
|
renderer/ # Overlay renderer (modularized UI/runtime)
|
||||||
handlers/ # Keyboard/mouse interaction modules
|
handlers/ # Keyboard/mouse interaction modules
|
||||||
modals/ # Jimaku/Kiku/subsync/runtime-options/session-help modals
|
modals/ # Jimaku/Kiku/subsync/runtime-options/session-help modals
|
||||||
positioning/ # Invisible-layer layout + offset controllers
|
positioning/ # Subtitle position controller (drag-to-reposition)
|
||||||
window-trackers/ # Backend-specific tracker implementations (Hyprland, Sway, X11, macOS)
|
window-trackers/ # Backend-specific tracker implementations (Hyprland, Sway, X11, macOS)
|
||||||
jimaku/ # Jimaku API integration helpers
|
jimaku/ # Jimaku API integration helpers
|
||||||
subsync/ # Subtitle sync (alass/ffsubsync) helpers
|
subsync/ # Subtitle sync (alass/ffsubsync) helpers
|
||||||
subtitle/ # Subtitle processing utilities
|
subtitle/ # Subtitle processing utilities
|
||||||
tokenizers/ # Tokenizer implementations
|
tokenizers/ # Tokenizer implementations
|
||||||
|
anki-integration/ # AnkiConnect proxy server + note-update enrichment workflow
|
||||||
token-mergers/ # Token merge strategies
|
token-mergers/ # Token merge strategies
|
||||||
translators/ # AI translation providers
|
translators/ # AI translation providers
|
||||||
```
|
```
|
||||||
|
|
||||||
### Service Layer (`src/core/services/`)
|
### Service Layer (`src/core/services/`)
|
||||||
|
|
||||||
- **Overlay/window runtime:** `overlay-manager.ts`, `overlay-window.ts`, `overlay-visibility.ts`, `overlay-bridge.ts`, `overlay-runtime-init.ts`, `overlay-content-measurement.ts`, `overlay-drop.ts`
|
- **Overlay/window runtime:** `overlay-manager.ts`, `overlay-window.ts`, `overlay-visibility.ts`, `overlay-bridge.ts`, `overlay-runtime-init.ts`, `overlay-content-measurement.ts`
|
||||||
- **Shortcuts/input:** `shortcut.ts`, `overlay-shortcut.ts`, `overlay-shortcut-handler.ts`, `shortcut-fallback.ts`, `numeric-shortcut.ts`
|
- **Shortcuts/input:** `shortcut.ts`, `overlay-shortcut.ts`, `overlay-shortcut-handler.ts`, `shortcut-fallback.ts`, `numeric-shortcut.ts`
|
||||||
- **MPV runtime:** `mpv.ts`, `mpv-transport.ts`, `mpv-protocol.ts`, `mpv-properties.ts`, `mpv-render-metrics.ts`
|
- **MPV runtime:** `mpv.ts`, `mpv-transport.ts`, `mpv-protocol.ts`, `mpv-properties.ts`, `mpv-render-metrics.ts`
|
||||||
- **Mining + Anki/Jimaku runtime:** `mining.ts`, `field-grouping.ts`, `field-grouping-overlay.ts`, `anki-jimaku.ts`, `anki-jimaku-ipc.ts`
|
- **Mining + Anki/Jimaku runtime:** `mining.ts`, `field-grouping.ts`, `field-grouping-overlay.ts`, `anki-jimaku.ts`, `anki-jimaku-ipc.ts`
|
||||||
- **Subtitle/token pipeline:** `subtitle-processing-controller.ts`, `subtitle-position.ts`, `subtitle-ws.ts`, `tokenizer.ts` + `tokenizer/*` stage modules
|
- **Subtitle/token pipeline:** `subtitle-processing-controller.ts`, `subtitle-position.ts`, `subtitle-ws.ts`, `tokenizer.ts` + `tokenizer/*` stage modules (including `parser-enrichment-worker-runtime.ts` for async MeCab enrichment and `yomitan-parser-runtime.ts`)
|
||||||
- **Integrations:** `jimaku.ts`, `subsync.ts`, `subsync-runner.ts`, `texthooker.ts`, `jellyfin.ts`, `jellyfin-remote.ts`, `discord-presence.ts`, `yomitan-extension-loader.ts`, `yomitan-settings.ts`
|
- **Integrations:** `jimaku.ts`, `subsync.ts`, `subsync-runner.ts`, `texthooker.ts`, `jellyfin.ts`, `jellyfin-remote.ts`, `discord-presence.ts`, `yomitan-extension-loader.ts`, `yomitan-settings.ts`
|
||||||
|
- **Anki integration:** `anki-integration.ts`, `anki-integration/anki-connect-proxy.ts` (local proxy for push-based auto-enrichment), `anki-integration/note-update-workflow.ts`
|
||||||
- **Config/runtime controls:** `config-hot-reload.ts`, `runtime-options-ipc.ts`, `cli-command.ts`, `startup.ts`
|
- **Config/runtime controls:** `config-hot-reload.ts`, `runtime-options-ipc.ts`, `cli-command.ts`, `startup.ts`
|
||||||
- **Domain submodules:** `anilist/*` (token/update queue/updater), `immersion-tracker/*` (storage/session/metadata/query/reducer)
|
- **Domain submodules:** `anilist/*` (token/update queue/updater), `immersion-tracker/*` (storage/session/metadata/query/reducer)
|
||||||
|
|
||||||
@@ -95,14 +99,15 @@ The renderer keeps `renderer.ts` focused on orchestration. UI behavior is delega
|
|||||||
src/renderer/
|
src/renderer/
|
||||||
renderer.ts # Entrypoint/orchestration only
|
renderer.ts # Entrypoint/orchestration only
|
||||||
context.ts # Shared runtime context contract
|
context.ts # Shared runtime context contract
|
||||||
state.ts # Centralized renderer mutable state
|
state.ts # Centralized renderer mutable state (visible overlay only)
|
||||||
error-recovery.ts # Global renderer error boundary + recovery actions
|
error-recovery.ts # Global renderer error boundary + recovery actions
|
||||||
overlay-content-measurement.ts # Reports rendered bounds to main process
|
overlay-content-measurement.ts # Reports rendered bounds to main process
|
||||||
subtitle-render.ts # Primary/secondary subtitle rendering + style application
|
subtitle-render.ts # Primary/secondary subtitle rendering + style application
|
||||||
positioning.ts # Facade export for positioning controller
|
positioning.ts # Facade export for positioning controller
|
||||||
|
yomitan-popup.ts # Yomitan popup iframe detection utilities
|
||||||
positioning/
|
positioning/
|
||||||
controller.ts # Position controller orchestration
|
controller.ts # Subtitle drag-position controller
|
||||||
position-state.ts # Position state helpers
|
position-state.ts # Position state helpers (yPercent)
|
||||||
handlers/
|
handlers/
|
||||||
keyboard.ts # Keybindings, chord handling, modal key routing
|
keyboard.ts # Keybindings, chord handling, modal key routing
|
||||||
mouse.ts # Hover/drag behavior, selection + observer wiring
|
mouse.ts # Hover/drag behavior, selection + observer wiring
|
||||||
@@ -120,7 +125,7 @@ src/renderer/
|
|||||||
### Launcher + Plugin Runtimes
|
### Launcher + Plugin Runtimes
|
||||||
|
|
||||||
- `launcher/main.ts` dispatches commands through `launcher/commands/*` and shared config readers in `launcher/config/*`. It handles mpv startup, app passthrough, Jellyfin helper commands, and playback handoff.
|
- `launcher/main.ts` dispatches commands through `launcher/commands/*` and shared config readers in `launcher/config/*`. It handles mpv startup, app passthrough, Jellyfin helper commands, and playback handoff.
|
||||||
- `plugin/subminer/main.lua` runs inside mpv and loads module files for overlay toggles, hover-token messages, and AniSkip intro-skip UX.
|
- `plugin/subminer/init.lua` runs inside mpv and loads modular Lua files: `main.lua` (orchestration), `bootstrap.lua` (startup), `lifecycle.lua` (connect/disconnect), `process.lua` (process management), `state.lua` (shared state), `messages.lua` (IPC), `hover.lua` (hover-token highlight rendering), `ui.lua` (OSD rendering), `options.lua` (config), `environment.lua` (detection), `log.lua` (logging), `binary.lua` (path resolution), `aniskip.lua` + `aniskip_match.lua` (intro-skip UX).
|
||||||
|
|
||||||
## Flow Diagram
|
## Flow Diagram
|
||||||
|
|
||||||
@@ -138,7 +143,7 @@ flowchart LR
|
|||||||
|
|
||||||
subgraph ExtRt["External Runtimes"]
|
subgraph ExtRt["External Runtimes"]
|
||||||
Launcher["launcher/<br/>CLI dispatch"]:::extrt
|
Launcher["launcher/<br/>CLI dispatch"]:::extrt
|
||||||
Plugin["subminer/main.lua<br/>mpv plugin"]:::extrt
|
Plugin["subminer/init.lua<br/>mpv plugin"]:::extrt
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Ext["External Systems"]
|
subgraph Ext["External Systems"]
|
||||||
@@ -161,8 +166,9 @@ flowchart LR
|
|||||||
|
|
||||||
subgraph Svc["Services — src/core/services/"]
|
subgraph Svc["Services — src/core/services/"]
|
||||||
Mpv["MPV Stack<br/>transport · protocol<br/>properties · metrics"]:::svc
|
Mpv["MPV Stack<br/>transport · protocol<br/>properties · metrics"]:::svc
|
||||||
Overlay["Overlay Manager<br/>window · visibility · bridge"]:::svc
|
OverlaySvc["Overlay Manager<br/>window · visibility · bridge<br/>mpv-sub-visibility"]:::svc
|
||||||
Mining["Mining & Subtitles<br/>mining · field-grouping<br/>subtitle-ws · tokenizer"]:::svc
|
Mining["Mining & Subtitles<br/>mining · field-grouping<br/>subtitle-ws · tokenizer"]:::svc
|
||||||
|
AnkiProxy["Anki Integration<br/>anki-connect-proxy<br/>note-update-workflow"]:::svc
|
||||||
Integrations["Integrations<br/>jimaku · subsync<br/>texthooker · yomitan"]:::svc
|
Integrations["Integrations<br/>jimaku · subsync<br/>texthooker · yomitan"]:::svc
|
||||||
Tracking["Tracking<br/>anilist · jellyfin<br/>immersion · discord"]:::svc
|
Tracking["Tracking<br/>anilist · jellyfin<br/>immersion · discord"]:::svc
|
||||||
Config["Config & Runtime<br/>hot-reload<br/>runtime-options"]:::svc
|
Config["Config & Runtime<br/>hot-reload<br/>runtime-options"]:::svc
|
||||||
@@ -171,7 +177,7 @@ flowchart LR
|
|||||||
Bridge(["preload.ts<br/>Electron IPC"]):::bridge
|
Bridge(["preload.ts<br/>Electron IPC"]):::bridge
|
||||||
|
|
||||||
subgraph Rend["Renderer — src/renderer/"]
|
subgraph Rend["Renderer — src/renderer/"]
|
||||||
Overlay["Main overlay window<br/>primary + secondary subtitles"]:::rend
|
OverlayWin["Main overlay window<br/>primary + secondary subtitles"]:::rend
|
||||||
UI["subtitle-render<br/>positioning<br/>handlers · modals"]:::rend
|
UI["subtitle-render<br/>positioning<br/>handlers · modals"]:::rend
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -182,16 +188,16 @@ flowchart LR
|
|||||||
Comp --> Svc
|
Comp --> Svc
|
||||||
|
|
||||||
mpvExt <-->|"JSON socket"| Mpv
|
mpvExt <-->|"JSON socket"| Mpv
|
||||||
AnkiExt <-->|"HTTP"| Mining
|
AnkiExt <-->|"HTTP"| AnkiProxy
|
||||||
JimakuExt <-->|"HTTP"| Integrations
|
JimakuExt <-->|"HTTP"| Integrations
|
||||||
TrackerExt <-->|"platform"| Overlay
|
TrackerExt <-->|"platform"| OverlaySvc
|
||||||
AnilistExt <-->|"HTTP"| Tracking
|
AnilistExt <-->|"HTTP"| Tracking
|
||||||
JellyfinExt <-->|"HTTP"| Tracking
|
JellyfinExt <-->|"HTTP"| Tracking
|
||||||
DiscordExt <-->|"RPC"| Integrations
|
DiscordExt <-->|"RPC"| Integrations
|
||||||
|
|
||||||
Overlay & Mining --> Bridge
|
OverlaySvc & Mining --> Bridge
|
||||||
Bridge --> Overlay
|
Bridge --> OverlayWin
|
||||||
Overlay --> UI
|
OverlayWin --> UI
|
||||||
|
|
||||||
style Comp fill:#363a4f,stroke:#494d64,color:#cad3f5
|
style Comp fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||||
style Svc fill:#363a4f,stroke:#494d64,color:#cad3f5
|
style Svc fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||||
@@ -259,10 +265,10 @@ For domains migrated to reducer-style transitions (for example AniList token/que
|
|||||||
- **Module-level init:** Before `app.ready`, the composition root registers protocols, sets platform flags, constructs all services, and wires dependency injection. `runAndApplyStartupState()` parses CLI args and detects the compositor backend.
|
- **Module-level init:** Before `app.ready`, the composition root registers protocols, sets platform flags, constructs all services, and wires dependency injection. `runAndApplyStartupState()` parses CLI args and detects the compositor backend.
|
||||||
- **Startup:** If `--generate-config` is passed, it writes the template and exits. Otherwise `app-lifecycle.ts` acquires the single-instance lock and registers Electron lifecycle hooks.
|
- **Startup:** If `--generate-config` is passed, it writes the template and exits. Otherwise `app-lifecycle.ts` acquires the single-instance lock and registers Electron lifecycle hooks.
|
||||||
- **Critical-path init:** Once `app.whenReady()` fires, `composeAppReadyRuntime()` runs strict config reload, resolves keybindings, creates the `MpvIpcClient` (which immediately connects and subscribes to 26 properties), and initializes the `RuntimeOptionsManager`, `SubtitleTimingTracker`, and `ImmersionTrackerService`.
|
- **Critical-path init:** Once `app.whenReady()` fires, `composeAppReadyRuntime()` runs strict config reload, resolves keybindings, creates the `MpvIpcClient` (which immediately connects and subscribes to 26 properties), and initializes the `RuntimeOptionsManager`, `SubtitleTimingTracker`, and `ImmersionTrackerService`.
|
||||||
- **Overlay runtime:** `initializeOverlayRuntime()` creates the primary overlay window (interactive Yomitan lookups and subtitle rendering) and registers global shortcuts and bounds tracking via the active window tracker.
|
- **Overlay runtime:** `initializeOverlayRuntime()` creates the primary overlay window (interactive Yomitan lookups and subtitle rendering), registers global shortcuts, and sets up bounds tracking via the active window tracker. mpv subtitle suppression is handled by a dedicated `overlay-mpv-sub-visibility` service.
|
||||||
- **Background warmups:** Non-critical services are launched asynchronously: MeCab tokenizer check, Yomitan extension load, JLPT + frequency dictionary prewarm, optional Jellyfin remote session, Discord presence service, and AniList token refresh. Warmup coverage is configurable through `startupWarmups` (including low-power mode that defers all but Yomitan).
|
- **Background warmups:** Non-critical services are launched asynchronously: MeCab tokenizer check (with async worker thread), Yomitan extension load, JLPT + frequency dictionary prewarm, optional Jellyfin remote session, Discord presence service, AniList token refresh, and optional AnkiConnect proxy server. Warmup coverage is configurable through `startupWarmups` (including low-power mode that defers all but Yomitan).
|
||||||
- **Runtime:** Event-driven. mpv property changes, IPC messages, CLI commands, overlay shortcuts, and hot-reload notifications route through runtime handlers/composers. Subtitle text flows through `SubtitlePipeline` (normalize → tokenize → merge), and results are sent to the main overlay renderer and modal surfaces.
|
- **Runtime:** Event-driven. mpv property changes, IPC messages, CLI commands, overlay shortcuts, and hot-reload notifications route through runtime handlers/composers. Subtitle text flows through `SubtitlePipeline` (normalize → tokenize → merge), and results are sent to the main overlay renderer and modal surfaces.
|
||||||
- **Shutdown:** `onWillQuitCleanup` destroys tray + config watcher, unregisters shortcuts, stops WebSocket + texthooker servers, closes the mpv socket + flushes OSD log, stops the window tracker, closes the Yomitan parser window, flushes the immersion tracker (SQLite), stops Jellyfin/Discord services, and cleans Anki/AniList state.
|
- **Shutdown:** `onWillQuitCleanup` destroys tray + config watcher, unregisters shortcuts, stops WebSocket + texthooker servers, closes the mpv socket + flushes OSD log, stops the window tracker, closes the Yomitan parser window, flushes the immersion tracker (SQLite), stops Jellyfin/Discord services, stops the AnkiConnect proxy server, and cleans Anki/AniList state.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
@@ -303,13 +309,14 @@ flowchart LR
|
|||||||
|
|
||||||
subgraph WarmupGroup[" "]
|
subgraph WarmupGroup[" "]
|
||||||
direction TB
|
direction TB
|
||||||
W1["MeCab"]:::warmup
|
W1["MeCab<br/>+ worker thread"]:::warmup
|
||||||
W2["Yomitan"]:::warmup
|
W2["Yomitan"]:::warmup
|
||||||
W3["JLPT + freq<br/>dictionaries"]:::warmup
|
W3["JLPT + freq<br/>dictionaries"]:::warmup
|
||||||
W4["Jellyfin"]:::warmup
|
W4["Jellyfin"]:::warmup
|
||||||
W5["Discord"]:::warmup
|
W5["Discord"]:::warmup
|
||||||
W6["AniList"]:::warmup
|
W6["AniList"]:::warmup
|
||||||
W1 ~~~ W2 ~~~ W3 ~~~ W4 ~~~ W5 ~~~ W6
|
W7["AnkiConnect<br/>proxy"]:::warmup
|
||||||
|
W1 ~~~ W2 ~~~ W3 ~~~ W4 ~~~ W5 ~~~ W6 ~~~ W7
|
||||||
end
|
end
|
||||||
|
|
||||||
Warmups --> WarmupGroup
|
Warmups --> WarmupGroup
|
||||||
@@ -333,7 +340,7 @@ flowchart LR
|
|||||||
Quit --> T1["Tray · config watcher<br/>global shortcuts"]:::shutdown
|
Quit --> T1["Tray · config watcher<br/>global shortcuts"]:::shutdown
|
||||||
Quit --> T2["WebSocket · texthooker<br/>mpv socket · OSD log"]:::shutdown
|
Quit --> T2["WebSocket · texthooker<br/>mpv socket · OSD log"]:::shutdown
|
||||||
Quit --> T3["Window tracker<br/>Yomitan parser"]:::shutdown
|
Quit --> T3["Window tracker<br/>Yomitan parser"]:::shutdown
|
||||||
Quit --> T4["Immersion tracker<br/>Jellyfin · Discord<br/>Anki · AniList"]:::shutdown
|
Quit --> T4["Immersion tracker<br/>Jellyfin · Discord<br/>Anki proxy · AniList"]:::shutdown
|
||||||
|
|
||||||
style Loop fill:#363a4f,stroke:#494d64,color:#cad3f5
|
style Loop fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,15 +1,40 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="ac" x1="6" y1="6" x2="36" y2="42" gradientUnits="userSpaceOnUse">
|
<linearGradient id="ac-card" x1="6" y1="8" x2="38" y2="44" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#34d399"/>
|
<stop stop-color="#34d399"/>
|
||||||
<stop offset="1" stop-color="#059669"/>
|
<stop offset="1" stop-color="#059669"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
<linearGradient id="ac-glow" x1="8" y1="10" x2="36" y2="42" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6ee7b7" stop-opacity="0.5"/>
|
||||||
|
<stop offset="1" stop-color="#059669" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<filter id="ac-soft" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="1.2"/>
|
||||||
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
<rect x="12" y="5" width="24" height="34" rx="3" fill="#059669" opacity="0.18"/>
|
<!-- Glow aura behind card -->
|
||||||
<rect x="8" y="9" width="24" height="34" rx="3" fill="url(#ac)"/>
|
<rect x="6" y="8" width="28" height="36" rx="5" fill="url(#ac-glow)" filter="url(#ac-soft)"/>
|
||||||
<rect x="13" y="18" width="14" height="2.5" rx="1.25" fill="white" opacity="0.85"/>
|
<!-- Shadow card (back) -->
|
||||||
<rect x="13" y="24" width="10" height="2.5" rx="1.25" fill="white" opacity="0.4"/>
|
<rect x="14" y="5" width="26" height="34" rx="4" fill="#059669" opacity="0.15"/>
|
||||||
<rect x="13" y="30" width="12" height="2.5" rx="1.25" fill="white" opacity="0.4"/>
|
<!-- Main card -->
|
||||||
<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"/>
|
<rect x="6" y="9" width="26" height="34" rx="4" fill="url(#ac-card)"/>
|
||||||
<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"/>
|
<!-- Sentence line -->
|
||||||
|
<rect x="10" y="16" width="16" height="2.5" rx="1.25" fill="white" opacity="0.9"/>
|
||||||
|
<!-- Audio waveform mini -->
|
||||||
|
<rect x="10" y="22" width="1.8" height="5" rx="0.9" fill="white" opacity="0.55"/>
|
||||||
|
<rect x="13" y="20.5" width="1.8" height="8" rx="0.9" fill="white" opacity="0.55"/>
|
||||||
|
<rect x="16" y="21.5" width="1.8" height="6" rx="0.9" fill="white" opacity="0.55"/>
|
||||||
|
<rect x="19" y="23" width="1.8" height="3" rx="0.9" fill="white" opacity="0.55"/>
|
||||||
|
<rect x="22" y="21" width="1.8" height="7" rx="0.9" fill="white" opacity="0.55"/>
|
||||||
|
<rect x="25" y="22.5" width="1.8" height="4" rx="0.9" fill="white" opacity="0.55"/>
|
||||||
|
<!-- Image thumbnail placeholder -->
|
||||||
|
<rect x="10" y="30" width="10" height="8" rx="2" fill="white" opacity="0.25"/>
|
||||||
|
<path d="M12.5 35.5l2-2.5 2 1.8 1.5-1 2.5 3h-8z" fill="white" opacity="0.5"/>
|
||||||
|
<!-- Translation line -->
|
||||||
|
<rect x="22" y="32" width="7" height="2" rx="1" fill="white" opacity="0.35"/>
|
||||||
|
<rect x="22" y="35.5" width="5" height="2" rx="1" fill="white" opacity="0.25"/>
|
||||||
|
<!-- Enrichment sparkle burst -->
|
||||||
|
<path d="M40 10l1.6 3.8 3.8 1.6-3.8 1.6L40 20.8l-1.6-3.8L34.6 15.4l3.8-1.6z" fill="#6ee7b7"/>
|
||||||
|
<path d="M37 29l0.9 2.1 2.1 0.9-2.1 0.9L37 35l-0.9-2.1-2.1-0.9 2.1-0.9z" fill="#6ee7b7" opacity="0.5"/>
|
||||||
|
<circle cx="43" cy="25" r="1.2" fill="#34d399" opacity="0.4"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 914 B After Width: | Height: | Size: 2.3 KiB |
@@ -1,13 +1,39 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="hl" x1="20" y1="14" x2="38" y2="34" gradientUnits="userSpaceOnUse">
|
<linearGradient id="hl-freq" x1="0" y1="0" x2="14" y2="8" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#fbbf24"/>
|
<stop stop-color="#fbbf24"/>
|
||||||
<stop offset="1" stop-color="#f59e0b"/>
|
<stop offset="1" stop-color="#f59e0b"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
<linearGradient id="hl-n1" x1="0" y1="0" x2="10" y2="10" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#60a5fa"/>
|
||||||
|
<stop offset="1" stop-color="#3b82f6"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="hl-jlpt" x1="0" y1="0" x2="12" y2="8" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#a78bfa"/>
|
||||||
|
<stop offset="1" stop-color="#7c3aed"/>
|
||||||
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<rect x="2" y="17" width="10" height="14" rx="3" fill="#fbbf24" opacity="0.3"/>
|
<!-- Viewport / video frame background -->
|
||||||
<rect x="14" y="17" width="7" height="14" rx="3" fill="#fbbf24" opacity="0.3"/>
|
<rect x="1" y="5" width="46" height="38" rx="4" fill="#1e293b" opacity="0.55"/>
|
||||||
<rect x="23" y="13" width="13" height="22" rx="3.5" fill="url(#hl)"/>
|
<rect x="1" y="5" width="46" height="38" rx="4" stroke="#334155" stroke-width="0.8" fill="none" opacity="0.5"/>
|
||||||
<rect x="38" y="17" width="8" height="14" rx="3" fill="#fbbf24" opacity="0.3"/>
|
<!-- Subtitle line 1 — tokens with frequency highlight -->
|
||||||
<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"/>
|
<rect x="6" y="18" width="9" height="5" rx="1.5" fill="#cbd5e1" opacity="0.2"/>
|
||||||
|
<!-- Frequency-highlighted token -->
|
||||||
|
<rect x="17" y="17" width="14" height="7" rx="2" fill="url(#hl-freq)" opacity="0.2"/>
|
||||||
|
<rect x="17.5" y="17.5" width="13" height="6" rx="1.8" fill="url(#hl-freq)"/>
|
||||||
|
<rect x="20" y="19.5" width="8" height="2" rx="1" fill="white" opacity="0.85"/>
|
||||||
|
<rect x="33" y="18" width="8" height="5" rx="1.5" fill="#cbd5e1" opacity="0.2"/>
|
||||||
|
<!-- Subtitle line 2 — tokens with N+1 dot and JLPT badge -->
|
||||||
|
<rect x="8" y="28" width="8" height="5" rx="1.5" fill="#cbd5e1" opacity="0.2"/>
|
||||||
|
<!-- N+1 targeted token with dot -->
|
||||||
|
<rect x="18" y="28" width="10" height="5" rx="1.5" fill="#60a5fa" opacity="0.15"/>
|
||||||
|
<rect x="18.5" y="28.5" width="9" height="4" rx="1.2" fill="#cbd5e1" opacity="0.3"/>
|
||||||
|
<circle cx="16.5" cy="30.5" r="2.2" fill="url(#hl-n1)"/>
|
||||||
|
<text x="16.5" y="31.9" text-anchor="middle" font-size="2.6" font-weight="800" fill="white" font-family="sans-serif">+1</text>
|
||||||
|
<!-- JLPT badge token -->
|
||||||
|
<rect x="30" y="28" width="7" height="5" rx="1.5" fill="#cbd5e1" opacity="0.3"/>
|
||||||
|
<rect x="37.5" y="27" width="9" height="7" rx="2" fill="url(#hl-jlpt)"/>
|
||||||
|
<text x="42" y="31.8" text-anchor="middle" font-size="3.5" font-weight="700" fill="white" font-family="sans-serif">N2</text>
|
||||||
|
<!-- Subtle sparkle -->
|
||||||
|
<path d="M43 10l0.7 1.6 1.6 0.7-1.6 0.7L43 14.6l-0.7-1.6-1.6-0.7 1.6-0.7z" fill="#fbbf24" opacity="0.5"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 729 B After Width: | Height: | Size: 2.4 KiB |
@@ -1,21 +1,31 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="kb" x1="2" y1="10" x2="46" y2="42" gradientUnits="userSpaceOnUse">
|
<linearGradient id="kb-main" x1="2" y1="10" x2="46" y2="42" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#c084fc"/>
|
<stop stop-color="#c084fc"/>
|
||||||
<stop offset="1" stop-color="#7c3aed"/>
|
<stop offset="1" stop-color="#7c3aed"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
<filter id="kb-glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="2"/>
|
||||||
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
<rect x="2" y="12" width="44" height="30" rx="5" fill="url(#kb)" opacity="0.12"/>
|
<!-- Keyboard body -->
|
||||||
<rect x="2" y="12" width="44" height="30" rx="5" stroke="url(#kb)" stroke-width="1.5" fill="none"/>
|
<rect x="2" y="14" width="44" height="28" rx="4.5" fill="url(#kb-main)" opacity="0.1"/>
|
||||||
<rect x="6" y="16" width="8" height="6" rx="2" fill="url(#kb)"/>
|
<rect x="2" y="14" width="44" height="28" rx="4.5" stroke="url(#kb-main)" stroke-width="1.4" fill="none"/>
|
||||||
<rect x="16" y="16" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
<!-- Row 1 -->
|
||||||
<rect x="26" y="16" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
<rect x="6" y="18" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||||
<rect x="36" y="16" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
<rect x="15" y="18" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||||
<rect x="6" y="24" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
<rect x="24" y="18" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||||
<rect x="16" y="24" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
<rect x="33" y="18" width="11" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||||
<rect x="26" y="24" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
<!-- Row 2 — active key with glow -->
|
||||||
<rect x="36" y="24" width="8" height="6" rx="2" fill="url(#kb)"/>
|
<rect x="6" y="25.5" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||||
<rect x="6" y="32" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
<!-- Active/pressed key glow -->
|
||||||
<rect x="16" y="32" width="16" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
<rect x="15" y="25.5" width="7" height="5.5" rx="1.8" fill="#c084fc" opacity="0.25" filter="url(#kb-glow)"/>
|
||||||
<rect x="34" y="32" width="10" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
<rect x="15" y="25.5" width="7" height="5.5" rx="1.8" fill="url(#kb-main)"/>
|
||||||
|
<text x="18.5" y="30" text-anchor="middle" font-size="3.5" font-weight="700" fill="white" font-family="sans-serif">M</text>
|
||||||
|
<rect x="24" y="25.5" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||||
|
<rect x="33" y="25.5" width="11" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||||
|
<!-- Row 3 — spacebar -->
|
||||||
|
<rect x="6" y="33" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||||
|
<rect x="15" y="33" width="16" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.25"/>
|
||||||
|
<rect x="33" y="33" width="11" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 141 KiB |
BIN
docs/public/assets/kiku-integration.gif
Normal file
|
After Width: | Height: | Size: 4.3 MiB |
BIN
docs/public/assets/kiku-integration.mkv
Normal file
BIN
docs/public/assets/kiku-integration.mp4
Normal file
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 13 MiB After Width: | Height: | Size: 12 MiB |
@@ -1,16 +1,35 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="sd" x1="4" y1="4" x2="44" y2="44" gradientUnits="userSpaceOnUse">
|
<linearGradient id="sd-main" x1="4" y1="4" x2="44" y2="44" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#22d3ee"/>
|
<stop stop-color="#22d3ee"/>
|
||||||
<stop offset="1" stop-color="#0891b2"/>
|
<stop offset="1" stop-color="#0891b2"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
<linearGradient id="sd-sync" x1="30" y1="28" x2="46" y2="44" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#34d399"/>
|
||||||
|
<stop offset="1" stop-color="#059669"/>
|
||||||
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<rect x="8" y="4" width="24" height="32" rx="3" fill="url(#sd)" opacity="0.15"/>
|
<!-- Subtitle file -->
|
||||||
<rect x="8" y="4" width="24" height="32" rx="3" stroke="url(#sd)" stroke-width="1.5" fill="none"/>
|
<rect x="4" y="3" width="26" height="34" rx="3.5" fill="url(#sd-main)" opacity="0.12"/>
|
||||||
<rect x="13" y="12" width="14" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.5"/>
|
<rect x="4" y="3" width="26" height="34" rx="3.5" stroke="url(#sd-main)" stroke-width="1.4" fill="none"/>
|
||||||
<rect x="13" y="18" width="10" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.35"/>
|
<!-- SRT-style timing line -->
|
||||||
<rect x="13" y="24" width="12" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.35"/>
|
<rect x="8.5" y="10" width="10" height="2" rx="1" fill="#22d3ee" opacity="0.35"/>
|
||||||
<line x1="38" y1="16" x2="38" y2="32" stroke="url(#sd)" stroke-width="2.5" stroke-linecap="round"/>
|
<rect x="20" y="10" width="3" height="2" rx="1" fill="#22d3ee" opacity="0.25"/>
|
||||||
<path d="M33 28l5 5 5-5" stroke="url(#sd)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
<!-- Subtitle text lines -->
|
||||||
<line x1="33" y1="40" x2="43" y2="40" stroke="url(#sd)" stroke-width="2" stroke-linecap="round" opacity="0.5"/>
|
<rect x="8.5" y="15" width="17" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.6"/>
|
||||||
|
<rect x="8.5" y="20" width="12" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.4"/>
|
||||||
|
<!-- Divider -->
|
||||||
|
<line x1="8.5" y1="25.5" x2="26" y2="25.5" stroke="#22d3ee" stroke-width="0.6" opacity="0.2"/>
|
||||||
|
<!-- Second block timing -->
|
||||||
|
<rect x="8.5" y="28" width="10" height="2" rx="1" fill="#22d3ee" opacity="0.35"/>
|
||||||
|
<!-- Second block text -->
|
||||||
|
<rect x="8.5" y="32.5" width="14" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.4"/>
|
||||||
|
<!-- Download arrow -->
|
||||||
|
<line x1="38" y1="6" x2="38" y2="20" stroke="url(#sd-main)" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<path d="M33 16.5l5 5.5 5-5.5" stroke="url(#sd-main)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
|
<!-- Sync arrows (circular) -->
|
||||||
|
<path d="M35 35a6 6 0 0 1 8.5-1.5" stroke="url(#sd-sync)" stroke-width="1.8" stroke-linecap="round" fill="none"/>
|
||||||
|
<path d="M44.5 35.5l-1-2.8-2.8 1" stroke="url(#sd-sync)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
|
<path d="M43.5 41a6 6 0 0 1-8.5 1.5" stroke="url(#sd-sync)" stroke-width="1.8" stroke-linecap="round" fill="none"/>
|
||||||
|
<path d="M34 40.5l1 2.8 2.8-1" stroke="url(#sd-sync)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -1,19 +1,46 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="th" x1="4" y1="6" x2="44" y2="42" gradientUnits="userSpaceOnUse">
|
<linearGradient id="th-main" x1="2" y1="6" x2="22" y2="42" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#f97316"/>
|
<stop stop-color="#f97316"/>
|
||||||
<stop offset="1" stop-color="#c2410c"/>
|
<stop offset="1" stop-color="#c2410c"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
<linearGradient id="th-browser" x1="28" y1="6" x2="46" y2="42" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#fb923c"/>
|
||||||
|
<stop offset="1" stop-color="#ea580c"/>
|
||||||
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<rect x="4" y="6" width="30" height="36" rx="4" fill="url(#th)" opacity="0.12"/>
|
<!-- Source panel (subtitle/text source) -->
|
||||||
<rect x="4" y="6" width="30" height="36" rx="4" stroke="url(#th)" stroke-width="1.5" fill="none"/>
|
<rect x="2" y="8" width="18" height="32" rx="3" fill="url(#th-main)" opacity="0.12"/>
|
||||||
<rect x="9" y="14" width="14" height="2.5" rx="1.25" fill="#f97316" opacity="0.6"/>
|
<rect x="2" y="8" width="18" height="32" rx="3" stroke="url(#th-main)" stroke-width="1.3" fill="none"/>
|
||||||
<rect x="9" y="20" width="18" height="2.5" rx="1.25" fill="#f97316" opacity="0.4"/>
|
<!-- Subtitle text lines streaming out -->
|
||||||
<rect x="9" y="26" width="12" height="2.5" rx="1.25" fill="#f97316" opacity="0.4"/>
|
<rect x="5" y="14" width="12" height="2" rx="1" fill="#f97316" opacity="0.6"/>
|
||||||
<rect x="9" y="32" width="16" height="2.5" rx="1.25" fill="#f97316" opacity="0.4"/>
|
<rect x="5" y="19" width="10" height="2" rx="1" fill="#f97316" opacity="0.5"/>
|
||||||
<circle cx="40" cy="18" r="3.5" fill="url(#th)" opacity="0.8"/>
|
<rect x="5" y="24" width="11" height="2" rx="1" fill="#f97316" opacity="0.4"/>
|
||||||
<circle cx="40" cy="30" r="3.5" fill="url(#th)" opacity="0.8"/>
|
<rect x="5" y="29" width="9" height="2" rx="1" fill="#f97316" opacity="0.35"/>
|
||||||
<line x1="36" y1="18" x2="34" y2="18" stroke="url(#th)" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>
|
<rect x="5" y="34" width="12" height="2" rx="1" fill="#f97316" opacity="0.3"/>
|
||||||
<line x1="36" y1="30" x2="34" y2="30" stroke="url(#th)" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>
|
<!-- WebSocket stream particles -->
|
||||||
<line x1="40" y1="21.5" x2="40" y2="26.5" stroke="url(#th)" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
|
<circle cx="23" cy="18" r="1.2" fill="#fb923c" opacity="0.7"/>
|
||||||
|
<circle cx="25" cy="24" r="1" fill="#fb923c" opacity="0.5"/>
|
||||||
|
<circle cx="23.5" cy="30" r="1.1" fill="#fb923c" opacity="0.4"/>
|
||||||
|
<!-- Connection line (wavy/flowing) -->
|
||||||
|
<path d="M20 15c2-1 4 2 6 1s3-3 5-2" stroke="#fb923c" stroke-width="1" stroke-linecap="round" fill="none" opacity="0.3"/>
|
||||||
|
<path d="M20 24c2.5 0 3 2 5 1.5s3-2.5 5.5-1.5" stroke="#fb923c" stroke-width="1" stroke-linecap="round" fill="none" opacity="0.25"/>
|
||||||
|
<path d="M20 33c2-1 3.5 1.5 5.5 0.5s3-2 5-1" stroke="#fb923c" stroke-width="1" stroke-linecap="round" fill="none" opacity="0.2"/>
|
||||||
|
<!-- Browser window (destination) -->
|
||||||
|
<rect x="28" y="8" width="18" height="32" rx="3" fill="url(#th-browser)" opacity="0.12"/>
|
||||||
|
<rect x="28" y="8" width="18" height="32" rx="3" stroke="url(#th-browser)" stroke-width="1.3" fill="none"/>
|
||||||
|
<!-- Browser chrome dots -->
|
||||||
|
<circle cx="32" cy="12" r="1.2" fill="#f97316" opacity="0.45"/>
|
||||||
|
<circle cx="35.5" cy="12" r="1.2" fill="#f97316" opacity="0.35"/>
|
||||||
|
<circle cx="39" cy="12" r="1.2" fill="#f97316" opacity="0.25"/>
|
||||||
|
<!-- Browser address bar -->
|
||||||
|
<rect x="31" y="15.5" width="12" height="2.5" rx="1.25" fill="#f97316" opacity="0.15"/>
|
||||||
|
<!-- Received text lines in browser -->
|
||||||
|
<rect x="31" y="21" width="11" height="2" rx="1" fill="#fb923c" opacity="0.55"/>
|
||||||
|
<rect x="31" y="25.5" width="9" height="2" rx="1" fill="#fb923c" opacity="0.45"/>
|
||||||
|
<rect x="31" y="30" width="10" height="2" rx="1" fill="#fb923c" opacity="0.35"/>
|
||||||
|
<rect x="31" y="34.5" width="8" height="2" rx="1" fill="#fb923c" opacity="0.25"/>
|
||||||
|
<!-- WS label -->
|
||||||
|
<rect x="21" y="5" width="8" height="5.5" rx="2.5" fill="#c2410c"/>
|
||||||
|
<text x="25" y="9.2" text-anchor="middle" font-size="3.2" font-weight="800" fill="white" font-family="sans-serif">WS</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -1,16 +1,34 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="tk" x1="0" y1="14" x2="48" y2="34" gradientUnits="userSpaceOnUse">
|
<linearGradient id="tk-bar" x1="0" y1="40" x2="0" y2="10" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#22d3ee"/>
|
<stop stop-color="#0891b2"/>
|
||||||
<stop offset="1" stop-color="#0891b2"/>
|
<stop offset="1" stop-color="#22d3ee"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="tk-glow" x1="4" y1="40" x2="44" y2="10" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#22d3ee" stop-opacity="0.25"/>
|
||||||
|
<stop offset="1" stop-color="#06b6d4" stop-opacity="0"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<rect x="2" y="12" width="12" height="24" rx="3.5" fill="url(#tk)"/>
|
<!-- Subtle grid lines -->
|
||||||
<rect x="18" y="12" width="12" height="24" rx="3.5" fill="url(#tk)"/>
|
<line x1="4" y1="14" x2="44" y2="14" stroke="#22d3ee" stroke-width="0.5" opacity="0.12"/>
|
||||||
<rect x="34" y="12" width="12" height="24" rx="3.5" fill="url(#tk)"/>
|
<line x1="4" y1="22" x2="44" y2="22" stroke="#22d3ee" stroke-width="0.5" opacity="0.12"/>
|
||||||
<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="4" y1="30" x2="44" y2="30" stroke="#22d3ee" stroke-width="0.5" opacity="0.12"/>
|
||||||
<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"/>
|
<!-- Base line -->
|
||||||
<rect x="5" y="22" width="6" height="2.5" rx="1.25" fill="white" opacity="0.7"/>
|
<line x1="4" y1="40" x2="44" y2="40" stroke="#0891b2" stroke-width="1" opacity="0.3"/>
|
||||||
<rect x="21" y="22" width="6" height="2.5" rx="1.25" fill="white" opacity="0.7"/>
|
<!-- Activity bars (daily rollups) -->
|
||||||
<rect x="37" y="22" width="6" height="2.5" rx="1.25" fill="white" opacity="0.7"/>
|
<rect x="5" y="30" width="4" height="10" rx="1.5" fill="url(#tk-bar)" opacity="0.4"/>
|
||||||
|
<rect x="11" y="24" width="4" height="16" rx="1.5" fill="url(#tk-bar)" opacity="0.55"/>
|
||||||
|
<rect x="17" y="28" width="4" height="12" rx="1.5" fill="url(#tk-bar)" opacity="0.5"/>
|
||||||
|
<rect x="23" y="18" width="4" height="22" rx="1.5" fill="url(#tk-bar)" opacity="0.7"/>
|
||||||
|
<rect x="29" y="22" width="4" height="18" rx="1.5" fill="url(#tk-bar)" opacity="0.6"/>
|
||||||
|
<rect x="35" y="14" width="4" height="26" rx="1.5" fill="url(#tk-bar)"/>
|
||||||
|
<rect x="41" y="20" width="4" height="20" rx="1.5" fill="url(#tk-bar)" opacity="0.65"/>
|
||||||
|
<!-- Trend line -->
|
||||||
|
<polyline points="7,28 13,22 19,25.5 25,16 31,20 37,12 43,18" stroke="#67e8f9" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.7"/>
|
||||||
|
<!-- Trend dot on peak -->
|
||||||
|
<circle cx="37" cy="12" r="2.2" fill="#22d3ee" opacity="0.6"/>
|
||||||
|
<circle cx="37" cy="12" r="1" fill="white" opacity="0.9"/>
|
||||||
|
<!-- Mini counter badge -->
|
||||||
|
<rect x="33" y="4" width="12" height="7" rx="3.5" fill="#0891b2"/>
|
||||||
|
<text x="39" y="9" text-anchor="middle" font-size="4" font-weight="700" fill="white" font-family="sans-serif">42d</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -12,12 +12,13 @@ Description:
|
|||||||
- <name>.mp4 (H.264 + AAC, prefers NVIDIA GPU if available)
|
- <name>.mp4 (H.264 + AAC, prefers NVIDIA GPU if available)
|
||||||
- <name>.webm (AV1/VP9 + Opus, prefers NVIDIA GPU if available)
|
- <name>.webm (AV1/VP9 + Opus, prefers NVIDIA GPU if available)
|
||||||
- <name>.gif (palette-optimised, 15 fps)
|
- <name>.gif (palette-optimised, 15 fps)
|
||||||
|
- <name>-poster.jpg (single frame for video poster fallback)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-f, --force Overwrite existing output files
|
-f, --force Overwrite existing output files
|
||||||
|
|
||||||
Encoding profile:
|
Encoding profile:
|
||||||
- Crop: 1920x1080 at x=760 y=180
|
- Crop: 1920x1080 at x=760 y=200
|
||||||
- MP4: H.264 + AAC
|
- MP4: H.264 + AAC
|
||||||
- WebM: AV1/VP9 + Opus at 30 fps
|
- WebM: AV1/VP9 + Opus at 30 fps
|
||||||
USAGE
|
USAGE
|
||||||
@@ -28,11 +29,11 @@ input=""
|
|||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-h|--help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
-f|--force)
|
-f | --force)
|
||||||
force=1
|
force=1
|
||||||
;;
|
;;
|
||||||
-*)
|
-*)
|
||||||
@@ -74,6 +75,7 @@ base="${filename%.*}"
|
|||||||
mp4_out="$dir/$base.mp4"
|
mp4_out="$dir/$base.mp4"
|
||||||
webm_out="$dir/$base.webm"
|
webm_out="$dir/$base.webm"
|
||||||
gif_out="$dir/$base.gif"
|
gif_out="$dir/$base.gif"
|
||||||
|
poster_out="$dir/$base-poster.jpg"
|
||||||
|
|
||||||
overwrite_flag="-n"
|
overwrite_flag="-n"
|
||||||
if [[ "$force" -eq 1 ]]; then
|
if [[ "$force" -eq 1 ]]; then
|
||||||
@@ -81,7 +83,7 @@ if [[ "$force" -eq 1 ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$force" -eq 0 ]]; then
|
if [[ "$force" -eq 0 ]]; then
|
||||||
for output in "$mp4_out" "$webm_out" "$gif_out"; do
|
for output in "$mp4_out" "$webm_out" "$gif_out" "$poster_out"; do
|
||||||
if [[ -e "$output" ]]; then
|
if [[ -e "$output" ]]; then
|
||||||
echo "Error: output exists: $output (use --force to overwrite)" >&2
|
echo "Error: output exists: $output (use --force to overwrite)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -94,7 +96,7 @@ has_encoder() {
|
|||||||
ffmpeg -hide_banner -encoders 2> /dev/null | grep -qE "[[:space:]]${encoder}[[:space:]]"
|
ffmpeg -hide_banner -encoders 2> /dev/null | grep -qE "[[:space:]]${encoder}[[:space:]]"
|
||||||
}
|
}
|
||||||
|
|
||||||
crop_vf="crop=1920:1080:760:180"
|
crop_vf="crop=1920:1080:760:205"
|
||||||
webm_vf="${crop_vf},fps=30"
|
webm_vf="${crop_vf},fps=30"
|
||||||
gif_vf="${crop_vf},fps=15,scale=960:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=bayer:bayer_scale=3"
|
gif_vf="${crop_vf},fps=15,scale=960:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=bayer:bayer_scale=3"
|
||||||
|
|
||||||
@@ -162,7 +164,15 @@ ffmpeg "$overwrite_flag" -i "$input" \
|
|||||||
-vf "$gif_vf" \
|
-vf "$gif_vf" \
|
||||||
"$gif_out"
|
"$gif_out"
|
||||||
|
|
||||||
|
echo "Generating poster: $poster_out"
|
||||||
|
ffmpeg "$overwrite_flag" -ss 00:00:05 -i "$input" \
|
||||||
|
-vf "$crop_vf" \
|
||||||
|
-vframes 1 \
|
||||||
|
-q:v 2 \
|
||||||
|
"$poster_out"
|
||||||
|
|
||||||
echo "Done."
|
echo "Done."
|
||||||
echo "MP4: $mp4_out"
|
echo "MP4: $mp4_out"
|
||||||
echo "WebM: $webm_out"
|
echo "WebM: $webm_out"
|
||||||
echo "GIF: $gif_out"
|
echo "GIF: $gif_out"
|
||||||
|
echo "Poster: $poster_out"
|
||||||
|
|||||||