openvibers/openvibe.live Commits
Rundown topics on a phone: 26,000 px to 3,600, by a counted fold (#504)
Changes
@@@ -228,3 +228,14 @@ a.rd-use:hover b { color: var(--accent); }.rd-onair-item { min-width: 200px; }.rd-streamer-head { align-items: flex-start; }}++++/* Topics (/rundown/topics): a card's summary is a four-line teaser, and the topics after the first dozen fold under++ their count. */++.rd-what-clip { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; line-clamp: 4; overflow: hidden; }++.rd-more { margin-top: 14px; border-radius: 14px; border: 1px solid var(--border, rgba(255,255,255,.1)); }++.rd-more > summary { padding: 14px 16px; cursor: pointer; font-weight: 700; list-style: none; display: flex; align-items: center; gap: 10px; }++.rd-more > summary::-webkit-details-marker { display: none; }++.rd-more > summary::before { content: ''; width: 8px; height: 8px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: translateY(-2px) rotate(-45deg); transition: transform .18s ease; }++.rd-more[open] > summary::before { transform: translateY(-4px) rotate(45deg); }++.rd-more > .rd-topic-grid { padding: 4px 12px 14px; }++@media (prefers-reduced-motion: reduce) { .rd-more > summary::before { transition: none; } }@@@ -247,6 +247,18 @@ async function _rdMore(btn) {}// ── Topics ───────────────────────────────────────────────────++// The first dozen show; the rest fold under their count (the owner, 10-31: short pages by counted folds, never by++// hiding). A card's summary is a teaser of four lines: the whole of it is the topic's own page.++const RD_FIRST = 12;++function _rdTopicCard(t) {++ return `<article class="rd-card rd-topic-card">++ ${_rdTopicChip(t)}++ ${t.headline ? `<h3 class="rd-h3">${_rdA(`/rundown/t/${encodeURIComponent(t.slug)}`, _rdEsc(t.headline))}</h3>` : ''}++ ${t.summary ? `<p class="rd-what rd-what-clip">${_rdEsc(t.summary)}</p>` : ''}++ <p class="rd-trend-meta"><span class="rd-stack" aria-hidden="true">${(t.people || []).map((p) => `<i style="--rd-c:${_rdEsc(p.profile_color || '#64748b')}">${p.avatar_url ? `<img src="${_rdEsc(p.avatar_url)}" alt="" width="20" height="20" loading="lazy">` : _rdEsc(_rdName(p).charAt(0).toUpperCase())}</i>`).join('')}</span>++ ${t.streamers} streamer${t.streamers === 1 ? '' : 's'} · ${t.takes} take${t.takes === 1 ? '' : 's'} · ${_rdEsc(_rdAgo(t.last_seen_at))}</p>++ </article>`;++}async function _rdTopics(root, query) {const sort = query.get('sort') === 'most' ? 'most' : 'recent';const kind = RD_KIND[query.get('kind')] ? query.get('kind') : '';@@@ -257,13 +269,7 @@ async function _rdTopics(root, query) {<div class="rd-seg" role="group" aria-label="Sort">${_rdA(href({ sort: '' }), 'Most recent', `rd-seg-btn${sort === 'recent' ? ' is-on' : ''}`)}${_rdA(href({ sort: 'most' }), 'Most talked about', `rd-seg-btn${sort === 'most' ? ' is-on' : ''}`)}</div><div class="rd-chips" role="group" aria-label="Kind">${_rdA(href({ kind: '' }), 'All', `rd-chip${kind ? '' : ' is-on'}`)}${Object.entries(RD_KIND).map(([k, [label, color]]) => _rdA(href({ kind: k }), label, `rd-chip${kind === k ? ' is-on' : ''}`).replace('class="rd-chip', `style="--rd-k:${color}" class="rd-chip`)).join('')}</div></div>-- ${d.topics.length ? `<div class="rd-topic-grid">${d.topics.map((t) => `<article class="rd-card rd-topic-card">-- ${_rdTopicChip(t)}-- ${t.headline ? `<h3 class="rd-h3">${_rdA(`/rundown/t/${encodeURIComponent(t.slug)}`, _rdEsc(t.headline))}</h3>` : ''}-- ${t.summary ? `<p class="rd-what">${_rdEsc(t.summary)}</p>` : ''}-- <p class="rd-trend-meta"><span class="rd-stack" aria-hidden="true">${(t.people || []).map((p) => `<i style="--rd-c:${_rdEsc(p.profile_color || '#64748b')}">${p.avatar_url ? `<img src="${_rdEsc(p.avatar_url)}" alt="" width="20" height="20" loading="lazy">` : _rdEsc(_rdName(p).charAt(0).toUpperCase())}</i>`).join('')}</span>-- ${t.streamers} streamer${t.streamers === 1 ? '' : 's'} · ${t.takes} take${t.takes === 1 ? '' : 's'} · ${_rdEsc(_rdAgo(t.last_seen_at))}</p>-- </article>`).join('')}</div>` : _rdEmpty('fa-comments', kind ? 'No topics of that kind yet.' : 'No topics yet: the desk is reading this week\'s streams.')}`;++ ${d.topics.length ? `<div class="rd-topic-grid">${d.topics.slice(0, RD_FIRST).map(_rdTopicCard).join('')}</div>${d.topics.length > RD_FIRST ? `<details class="rd-more"><summary>${d.topics.length - RD_FIRST} more topic${d.topics.length - RD_FIRST === 1 ? '' : 's'}</summary><div class="rd-topic-grid">${d.topics.slice(RD_FIRST).map(_rdTopicCard).join('')}</div></details>` : ''}` : _rdEmpty('fa-comments', kind ? 'No topics of that kind yet.' : 'No topics yet: the desk is reading this week\'s streams.')}`;document.title = 'Topics · The Rundown · OpenVibe.Live';}async function _rdTopic(root, slug) {
@@ -228,3 +228,14 @@ a.rd-use:hover b { color: var(--accent); }
@@ -228,3 +228,14 @@ a.rd-use:hover b { color: var(--accent); }
.rd-onair-item { min-width: 200px; }
.rd-onair-item { min-width: 200px; }
.rd-streamer-head { align-items: flex-start; }
.rd-streamer-head { align-items: flex-start; }
}
}
+
+/* Topics (/rundown/topics): a card's summary is a four-line teaser, and the topics after the first dozen fold under
+ their count. */
+.rd-what-clip { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; line-clamp: 4; overflow: hidden; }
+.rd-more { margin-top: 14px; border-radius: 14px; border: 1px solid var(--border, rgba(255,255,255,.1)); }
+.rd-more > summary { padding: 14px 16px; cursor: pointer; font-weight: 700; list-style: none; display: flex; align-items: center; gap: 10px; }
+.rd-more > summary::-webkit-details-marker { display: none; }
+.rd-more > summary::before { content: ''; width: 8px; height: 8px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: translateY(-2px) rotate(-45deg); transition: transform .18s ease; }
+.rd-more[open] > summary::before { transform: translateY(-4px) rotate(45deg); }
+.rd-more > .rd-topic-grid { padding: 4px 12px 14px; }
+@media (prefers-reduced-motion: reduce) { .rd-more > summary::before { transition: none; } }
@@ -247,6 +247,18 @@ async function _rdMore(btn) {
@@ -247,6 +247,18 @@ async function _rdMore(btn) {
}
}
// ── Topics ───────────────────────────────────────────────────
// ── Topics ───────────────────────────────────────────────────
+// The first dozen show; the rest fold under their count (the owner, 10-31: short pages by counted folds, never by
+// hiding). A card's summary is a teaser of four lines: the whole of it is the topic's own page.
+const RD_FIRST = 12;
+function _rdTopicCard(t) {
+ return `<article class="rd-card rd-topic-card">
+ ${_rdTopicChip(t)}
+ ${t.headline ? `<h3 class="rd-h3">${_rdA(`/rundown/t/${encodeURIComponent(t.slug)}`, _rdEsc(t.headline))}</h3>` : ''}
+ ${t.summary ? `<p class="rd-what rd-what-clip">${_rdEsc(t.summary)}</p>` : ''}
+ <p class="rd-trend-meta"><span class="rd-stack" aria-hidden="true">${(t.people || []).map((p) => `<i style="--rd-c:${_rdEsc(p.profile_color || '#64748b')}">${p.avatar_url ? `<img src="${_rdEsc(p.avatar_url)}" alt="" width="20" height="20" loading="lazy">` : _rdEsc(_rdName(p).charAt(0).toUpperCase())}</i>`).join('')}</span>
+ ${t.streamers} streamer${t.streamers === 1 ? '' : 's'} · ${t.takes} take${t.takes === 1 ? '' : 's'} · ${_rdEsc(_rdAgo(t.last_seen_at))}</p>
+ </article>`;
+}
async function _rdTopics(root, query) {
async function _rdTopics(root, query) {
const sort = query.get('sort') === 'most' ? 'most' : 'recent';
const sort = query.get('sort') === 'most' ? 'most' : 'recent';
const kind = RD_KIND[query.get('kind')] ? query.get('kind') : '';
const kind = RD_KIND[query.get('kind')] ? query.get('kind') : '';
@@ -257,13 +269,7 @@ async function _rdTopics(root, query) {
@@ -257,13 +269,7 @@ async function _rdTopics(root, query) {
<div class="rd-seg" role="group" aria-label="Sort">${_rdA(href({ sort: '' }), 'Most recent', `rd-seg-btn${sort === 'recent' ? ' is-on' : ''}`)}${_rdA(href({ sort: 'most' }), 'Most talked about', `rd-seg-btn${sort === 'most' ? ' is-on' : ''}`)}</div>
<div class="rd-seg" role="group" aria-label="Sort">${_rdA(href({ sort: '' }), 'Most recent', `rd-seg-btn${sort === 'recent' ? ' is-on' : ''}`)}${_rdA(href({ sort: 'most' }), 'Most talked about', `rd-seg-btn${sort === 'most' ? ' is-on' : ''}`)}</div>
<div class="rd-chips" role="group" aria-label="Kind">${_rdA(href({ kind: '' }), 'All', `rd-chip${kind ? '' : ' is-on'}`)}${Object.entries(RD_KIND).map(([k, [label, color]]) => _rdA(href({ kind: k }), label, `rd-chip${kind === k ? ' is-on' : ''}`).replace('class="rd-chip', `style="--rd-k:${color}" class="rd-chip`)).join('')}</div>
<div class="rd-chips" role="group" aria-label="Kind">${_rdA(href({ kind: '' }), 'All', `rd-chip${kind ? '' : ' is-on'}`)}${Object.entries(RD_KIND).map(([k, [label, color]]) => _rdA(href({ kind: k }), label, `rd-chip${kind === k ? ' is-on' : ''}`).replace('class="rd-chip', `style="--rd-k:${color}" class="rd-chip`)).join('')}</div>
</div>
</div>
- ${d.topics.length ? `<div class="rd-topic-grid">${d.topics.map((t) => `<article class="rd-card rd-topic-card">
- ${_rdTopicChip(t)}
- ${t.headline ? `<h3 class="rd-h3">${_rdA(`/rundown/t/${encodeURIComponent(t.slug)}`, _rdEsc(t.headline))}</h3>` : ''}
- ${t.summary ? `<p class="rd-what">${_rdEsc(t.summary)}</p>` : ''}
- <p class="rd-trend-meta"><span class="rd-stack" aria-hidden="true">${(t.people || []).map((p) => `<i style="--rd-c:${_rdEsc(p.profile_color || '#64748b')}">${p.avatar_url ? `<img src="${_rdEsc(p.avatar_url)}" alt="" width="20" height="20" loading="lazy">` : _rdEsc(_rdName(p).charAt(0).toUpperCase())}</i>`).join('')}</span>
- ${t.streamers} streamer${t.streamers === 1 ? '' : 's'} · ${t.takes} take${t.takes === 1 ? '' : 's'} · ${_rdEsc(_rdAgo(t.last_seen_at))}</p>
- </article>`).join('')}</div>` : _rdEmpty('fa-comments', kind ? 'No topics of that kind yet.' : 'No topics yet: the desk is reading this week\'s streams.')}`;
+ ${d.topics.length ? `<div class="rd-topic-grid">${d.topics.slice(0, RD_FIRST).map(_rdTopicCard).join('')}</div>${d.topics.length > RD_FIRST ? `<details class="rd-more"><summary>${d.topics.length - RD_FIRST} more topic${d.topics.length - RD_FIRST === 1 ? '' : 's'}</summary><div class="rd-topic-grid">${d.topics.slice(RD_FIRST).map(_rdTopicCard).join('')}</div></details>` : ''}` : _rdEmpty('fa-comments', kind ? 'No topics of that kind yet.' : 'No topics yet: the desk is reading this week\'s streams.')}`;
document.title = 'Topics · The Rundown · OpenVibe.Live';
document.title = 'Topics · The Rundown · OpenVibe.Live';
}
}
async function _rdTopic(root, slug) {
async function _rdTopic(root, slug) {