fix(detail): keep the Back button from covering detail content

The shell's sticky Back control floated over its scroll owner: it covered
the start of the "Seasons and Episodes" heading and the inline player's
top-left corner. While Back is shown, every content column (hero, player,
episodes, About, extras) now starts after a 72px lane (16 + 40 + 16),
published as --detail-back-lane so content-hero and content-about honour
it too; hosts without Back (M3U) keep their inset. At the 640px phone
breakpoint a column that wide would clip the player controls, so there
the wrapper becomes an in-flow 56px sticky bar on --app-header-bg.

The season header now wraps its actions onto their own row before the
heading wraps (the pane, not the viewport, decides), and the narrow
inline player's title ellipsizes inside its card instead of spilling past
both edges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-09-30 08:38:36 +02:00
1 parent 388fa9e29d
commit 377b22124b
11 files changed
+278 -7

No files matched your search

@@ -144,6 +144,8 @@
gap: 32px;
align-items: flex-start;
padding: 140px 40px 60px;
// Reserved by the detail shell while its sticky Back control shows.
padding-inline-start: max(40px, var(--detail-back-lane, 0px));
max-width: var(--content-max-width);
margin: 0 auto;
box-sizing: border-box;
@@ -511,6 +513,7 @@
&__content {
padding: 60px 20px 40px;
padding-inline-start: max(20px, var(--detail-back-lane, 0px));
gap: 16px;
}
}
@@ -552,6 +555,7 @@
flex-direction: column;
align-items: stretch;
padding-inline: 16px;
padding-inline-start: max(16px, var(--detail-back-lane, 0px));
}
.poster {
@@ -6,6 +6,8 @@
max-width: var(--content-max-width, 1400px);
margin: 0 auto;
padding: 24px 40px 40px;
// The detail shell's sticky Back lane (0 when there is no Back).
padding-inline-start: max(40px, var(--detail-back-lane, 0px));
border-top: 1px solid var(--tag-border, rgba(255, 255, 255, 0.12));
&__heading {
@@ -127,6 +129,7 @@
@media (max-width: 800px) {
.about {
padding: 20px 20px 32px;
padding-inline-start: max(20px, var(--detail-back-lane, 0px));
&__poster {
width: 92px;
@@ -13,6 +13,13 @@
--tag-border: rgba(0, 0, 0, 0.12);
--poster-shadow: 0 8px 40px rgba(0, 0, 0, 0.15);
--content-max-width: 1400px;
// The sticky Back control floats over the scroll owner. While it is shown,
// every content column (hero, player, episodes, About, extras) starts
// after its lane — inset + button + the same inset again — so nothing
// scrolls beneath it. content-hero and content-about read the lane too.
--shell-back-inset: 16px;
--shell-back-size: 40px;
--detail-back-lane: 0px;
display: block;
height: 100%;
@@ -30,6 +37,12 @@
}
}
:host(.shell-host--back) {
--detail-back-lane: calc(
2 * var(--shell-back-inset) + var(--shell-back-size)
);
}
:host-context(.dark-theme) {
--surface-bg: #141414;
--text-primary: #fff;
@@ -43,7 +56,7 @@
.shell__navigation {
// Direct child of the scroll owner: sticky lasts through the entire page.
position: sticky;
top: 16px;
top: var(--shell-back-inset);
height: 0;
z-index: 10;
pointer-events: none;
@@ -51,15 +64,15 @@
.shell__back-button {
position: absolute;
left: 16px;
inset-inline-start: var(--shell-back-inset);
pointer-events: auto;
app-region: no-drag;
-webkit-app-region: no-drag;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
width: var(--shell-back-size);
height: var(--shell-back-size);
border-radius: 50%;
background: var(--app-widget-bg, var(--surface-bg));
color: var(--app-on-surface, var(--text-primary));
@@ -122,6 +135,7 @@
&--active {
padding: 16px 40px 0;
padding-inline-start: max(40px, var(--detail-back-lane));
animation: shell-fade-in 300ms ease 50ms both;
}
}
@@ -130,6 +144,7 @@
max-width: var(--content-max-width);
margin: 0 auto;
padding: 0 40px;
padding-inline-start: max(40px, var(--detail-back-lane));
}
.shell__about {
@@ -143,6 +158,7 @@
max-width: var(--content-max-width);
margin: 0 auto;
padding: 0 40px 40px;
padding-inline-start: max(40px, var(--detail-back-lane));
}
@keyframes shell-fade-in {
@@ -159,14 +175,39 @@
@media (max-width: 800px) {
.shell__player--active {
padding: 8px 12px 0;
padding-inline-start: max(12px, var(--detail-back-lane));
}
.shell__episodes {
padding: 0 20px;
padding-inline-start: max(20px, var(--detail-back-lane));
}
.shell__extras {
padding: 0 20px 32px;
padding-inline-start: max(20px, var(--detail-back-lane));
}
}
// A phone cannot spare a 72px column: the player's controls would clip and
// the hero actions would wrap. There the control takes a row instead — an
// opaque sticky bar in flow that content scrolls beneath, never alongside.
@media (max-width: 640px) {
:host(.shell-host--back) {
--detail-back-lane: 0px;
}
.shell__navigation {
top: 0;
height: calc(var(--shell-back-size) + 16px);
background: var(--app-header-bg);
// The bar hides what scrolls beneath it, so it must not let taps
// through to that hidden content either.
pointer-events: auto;
}
.shell__back-button {
top: 8px;
}
}
@@ -61,6 +61,8 @@ import {
// on body, while preserving already-handled events and overlay guards.
'(keydown.escape)': 'onEscape($event)',
'[class.shell-host--watch]': 'isWatch()',
// Content columns reserve the sticky Back control's lane.
'[class.shell-host--back]': 'backAvailable()',
'(document:keydown.escape)': 'onEscape($event)',
},
})
@@ -4,6 +4,10 @@
.section-header {
display: flex;
// The actions move onto their own row before the title has to wrap. The
// pane is narrower than the window (context panel, Back lane), so a
// viewport breakpoint cannot tell when the two stop fitting side by side.
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
@@ -14,14 +18,16 @@
display: flex;
align-items: center;
gap: 16px;
flex: 1;
flex: 1 1 auto;
}
// Beside the title the header's space-between keeps the actions at the
// end; once they move to their own row they start under the title.
&__actions {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
justify-content: flex-start;
gap: 8px;
}
@@ -117,7 +123,6 @@
@media (max-width: 560px) {
.section-header {
align-items: flex-start;
flex-wrap: wrap;
&__actions {
width: 100%;
@@ -203,6 +203,9 @@
.player-shell__title-row {
flex-direction: column;
// Centered, the nowrap heading took its full text width and spilled
// past both card edges; stretched, the title ellipsizes in the card.
align-items: stretch;
}
.player-shell__actions {