rnotes-online/browser-extension/voice.html

415 lines
9.6 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 360px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0a0a0a;
color: #e5e5e5;
font-size: 13px;
overflow: hidden;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 14px;
background: #171717;
border-bottom: 1px solid #262626;
-webkit-app-region: drag;
}
.header .brand {
font-weight: 700;
font-size: 14px;
color: #ef4444;
}
.header .brand-sub {
color: #a3a3a3;
font-weight: 400;
font-size: 12px;
}
.header .close-btn {
-webkit-app-region: no-drag;
background: none;
border: none;
color: #737373;
cursor: pointer;
font-size: 18px;
padding: 2px 6px;
border-radius: 4px;
}
.header .close-btn:hover {
color: #e5e5e5;
background: #262626;
}
.auth-warning {
padding: 10px 14px;
background: #451a03;
border-bottom: 1px solid #78350f;
text-align: center;
font-size: 12px;
color: #fbbf24;
}
.recorder {
padding: 20px 14px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
/* Record button */
.rec-btn {
width: 72px;
height: 72px;
border-radius: 50%;
border: 3px solid #404040;
background: #171717;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
position: relative;
}
.rec-btn:hover {
border-color: #ef4444;
}
.rec-btn .inner {
width: 32px;
height: 32px;
background: #ef4444;
border-radius: 50%;
transition: all 0.2s;
}
.rec-btn.recording {
border-color: #ef4444;
}
.rec-btn.recording .inner {
width: 24px;
height: 24px;
border-radius: 4px;
background: #ef4444;
}
.rec-btn.recording::after {
content: '';
position: absolute;
inset: -6px;
border-radius: 50%;
border: 2px solid rgba(239, 68, 68, 0.3);
animation: pulse-ring 1.5s infinite;
}
@keyframes pulse-ring {
0% { transform: scale(1); opacity: 1; }
100% { transform: scale(1.15); opacity: 0; }
}
.timer {
font-size: 28px;
font-family: 'SF Mono', 'Consolas', 'Courier New', monospace;
font-weight: 600;
color: #e5e5e5;
letter-spacing: 2px;
}
.timer.recording {
color: #ef4444;
}
.status-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1.5px;
font-weight: 600;
}
.status-label.idle { color: #737373; }
.status-label.recording { color: #ef4444; }
.status-label.processing { color: #f59e0b; }
.status-label.done { color: #4ade80; }
/* Transcript area */
.transcript-area {
width: 100%;
padding: 0 14px 12px;
display: none;
}
.transcript-area.visible {
display: block;
}
.transcript-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1px;
color: #737373;
margin-bottom: 6px;
font-weight: 600;
}
.transcript-text {
background: #171717;
border: 1px solid #262626;
border-radius: 6px;
padding: 10px 12px;
font-size: 13px;
line-height: 1.5;
color: #d4d4d4;
max-height: 120px;
overflow-y: auto;
min-height: 40px;
white-space: pre-wrap;
}
.transcript-text.editable {
outline: none;
border-color: #404040;
cursor: text;
}
.transcript-text.editable:focus {
border-color: #f59e0b;
}
.transcript-text .placeholder {
color: #525252;
font-style: italic;
}
.transcript-text .final-text {
color: #d4d4d4;
}
.transcript-text .interim-text {
color: #737373;
font-style: italic;
}
/* Controls row */
.controls {
width: 100%;
padding: 0 14px 10px;
}
.controls select {
width: 100%;
padding: 6px 8px;
background: #171717;
border: 1px solid #404040;
border-radius: 4px;
color: #e5e5e5;
font-size: 12px;
outline: none;
}
.controls select:focus {
border-color: #f59e0b;
}
.controls label {
display: block;
font-size: 10px;
color: #737373;
margin-bottom: 3px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Action buttons */
.actions {
width: 100%;
padding: 0 14px 12px;
display: flex;
gap: 8px;
}
.actions button {
flex: 1;
padding: 8px 12px;
border: none;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.15s;
}
.actions button:hover:not(:disabled) { opacity: 0.85; }
.actions button:disabled { opacity: 0.35; cursor: not-allowed; }
.btn-save {
background: #f59e0b;
color: #0a0a0a;
}
.btn-discard {
background: #262626;
color: #a3a3a3;
border: 1px solid #404040;
}
.btn-copy {
background: #172554;
color: #93c5fd;
border: 1px solid #1e40af;
}
/* Status bar */
.status-bar {
padding: 8px 14px;
border-top: 1px solid #262626;
font-size: 11px;
color: #525252;
text-align: center;
display: none;
}
.status-bar.visible {
display: block;
}
.status-bar.success { color: #4ade80; background: #052e16; border-top-color: #166534; }
.status-bar.error { color: #fca5a5; background: #450a0a; border-top-color: #991b1b; }
.status-bar.loading { color: #93c5fd; background: #172554; border-top-color: #1e40af; }
/* Live indicator */
.live-indicator {
display: none;
align-items: center;
gap: 5px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #4ade80;
}
.live-indicator.visible {
display: flex;
}
.live-indicator .dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #4ade80;
animation: pulse-dot 1s infinite;
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* Progress bar (for model download) */
.progress-area {
width: 100%;
padding: 0 14px 8px;
display: none;
}
.progress-area.visible {
display: block;
}
.progress-label {
font-size: 11px;
color: #a3a3a3;
margin-bottom: 4px;
}
.progress-bar {
width: 100%;
height: 6px;
background: #262626;
border-radius: 3px;
overflow: hidden;
}
.progress-bar .fill {
height: 100%;
background: #f59e0b;
border-radius: 3px;
transition: width 0.3s;
width: 0%;
}
/* Audio preview */
.audio-preview {
width: 100%;
padding: 0 14px 8px;
display: none;
}
.audio-preview.visible {
display: block;
}
.audio-preview audio {
width: 100%;
height: 32px;
}
/* Keyboard hint */
.kbd-hint {
padding: 4px 14px 8px;
text-align: center;
font-size: 10px;
color: #404040;
}
.kbd-hint kbd {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 3px;
padding: 1px 5px;
font-family: inherit;
font-size: 10px;
}
</style>
</head>
<body>
<div class="header">
<span>
<span class="brand">rVoice</span>
<span class="brand-sub">voice notes</span>
</span>
<button class="close-btn" id="closeBtn" title="Close">&times;</button>
</div>
<div id="authWarning" class="auth-warning" style="display: none;">
Sign in via rNotes Clipper settings first.
</div>
<div class="recorder">
<div class="status-label idle" id="statusLabel">Ready</div>
<button class="rec-btn" id="recBtn" title="Start recording">
<div class="inner"></div>
</button>
<div class="timer" id="timer">00:00</div>
<div class="live-indicator" id="liveIndicator">
<span class="dot"></span>
Live transcribe
</div>
</div>
<div class="progress-area" id="progressArea">
<div class="progress-label" id="progressLabel">Loading model...</div>
<div class="progress-bar"><div class="fill" id="progressFill"></div></div>
</div>
<div class="audio-preview" id="audioPreview">
<audio controls id="audioPlayer"></audio>
</div>
<div class="transcript-area" id="transcriptArea">
<div class="transcript-label">Transcript</div>
<div class="transcript-text editable" id="transcriptText" contenteditable="true">
<span class="placeholder">Transcribing...</span>
</div>
</div>
<div class="controls" id="notebookControls">
<label for="notebook">Save to notebook</label>
<select id="notebook">
<option value="">Default notebook</option>
</select>
</div>
<div class="actions" id="postActions" style="display: none;">
<button class="btn-discard" id="discardBtn">Discard</button>
<button class="btn-copy" id="copyBtn" title="Copy transcript">Copy</button>
<button class="btn-save" id="saveBtn">Save to rNotes</button>
</div>
<div class="status-bar" id="statusBar"></div>
<div class="kbd-hint">
<kbd>Space</kbd> to record &middot; <kbd>Esc</kbd> to close &middot; Offline ready
</div>
<script src="parakeet-offline.js" type="module"></script>
<script src="voice.js"></script>
</body>
</html>