Files
SubMiner/src/renderer/index.html
T
sudacode 475f81d96d fix(anki): allow timing review cards without media
- Keep existing or create new cards without generating audio or images
- Refine timing review timeline expansion labels and boundary markers
2026-08-30 01:56:16 -07:00

765 lines
32 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
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:; img-src 'self' data: blob: chrome-extension:; worker-src 'self' blob:;"
/>
<title>SubMiner</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- Programmatic focus fallback target for Electron/window focus management. -->
<div id="overlay" tabindex="-1">
<div
id="controllerStatusToast"
class="controller-status-toast hidden"
role="status"
aria-live="polite"
></div>
<div
id="overlayErrorToast"
class="overlay-error-toast hidden"
role="status"
aria-live="polite"
></div>
<div
id="overlayNotificationStack"
class="overlay-notification-stack position-top-right hidden"
aria-live="polite"
aria-atomic="false"
></div>
<aside
id="overlayNotificationHistory"
class="notification-history side-right"
role="dialog"
aria-label="Notification history"
aria-hidden="true"
>
<header class="notification-history-header">
<span class="notification-history-title">Notifications</span>
<div class="notification-history-header-actions">
<button class="notification-history-clear" type="button">Clear</button>
<button
class="notification-history-close"
type="button"
aria-label="Close notification history"
>
×
</button>
</div>
</header>
<div class="notification-history-body">
<ul class="notification-history-list"></ul>
<div class="notification-history-empty">No notifications yet</div>
</div>
</aside>
<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="tsukihimeModal" class="modal hidden" aria-hidden="true">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">TsukiHime Subtitles</div>
<button id="tsukihimeClose" class="modal-close" type="button">Close</button>
</div>
<div class="modal-body">
<div class="tsukihime-tabs" role="tablist">
<button
id="tsukihimeTabSecondary"
class="tsukihime-tab active"
type="button"
role="tab"
aria-selected="true"
>
English
</button>
<button
id="tsukihimeTabPrimary"
class="tsukihime-tab"
type="button"
role="tab"
aria-selected="false"
>
Japanese
</button>
</div>
<div class="jimaku-form">
<label class="jimaku-field">
<span>Title</span>
<input id="tsukihimeTitle" type="text" placeholder="Anime title" />
</label>
<label class="jimaku-field">
<span>Episode</span>
<input id="tsukihimeEpisode" type="number" min="1" placeholder="1" />
</label>
<button id="tsukihimeSearch" class="jimaku-button" type="button">Search</button>
</div>
<div id="tsukihimeStatus" class="jimaku-status"></div>
<div id="tsukihimeEntriesSection" class="jimaku-section hidden">
<div class="jimaku-section-title">Releases</div>
<ul id="tsukihimeEntries" class="jimaku-list"></ul>
</div>
<div id="tsukihimeFilesSection" class="jimaku-section hidden">
<div class="jimaku-section-title">Subtitle tracks</div>
<ul id="tsukihimeFiles" class="jimaku-list"></ul>
</div>
</div>
</div>
</div>
<div id="youtubePickerModal" class="modal hidden" aria-hidden="true">
<div class="modal-content youtube-picker-content">
<div class="modal-header">
<div class="modal-title">YouTube Subtitle Tracks</div>
<button id="youtubePickerCloseButton" class="modal-close" type="button">
Continue without subtitles
</button>
</div>
<div class="modal-body youtube-picker-body">
<div id="youtubePickerTitle" class="youtube-picker-title"></div>
<div class="youtube-picker-grid">
<label class="youtube-picker-field">
<span>Primary subtitle</span>
<select id="youtubePickerPrimarySelect"></select>
</label>
<label class="youtube-picker-field">
<span>Secondary subtitle</span>
<select id="youtubePickerSecondarySelect"></select>
</label>
</div>
<div id="youtubePickerStatus" class="youtube-picker-status"></div>
<ul id="youtubePickerTracks" class="youtube-picker-tracks"></ul>
<div class="youtube-picker-footer">
<button id="youtubePickerContinueButton" class="kiku-confirm-button" type="button">
Use selected subtitles
</button>
</div>
</div>
</div>
</div>
<div
id="mediaTimingReviewModal"
class="modal media-timing-review-modal hidden"
aria-hidden="true"
>
<div
class="modal-content media-timing-review-content"
role="dialog"
aria-modal="true"
aria-labelledby="mediaTimingReviewTitle"
>
<div class="media-timing-review-header">
<div class="media-timing-review-heading">
<div id="mediaTimingReviewTitle" class="media-timing-review-title">
Review media timing
</div>
<div id="mediaTimingReviewKind" class="media-timing-review-kind">Sentence card</div>
</div>
<button
id="mediaTimingReviewCancel"
class="media-timing-review-quiet-button"
type="button"
>
Cancel
</button>
</div>
<div id="mediaTimingReviewEditor" class="media-timing-review-editor">
<blockquote id="mediaTimingReviewText" class="media-timing-review-text"></blockquote>
<div class="media-timing-review-readout" aria-live="polite">
<div>
<span>Starts</span>
<strong id="mediaTimingReviewStartValue">00:00.000</strong>
</div>
<div class="media-timing-review-duration">
<span>Clip length</span>
<strong id="mediaTimingReviewDuration">0.00s</strong>
</div>
<div>
<span>Ends</span>
<strong id="mediaTimingReviewEndValue">00:00.000</strong>
</div>
</div>
<div class="media-timing-review-timeline-shell">
<div class="media-timing-review-timeline-labels">
<span id="mediaTimingReviewTimelineStart">00:00</span>
<span id="mediaTimingReviewWaveformLabel">speech-weighted waveform</span>
<span id="mediaTimingReviewTimelineEnd">00:00</span>
</div>
<div id="mediaTimingReviewSelectionTrack" class="media-timing-review-track">
<svg
class="media-timing-review-waveform"
viewBox="0 0 1000 100"
preserveAspectRatio="none"
aria-hidden="true"
>
<defs>
<linearGradient id="mediaTimingReviewWaveformFill" x1="0" y1="0" x2="0" y2="1">
<stop class="media-timing-review-waveform-edge-stop" offset="0%" />
<stop class="media-timing-review-waveform-core-stop" offset="50%" />
<stop class="media-timing-review-waveform-edge-stop" offset="100%" />
</linearGradient>
</defs>
<path id="mediaTimingReviewWaveformPath"></path>
</svg>
<div
class="media-timing-review-original-range"
title="Mined subtitle timing"
aria-hidden="true"
>
<span class="media-timing-review-original-boundary is-start"></span>
<span class="media-timing-review-original-boundary is-end"></span>
</div>
<span class="media-timing-review-original-label is-start" aria-hidden="true">
Line start
</span>
<span class="media-timing-review-original-label is-end" aria-hidden="true">
Line end
</span>
<div
id="mediaTimingReviewSelectedRange"
class="media-timing-review-selected-range"
aria-hidden="true"
></div>
<div class="media-timing-review-playhead" aria-hidden="true"></div>
<div
id="mediaTimingReviewStartHandle"
class="media-timing-review-handle media-timing-review-handle-start"
role="slider"
tabindex="0"
aria-label="Clip start"
aria-orientation="horizontal"
></div>
<div
id="mediaTimingReviewEndHandle"
class="media-timing-review-handle media-timing-review-handle-end"
role="slider"
tabindex="0"
aria-label="Clip end"
aria-orientation="horizontal"
></div>
</div>
<div class="media-timing-review-expand-row">
<button
id="mediaTimingReviewShowEarlier"
class="media-timing-review-expand-button"
type="button"
aria-label="Show two more seconds before the visible timeline without moving the selected clip"
title="Show 2 more seconds before the visible timeline. The selected clip does not move."
>
Earlier &minus;2s
</button>
<span>Drag an edge to trim, or drag the highlighted clip to move it.</span>
<button
id="mediaTimingReviewShowLater"
class="media-timing-review-expand-button"
type="button"
aria-label="Show two more seconds after the visible timeline without moving the selected clip"
title="Show 2 more seconds after the visible timeline. The selected clip does not move."
>
Later +2s
</button>
</div>
</div>
<div class="media-timing-review-fine-grid">
<div class="media-timing-review-fine-control">
<span>Start</span>
<button
id="mediaTimingReviewStartBack"
type="button"
aria-label="Move start backward 100 milliseconds"
>
&minus;0.1s
</button>
<button
id="mediaTimingReviewStartForward"
type="button"
aria-label="Move start forward 100 milliseconds"
>
+0.1s
</button>
</div>
<div class="media-timing-review-fine-control">
<span>End</span>
<button
id="mediaTimingReviewEndBack"
type="button"
aria-label="Move end backward 100 milliseconds"
>
&minus;0.1s
</button>
<button
id="mediaTimingReviewEndForward"
type="button"
aria-label="Move end forward 100 milliseconds"
>
+0.1s
</button>
</div>
</div>
<div
id="mediaTimingReviewStatus"
class="media-timing-review-status"
role="status"
aria-live="polite"
></div>
<div class="media-timing-review-footer">
<div class="media-timing-review-preview-actions">
<button
id="mediaTimingReviewPlay"
class="media-timing-review-play-button"
type="button"
>
<span class="media-timing-review-play-glyph" aria-hidden="true"></span>
<span id="mediaTimingReviewPlayLabel">Play selection</span>
</button>
<button
id="mediaTimingReviewReset"
class="media-timing-review-quiet-button"
type="button"
>
Reset timing
</button>
</div>
<button
id="mediaTimingReviewConfirm"
class="media-timing-review-confirm-button"
type="button"
>
Use this timing
</button>
</div>
<div class="media-timing-review-hints" aria-hidden="true">
<span><kbd>Space</kbd> preview</span>
<span><kbd>&larr;</kbd><kbd>&rarr;</kbd> nudge focused edge</span>
<span><kbd>Enter</kbd> confirm</span>
<span><kbd>Esc</kbd> cancel</span>
</div>
</div>
<div id="mediaTimingReviewCancelStep" class="media-timing-review-cancel-step hidden">
<div class="media-timing-review-cancel-mark" aria-hidden="true">?</div>
<h2>Stop reviewing?</h2>
<p id="mediaTimingReviewCancelMessage">Choose what should happen to this card.</p>
<div class="media-timing-review-cancel-actions">
<button
id="mediaTimingReviewCancelBack"
class="media-timing-review-quiet-button"
type="button"
>
Keep editing
</button>
<button
id="mediaTimingReviewUseOriginal"
class="media-timing-review-original-button"
type="button"
>
Use original timing
</button>
<button
id="mediaTimingReviewSkipMedia"
class="media-timing-review-skip-button"
type="button"
title="Keep this card but do not add audio or an image."
>
Keep without media
</button>
<button
id="mediaTimingReviewDiscard"
class="media-timing-review-discard-button"
type="button"
>
Delete card
</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="characterDictionaryModal" class="modal hidden" aria-hidden="true">
<div class="modal-content character-dictionary-content">
<div class="modal-header">
<div class="modal-title">Character Dictionary Management</div>
<button id="characterDictionaryClose" class="modal-close" type="button">Close</button>
</div>
<div class="modal-body">
<div
class="character-dictionary-tabs"
role="tablist"
aria-label="Character dictionary views"
>
<button
id="characterDictionaryOverrideTab"
class="character-dictionary-tab active"
type="button"
role="tab"
aria-selected="true"
>
Override
</button>
<button
id="characterDictionaryManageTab"
class="character-dictionary-tab"
type="button"
role="tab"
aria-selected="false"
>
Manage
</button>
</div>
<div id="characterDictionarySummary" class="runtime-options-hint"></div>
<div id="characterDictionarySearchPanel">
<div class="character-dictionary-search">
<input
id="characterDictionarySearchInput"
class="character-dictionary-search-input"
type="text"
aria-label="Search character dictionary"
autocomplete="off"
/>
<button
id="characterDictionarySearchButton"
class="character-dictionary-use"
type="button"
>
Search
</button>
</div>
<div id="characterDictionaryCurrent" class="character-dictionary-current"></div>
<ul id="characterDictionaryCandidates" class="character-dictionary-candidates"></ul>
</div>
<div id="characterDictionaryManagerPanel" class="hidden">
<ul
id="characterDictionaryManagedEntries"
class="character-dictionary-candidates character-dictionary-managed-entries"
></ul>
</div>
<div id="characterDictionaryStatus" 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">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="subsyncReferenceLabel" class="subsync-field">
<span>Reference (correct timing, for alass)</span>
<select id="subsyncReferenceSelect"></select>
</label>
<label id="subsyncTargetLabel" class="subsync-field">
<span>Out-of-sync Subtitle (gets retimed)</span>
<select id="subsyncTargetSelect"></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 id="controllerSelectModal" class="modal hidden" aria-hidden="true">
<div class="modal-content runtime-modal-content">
<div class="modal-header">
<div class="modal-title">Controller Configuration</div>
<button id="controllerSelectClose" class="modal-close" type="button">Close</button>
</div>
<div class="modal-body">
<label class="controller-select-field">
<span>Preferred Controller</span>
<select id="controllerSelectPicker"></select>
</label>
<div id="controllerSelectSummary" class="controller-select-summary"></div>
<div id="controllerConfigList" class="controller-config-list"></div>
<div id="controllerSelectStatus" class="runtime-options-status"></div>
<div class="subsync-footer">
<button id="controllerSelectSave" class="kiku-confirm-button" type="button">
Save Controller
</button>
</div>
</div>
</div>
</div>
<div id="controllerDebugModal" class="modal hidden" aria-hidden="true">
<div class="modal-content runtime-modal-content controller-debug-content">
<div
id="controllerDebugToast"
class="controller-debug-toast hidden"
aria-live="polite"
></div>
<div class="modal-header">
<div class="modal-title">Controller Debug</div>
<button id="controllerDebugClose" class="modal-close" type="button">Close</button>
</div>
<div class="modal-body">
<div id="controllerDebugStatus" class="runtime-options-status"></div>
<div id="controllerDebugSummary" class="controller-debug-summary"></div>
<div class="controller-debug-grid">
<div>
<div class="jimaku-section-title">Axes</div>
<pre id="controllerDebugAxes" class="controller-debug-pre"></pre>
</div>
<div>
<div class="jimaku-section-title">Buttons</div>
<pre id="controllerDebugButtons" class="controller-debug-pre"></pre>
</div>
<div class="controller-debug-span">
<div class="controller-debug-section-header">
<div class="jimaku-section-title">Config</div>
<button id="controllerDebugCopy" class="kiku-cancel-button" type="button">
Copy
</button>
</div>
<pre id="controllerDebugButtonIndices" class="controller-debug-pre"></pre>
</div>
</div>
</div>
</div>
</div>
<div id="subtitleSidebarModal" class="modal hidden subtitle-sidebar-modal" aria-hidden="true">
<div id="subtitleSidebarContent" class="modal-content subtitle-sidebar-content">
<div class="modal-header">
<div class="modal-title">Subtitle Sidebar</div>
<button id="subtitleSidebarClose" class="modal-close" type="button">Close</button>
</div>
<div class="modal-body subtitle-sidebar-body">
<div id="subtitleSidebarStatus" class="runtime-options-status"></div>
<ul id="subtitleSidebarList" class="subtitle-sidebar-list"></ul>
</div>
</div>
</div>
<div id="sessionHelpModal" class="modal hidden" aria-hidden="true">
<div class="modal-content session-help-content">
<div class="modal-header">
<div class="modal-title">Session Help</div>
<button id="sessionHelpClose" class="modal-close" type="button">Close</button>
</div>
<div class="modal-body">
<div id="sessionHelpShortcut" class="session-help-shortcut"></div>
<div id="sessionHelpWarning" class="session-help-warning"></div>
<div id="sessionHelpStatus" class="session-help-status"></div>
<input
id="sessionHelpFilter"
class="session-help-filter"
type="text"
placeholder="Type / to search shortcuts"
autocomplete="off"
spellcheck="false"
/>
<div id="sessionHelpContent" class="session-help-content-list"></div>
</div>
</div>
</div>
<div id="changelogModal" class="modal hidden" aria-hidden="true">
<div class="modal-content changelog-content">
<div class="modal-header">
<div class="modal-title">Changelog</div>
<div class="changelog-header-actions">
<button id="changelogRefresh" class="changelog-refresh" type="button">Refresh</button>
<button id="changelogClose" class="modal-close" type="button">Close</button>
</div>
</div>
<div class="modal-body">
<div class="changelog-meta">
<span id="changelogInstalled" class="changelog-installed"></span>
<span id="changelogSource" class="changelog-source"></span>
</div>
<div id="changelogWarning" class="changelog-warning"></div>
<div id="changelogStatus" class="changelog-status"></div>
<div id="changelogList" class="changelog-list"></div>
</div>
</div>
</div>
<div id="playlistBrowserModal" class="modal hidden" aria-hidden="true">
<div class="modal-content playlist-browser-content">
<div class="modal-header">
<div class="modal-title">Playlist Browser</div>
<button id="playlistBrowserClose" class="modal-close" type="button">Close</button>
</div>
<div class="modal-body playlist-browser-body">
<div id="playlistBrowserTitle" class="playlist-browser-title"></div>
<div id="playlistBrowserStatus" class="playlist-browser-status"></div>
<div class="playlist-browser-grid">
<div class="playlist-browser-pane">
<div class="playlist-browser-pane-title">Directory</div>
<ul id="playlistBrowserDirectoryList" class="playlist-browser-list"></ul>
</div>
<div class="playlist-browser-pane">
<div class="playlist-browser-pane-title">Playlist</div>
<ul id="playlistBrowserPlaylistList" class="playlist-browser-list"></ul>
</div>
</div>
<div class="playlist-browser-footer">
<span>Tab switch pane</span>
<span>Enter activate</span>
<span>Delete remove</span>
<span>Ctrl/Cmd+Arrows reorder</span>
<span>Esc close</span>
</div>
</div>
</div>
</div>
</div>
<script type="module" src="renderer.js"></script>
</body>
</html>