mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
416 lines
10 KiB
SCSS
416 lines
10 KiB
SCSS
@mixin play-button-copy($meta-color: currentColor) {
|
|
&__copy {
|
|
display: inline-flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 2px;
|
|
min-width: 0;
|
|
line-height: 1.1;
|
|
text-align: left;
|
|
}
|
|
|
|
&__label {
|
|
display: block;
|
|
min-width: 0;
|
|
line-height: 1.15;
|
|
}
|
|
|
|
&__meta {
|
|
display: block;
|
|
max-width: min(42vw, 360px);
|
|
overflow: hidden;
|
|
color: $meta-color;
|
|
font-size: 0.78rem;
|
|
font-weight: 500;
|
|
line-height: 1.2;
|
|
opacity: 0.72;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
// Secondary detail-view actions (Favorite, Download). Per UX audit: only Play
|
|
// should read as the primary CTA, so these stay ghost/neutral. State accents
|
|
// (favorite=red destructive, download=green completed) keep their semantic
|
|
// meaning but ride on transparent fills so they never out-shout Play.
|
|
@mixin secondary-action-button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-height: 64px;
|
|
padding: 0 24px;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
background: color-mix(in srgb, var(--app-on-surface) 6%, transparent);
|
|
color: var(--text-primary, #fff);
|
|
border: 1px solid color-mix(in srgb, var(--app-on-surface) 14%, transparent);
|
|
border-radius: 16px;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
|
|
&:hover {
|
|
background: color-mix(in srgb, var(--app-on-surface) 10%, transparent);
|
|
border-color: color-mix(
|
|
in srgb,
|
|
var(--app-on-surface) 22%,
|
|
transparent
|
|
);
|
|
}
|
|
|
|
mat-icon {
|
|
font-size: 24px;
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
}
|
|
|
|
@mixin favorite-button-state {
|
|
&--active {
|
|
background: rgba(255, 91, 110, 0.16);
|
|
border-color: rgba(255, 91, 110, 0.4);
|
|
color: #ff8f8f;
|
|
|
|
&:hover {
|
|
background: rgba(255, 91, 110, 0.24);
|
|
}
|
|
}
|
|
|
|
// The watched toggle shares the ghost surface but reads green when
|
|
// active, like the catalog's watched badge.
|
|
&.watched-btn.favorite-btn--active {
|
|
background: rgba(74, 222, 128, 0.16);
|
|
border-color: rgba(74, 222, 128, 0.4);
|
|
color: #4ade80;
|
|
|
|
&:hover {
|
|
background: rgba(74, 222, 128, 0.24);
|
|
}
|
|
}
|
|
|
|
&.watched-btn:disabled {
|
|
opacity: 0.55;
|
|
cursor: default;
|
|
pointer-events: none;
|
|
}
|
|
}
|
|
|
|
@mixin download-button-state {
|
|
// Idle download stays neutral — extends secondary-action-button.
|
|
|
|
&--completed {
|
|
background: rgba(74, 222, 128, 0.16);
|
|
border-color: rgba(74, 222, 128, 0.4);
|
|
color: #4ade80;
|
|
|
|
&:hover {
|
|
background: rgba(74, 222, 128, 0.24);
|
|
}
|
|
}
|
|
|
|
&--downloading {
|
|
background: color-mix(in srgb, var(--app-on-surface) 4%, transparent);
|
|
border-color: color-mix(
|
|
in srgb,
|
|
var(--app-on-surface) 14%,
|
|
transparent
|
|
);
|
|
cursor: not-allowed;
|
|
opacity: 0.6;
|
|
|
|
&:hover {
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--app-on-surface) 4%,
|
|
transparent
|
|
);
|
|
}
|
|
}
|
|
|
|
&--paused {
|
|
background: rgba(251, 191, 36, 0.14);
|
|
border-color: rgba(251, 191, 36, 0.4);
|
|
color: #fbbf24;
|
|
|
|
&:hover {
|
|
background: rgba(251, 191, 36, 0.22);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Icon-only secondary actions (Favorites, Download) from the sources-popup
|
|
// handoff: square ghosts at the primary CTA's height, so the label-less pair
|
|
// reads as satellites of Play instead of competing with it. The translucent
|
|
// surface keeps the border — over bright hero art these buttons otherwise
|
|
// lose their edges.
|
|
@mixin icon-action-button {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex: 0 0 auto;
|
|
width: 64px;
|
|
height: 64px;
|
|
min-height: 64px;
|
|
padding: 0;
|
|
// Theme-aware ghost surface: over a bright light-theme hero a white-alpha
|
|
// fill and border simply vanish, which is exactly the lost-edges failure
|
|
// the border exists to prevent. The tag vars already flip per theme.
|
|
background: var(--tag-bg, rgba(255, 255, 255, 0.08));
|
|
color: var(--text-secondary, rgba(255, 255, 255, 0.7));
|
|
border: 1px solid var(--tag-border, rgba(255, 255, 255, 0.12));
|
|
border-radius: 16px;
|
|
cursor: pointer;
|
|
transition:
|
|
background 0.2s ease,
|
|
border-color 0.2s ease,
|
|
color 0.2s ease;
|
|
|
|
&:hover {
|
|
background: var(--tag-border, rgba(255, 255, 255, 0.14));
|
|
border-color: var(--text-muted, rgba(255, 255, 255, 0.5));
|
|
color: var(--text-primary, #fff);
|
|
}
|
|
|
|
mat-icon {
|
|
font-size: 26px;
|
|
width: 26px;
|
|
height: 26px;
|
|
}
|
|
}
|
|
|
|
@mixin icon-action-button-states {
|
|
// Favorited: same ghost surface — only the glyph fills and turns red.
|
|
&--favorite.is-active {
|
|
color: #ff5b6e;
|
|
|
|
&:hover {
|
|
color: #ff7b8b;
|
|
}
|
|
}
|
|
|
|
&--favorite.is-pulsing mat-icon {
|
|
animation: icon-action-pulse 180ms ease;
|
|
}
|
|
|
|
// Watched: the same green as the catalog badge, filled glyph.
|
|
&--watched.is-active {
|
|
color: #4ade80;
|
|
|
|
&:hover {
|
|
color: #6ee7a0;
|
|
}
|
|
}
|
|
|
|
&--watched:disabled {
|
|
opacity: 0.55;
|
|
cursor: default;
|
|
pointer-events: none;
|
|
}
|
|
|
|
// Downloaded: green check; the click now opens the file's location.
|
|
&--done {
|
|
color: #4ade80;
|
|
|
|
&:hover {
|
|
color: #6ee7a0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The download button's circular progress: track + value arc, spinning when
|
|
// the total size — and so the percent — is unknown.
|
|
@mixin download-progress-ring {
|
|
.download-ring {
|
|
width: 30px;
|
|
height: 30px;
|
|
transform: rotate(-90deg);
|
|
|
|
&--indeterminate {
|
|
animation: download-ring-spin 0.9s linear infinite;
|
|
}
|
|
}
|
|
|
|
.download-ring__track {
|
|
fill: none;
|
|
stroke: var(--tag-border, rgba(255, 255, 255, 0.12));
|
|
stroke-width: 3;
|
|
}
|
|
|
|
.download-ring__value {
|
|
fill: none;
|
|
stroke: var(--accent-color, #4f8eff);
|
|
stroke-width: 3;
|
|
stroke-linecap: round;
|
|
transition: stroke-dashoffset 0.3s ease;
|
|
}
|
|
|
|
.icon-action-btn__center {
|
|
position: absolute;
|
|
font-size: 16px;
|
|
width: 16px;
|
|
height: 16px;
|
|
line-height: 16px;
|
|
}
|
|
}
|
|
|
|
@mixin icon-action-keyframes {
|
|
@keyframes icon-action-pulse {
|
|
50% {
|
|
transform: scale(1.15);
|
|
}
|
|
}
|
|
|
|
@keyframes download-ring-spin {
|
|
to {
|
|
transform: rotate(270deg);
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.icon-action-btn mat-icon {
|
|
animation: none !important;
|
|
}
|
|
|
|
.download-ring--indeterminate {
|
|
animation-duration: 3.6s;
|
|
}
|
|
}
|
|
}
|
|
|
|
@mixin mobile-action-buttons {
|
|
.play-btn,
|
|
.favorite-btn,
|
|
.download-btn {
|
|
flex: 1;
|
|
justify-content: center;
|
|
min-height: 58px;
|
|
padding: 10px 16px;
|
|
}
|
|
|
|
.play-btn__meta {
|
|
max-width: 100%;
|
|
}
|
|
}
|
|
|
|
@mixin projected-mobile-action-buttons {
|
|
::ng-deep .play-btn,
|
|
::ng-deep .favorite-btn,
|
|
::ng-deep .download-btn {
|
|
flex: 1;
|
|
justify-content: center;
|
|
min-height: 58px;
|
|
padding: 10px 16px;
|
|
}
|
|
|
|
::ng-deep .play-btn__meta {
|
|
max-width: 100%;
|
|
}
|
|
}
|
|
|
|
// ── Cinematic action row (VOD details hero + dashboard hero) ────────────────
|
|
// One light primary and one ghost surface, shared by the detail pages and the
|
|
// dashboard hero so "Continue" means the same thing everywhere. Fixed
|
|
// geometry: 44px tall in the details, 38px in the dashboard hero; the mixins
|
|
// only size the type and radius, the caller sets the height.
|
|
|
|
@mixin light-primary-button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
box-sizing: border-box;
|
|
padding: 0 20px 0 16px;
|
|
border: 0;
|
|
border-radius: 10px;
|
|
background: var(--app-cta-bg, #f2f4f8);
|
|
color: var(--app-cta-fg, #0a0e14);
|
|
font-family: inherit;
|
|
font-size: 13.5px;
|
|
font-weight: 600;
|
|
line-height: 1.15;
|
|
white-space: nowrap;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
transition:
|
|
background-color 0.15s ease,
|
|
transform 0.15s ease;
|
|
|
|
&:hover:not(:disabled) {
|
|
background: var(--app-cta-hover-bg, #fff);
|
|
}
|
|
|
|
&:active:not(:disabled) {
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline: 2px solid var(--app-selection-color, #78adff);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
&:disabled {
|
|
cursor: default;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
mat-icon {
|
|
flex: 0 0 auto;
|
|
width: 20px;
|
|
height: 20px;
|
|
font-size: 20px;
|
|
line-height: 20px;
|
|
}
|
|
}
|
|
|
|
@mixin ghost-action-surface {
|
|
background: color-mix(in srgb, var(--app-on-surface, #e6e1e5) 12%, transparent);
|
|
color: var(--app-heading-color, #d8dce8);
|
|
border: 1px solid
|
|
color-mix(in srgb, var(--app-on-surface, #e6e1e5) 14%, transparent);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
backdrop-filter: blur(8px);
|
|
transition:
|
|
background-color 0.15s ease,
|
|
border-color 0.15s ease,
|
|
color 0.15s ease;
|
|
|
|
&:hover:not(:disabled) {
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--app-on-surface, #e6e1e5) 20%,
|
|
transparent
|
|
);
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline: 2px solid var(--app-selection-color, #78adff);
|
|
outline-offset: 2px;
|
|
}
|
|
}
|
|
|
|
// Text secondary action ("Trailer", "Details"): the ghost surface at the
|
|
// primary's height.
|
|
@mixin ghost-text-button {
|
|
@include ghost-action-surface;
|
|
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
box-sizing: border-box;
|
|
padding: 0 18px;
|
|
border-radius: 10px;
|
|
font-family: inherit;
|
|
font-size: 13.5px;
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
white-space: nowrap;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
|
|
mat-icon {
|
|
width: 18px;
|
|
height: 18px;
|
|
font-size: 18px;
|
|
line-height: 18px;
|
|
}
|
|
}
|