playlists(#37): FEATURE_PLAYLISTS flag + channel "Playlists" section

- constants.js: FEATURE_PLAYLISTS (default OFF) so nothing is user-visible until
  the whole playlist UI ships; flip to preview on unstable.
- js/channelPlaylists.js: wireChannelPlaylists(pubkey) fetches the creator's
  playlists (facade) and renders a responsive row of cards (thumbnail + video
  count + title) linking to `#view=playlist&pubkey=…&id=…`. Hidden when the flag
  is off, no playlists, or the container is missing; a nav-token guards against a
  stale fetch resolving after a channel switch. DOM built with textContent (no
  innerHTML). Kept out of the 5729-line-capped channelProfile.js.
- channelProfile.js: one-line wireChannelPlaylists(currentChannelHex) hook next
  to wireChannelZapTotal (no-op while the flag is off).
- views/channel-profile.html: #channelPlaylistsSection above the video grid.
- tailwind.source.css: .playlist-grid / .playlist-card* (token-only; responsive
  via @apply like .bv-grid-video).

Lint + build clean. The playlist view the cards link to lands next.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-07-06 13:12:37 -04:00
1 parent 0caf468c36
commit 85903965a4
5 files changed
+210

No files matched your search

+62
View File
@@ -2951,6 +2951,68 @@
background-color: currentColor;
}
/* #37: channel playlists section — a responsive row of playlist cards. */
.playlist-grid {
@apply grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4;
gap: var(--space-md);
}
.playlist-card {
display: flex;
flex-direction: column;
gap: var(--space-2xs);
text-decoration: none;
transition: transform 0.12s ease-out;
}
.playlist-card:hover {
transform: translateY(-2px);
}
.playlist-card__thumb {
position: relative;
display: flex;
align-items: flex-end;
justify-content: flex-end;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: var(--radius-sm);
background-color: var(--color-page-alt);
border: var(--border-width-hairline) solid var(--color-page-alt);
}
.playlist-card__image {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
object-fit: cover;
}
.playlist-card__count {
position: relative;
margin: var(--space-2xs);
border-radius: var(--radius-sm);
padding-block: var(--space-3xs);
padding-inline: var(--space-2xs);
font-size: var(--font-size-2xs);
font-weight: 500;
color: var(--color-text-strong);
background-color: var(--color-page);
}
.playlist-card__title {
margin: 0;
font-size: var(--font-size-sm);
font-weight: 500;
color: var(--color-text);
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* #43: always-visible active-identity pill. Rendered after the header in
flow and pulled up with a negative block-start margin so it sits half-on /
half-off the header divider — every pane shows who you're acting as. */