mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
* fix(ui): restore channel and detail keyboard scrolling * test(ui): drag below the Windows scrollbar arrow
249 lines
8.0 KiB
SCSS
249 lines
8.0 KiB
SCSS
// ─── Shared mixin for live-stream portal layouts ──────────────────────────────
|
|
// Used by: xtream live-stream-layout and stalker live-stream-layout.
|
|
//
|
|
// Include this mixin in a component's SCSS to get the shared flex host+sidebar
|
|
// skeleton, then add only the component-specific rules underneath.
|
|
//
|
|
// Choose the relative @use path from each consuming stylesheet. Current portal
|
|
// live layouts use the example below; other consumers may need a different
|
|
// depth.
|
|
// @use '../../../../../../ui/styles/portal-layout' as portal;
|
|
// @include portal.live-layout;
|
|
|
|
@mixin live-layout {
|
|
:host {
|
|
display: flex;
|
|
flex-direction: row;
|
|
overflow: hidden;
|
|
height: 100%;
|
|
width: 100%;
|
|
}
|
|
|
|
// ─── Sidebar ─────────────────────────────────────────────────────────────
|
|
.sidebar {
|
|
// Width controlled by the resizable directive at runtime
|
|
width: 400px;
|
|
box-sizing: border-box;
|
|
padding-inline-end: 8px;
|
|
min-width: 250px;
|
|
max-width: 600px;
|
|
border-right: 1px solid var(--app-separator);
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-shrink: 0;
|
|
overflow: hidden;
|
|
position: relative;
|
|
container-type: inline-size;
|
|
transition:
|
|
width 0.18s ease,
|
|
min-width 0.18s ease,
|
|
border-color 0.18s ease;
|
|
|
|
&.sidebar-collapsed {
|
|
// Beats the inline width set by the resizable directive while
|
|
// preserving it so uncollapsing snaps back to the previous size.
|
|
width: 0 !important;
|
|
padding-inline-end: 0;
|
|
min-width: 0 !important;
|
|
border-right-color: transparent;
|
|
}
|
|
|
|
&.sidebar-collapsed .resize-handle {
|
|
display: none;
|
|
}
|
|
|
|
app-category-view {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
margin-right: 2px;
|
|
}
|
|
}
|
|
|
|
// ─── Main content column ─────────────────────────────────────────────────
|
|
.content-container {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
min-height: 0;
|
|
position: relative;
|
|
}
|
|
|
|
// ─── Floating restore handle (visible when sidebar is collapsed) ─────────
|
|
// Anchored top-left of .content-container so it sits in roughly the same
|
|
// spot as the hide toggle that lived at the sidebar header's top-right.
|
|
.sidebar-restore {
|
|
position: absolute;
|
|
top: 6px;
|
|
left: 6px;
|
|
// Player internals, including diagnostic overlays, may use z-index 5
|
|
// inside the content area. Keep the restore affordance above them for
|
|
// both paint order and hit testing.
|
|
z-index: 6;
|
|
background: rgba(128, 128, 128, 0.18);
|
|
color: var(--mat-sys-on-surface, rgba(255, 255, 255, 0.75));
|
|
box-shadow: none;
|
|
opacity: 0.7;
|
|
transition:
|
|
opacity 0.15s ease,
|
|
background 0.15s ease;
|
|
|
|
.mat-mdc-button-persistent-ripple,
|
|
.mat-mdc-button-touch-target {
|
|
display: none;
|
|
}
|
|
|
|
mat-icon {
|
|
font-size: 20px;
|
|
width: 20px;
|
|
height: 20px;
|
|
line-height: 20px;
|
|
}
|
|
|
|
&:hover,
|
|
&:focus-visible {
|
|
opacity: 1;
|
|
background: rgba(128, 128, 128, 0.32);
|
|
}
|
|
}
|
|
|
|
.video-player {
|
|
flex: 1 1 0;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
display: flex;
|
|
|
|
> app-web-player-view {
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
}
|
|
|
|
.epg {
|
|
flex: 1 1 0;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
transition:
|
|
flex-basis 0.18s ease,
|
|
min-height 0.18s ease;
|
|
|
|
// Inline (player visible): the guide is a compact panel below the
|
|
// player, not half the screen. The responsive clamp keeps the player
|
|
// dominant on tall screens and shrinks the panel on short ones.
|
|
// External player: no .epg--inline → keeps flex:1 above and fills.
|
|
&.epg--inline {
|
|
flex: 0 0 var(--epg-inline-height, clamp(180px, 36vh, 264px));
|
|
}
|
|
|
|
// Vertical list view (epgViewMode = 'list'): rows need more height to
|
|
// be useful — raise the inline clamp only. Sets just the variable, so
|
|
// the later `.epg-collapsed` flex rule still wins when collapsed.
|
|
&.epg--list {
|
|
--epg-inline-height: clamp(280px, 46vh, 430px);
|
|
}
|
|
|
|
&.epg-collapsed {
|
|
flex: 0 0 56px;
|
|
min-height: 56px;
|
|
}
|
|
|
|
> app-epg-timeline,
|
|
> app-epg-list-view {
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
}
|
|
|
|
.epg-content {
|
|
height: 100%;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
> app-epg-timeline,
|
|
> app-epg-list-view {
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
}
|
|
|
|
// ─── Fix artplayer sizing when nested inside .video-player ───────────────
|
|
::ng-deep {
|
|
.video-player
|
|
> app-web-player-view
|
|
> app-art-player
|
|
> .artplayer-container {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
}
|
|
|
|
// ─── Phone layout ────────────────────────────────────────────────────────
|
|
// Side by side, the 400px channel list leaves nothing for the player on a
|
|
// phone, so the two stack instead. The width needs `!important` because
|
|
// ResizableDirective writes the persisted desktop width inline; dragging
|
|
// is meaningless at full width, so the handle goes too.
|
|
@media (max-width: 640px) {
|
|
:host {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.sidebar {
|
|
padding-inline-end: 0;
|
|
width: 100% !important;
|
|
min-width: 0 !important;
|
|
max-width: none;
|
|
flex-shrink: 1;
|
|
max-height: 42vh;
|
|
// Without a floor of its own the player's minimum can take the
|
|
// whole body on a short screen and the list vanishes while still
|
|
// marked expanded, leaving no way to pick another channel.
|
|
min-height: 72px;
|
|
border-right: none;
|
|
border-bottom: 1px solid var(--app-separator);
|
|
|
|
&.sidebar-collapsed {
|
|
max-height: 0;
|
|
min-height: 0;
|
|
border-bottom-color: transparent;
|
|
}
|
|
|
|
.resize-handle {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.content-container {
|
|
flex: 1 1 auto;
|
|
}
|
|
|
|
// Inside that container the guide is `flex: 0 0 <basis>` while the
|
|
// video is `flex: 1 1 0`, so shrinking is distributed by basis and
|
|
// the video — basis zero — absorbed none of it and collapsed. Here
|
|
// the video keeps a floor and the guide is what gives way.
|
|
.video-player {
|
|
min-height: 96px;
|
|
}
|
|
|
|
.epg.epg--inline {
|
|
flex-shrink: 1;
|
|
min-height: 0;
|
|
}
|
|
}
|
|
|
|
// The player's floor, not the list's ceiling, is what has to hold — but
|
|
// only where the screen can afford it. A landscape phone already spends
|
|
// 30vh on the stacked categories panel, so claiming 240px more here left
|
|
// nothing for the channel list; there, the two panes just share what is
|
|
// left. Keyed on height because that, not width, is what runs out.
|
|
@media (max-width: 640px) and (min-height: 600px) {
|
|
.content-container {
|
|
min-height: 240px;
|
|
}
|
|
}
|
|
}
|