From 9e3dfcb8229e0cb3d8e296acd09cf5e2acb9565e Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 2 Sep 2026 09:49:41 +0200 Subject: fix(hub): the show detail modal must not move when the season does MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_014UtzVrzM7e2tG9fSpkR9ML --- docs/mediacenter.md | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) (limited to 'docs/mediacenter.md') diff --git a/docs/mediacenter.md b/docs/mediacenter.md index b4526aa..e983ef9 100644 --- a/docs/mediacenter.md +++ b/docs/mediacenter.md @@ -827,6 +827,26 @@ episode list are, so the thing just clicked was no longer under the pointer. - **The cast line was however long the cast was.** Clamped to two lines, ellipsised at the end of the second. Nothing to expand to here, so this one is a plain `-webkit-line-clamp`. +- **The episode list is however many episodes the season has**, and that is + the half neither clamp can 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 — and `.video-overlay` centres its child, so the + taller modal *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 `.video-season-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 season that scrolls also draws a scrollbar where one that does not draws + none, which is a scrollbar's width of content and re-wrapped the file path + above it; `scrollbar-gutter: stable` reserves it either way. +- **The season menu's panel is `position: fixed`.** As an absolutely + positioned child it was clipped by the modal's own `overflow: hidden` + whenever the seasons outran the room under the picker — measured at a 740 px + viewport, a thirteen-season panel wanted 320 px and had 288, and the rest sat + outside the modal where no scroll could reach it. `placeSeasonPanel()` takes + the trigger's rect and the window height, picks whichever side has more room, + and caps the panel to it. It is the only part of this menu the stylesheet + does not decide, so it has its own test. - **The season tab bar scrolled sideways.** A show with a dozen seasons hid most of them behind a horizontal scrollbar — unusable on a phone, and ugly everywhere. `SeasonMenu` replaces `SeasonTabs`: one trigger reading @@ -846,14 +866,21 @@ keeps its `null` until `TMDB_META_TTL_SECS` expires or an operator re-matches. Measured, not read out of the stylesheet (`test_video_detail_measured.py`, via the `layout_probe.py` harness): the season picker's offset inside its own -modal body is the same pixel for a one-line and a twelve-line synopsis, at -every width from 320 px up; the clamped synopsis is three line-heights either -way and the cast at most two; the read-more link lands on the third line; an -unreserved short synopsis has no floor under it; and the open menu fits inside -the modal at 320 px. Each measured block sits in a whole-pixel-height -container — stacked in flow they start at fractional offsets, and two -identical layouts an eighth of a pixel apart round to tops one pixel apart, -which reads as a defect in the thing being measured and is not one. +modal body is the same pixel for a one-line and a twelve-line synopsis *and* +for a six- and a twenty-four-episode season, at every width from 320 px up; +the modal's own top and height are the same for all three; the episode list +never runs past the bottom of its modal; the clamped synopsis is three +line-heights either way and the cast at most two; the read-more link lands on +the third line; and a movie gets neither the floor nor the fixed height. Each +measured block sits inside a real `.video-overlay` — the centring is half the +defect — in a whole-pixel-height container, since stacked in flow they start at +fractional offsets and two identical layouts an eighth of a pixel apart round +to tops one pixel apart, which reads as a defect in the thing being measured +and is not one. `test_season_panel_placement.py` runs `placeSeasonPanel()` in +node over a rect and a window height: down, flipped up, capped to the room +available, floored when cramped. Two things there are declarations rather than +rectangles, and say so in their own docstrings: `scrollbar-gutter`, because +headless Chrome gives the probe zero-width overlay scrollbars. `test_tmdb_show_director.py` covers the credit. ## 11. Acceptance before shipping -- cgit v1.2.3