diff --git a/libs/ui/styles/_content-grid.scss b/libs/ui/styles/_content-grid.scss new file mode 100644 index 000000000..31c8af011 --- /dev/null +++ b/libs/ui/styles/_content-grid.scss @@ -0,0 +1,120 @@ +// Shared content grid and card styles +// +// Usage (after adding libs/ui/styles to stylePreprocessorOptions.includePaths): +// @use 'content-grid' as grid; +// @include grid.content-grid; +// @include grid.content-card; + +@mixin content-grid($min-width: 140px, $gap: 20px) { + display: grid; + grid-template-columns: repeat(auto-fill, minmax($min-width, 1fr)); + gap: $gap; +} + +@mixin content-card { + cursor: pointer; + border-radius: 8px; + overflow: hidden; + transition: transform 0.2s ease, box-shadow 0.2s ease; + + &:hover { + transform: translateY(-3px); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); + } +} + +@mixin thumbnail-wrapper { + position: relative; + border-radius: 8px; + overflow: hidden; + + .poster { + width: 100%; + aspect-ratio: 2/3; + object-fit: cover; + display: block; + } + + .poster-placeholder { + width: 100%; + aspect-ratio: 2/3; + display: flex; + align-items: center; + justify-content: center; + background: rgba(128, 128, 128, 0.1); + + mat-icon { + font-size: 36px; + width: 36px; + height: 36px; + opacity: 0.25; + } + } +} + +@mixin type-badge { + position: absolute; + top: 6px; + left: 6px; + padding: 3px 6px; + border-radius: 4px; + font-size: 9px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + background: rgba(0, 0, 0, 0.85); + color: #fff; + + &.live { + color: #ef5350; + } + &.movie { + color: #42a5f5; + } + &.series { + color: #66bb6a; + } +} + +@mixin remove-button { + position: absolute; + top: 4px; + right: 4px; + z-index: 1; + background: rgba(0, 0, 0, 0.5); + width: 28px; + height: 28px; + padding: 0; + + &:hover { + background: rgba(0, 0, 0, 0.8); + } + + mat-icon { + color: white; + font-size: 18px; + width: 18px; + height: 18px; + } +} + +@mixin card-info { + padding: 8px 10px; + + h3 { + margin: 0; + font-size: 0.8rem; + font-weight: 500; + line-height: 1.3; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + + .date-info { + margin: 4px 0 0; + font-size: 0.7rem; + opacity: 0.6; + } +} diff --git a/libs/ui/styles/_index.scss b/libs/ui/styles/_index.scss new file mode 100644 index 000000000..21a81b4d2 --- /dev/null +++ b/libs/ui/styles/_index.scss @@ -0,0 +1,11 @@ +// ─── IPTVnator UI styles library ───────────────────────────────────────────── +// Add libs/ui/styles to stylePreprocessorOptions.includePaths in project.json +// to import these without relative paths: +// +// @use 'portal-layout' as portal; +// @use 'content-grid' as grid; +// @use 'portal-sidebar'; + +@forward 'portal-layout'; +@forward 'content-grid'; +@forward 'portal-sidebar'; diff --git a/libs/ui/styles/_portal-layout.scss b/libs/ui/styles/_portal-layout.scss new file mode 100644 index 000000000..b79d904bf --- /dev/null +++ b/libs/ui/styles/_portal-layout.scss @@ -0,0 +1,93 @@ +// ─── Shared mixin for live-stream portal layouts ────────────────────────────── +// Used by: xtream live-stream-layout, stalker live-stream-layout, +// portal-collection-live-shell +// +// Include this mixin in a component's SCSS to get the shared flex host+sidebar +// skeleton, then add only the component-specific rules underneath. +// +// Usage (after adding libs/ui/styles to stylePreprocessorOptions.includePaths): +// @use '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; + 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; + + 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; + } + + .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; + } + + .epg-content { + height: 100%; + min-height: 0; + overflow: hidden; + display: flex; + flex-direction: column; + + > app-epg-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; + } + } +} diff --git a/libs/ui/styles/_portal-sidebar.scss b/libs/ui/styles/_portal-sidebar.scss new file mode 100644 index 000000000..103de464b --- /dev/null +++ b/libs/ui/styles/_portal-sidebar.scss @@ -0,0 +1,49 @@ +// ─── Shared sidebar + sidebar-header styles ─────────────────────────────────── +// Plain CSS rules (not a mixin) used for M3U/legacy sidebar layouts. +// +// Usage (after adding libs/ui/styles to stylePreprocessorOptions.includePaths): +// @use 'portal-sidebar'; (no alias needed — no exported members to call) + +.sidebar { + // Width is controlled by the resizable directive + // Default fallback if directive not applied + width: 400px; + min-width: 250px; + max-width: 600px; + border-right: 1px solid var(--app-separator); + display: flex; + flex-direction: column; + flex-shrink: 0; // Prevent sidebar from shrinking + + .sidebar-header { + padding: 8px 12px; + background: var(--app-widget-header-bg, var(--mat-sys-surface-container-high)); + border-bottom: 1px solid var(--app-separator); + app-region: drag; + display: flex; + align-items: center; + gap: 8px; + + h2 { + margin: 0; + font-size: 0.94rem; + font-weight: 600; + letter-spacing: -0.01em; + color: var(--mat-sys-on-surface); + flex: 1; + } + + button { + app-region: no-drag; + } + + .switcher { + flex: 1; + min-width: 0; + } + + .manage-categories-btn { + margin-left: auto; + } + } +}