v4: an uploaded audio file plays from its cover everywhere, and a card's play badge sits in the middle of its thumbnail — a link played from its thumbnail in every view, but an uploaded file only did on a list card: a journal tile opened the dump, an audio file's page showed a round button by its waveform and never its uploaded cover, and an uploaded video's page was a black box until played. The list card's badge sat in the thumbnail's bottom-left corner, where it looked misplaced, and clicking a playing file restarted it. The new usePlayFile hook builds the player's item and the file's artwork (fileArtworkUrl) for the card, the journal tile and the dump's page, and plays, or pauses once the file is the current one. A card's badge, ▶ or ⏸, is now centred, as is the ↗ on a link out; over a placeholder it takes the place of the glyph. An audio file without a cover shows a placeholder tinted from its file name, not a bare 🎵. In the journal, audio counts as having art (hasThumbnail), so it is an image tile, its glyph raised above the title, and a file tile plays and pauses the file. On the dump's page, the cover (96px, the card's badge on it) plays the file beside the shared Waveform, which replaces FilePreview's copy of it; a video takes the server's still as its poster. 674 frontend tests pass, and tsc, eslint, deno fmt, doc:lint and i18n:compile --strict are clean; checked in screenshots in Smooth light at 1280 and 390px, Brutalist dark on the journal at 390px and Kanagawa dark at 390px, playing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-08 22:33:33 +00:00
parent 2c031b0102
commit 4ea192e0b1
11 changed files with 323 additions and 271 deletions

View File

@@ -1064,8 +1064,18 @@
fill: color-mix(in srgb, var(--color-accent) 45%, var(--color-border) 55%);
}
.audio-file-preview--active .audio-player-btn {
background: var(--color-accent-hover);
/* The cover plays the file, as a card's thumbnail does, and wears the same
badge (see "Two targets, one card"). */
.audio-file-preview .rich-content-thumbnail-btn {
width: 96px;
height: 96px;
border-radius: var(--radius-md);
}
.audio-file-preview .rich-content-play-overlay {
width: 34px;
height: 34px;
font-size: var(--text-sm);
}
/* ── Video player ── */
@@ -4045,7 +4055,8 @@ body.has-player .chat-fab {
/* The initial has no intrinsic size, so scale it per surface. */
.dump-card-preview .thumb-placeholder-initials,
.playlist-card-preview .thumb-placeholder-initials {
.playlist-card-preview .thumb-placeholder-initials,
.audio-file-preview .thumb-placeholder-initials {
font-size: var(--text-3xl);
}
@@ -4053,6 +4064,13 @@ body.has-player .chat-fab {
font-size: var(--text-display);
}
/* The title and meta cover the tile's foot, so the glyph is centred in the
space above them, where a one-row tile has room for it. */
.journal-card-image .thumb-placeholder {
box-sizing: border-box;
padding-bottom: 5rem;
}
.rich-content-thumbnail.thumb-placeholder .thumb-placeholder-initials {
font-size: var(--text-display);
}
@@ -4086,16 +4104,23 @@ body.has-player .chat-fab {
The card opens the dump page; a playable (or source-linked) thumbnail does
its own thing. Only one of them may look armed at a time: over the thumb
the card's "open" cue (the accent title) stands down and the thumb's own
dim and badge take over. At rest the thumb carries only a small corner
badge, so it reads as "playable" without claiming the whole card. */
dim and badge take over. At rest the thumb carries only a small badge in
its centre, so it reads as "playable" without claiming the whole card. An
audio file's cover on its dump's page wears the same badge. */
.dump-card-inner:has(.dump-card-preview :is(button, a):hover) .dump-card-title {
color: var(--color-text);
}
.dump-card-preview .rich-content-play-overlay,
/* Over a placeholder, the badge stands in for its glyph. */
:is(.dump-card-preview, .audio-file-preview) .rich-content-thumbnail-btn .thumb-placeholder-initials {
visibility: hidden;
}
:is(.dump-card-preview, .audio-file-preview) .rich-content-play-overlay,
.rich-content-open-overlay {
position: absolute;
inset: auto auto 5px 5px;
inset: 0;
margin: auto;
z-index: 1;
width: 22px;
height: 22px;
@@ -4121,6 +4146,7 @@ body.has-player .chat-fab {
}
.dump-card-preview .rich-content-thumbnail-btn::after,
.audio-file-preview .rich-content-thumbnail-btn::after,
.dump-card-preview .rich-content-compact::after {
content: "";
position: absolute;
@@ -4136,12 +4162,12 @@ body.has-player .chat-fab {
opacity: 0;
}
.dump-card-preview .rich-content-thumbnail-btn:is(:hover, :focus-visible)::after,
:is(.dump-card-preview, .audio-file-preview) .rich-content-thumbnail-btn:is(:hover, :focus-visible)::after,
.dump-card-preview .rich-content-compact:is(:hover, :focus-visible)::after {
opacity: 1;
}
.dump-card-preview .rich-content-thumbnail-btn:is(:hover, :focus-visible) .rich-content-play-overlay,
:is(.dump-card-preview, .audio-file-preview) .rich-content-thumbnail-btn:is(:hover, :focus-visible) .rich-content-play-overlay,
.rich-content-compact:is(:hover, :focus-visible) .rich-content-open-overlay {
transform: scale(1.2);
background: rgba(0, 0, 0, 0.8);
@@ -4149,12 +4175,12 @@ body.has-player .chat-fab {
}
/* The one playing keeps its badge lit, in the accent, showing ⏸. */
.dump-card-preview .rich-content-thumbnail-btn.is-playing .rich-content-play-overlay {
:is(.dump-card-preview, .audio-file-preview) .rich-content-thumbnail-btn.is-playing .rich-content-play-overlay {
background: var(--color-accent);
color: var(--color-on-accent);
}
.dump-card-preview .rich-content-compact-thumbnail {
:is(.dump-card-preview, .audio-file-preview) .rich-content-compact-thumbnail {
width: 100%;
height: 100%;
object-fit: cover;