zebra-spaces: unified popularity list for screens + cameras + game-shares

Per fox. Previously the left column had three separate sections
(cameras / screens-thumbs / games) each with their own header. Now:

- one #tiles-thumbs container holds every camera, screen-share, and
  game-share thumbnail
- single 'shares' h2 header (hidden when no thumbs)
- games stay below as a fixed persistent group (not popularity-ranked)
- TILE_KINDS.{screen,camera,gameshare} all set
  thumbContainer:'tiles-thumbs'
- reorderTiles is now ONE sort over the unified list. Each tile carries
  data-kind on the dataset so tileScore can pick the right TILE_KINDS
  entry for owner-role lookup. A speaker's screen with 3 viewers ranks
  above the same speaker's camera with 1 viewer; both rank below a
  host's screen regardless of viewers.
- updateContainerVisibility collapsed to one anyThumb check
- mobile media query updates to grid-auto-fill the unified container
  instead of just #cameras

Stale CSS (#cameras column rule, .screens-thumbs-h2) removed.
This commit is contained in:
Russell Ballestrini 2026-06-02 14:15:10 -04:00
parent 3526596936
commit 4ddba0d763
No known key found for this signature in database

View file

@ -235,7 +235,7 @@
.cameras-col { grid-row: 3;
border-right: none; border-top: 1px solid #ddd;
padding-right: 0; padding-top: 1rem; }
#cameras { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
#tiles-thumbs { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
}
/* never let dynamically-appended <audio> sinks (one per remote
* speaker) render their default ~300px control strip — they're just
@ -247,9 +247,11 @@
* the cameras-col. Clicking a thumbnail promotes it to the spotlight
* and demotes the current one back to the column. */
#spotlight { display: grid; grid-template-columns: 1fr; gap: 0; }
#screens-thumbs { display: grid; grid-template-columns: 1fr; gap: 0.45rem; margin-top: 0.5rem; }
.screens-thumbs-h2 { margin-top: 0.5rem; }
.screens-thumbs-h2.hidden { display: none; }
/* unified popularity-sorted thumb list — screens + cameras + game-shares
* all flow through here, ordered by tileScore() */
#tiles-thumbs { display: grid; grid-template-columns: 1fr; gap: 0.45rem; }
#tiles-h2 { margin-top: 0.5rem; }
#tiles-h2.hidden { display: none; }
/* a thumbnail tile is the same DOM as a spotlight tile but with a
* thumb modifier class; clicking it triggers a spotlight swap */
.tile-thumb { cursor: pointer; position: relative; }
@ -315,9 +317,9 @@
/* spotlight active → hide the game iframe so the spotlight tile owns the column */
/* (games live in sec-cameras now — no timeline-side hide rule needed) */
/* camera tiles live in the LEFT column now — single column, stacked
* vertically. Each tile keeps a 16:9 letterbox so faces don't squash. */
#cameras { display: grid; grid-template-columns: 1fr; gap: 0.45rem; }
/* camera tiles share the unified #tiles-thumbs container with screens
* and game-shares (popularity-sorted). 16:9 letterbox keeps faces
* proportional regardless of incoming stream. */
.camera-tile {
border: 1px solid #ddd; background: #fff; padding: 0;
display: grid; grid-auto-rows: max-content; position: relative;
@ -549,10 +551,12 @@ try {
join + share. People can play during the meeting without the games
ever competing with a live screen-share for the middle slot. -->
<aside id="sec-cameras" class="cameras-col">
<h2>cameras</h2>
<div id="cameras"></div>
<h2 class="screens-thumbs-h2 hidden">screens</h2>
<div id="screens-thumbs"></div>
<!-- single popularity-sorted list. Cameras, screen-shares and
game-shares all live here, ordered by tileScore() (host boost
on the owner + viewer count). Games stay below as a fixed
persistent group — they're not popularity-ranked. -->
<h2 id="tiles-h2" class="hidden">shares</h2>
<div id="tiles-thumbs"></div>
<h2 class="games-h2">games</h2>
<div id="games-thumbs"></div>
</aside>
@ -1532,9 +1536,9 @@ function watchVideoTrackForRemoval(track, removeFn){
* #cameras left column: camera thumbnails (always rendered)
* #screens-thumbs left column: screen thumbnails (always rendered) */
const TILE_KINDS = {
screen: { thumbContainer:'screens-thumbs', tileClass:'screen-tile', labelPrefix:'screen', store: screenVideos, streams: screenStreams },
camera: { thumbContainer:'cameras', tileClass:'camera-tile', labelPrefix:'camera', store: cameraVideos, streams: cameraStreams },
gameshare: { thumbContainer:'screens-thumbs', tileClass:'screen-tile', labelPrefix:'gameplay', store: gameVideos, streams: gameStreams },
screen: { thumbContainer:'tiles-thumbs', tileClass:'screen-tile', labelPrefix:'screen', store: screenVideos, streams: screenStreams },
camera: { thumbContainer:'tiles-thumbs', tileClass:'camera-tile', labelPrefix:'camera', store: cameraVideos, streams: cameraStreams },
gameshare: { thumbContainer:'tiles-thumbs', tileClass:'screen-tile', labelPrefix:'gameplay', store: gameVideos, streams: gameStreams },
};
/* the active spotlight, or null when nothing is spotlit */
let spotlight = null; // { kind, pubHex, tile (DOM), video (DOM) }
@ -1617,15 +1621,14 @@ function tileScore(kind, pubHex){
for (const [, k] of spotlights) if (k === key) viewers++;
return boost + viewers;
}
function sortThumbContainer(containerId, kind){
const c = $(containerId); if (!c) return;
const els = Array.from(c.children);
els.sort((a, b) => tileScore(kind, b.dataset.pubHex) - tileScore(kind, a.dataset.pubHex));
for (const el of els) c.appendChild(el);
}
/* one popularity-sorted list across screens + cameras + game-shares.
* Each thumb stamps its kind on tile.dataset.kind so tileScore can read
* the owner role and viewer count via the right TILE_KINDS entry. */
function reorderTiles(){
sortThumbContainer('cameras', 'camera');
sortThumbContainer('screens-thumbs', 'screen');
const c = $('tiles-thumbs'); if (!c) return;
const els = Array.from(c.children);
els.sort((a, b) => tileScore(b.dataset.kind, b.dataset.pubHex) - tileScore(a.dataset.kind, a.dataset.pubHex));
for (const el of els) c.appendChild(el);
}
function broadcastSpotlight(){
/* fire-and-forget to the signal server; no-op if not connected yet */
@ -1640,15 +1643,11 @@ function logSpotlightChange(uuid, key){
function updateContainerVisibility(){
$('sec-spotlight').classList.toggle('hidden', !spotlight);
/* sec-cameras is ALWAYS visible now — the games tiles live there as
* persistent residents that get pushed down by arriving cameras +
* screens. Only the cameras-header and screens-header toggle. */
const anyCam = cameraVideos.size > 0;
const anyScreen = screenVideos.size > 0;
/* hide the 'cameras' h2 when no live cameras to avoid an empty header */
const camH2 = document.querySelector('#sec-cameras > h2:first-of-type');
if (camH2) camH2.classList.toggle('hidden', !anyCam);
document.querySelector('.screens-thumbs-h2').classList.toggle('hidden', !anyScreen);
/* sec-cameras is always visible (games are persistent residents).
* 'shares' header only appears when at least one camera / screen /
* game-share thumb is live. */
const anyThumb = cameraVideos.size + screenVideos.size + gameVideos.size > 0;
$('tiles-h2').classList.toggle('hidden', !anyThumb);
}
/* build a tile DOM. opts.thumb = true for thumb-style (no fullscreen
@ -3526,8 +3525,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> &nbsp;·&nbsp; built <span class="stamp-date">2026-06-02</span><br>
md5 <span class="stamp-md5">db7864ab02b2dfe8c011bbce6b12cdd9</span><br>
sha256 <span class="stamp-sha">cf6dea71571ba9dba2c5c68b71660332186c77383056c5ee2e7a928b93fd822d</span><br>
md5 <span class="stamp-md5">933f3fed73b0a824ed75b1cea22bba9a</span><br>
sha256 <span class="stamp-sha">836f1145f451e86c871813a9a1ff1dd2adbd4c61ddceb2e59a8a58fe24b14f08</span><br>
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
</footer>