@joewinke/jatui 0.38.4 → 0.38.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -1
- package/dist/components/VideoPlayer.svelte +167 -8
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -119,7 +119,7 @@ Item-by-item review surfaces (MOS review-ux, design mos-z8vgx). One `VerdictOpti
|
|
|
119
119
|
| `ThumbnailGrid` | Generic thumbnail strip (chimaro port) — image tiles with text-tile fallback, +N overflow counter, remove/add affordances. Decoupled from any media library: pass `{id, url, alt}` items, or a `cell` snippet for full control of tile contents. `removeMode` — `'hover'` (default) reveals the × on hover/focus and keeps it visible on coarse (touch) pointers (ContextMenuTrigger pattern); `'always'` pins it for touch-first flows. `gridCols` (e.g. `'grid-cols-2 sm:grid-cols-4'`) switches the strip to a responsive CSS grid with column-filling tiles. | `items: ThumbnailItem[]`, `max`, `size` (`xs\|sm\|md\|lg`), `removable`+`onRemove`, `removeMode` (`hover\|always`), `addable`+`onAdd`, `onSelect`, `gridCols`, `class`, `cell` snippet |
|
|
120
120
|
| `MediaWorkbench` | Full-featured image editor modal — upload, AI generate, AI edit, background removal, annotation canvas. **Preferred over `SmartImageEditor` for new image fields.** Two-column modal: preview left, 4 action tabs right (Generate, Edit, Remove BG, Annotate). Save uploads blob to `uploadEndpoint` or emits `onComplete`. Requires proxy endpoints from jst-23w1l.2. | `value` (bindable `string \| null`), `uploadEndpoint?` (default `/api/media`), `onComplete?`, `accept?` |
|
|
121
121
|
| `MediaLightbox` | Full-screen media preview — z-9040 backdrop, arrow-key paging when given an array, `+/-/0` zoom, download action, Escape/backdrop close. **Use this for the general "just show it big" case** — replaces hand-rolled `fixed inset-0 z-[9040]` modals. For annotated / markup-overlaid image previews, use `ImageLightbox` instead. | `src?`, `filename?`, `kind?: 'image' \| 'video'`, `downloadUrl?`, `items?: MediaItem[]`, `initialIndex?`, `onClose?`, `zIndex?` |
|
|
122
|
-
| `VideoPlayer` | **The default video surface — reach for this over a bare `<video controls>`.** One timeline band carrying a waveform-envelope scrubber, chapter flags (top edge), bookmark diamonds (bottom edge), diarized speaker colouring and scroll-to-zoom, plus an optional transcript pane (click to seek, active line auto-scroll, solo/skip per speaker, `Shift+T` toggle) and a "Talk time" legend (2+ speakers only; one voice gets no legend and no per-row name). **`kind="audio"` renders no stage**, and **`transport="band"`** (the audio default) drops the native `<video controls>` bar for a transport row on the band (play/pause, `now / total`, speed, mute + volume, fullscreen for video), so the band is the ONE timeline. **Every intelligence prop is optional** — a bare mp4 still gets the scrubber, hover-time card, wheel volume and the full keyboard map (Space/K, arrows/J/L, vol/mute, −/= speed + Backspace reset, </> frame step, [/] chapters, B bookmark, t theater, ? help overlay), and each pane lights up as its data arrives (nothing shifts layout when diarization lands late). Wheel means volume over the video (shift = seek, alt = speed) and zoom over the band. Internals live in `src/lib/video/` (band/keys/rate/deepLink/settings + contracts for audio/night/presence — see that README; gap audit vs the extension: `VIDEO-PLAYER-GAP-AUDIT.md`). | `src`, `kind?` (`'video' \| 'audio'`), `transport?` (`'native' \| 'band'`), `chapters?`, `speakers?`, `transcript?`, `bookmarks?`, `waveform?`, `talkTime?`, `startAt?`, `poster?`, `captionsSrc?`, `crossorigin?` (`'anonymous'` — what frame-reading features need), `syncUrlTime?` (`?t=` deep-link, opt-in), `transcriptPosition?` (`'below' \| 'side'`), `transcriptMaxHeight?` (pane bound — default `min(40vh, 28rem)` below, the player's height beside; the active-line follow scrolls only the pane; only a gesture that actually moves the PANE pauses it — a wheel that scrolls the page doesn't — behind a "Resume following" pill, and it resumes by itself on seek / line or chapter click / play after pause / `followResumeMs` (6000) idle while playing; a hovered chapter fills its whole span in the theme accent with full-height edges), `followResumeMs?`, `theater?` (bindable — theater mode, NOT fullscreen: control-bar button + `t`, the HOST lays the page out full-width; a side transcript moves below; remembered per viewer as the `player.theater` setting via `settingsStore`, else guarded localStorage), `theaterButton?` (true), `onTheaterChange?`, `onTimeUpdate?`, `onAddBookmark?`, `onBookmarkContext?` |
|
|
122
|
+
| `VideoPlayer` | **The default video surface — reach for this over a bare `<video controls>`.** One timeline band carrying a waveform-envelope scrubber, chapter flags (top edge), bookmark diamonds (bottom edge), diarized speaker colouring and scroll-to-zoom, plus an optional transcript pane (click to seek, active line auto-scroll, solo/skip per speaker, `Shift+T` toggle) and a "Talk time" legend (2+ speakers only; one voice gets no legend and no per-row name). **`kind="audio"` renders no stage**, and **`transport="band"`** (the audio default) drops the native `<video controls>` bar for a transport row on the band (play/pause, `now / total`, speed, mute + volume, fullscreen for video), so the band is the ONE timeline. **Every intelligence prop is optional** — a bare mp4 still gets the scrubber, hover-time card, wheel volume and the full keyboard map (Space/K, arrows/J/L, vol/mute, −/= speed + Backspace reset, </> frame step, [/] chapters, B bookmark, t theater, ? help overlay), and each pane lights up as its data arrives (nothing shifts layout when diarization lands late). Wheel means volume over the video (shift = seek, alt = speed; a wheel/keyboard volume change also flashes a clear volume OSD — glyph, level bar and %, "Muted" at 0, opacity-only fade) and zoom over the band. Internals live in `src/lib/video/` (band/keys/rate/deepLink/settings + contracts for audio/night/presence — see that README; gap audit vs the extension: `VIDEO-PLAYER-GAP-AUDIT.md`). | `src`, `kind?` (`'video' \| 'audio'`), `transport?` (`'native' \| 'band'`), `chapters?`, `speakers?`, `transcript?`, `bookmarks?`, `waveform?`, `talkTime?`, `startAt?`, `poster?`, `captionsSrc?`, `crossorigin?` (`'anonymous'` — what frame-reading features need), `syncUrlTime?` (`?t=` deep-link, opt-in), `transcriptPosition?` (`'below' \| 'side'`), `transcriptMaxHeight?` (pane bound — default `min(40vh, 28rem)` below, the player's height beside; the active-line follow scrolls only the pane; only a gesture that actually moves the PANE pauses it — a wheel that scrolls the page doesn't — behind a "Resume following" pill, and it resumes by itself on seek / line or chapter click / play after pause / `followResumeMs` (6000) idle while playing; a hovered chapter fills its whole span in the theme accent with full-height edges), `followResumeMs?`, `theater?` (bindable — theater mode, NOT fullscreen: control-bar button + `t`, the HOST lays the page out full-width; a side transcript moves below; remembered per viewer as the `player.theater` setting via `settingsStore`, else guarded localStorage), `theaterButton?` (true), `onTheaterChange?`, `onTimeUpdate?`, `onAddBookmark?`, `onBookmarkContext?` |
|
|
123
123
|
| `MediaCapture` | One-tap audio/selfie-video/screen(+mic) recording — a host-agnostic PRIMITIVE. Owns permission requests, MIME negotiation, the record/stop state machine, live self-preview, and mixes screen+mic audio into one track via AudioContext. Never uploads anything — hands the finished clip to `onCapture` as a Blob and the host decides what to do with it. `onCapture` may be async; the component shows "processing" until it resolves. | `onCapture: (result: MediaCaptureResult) => void \| Promise<void>`, `modes?` (default all three), `maxDurationMs?` (default 120000), `label?`, `hint?`, `compact?`, `disabled?` |
|
|
124
124
|
| `MultimodalCaptureField` | "Upload a file, or record yourself going over it" as one input surface — composes `FileDropzone` (capped to one file) with `MediaCapture`. File and recording are independent (either, both, or neither); neither half uploads anything, same "validate and hand back" contract as `FileDropzone`. | `onFileSelected?`, `onFileRemoved?`, `onCapture?`, `onRecordingRemoved?`, `accept?`, `maxFileSize?`, `modes?`, `maxDurationMs?`, `file?`/`fileUrl?`/`fileName?`, `recordingSummary?` |
|
|
125
125
|
| `ListenControl` | "Listen to this page" — a compact pill that expands into a TWO-ROW playback bar over the host's PER-SENTENCE audio (TTS): row 1 = play/pause · chapter chip + current sentence (full remaining width) · ONE settings pill ("1.25× · Sarah" → speed 0.75–2× + voice menu) · close; row 2 = prev/next sentence and −15s/+15s · a document-wide progress bar with chapter ticks (a hatched tail marks the not-yet-synthesized part), time, close. The host supplies an async `source` (sentences/paragraphs with ids → audio URLs, streamed page by page so playback starts on the first chunk while the rest synthesizes; pages merge by id so late URLs fill in) and gets `onSegmentChange(id)` at every boundary for highlight + scroll-follow. Media Session (lock-screen/headset controls, one document-wide position), keyboard scoped to the player (Space/K, ←/→ sentences, Shift+←/→ ±15s, [ / ] speed, Escape), speed+voice persisted in localStorage, `bind:api` for hosts wiring their own global keys, reduced-motion aware, token-only theming, never overflows its host (border-box, `max-width: 100%`, `min-width: 0` chain), fixed sticky mini-bar (same two rows) on narrow screens. Internals in `src/lib/listen/` (one timeline across per-sentence files — `buildTimeline` offsets; see `video/` for the doctrine). Demo synthesizes real WAVs locally. | `source`, `voices?`, `voiceId?` (bindable), `chapters?`, `title?`, `triggerLabel?`, `open?` (bindable), `preload?`, `estimateSec?`, `storageKey?`, `onSegmentChange?`, `onOpenChange?`, `api?` (bindable), `class?`, `children?` (custom trigger) |
|
|
@@ -809,6 +809,7 @@
|
|
|
809
809
|
if (!v) return;
|
|
810
810
|
v.muted = !v.muted;
|
|
811
811
|
if (!v.muted && v.volume === 0) v.volume = 0.5;
|
|
812
|
+
showVolumeOsd(v.volume, v.muted);
|
|
812
813
|
}
|
|
813
814
|
function onVolumeInput(e: Event) {
|
|
814
815
|
const v = videoEl;
|
|
@@ -816,6 +817,7 @@
|
|
|
816
817
|
const next = Number((e.currentTarget as HTMLInputElement).value);
|
|
817
818
|
v.volume = Math.min(1, Math.max(0, next));
|
|
818
819
|
v.muted = next === 0;
|
|
820
|
+
showVolumeOsd(v.volume, v.muted);
|
|
819
821
|
}
|
|
820
822
|
function toggleFullscreen() {
|
|
821
823
|
if (typeof document === 'undefined') return;
|
|
@@ -858,6 +860,16 @@
|
|
|
858
860
|
let osd = $state(''); // transient wheel/keyboard readout (volume / seek / speed)
|
|
859
861
|
let osdOn = $state(false);
|
|
860
862
|
|
|
863
|
+
// ── Volume OSD (jatui-pu698) ────────────────────────────────────────────
|
|
864
|
+
// Wheel/keyboard volume feedback: a speaker glyph + horizontal level bar
|
|
865
|
+
// + % value (or "Muted" at 0), floated over the stage. Distinct from the
|
|
866
|
+
// tiny corner readout: a volume change is a physical gesture that deserves
|
|
867
|
+
// to be SEEN, not just a number blink.
|
|
868
|
+
let volOsdOn = $state(false);
|
|
869
|
+
let volOsdVolume = $state(0);
|
|
870
|
+
let volOsdMuted = $state(false);
|
|
871
|
+
let volOsdTimer: ReturnType<typeof setTimeout> | null = null;
|
|
872
|
+
|
|
861
873
|
// ── Speaker filter state ────────────────────────────────────────────────
|
|
862
874
|
let soloKey = $state(ALL_KEY);
|
|
863
875
|
let mutedKeys = $state(new Set<string>());
|
|
@@ -1382,6 +1394,24 @@
|
|
|
1382
1394
|
zoomTimer = setTimeout(() => (zoomLabelOn = false), 900);
|
|
1383
1395
|
}
|
|
1384
1396
|
|
|
1397
|
+
// ── Volume OSD (jatui-pu698) ────────────────────────────────────────────
|
|
1398
|
+
// Big, legible volume indicator: speaker glyph + level bar + % ("Muted" at
|
|
1399
|
+
// 0). Replaces the tiny corner badge for wheel/keyboard volume gestures.
|
|
1400
|
+
// Shares `osdTimer` with the plain OSD so only ONE readout floats at a
|
|
1401
|
+
// time, and any pending plain flash is superseded (not queued behind).
|
|
1402
|
+
function showVolumeOsd(volume: number, muted: boolean) {
|
|
1403
|
+
if (volOsdTimer) clearTimeout(volOsdTimer);
|
|
1404
|
+
if (osdTimer) {
|
|
1405
|
+
clearTimeout(osdTimer);
|
|
1406
|
+
osdTimer = null;
|
|
1407
|
+
}
|
|
1408
|
+
osdOn = false; // only one readout at a time
|
|
1409
|
+
volOsdVolume = volume;
|
|
1410
|
+
volOsdMuted = muted;
|
|
1411
|
+
volOsdOn = true;
|
|
1412
|
+
volOsdTimer = setTimeout(() => (volOsdOn = false), 800);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1385
1415
|
function flashOsd(text: string, ms = 900) {
|
|
1386
1416
|
if (osdTimer) clearTimeout(osdTimer);
|
|
1387
1417
|
if (!text || ms <= 0) {
|
|
@@ -2787,7 +2817,7 @@
|
|
|
2787
2817
|
const next = Math.min(1, Math.max(0, videoEl.volume - Math.sign(e.deltaY) * 0.05));
|
|
2788
2818
|
videoEl.volume = next;
|
|
2789
2819
|
if (next > 0) videoEl.muted = false;
|
|
2790
|
-
|
|
2820
|
+
showVolumeOsd(next, next === 0);
|
|
2791
2821
|
}
|
|
2792
2822
|
|
|
2793
2823
|
/**
|
|
@@ -2853,15 +2883,15 @@
|
|
|
2853
2883
|
case 'ArrowUp':
|
|
2854
2884
|
v.volume = Math.min(1, v.volume + 0.05);
|
|
2855
2885
|
if (v.volume > 0) v.muted = false;
|
|
2856
|
-
|
|
2886
|
+
showVolumeOsd(v.volume, v.muted);
|
|
2857
2887
|
break;
|
|
2858
2888
|
case 'ArrowDown':
|
|
2859
2889
|
v.volume = Math.max(0, v.volume - 0.05);
|
|
2860
|
-
|
|
2890
|
+
showVolumeOsd(v.volume, v.muted);
|
|
2861
2891
|
break;
|
|
2862
2892
|
case 'm':
|
|
2863
2893
|
v.muted = !v.muted;
|
|
2864
|
-
|
|
2894
|
+
showVolumeOsd(v.volume, v.muted);
|
|
2865
2895
|
break;
|
|
2866
2896
|
case '-':
|
|
2867
2897
|
case '_':
|
|
@@ -3135,6 +3165,7 @@
|
|
|
3135
3165
|
class:vp-rail-side={railOn}
|
|
3136
3166
|
class:vp-one-axis={nativeBarHidden}
|
|
3137
3167
|
class:vp-band-transport={bandTransport}
|
|
3168
|
+
class:vp-vol-flash={volOsdOn}
|
|
3138
3169
|
class:vp-audio={isAudio}
|
|
3139
3170
|
class:vp-night-capable={nightPresent}
|
|
3140
3171
|
class:vp-night-surround={surroundMix > 0.001}
|
|
@@ -3255,10 +3286,41 @@
|
|
|
3255
3286
|
<div class="vp-wind-veil" aria-hidden="true"></div>
|
|
3256
3287
|
{/if}
|
|
3257
3288
|
<div class="vp-osd" class:on={osdOn} aria-hidden="true">{osd}</div>
|
|
3258
|
-
|
|
3259
|
-
|
|
3289
|
+
<!-- Volume OSD (jatui-pu698): speaker glyph + level bar + %, centred
|
|
3290
|
+
near the top of the stage so a wheel/keyboard volume gesture is
|
|
3291
|
+
SEEN, not just a corner number blink. aria-hidden: the volume
|
|
3292
|
+
slider itself already reports the value to AT. -->
|
|
3293
|
+
<div class="vp-volume-osd" class:on={volOsdOn} aria-hidden="true">
|
|
3294
|
+
<svg
|
|
3295
|
+
class="vp-vol-glyph"
|
|
3296
|
+
viewBox="0 0 24 24"
|
|
3297
|
+
width="24"
|
|
3298
|
+
height="24"
|
|
3299
|
+
fill="none"
|
|
3300
|
+
stroke="currentColor"
|
|
3301
|
+
stroke-width="1.8"
|
|
3302
|
+
stroke-linecap="round"
|
|
3303
|
+
stroke-linejoin="round"
|
|
3304
|
+
>
|
|
3305
|
+
<path d="M4 9.5h3.5L12 5.5v13l-4.5-4H4z" fill="currentColor" stroke="none" />
|
|
3306
|
+
{#if volOsdMuted || volOsdVolume === 0}
|
|
3307
|
+
<path d="M16 9.5l5 5M21 9.5l-5 5" />
|
|
3308
|
+
{:else}
|
|
3309
|
+
<path d="M15.5 9a4 4 0 0 1 0 6" />
|
|
3310
|
+
{#if volOsdVolume > 0.5}<path d="M18 6.5a7.5 7.5 0 0 1 0 11" />{/if}
|
|
3311
|
+
{/if}
|
|
3312
|
+
</svg>
|
|
3313
|
+
<span class="vp-vol-bar">
|
|
3314
|
+
<span class="vp-vol-fill" style="width: {Math.round(volOsdVolume * 100)}%"></span>
|
|
3315
|
+
</span>
|
|
3316
|
+
<span class="vp-vol-text">
|
|
3317
|
+
{volOsdMuted || volOsdVolume === 0 ? 'Muted' : `${Math.round(volOsdVolume * 100)}%`}
|
|
3318
|
+
</span>
|
|
3319
|
+
</div>
|
|
3320
|
+
</div>
|
|
3321
|
+
</div>
|
|
3260
3322
|
|
|
3261
|
-
|
|
3323
|
+
<!-- The band: one time axis for the envelope, chapters, speakers and
|
|
3262
3324
|
bookmarks. Its height is reserved from the first frame, so data
|
|
3263
3325
|
arriving late fades into already-owned space. -->
|
|
3264
3326
|
<div
|
|
@@ -3349,7 +3411,34 @@
|
|
|
3349
3411
|
{#if isAudio}
|
|
3350
3412
|
<!-- No stage to float the readout over: it rides the band. -->
|
|
3351
3413
|
<div class="vp-osd vp-osd-band" class:on={osdOn} aria-hidden="true">{osd}</div>
|
|
3352
|
-
|
|
3414
|
+
<div class="vp-volume-osd vp-volume-osd-band" class:on={volOsdOn} aria-hidden="true">
|
|
3415
|
+
<svg
|
|
3416
|
+
class="vp-vol-glyph"
|
|
3417
|
+
viewBox="0 0 24 24"
|
|
3418
|
+
width="24"
|
|
3419
|
+
height="24"
|
|
3420
|
+
fill="none"
|
|
3421
|
+
stroke="currentColor"
|
|
3422
|
+
stroke-width="1.8"
|
|
3423
|
+
stroke-linecap="round"
|
|
3424
|
+
stroke-linejoin="round"
|
|
3425
|
+
>
|
|
3426
|
+
<path d="M4 9.5h3.5L12 5.5v13l-4.5-4H4z" fill="currentColor" stroke="none" />
|
|
3427
|
+
{#if volOsdMuted || volOsdVolume === 0}
|
|
3428
|
+
<path d="M16 9.5l5 5M21 9.5l-5 5" />
|
|
3429
|
+
{:else}
|
|
3430
|
+
<path d="M15.5 9a4 4 0 0 1 0 6" />
|
|
3431
|
+
{#if volOsdVolume > 0.5}<path d="M18 6.5a7.5 7.5 0 0 1 0 11" />{/if}
|
|
3432
|
+
{/if}
|
|
3433
|
+
</svg>
|
|
3434
|
+
<span class="vp-vol-bar">
|
|
3435
|
+
<span class="vp-vol-fill" style="width: {Math.round(volOsdVolume * 100)}%"></span>
|
|
3436
|
+
</span>
|
|
3437
|
+
<span class="vp-vol-text">
|
|
3438
|
+
{volOsdMuted || volOsdVolume === 0 ? 'Muted' : `${Math.round(volOsdVolume * 100)}%`}
|
|
3439
|
+
</span>
|
|
3440
|
+
</div>
|
|
3441
|
+
{/if}
|
|
3353
3442
|
|
|
3354
3443
|
{#if hover}
|
|
3355
3444
|
<div
|
|
@@ -4146,6 +4235,64 @@
|
|
|
4146
4235
|
opacity: 1;
|
|
4147
4236
|
}
|
|
4148
4237
|
|
|
4238
|
+
/* ── Volume OSD (jatui-pu698) ───────────────────────────────────────── */
|
|
4239
|
+
/* Wheel/keyboard volume feedback: speaker glyph + level bar + %, centred
|
|
4240
|
+
near the top of the stage so a gesture is SEEN, not just a corner number
|
|
4241
|
+
blink. Token surface with backdrop so it is legible on any frame. The
|
|
4242
|
+
flash is purely opacity — no slide — so it is reduced-motion safe. */
|
|
4243
|
+
.vp-volume-osd {
|
|
4244
|
+
position: absolute;
|
|
4245
|
+
top: 10%;
|
|
4246
|
+
left: 50%;
|
|
4247
|
+
transform: translateX(-50%);
|
|
4248
|
+
display: flex;
|
|
4249
|
+
align-items: center;
|
|
4250
|
+
gap: 10px;
|
|
4251
|
+
padding: 10px 16px;
|
|
4252
|
+
border-radius: 999px;
|
|
4253
|
+
background: color-mix(in oklch, var(--color-base-100) 85%, transparent);
|
|
4254
|
+
backdrop-filter: blur(8px);
|
|
4255
|
+
color: var(--color-base-content);
|
|
4256
|
+
font-size: 0.9rem;
|
|
4257
|
+
font-weight: 600;
|
|
4258
|
+
font-variant-numeric: tabular-nums;
|
|
4259
|
+
pointer-events: none;
|
|
4260
|
+
opacity: 0;
|
|
4261
|
+
transition: opacity var(--motion-enter, 200ms) var(--motion-glide, ease-out);
|
|
4262
|
+
z-index: 10;
|
|
4263
|
+
box-shadow: 0 4px 16px color-mix(in oklch, black 20%, transparent);
|
|
4264
|
+
}
|
|
4265
|
+
|
|
4266
|
+
.vp-volume-osd.on {
|
|
4267
|
+
opacity: 1;
|
|
4268
|
+
}
|
|
4269
|
+
|
|
4270
|
+
.vp-vol-glyph {
|
|
4271
|
+
flex: none;
|
|
4272
|
+
opacity: 0.9;
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
.vp-vol-bar {
|
|
4276
|
+
display: block;
|
|
4277
|
+
width: 80px;
|
|
4278
|
+
height: 4px;
|
|
4279
|
+
border-radius: 999px;
|
|
4280
|
+
background: color-mix(in oklch, var(--color-base-content) 20%, transparent);
|
|
4281
|
+
overflow: hidden;
|
|
4282
|
+
}
|
|
4283
|
+
|
|
4284
|
+
.vp-vol-fill {
|
|
4285
|
+
display: block;
|
|
4286
|
+
height: 100%;
|
|
4287
|
+
background: var(--color-primary);
|
|
4288
|
+
border-radius: 999px;
|
|
4289
|
+
transition: width 150ms ease-out;
|
|
4290
|
+
}
|
|
4291
|
+
|
|
4292
|
+
.vp-vol-text {
|
|
4293
|
+
min-width: 3.5rem;
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4149
4296
|
/* The band owns its height from the first frame so a waveform or speaker
|
|
4150
4297
|
turns arriving minutes in fade into space that is already theirs. */
|
|
4151
4298
|
.vp-band {
|
|
@@ -4162,6 +4309,17 @@
|
|
|
4162
4309
|
opacity: 1;
|
|
4163
4310
|
}
|
|
4164
4311
|
|
|
4312
|
+
/* Volume flash (jatui-pu698): reveal the transport and its volume slider
|
|
4313
|
+
on a wheel/keyboard volume change. */
|
|
4314
|
+
.vp-vol-flash .vp-transport {
|
|
4315
|
+
opacity: 1;
|
|
4316
|
+
visibility: visible;
|
|
4317
|
+
}
|
|
4318
|
+
|
|
4319
|
+
.vp-vol-flash .vp-volume {
|
|
4320
|
+
opacity: 1;
|
|
4321
|
+
}
|
|
4322
|
+
|
|
4165
4323
|
/* Audio: no picture, so no stage at all. The element still plays. */
|
|
4166
4324
|
.vp-stage-audio {
|
|
4167
4325
|
display: none;
|
|
@@ -4828,6 +4986,7 @@
|
|
|
4828
4986
|
@media (prefers-reduced-motion: reduce) {
|
|
4829
4987
|
.vp-band,
|
|
4830
4988
|
.vp-osd,
|
|
4989
|
+
.vp-volume-osd,
|
|
4831
4990
|
.vp-zoom,
|
|
4832
4991
|
.vp-lane-span,
|
|
4833
4992
|
.vp-chip,
|