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:
parent
3526596936
commit
4ddba0d763
1 changed files with 32 additions and 33 deletions
|
|
@ -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> · 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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue