diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-02 09:49:41 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-02 09:49:41 +0200 |
| commit | 9e3dfcb8229e0cb3d8e296acd09cf5e2acb9565e (patch) | |
| tree | 70424b9745a462c16947779f14b4b51bf37f20f3 /packages/meshbay-hub/src/meshbay_hub/static/style.css | |
| parent | 12b6dc4dd3e009f2e844d87181800aa12d07a3a6 (diff) | |
| download | meshbay-9e3dfcb8229e0cb3d8e296acd09cf5e2acb9565e.tar.gz | |
fix(hub): the show detail modal must not move when the season does
The previous pass fixed the synopsis and the cast, and the dialog still
jumped: the episode count moves things a fixed-height synopsis cannot reach.
- The body scrolled as a whole, so a thirteen-episode season pushed the modal
to its max-height where a six-episode one had not. `.video-overlay` centres
its child, so the taller modal also *started higher up the screen* — title
bar, close button and all. `.video-detail-steady` (a multi-season show only)
gives the modal a height rather than a max-height, makes the body a flex
column, and hands the leftover to the episode list as the one scrolling
part. A constant-height box is centred in the same place every time, so both
halves settle at once.
- A scrolling season draws a scrollbar where a non-scrolling one draws none,
which is a scrollbar's width of content and re-wrapped the file path above
it, shifting everything below by a line. `scrollbar-gutter: stable`.
- The season panel was clipped by the modal's own `overflow: hidden` whenever
the seasons outran the room under the picker — at a 740px viewport it wanted
320px and had 288, and the rest sat where no scroll could reach it. It is
`position: fixed` now, placed by `placeSeasonPanel()`, which takes the
trigger's rect and the window height, picks whichever side has more room,
and caps the panel to it.
Scoped to multi-season shows throughout: a movie has no season to switch to
and a fixed height would buy it nothing but empty space.
test_video_detail_measured.py now builds each block inside a real
`.video-overlay`, since the centring is half the defect, and asserts the modal
top and height as well as the picker's offset — for a long and a short
synopsis and for a six- and a twenty-four-episode season.
test_season_panel_placement.py runs placeSeasonPanel() in node over a rect and
a window height. Two guards are declarations rather than rectangles and say so
in their docstrings: headless Chrome gives the probe zero-width overlay
scrollbars, so the gutter cannot be measured there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014UtzVrzM7e2tG9fSpkR9ML
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/style.css')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/style.css | 45 |
1 files changed, 40 insertions, 5 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css index cb8cd44..c33c91a 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/style.css +++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css @@ -3025,6 +3025,31 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; } display: flex; flex-direction: column; } + +/* A multi-season show: one size, one position, every season. + Everything above the episode list is already a fixed height (three lines of + synopsis, two of cast), so the last thing that could move the season picker + is the list itself — the body scrolled as a whole, and a season with twice + the episodes pushed the modal to its max height. That moved the modal as + well as its contents: `.video-overlay` centres its child, so the taller + season *started higher up the screen*, title bar and all. A constant height + settles both — centred, a box of constant height is centred in the same + place — which is why this is a `height` and not a `max-height`. Scoped to multi-season shows on purpose: a movie or a + single-season show has no season to switch to, and a fixed height would buy + it nothing but empty space. Capped at 760px so a tall desktop window does + not turn a six-episode season into a mostly empty column. */ +.video-detail.video-detail-steady { height: min(calc(100vh - 100px), 760px); } +.video-detail.video-detail-steady .video-detail-body { overflow: hidden; } +.video-detail.video-detail-steady .video-season-list { + flex: 1; + min-height: 0; + overflow-y: auto; + /* Reserved whether or not this particular season overflows: without it a + scrolling season is a scrollbar narrower than a non-scrolling one, which + re-wraps the file path above it and shifts everything below by a line — + the same jump by a different route. */ + scrollbar-gutter: stable; +} .video-detail .video-top-bar { position: static; background: var(--bg-raised); @@ -3037,6 +3062,12 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; } .video-detail-body { padding: 16px 20px; overflow-y: auto; + /* A column so the episode list can be given the leftover height and scroll + inside it. Safe against the usual flex surprise — `* { margin: 0 }` at the + top of this file means there are no collapsing margins here to lose. */ + display: flex; + flex-direction: column; + min-height: 0; } /* The synopsis, and why it is not a -webkit-line-clamp. Three lines, the third ending in "… Read more" rather than at the right @@ -3262,18 +3293,22 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; } .video-season-current { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .video-season-caret { width: 14px; height: 14px; flex-shrink: 0; color: var(--text-dim); } +/* Fixed to the viewport, with left/width/top-or-bottom/max-height supplied by + placeSeasonPanel() in video-app.js — see the comment there. Absolutely + positioned inside the modal it was clipped by the modal's own + `overflow: hidden` whenever the seasons outran the room under the picker. */ .video-season-options { - position: absolute; - left: 0; right: 0; top: calc(100% + 4px); - z-index: 40; + position: fixed; + z-index: 240; background: var(--bg-surface); border: 1px solid var(--border); border-radius: 8px; box-shadow: var(--shadow-lg); padding: 4px 0; /* Twenty-five seasons will not fit a phone: the panel scrolls, the modal - behind it does not have to. */ - max-height: min(320px, 50vh); + behind it does not have to. The inline max-height narrows this further to + whatever room the trigger actually has. */ + max-height: 320px; overflow-y: auto; } .video-season-option { |