diff --git a/src/lib/CaptionEditor.svelte b/src/lib/CaptionEditor.svelte
index 4e7f592..83a73be 100644
--- a/src/lib/CaptionEditor.svelte
+++ b/src/lib/CaptionEditor.svelte
@@ -3,10 +3,12 @@
Two modes:
- "edit": Edit the single caption currently showing on screen
- "full": Full caption list editor with timestamps, text, and speakers
+ Speakers can be added by searching handles, then assigned via dropdown.
-->
@@ -191,6 +254,40 @@
+
+
+
+
+
+
+ {speakerSearching ? '...' : 'add'}
+
+
+ {#if speakerError}
+
{speakerError}
+ {/if}
+ {#if speakers.length > 0}
+
+ {#each speakers as speaker}
+
+ {#if speaker.avatar}
+
+ {/if}
+
{speaker.displayName}
+
setAllSpeaker(speaker.did)} title="Set all captions to this speaker">set all
+
removeSpeaker(speaker.did)} title="Remove speaker">x
+
+ {/each}
+
+ {/if}
+
+
{#if mode === 'edit' && currentCaptionIdx >= 0}
@@ -206,8 +303,13 @@
- speaker (DID)
- updateCaption(currentCaptionIdx, 'speaker', e.currentTarget.value)} />
+ speaker
+ updateCaption(currentCaptionIdx, 'speaker', e.currentTarget.value)}>
+ — none —
+ {#each speakers as s}
+ {s.displayName} (@{s.handle})
+ {/each}
+
{:else}
@@ -219,6 +321,12 @@
-
updateCaption(i, 'end', e.currentTarget.value)} />
+ updateCaption(i, 'speaker', e.currentTarget.value)}>
+
+ {#each speakers as s}
+ {s.displayName}
+ {/each}
+
updateCaption(i, 'text', e.currentTarget.value)} />
deleteCaption(i)} title="Delete">x
@@ -291,6 +399,123 @@
gap: 6px;
}
+ /* Speakers panel */
+ .speakers-panel {
+ border-top: 1.5px solid rgba(10, 24, 43, 0.15);
+ padding-top: 12px;
+ }
+
+ .speakers-header {
+ font-family: 'PicNic', cursive, system-ui;
+ font-size: 1rem;
+ color: #0A182B;
+ margin-bottom: 8px;
+ }
+
+ .speakers-search {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+ }
+
+ .speaker-search-input {
+ font-family: 'Fang', system-ui, sans-serif;
+ font-size: 0.85rem;
+ padding: 6px 10px;
+ border: 2px solid #0A182B;
+ background: #FFDEED;
+ color: #0A182B;
+ flex: 1;
+ outline: none;
+ }
+
+ .speaker-search-input::placeholder {
+ color: #0A182B;
+ opacity: 0.4;
+ }
+
+ .speaker-error {
+ font-family: 'Fang', system-ui, sans-serif;
+ font-size: 0.75rem;
+ color: #FF3992;
+ margin: 4px 0 0;
+ }
+
+ .speakers-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ margin-top: 8px;
+ }
+
+ .speaker-tag {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ background: rgba(10, 24, 43, 0.08);
+ padding: 4px 8px;
+ border-radius: 16px;
+ }
+
+ .speaker-avatar {
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ object-fit: cover;
+ }
+
+ .speaker-name {
+ font-family: 'Fang', system-ui, sans-serif;
+ font-size: 0.8rem;
+ color: #0A182B;
+ }
+
+ .speaker-setall {
+ all: unset;
+ font-family: 'Fang', system-ui, sans-serif;
+ font-size: 0.65rem;
+ color: #3992FF;
+ cursor: pointer;
+ text-decoration: underline;
+ }
+
+ .speaker-setall:hover {
+ color: #0A182B;
+ }
+
+ .speaker-remove {
+ all: unset;
+ font-family: monospace;
+ font-size: 0.75rem;
+ color: #FF3992;
+ cursor: pointer;
+ opacity: 0.6;
+ }
+
+ .speaker-remove:hover { opacity: 1; }
+
+ /* Speaker selects */
+ .speaker-select, .speaker-select-sm {
+ font-family: 'Fang', system-ui, sans-serif;
+ border: 2px solid #0A182B;
+ background: #FFDEED;
+ color: #0A182B;
+ outline: none;
+ }
+
+ .speaker-select {
+ font-size: 0.85rem;
+ padding: 6px 8px;
+ width: 100%;
+ }
+
+ .speaker-select-sm {
+ font-size: 0.7rem;
+ padding: 2px 4px;
+ width: 80px;
+ flex-shrink: 0;
+ }
+
/* Single edit mode */
.single-edit {
display: flex;
@@ -326,7 +551,7 @@
.time-sep { color: #0A182B; opacity: 0.5; }
- .text-input, .speaker-input {
+ .text-input {
font-family: 'Fang', system-ui, sans-serif;
font-size: 0.9rem;
padding: 8px 10px;
diff --git a/static/client-metadata.json b/static/client-metadata.json
index 426f774..53053e1 100644
--- a/static/client-metadata.json
+++ b/static/client-metadata.json
@@ -4,7 +4,7 @@
"client_uri": "https://vods.sky.boo",
"logo_uri": "https://vods.sky.boo/frogicon.png",
"redirect_uris": ["https://vods.sky.boo/oauth/callback"],
- "scope": "atproto repo:sky.boo.vods.watchlist",
+ "scope": "atproto repo:sky.boo.vods.watchlist repo:boo.sky.vods.captions",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",