diff --git a/src/App.css b/src/App.css
index a7eca11b..92399a30 100644
--- a/src/App.css
+++ b/src/App.css
@@ -3663,11 +3663,86 @@ body.has-player .chat-fab {
}
.dump-card-preview .rich-content-compact {
+ position: relative;
width: 100%;
height: 100%;
justify-content: center;
}
+/* ── Two targets, one card ──
+ 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. */
+.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,
+.rich-content-open-overlay {
+ position: absolute;
+ inset: auto auto 5px 5px;
+ z-index: 1;
+ width: 22px;
+ height: 22px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.6);
+ color: #fff;
+ font-size: var(--text-xs);
+ line-height: 1;
+ pointer-events: none;
+ transition:
+ background var(--dur-base) ease,
+ opacity var(--dur-base) ease,
+ transform var(--dur-base) ease;
+}
+
+/* The badge never moves: card hover grows it a touch; hovering the thumb
+ itself grows it a touch more while a soft dim fades in behind it. */
+.dump-card-inner:hover .dump-card-preview .rich-content-play-overlay {
+ transform: scale(1.1);
+}
+
+.dump-card-preview .rich-content-thumbnail-btn::after,
+.dump-card-preview .rich-content-compact::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.2);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity var(--dur-base) ease;
+}
+
+/* A link out has nothing to badge at rest — the ↗ only appears on hover. */
+.rich-content-open-overlay {
+ opacity: 0;
+}
+
+.dump-card-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,
+.rich-content-compact:is(:hover, :focus-visible) .rich-content-open-overlay {
+ transform: scale(1.2);
+ background: rgba(0, 0, 0, 0.8);
+ opacity: 1;
+}
+
+/* The one playing keeps its badge lit, in the accent, showing ⏸. */
+.dump-card-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 {
width: 100%;
height: 100%;
@@ -5421,13 +5496,36 @@ body.has-player .chat-fab {
/* The play affordance is decorative — clicks fall through to the card, which
plays the embed. This keeps the hit target from being covered by the text
- overlay on smaller card sizes. */
+ overlay on smaller card sizes.
+ Here the card *is* the play button and only the title opens the dump. The
+ badge sits in the top corner, clear of the text on every card size, and
+ speaks the list view's language: card hover wakes it a touch, and it
+ settles back while the pointer is on the title or the vote. */
.journal-card--image .rich-content-play-overlay {
pointer-events: none;
+ inset: 0.6rem 0.6rem auto auto;
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ background: rgba(0, 0, 0, 0.6);
+ /* Keeps it findable on black letterbox bars. */
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.3);
+ font-size: var(--text-xs);
+ line-height: 1;
+ transition:
+ background var(--dur-base) ease,
+ transform var(--dur-base) ease;
}
.journal-card--image:hover .rich-content-play-overlay {
- background: rgba(0, 0, 0, 0.5);
+ transform: scale(1.15);
+ background: rgba(0, 0, 0, 0.75);
+}
+
+.journal-card--image:has(.journal-card-title:hover, .journal-card-vote:hover)
+ .rich-content-play-overlay {
+ transform: none;
+ background: rgba(0, 0, 0, 0.6);
}
.journal-card--image .journal-card-overlay {
diff --git a/src/components/FilePreview.tsx b/src/components/FilePreview.tsx
index bc405b94..132ffbd8 100644
--- a/src/components/FilePreview.tsx
+++ b/src/components/FilePreview.tsx
@@ -208,9 +208,12 @@ export default function FilePreview(
e.stopPropagation();
play(filePlayerItem(dump, fileUrl, mime, token));
}}
+ aria-label="Play"
>
- ▶
+
+ ▶
+
);
}
@@ -226,6 +229,7 @@ export default function FilePreview(
e.stopPropagation();
play(filePlayerItem(dump, fileUrl, mime, token));
}}
+ aria-label="Play"
>
{thumbOverride
?
@@ -234,6 +238,9 @@ export default function FilePreview(
{mimeIcon(mime)}
)}
+
+ ▶
+
);
}
diff --git a/src/components/RichContentCard.tsx b/src/components/RichContentCard.tsx
index bd3229f7..9b3fc5d9 100644
--- a/src/components/RichContentCard.tsx
+++ b/src/components/RichContentCard.tsx
@@ -96,8 +96,14 @@ export default function RichContentCard(
rel="noopener noreferrer"
className="rich-content-compact"
onClick={(e) => e.stopPropagation()}
+ aria-label={richContent.siteName
+ ? `Open on ${richContent.siteName}`
+ : "Open source"}
>
{thumbnail}
+
+ ↗
+
);
}
diff --git a/src/themes/nyt.css b/src/themes/nyt.css
index a9677edf..819fab07 100644
--- a/src/themes/nyt.css
+++ b/src/themes/nyt.css
@@ -129,6 +129,17 @@
text-decoration: underline;
text-underline-offset: 2px;
}
+/* …except over a photo, where the headline is white on a dark scrim: ink
+ there goes black-on-black in the light scheme. */
+[data-style="nyt"] .journal-card--image .journal-card-title:hover {
+ color: #fff;
+}
+/* Over a playable thumb the card's "open" cue stands down (see App.css). */
+[data-style="nyt"]
+ .dump-card-inner:has(.dump-card-preview :is(button, a):hover)
+ .dump-card-title {
+ text-decoration: none;
+}
/* ── Kickers — section labels & badges in spaced capitals ───────── */
[data-style="nyt"] .comment-section-title,