mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:56:16 -08:00
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:
1 parent
388fa9e29d
commit
07d3f48bc2
4 files changed
+92
-41
No files matched your search
@@ -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
|
||||
|
||||
+65
-36
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-3
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user