Files
iptvnator/libs/ui/styles/_detail-view-actions.scss
T

309 lines
7.4 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%;
}
}