Split the reels view into panels

A panel can be split to the right or below, and the panel that appears can
be split again, so any arrangement is reachable. The layout is a binary
tree and the leaves, read in order, are the panels of a step.

Scrolling drives all of them: with N panels a step covers N videos and one
swipe advances the whole set. That runs through every index in the feed --
opening on a video, realigning after a rotation, the prefetch buffer, the
render window -- all of which now convert between a video and the step
that holds it.

Each panel has its own sound, so two can play at once if that is what you
want. A new panel inherits the feed-wide setting rather than starting
muted, so a step built later doesn't disagree with what is already on
screen, and the feed-wide button reads as muted only while every panel is.

Two things fall out of panels that are worth knowing. The render window
narrows as panels are added -- five steps ahead of a four-panel split
would be twenty live <video> elements -- so splitting does not multiply
decoding. And splitting rebuilds around the video you are on rather than
keeping it in the panel you split from: steps are aligned to the panel
count, so it stays on screen but not necessarily first.

The per-video helpers were already written against an element holding one
video's controls, so they took a panel unchanged; a slide became the
container that fans out over them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd
This commit is contained in:
Simon
2026-09-10 18:23:51 +00:00
parent 6e5ab68a94
commit f0df53365d
2 changed files with 415 additions and 108 deletions

View File

@@ -1324,7 +1324,7 @@ body.theme-light .video-menu-btn {
}
/* Live streams can't be seeked, so hide the feed scrubber. */
.feed-slide.is-live .feed-timeline {
.feed-pane.is-live .feed-timeline {
display: none;
}
@@ -2158,6 +2158,9 @@ body.feed-mode-open .mode-toggle-btn .icon-svg {
flex: none;
}
/* One screenful. It holds the pane tree rather than a video directly: with
several panes, a step shows that many videos at once and one swipe advances
the whole set. */
.feed-slide {
position: relative;
width: 100%;
@@ -2165,6 +2168,34 @@ body.feed-mode-open .mode-toggle-btn .icon-svg {
height: 100dvh;
scroll-snap-align: start;
scroll-snap-stop: always;
overflow: hidden;
background: #000;
}
/* A branch of the tree: two children sharing the space, side by side or
stacked. Nesting these is what makes any arrangement reachable. */
.feed-split {
display: flex;
width: 100%;
height: 100%;
gap: 2px;
}
.feed-split.is-row { flex-direction: row; }
.feed-split.is-col { flex-direction: column; }
/* min-* is what stops a flex child refusing to shrink below its content. */
.feed-split > .feed-pane,
.feed-split > .feed-split {
flex: 1 1 0;
min-width: 0;
min-height: 0;
}
.feed-pane {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
@@ -2172,6 +2203,45 @@ body.feed-mode-open .mode-toggle-btn .icon-svg {
background: #000;
}
.feed-pane-tools {
position: absolute;
top: 12px;
right: 12px;
display: flex;
gap: 6px;
/* Above the video, below the right-hand controls it sits beside. */
z-index: 5;
}
.feed-pane-btn {
width: 34px;
height: 34px;
border-radius: 50%;
border: 1px solid rgba(244, 232, 212, 0.18);
background: rgba(0, 0, 0, 0.45);
color: var(--text-primary);
font-size: 14px;
line-height: 1;
cursor: pointer;
backdrop-filter: blur(6px);
transition: background 0.2s ease, border-color 0.2s ease;
}
.feed-pane-btn:hover {
background: rgba(0, 0, 0, 0.7);
border-color: var(--border-hover);
}
.feed-pane-mute.is-muted {
border-color: var(--accent);
}
/* The HUD's idle fade takes the panel tools with it, like every other control. */
body.feed-hud-idle .feed-pane-tools {
opacity: 0;
transition: opacity 0.4s ease;
}
.feed-poster,
.feed-video {
position: absolute;
@@ -2185,7 +2255,7 @@ body.feed-mode-open .mode-toggle-btn .icon-svg {
transition: opacity 0.2s ease;
}
.feed-slide.is-loaded .feed-poster {
.feed-pane.is-loaded .feed-poster {
opacity: 0;
}
@@ -2310,6 +2380,22 @@ body.feed-mode-open .mode-toggle-btn .icon-svg {
}
/* Per-slide favorite (heart) button on the feed HUD right rail. */
/* The right-hand controls stack upwards from just above the caption, rather
than sitting at fixed distances from the bottom. A pane can be a third of
the viewport tall, and viewport-scale offsets put these outside it -- clipped
by the pane's own overflow and unreachable. */
.feed-pane .feed-fav-btn,
.feed-pane .feed-pip-btn,
.feed-pane .feed-format-btn {
position: absolute;
right: 12px;
bottom: auto;
top: 56px;
}
.feed-pane .feed-pip-btn { top: 100px; }
.feed-pane .feed-format-btn { top: 144px; }
.feed-fav-btn {
position: absolute;
top: auto;