feat(playback): give the fullscreen side panel the settings panel's glass look

The fullscreen channel/episode panel was a full-height graphite strip while
the controls' settings panel is an inset, rounded glass card. The side panel
now wears the same surface: 16px inset (8px under 560px), 20px corners, the
controls' glass fill, hairline border, blur and shadow, the same open motion
and a 32px square close button. The edge hint uses the same glass, and the
episode list marks the playing row with the settings panel's selected cyan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-09-30 20:06:43 +02:00
1 parent 388fa9e29d
commit 07d3f48bc2
4 files changed
+92 -41

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
---
type: feature
area: playback
---
The fullscreen channel and episode list now floats over the video as a rounded
glass card, matching the player's settings panel, and the playing episode is
highlighted the same way as a selected setting.
@@ -767,9 +767,16 @@ ends. The panel carries the `dark-theme` context class, so app and Material
tokens inside it resolve to the dark palette whatever the app theme is — and
because the app's global `.dark-theme { background: … !important }` rule
(`apps/web/src/styles.scss`) claims the background of every element wearing
that class, the panel's translucent gradient is declared on the compound
that class, the panel's glass fill is declared on the compound
`.fullscreen-channel-panel.dark-theme` selector with `!important`; without
that the panel painted no background at all.
that the panel painted no background at all. The panel wears the same surface
as the controls' settings panel — a card inset 16px from the player's top,
bottom and left edges (8px in a player narrower than 560px), 20px corners,
the `--pc-glass-bg` fill, hairline `--pc-glass-border`, the same blur and
shadow, and a 32px square close button — with the values written literally,
because the panel is a sibling of the controls host, outside the scope that
declares the `--pc-*` palette. Its episode list marks the playing row with the
settings panel's selected-option cyan tint.
Keyboard: `C` is ignored while any editable element has focus and while the
player sits inside an `inert` region; the search field is an ordinary input,
so the controls' Space/K/F/M shortcuts stay out of it. That is also why `C` is
@@ -34,12 +34,12 @@
left: 0;
width: 22px;
height: 96px;
border: 1px solid rgba(255, 255, 255, 0.16);
border: 1px solid rgba(255, 255, 255, 0.09);
border-left: 0;
border-radius: 0 12px 12px 0;
background: rgba(13, 15, 18, 0.72);
background: rgba(12, 16, 23, 0.72);
box-shadow: 4px 0 18px rgba(0, 0, 0, 0.35);
backdrop-filter: blur(12px);
backdrop-filter: blur(18px) saturate(1.3);
opacity: 0;
transform: translate(-100%, -50%);
pointer-events: none;
@@ -68,8 +68,8 @@
}
.fullscreen-channel-panel__edge-hint--armed {
border-color: var(--app-selection-color, #78adff);
background: rgba(13, 15, 18, 0.9);
border-color: rgba(255, 255, 255, 0.28);
background: rgba(12, 16, 23, 0.88);
}
.fullscreen-channel-panel__edge-hint--armed::after {
@@ -78,6 +78,12 @@
// ─── Open state ──────────────────────────────────────────────────────────────
// A floating glass card inset from the player edges — the same surface as the
// player's settings panel (`player-settings-panel.component.scss`): 20px
// corners, the controls' glass fill, hairline border and blur. The values are
// literal because the panel sits beside the controls host, outside the scope
// that declares the `--pc-*` palette.
.fullscreen-channel-panel__scrim {
position: absolute;
inset: 0;
@@ -86,43 +92,46 @@
.fullscreen-channel-panel {
position: absolute;
top: 0;
bottom: 0;
left: 0;
top: 16px;
bottom: 16px;
left: 16px;
z-index: 8;
display: flex;
flex-direction: column;
width: min(400px, calc(100% - 56px));
color: var(--app-heading-color, #d8dce8);
border-right: 1px solid rgba(255, 255, 255, 0.12);
box-shadow: 18px 0 48px rgba(0, 0, 0, 0.55);
backdrop-filter: blur(24px) saturate(140%);
transform: translateX(-100%);
box-sizing: border-box;
width: min(400px, calc(100% - 72px));
color: #e7ecf3;
border: 1px solid rgba(255, 255, 255, 0.09);
border-radius: 20px;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
backdrop-filter: blur(18px) saturate(1.3);
overflow: hidden;
opacity: 0;
// Past its own inset, so no rounded corner or shadow peeks in while shut.
transform: translateX(calc(-100% - 24px));
visibility: hidden;
transition:
transform 240ms cubic-bezier(0.2, 0.7, 0.2, 1),
visibility 0s linear 240ms;
transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
opacity 220ms ease-out,
visibility 0s linear 220ms;
}
// Translucent graphite over the video, matching the controls' popovers. The
// panel carries `dark-theme` for its tokens, and the app's global
// The controls' glass fill over the video. The panel carries `dark-theme` for its tokens, and the app's global
// `.dark-theme { background: var(--mat-sys-surface) !important }`
// (apps/web/src/styles.scss) would replace this gradient with the flat
// surface — or with no background at all where that token is not defined —
// so the compound selector and `!important` are required to out-rank it.
.fullscreen-channel-panel.dark-theme {
background: linear-gradient(
to right,
rgba(13, 15, 18, 0.94),
rgba(13, 15, 18, 0.88)
) !important;
background: rgba(12, 16, 23, 0.72) !important;
}
.fullscreen-channel-panel--open {
opacity: 1;
transform: translateX(0);
visibility: visible;
transition:
transform 240ms cubic-bezier(0.2, 0.7, 0.2, 1),
transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
opacity 220ms ease-out,
visibility 0s;
}
@@ -131,9 +140,9 @@
.fullscreen-channel-panel__header {
display: flex;
align-items: center;
gap: 4px;
gap: 8px;
flex-shrink: 0;
padding: 10px 6px 10px 12px;
padding: 14px 12px 12px 16px;
}
// A host without a search field (`panelSearchEnabled` false) shows its title in
@@ -144,9 +153,9 @@
margin: 0;
padding: 0 4px;
line-height: 36px;
font-size: 0.95rem;
font-size: 16px;
font-weight: 600;
color: #ffffff;
color: #e7ecf3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -166,9 +175,9 @@
min-width: 0;
height: 36px;
padding: 0 6px 0 10px;
border: 1px solid rgba(255, 255, 255, 0.1);
border: 1px solid transparent;
border-radius: 10px;
background: rgba(255, 255, 255, 0.07);
background: rgba(255, 255, 255, 0.06);
cursor: text;
transition:
border-color 150ms ease-out,
@@ -176,7 +185,7 @@
}
.fullscreen-channel-panel__search:focus-within {
border-color: var(--app-selection-color, #78adff);
border-color: rgba(255, 255, 255, 0.28);
background: rgba(255, 255, 255, 0.1);
}
@@ -217,7 +226,7 @@
height: 26px;
padding: 0;
border: 0;
border-radius: 50%;
border-radius: 8px;
color: rgba(255, 255, 255, 0.75);
background: transparent;
cursor: pointer;
@@ -226,7 +235,7 @@
.fullscreen-channel-panel__search-clear:hover,
.fullscreen-channel-panel__search-clear:focus-visible {
color: #ffffff;
background: rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.1);
}
.fullscreen-channel-panel__search-clear mat-icon {
@@ -235,15 +244,31 @@
font-size: 18px;
}
// The settings panel's close button: a 32px, 10px-cornered square with a flat
// hover fill instead of Material's round state layer.
.fullscreen-channel-panel__close {
flex-shrink: 0;
color: rgba(255, 255, 255, 0.8);
width: 32px;
height: 32px;
padding: 4px;
border-radius: 10px;
color: #e7ecf3;
--mdc-icon-button-icon-color: currentColor;
--mat-icon-button-icon-color: currentColor;
--mat-icon-button-state-layer-size: 32px;
--mat-icon-button-container-shape: 10px;
--mat-icon-button-icon-size: 20px;
--mat-icon-button-hover-state-layer-opacity: 0;
}
.fullscreen-channel-panel__close mat-icon {
width: 20px;
height: 20px;
font-size: 20px;
}
.fullscreen-channel-panel__close:hover {
color: #ffffff;
background: rgba(255, 255, 255, 0.1);
}
// ─── Body ────────────────────────────────────────────────────────────────────
@@ -278,7 +303,11 @@
@container (max-width: 560px) {
.fullscreen-channel-panel {
width: calc(100% - 44px);
top: 8px;
bottom: 8px;
left: 8px;
width: calc(100% - 52px);
border-radius: 16px;
}
}
@@ -121,6 +121,11 @@
background: rgba(255, 255, 255, 0.1);
}
&:focus-visible {
outline: 2px solid #e7ecf3;
outline-offset: -2px;
}
mat-icon {
width: 16px;
height: 16px;
@@ -152,13 +157,15 @@
}
&:focus-visible {
outline: 2px solid var(--app-selection-color, #78adff);
outline: 2px solid #e7ecf3;
outline-offset: -2px;
}
}
// The playing row wears the settings panel's selected-option tint: cyan means
// "this is on" across the player chrome.
.episode-panel__item--playing {
background: rgba(255, 255, 255, 0.14);
background: rgba(92, 214, 255, 0.14);
cursor: default;
}
@@ -266,7 +273,7 @@
font-size: 0.62rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: #7dd87d;
color: #5cd6ff;
}
.episode-panel__title {