mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 21:26:15 -08:00
* feat(settings): split settings into per-section pages with an unsaved-changes bar Replace the single scrolling settings page with routed section pages (/workspace/settings/:section): the context-panel rail links each section, only the active section renders, and unknown or capability-gated sections redirect to General. The shared form lives on the parent component, so staged edits survive section switches; a floating unsaved-changes bar (Save/Discard) replaces the always-visible footer Save button. Rail links navigate with replaceUrl so Back still leaves settings in one step. Along the way: - delete the unreachable settings dialog mode and the dead AppPortalNavigationActionsService with both of its never-injected DI tokens (PORTAL_NAVIGATION_ACTIONS, PLAYLIST_PLAYER_ACTIONS) - delete the scroll-spy directive and pendingScrollTarget plumbing - revive the EPG panel's "Open EPG settings" empty-state button as a deep link to /workspace/settings/epg; the M3U player now reports m3u-needs-setup only when the channel has no programmes and no EPG source exists in settings or on the playlist itself - load TMDB cache stats when the Metadata page opens (the section component now only exists while its page is open) - add SETTINGS.UNSAVED_CHANGES / SETTINGS.DISCARD_CHANGES to all 19 locales Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(settings): confirm before leaving with unsaved changes Add settingsUnsavedChangesGuard (canDeactivate on the :section route) with a three-action dialog: save and leave, leave without saving, keep editing. The guard only intercepts leaving the settings AREA — section switches share the one settings form and pass unconditionally, so the dialog can never nag while moving between pages. A failed save cancels the navigation instead of silently dropping the edits it promised to keep; leaving without saving also reverts the live theme preview. Save-and-leave is disabled while the form is invalid, with a hint explaining why. New SETTINGS.UNSAVED_DIALOG_* keys in all 19 locales. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(settings): stage cover size and EPG view mode; adapt e2e to section pages Cover size and EPG view mode were the only two controls that persisted eagerly on click, which made Discard (and leave-without-saving) unable to revert them: hydrateFromStore() faithfully reloaded the just-persisted edit. They now stage in the form like every other setting and reach the store on Save. Review finding by Greptile (P1) and Codex. E2E suites that walk through settings are updated for one-section-page rendering (epg, backup-roundtrip, xtream-epg, remote-control) and for the staged cover size (downloads asserts the dataset after Save); the EPG icon fallback test saves before leaving settings so the new unsaved-changes dialog does not block its navigation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
880 lines
19 KiB
SCSS
880 lines
19 KiB
SCSS
.settings-page-host {
|
|
display: block;
|
|
}
|
|
|
|
.settings-container {
|
|
height: auto;
|
|
overflow: visible;
|
|
padding: 18px 20px calc(36px + var(--settings-safe-bottom));
|
|
}
|
|
|
|
.settings-layout {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
align-items: stretch;
|
|
max-width: 920px;
|
|
margin: 0 auto;
|
|
padding-bottom: calc(
|
|
var(--settings-action-bar-space) + var(--settings-safe-bottom)
|
|
);
|
|
}
|
|
|
|
.settings-content {
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
// Flat section — no panel chrome. The v0.22 mockup treats Settings as a
|
|
// single scroll column with each section anchored by a large title +
|
|
// bottom-border separator. The previous card frame (border-radius 22px,
|
|
// filled bg, box-shadow, active-state ring + icon-circle) made every
|
|
// section look like a feature card, which fought the inline form rhythm.
|
|
.settings-group {
|
|
position: relative;
|
|
background: transparent;
|
|
border: 0;
|
|
border-radius: 0;
|
|
box-shadow: none;
|
|
overflow: visible;
|
|
padding: 0;
|
|
margin-top: 24px;
|
|
scroll-margin-top: 24px;
|
|
transition: none;
|
|
|
|
&:first-of-type {
|
|
margin-top: 0;
|
|
}
|
|
}
|
|
|
|
.settings-group__header {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 14px;
|
|
padding: 4px 0 18px;
|
|
margin: 0 0 8px;
|
|
border-bottom: 1px solid var(--settings-item-border);
|
|
|
|
// Icon-circle removed per UX-audit recommendation (the section title
|
|
// is the anchor, not a card). Hidden rather than ripped out of every
|
|
// section template so the existing markup stays untouched.
|
|
.settings-group__header-icon {
|
|
display: none;
|
|
}
|
|
|
|
.settings-group__header-text {
|
|
h3 {
|
|
margin: 0;
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
letter-spacing: -0.015em;
|
|
line-height: 1.2;
|
|
color: var(--app-heading-color);
|
|
}
|
|
|
|
p {
|
|
margin: 6px 0 0;
|
|
color: var(--app-body-color);
|
|
font-size: 0.92rem;
|
|
line-height: 1.45;
|
|
}
|
|
}
|
|
}
|
|
|
|
.settings-group__footer {
|
|
padding: 12px 18px 16px;
|
|
border-top: 1px solid var(--settings-item-border);
|
|
}
|
|
|
|
// Row layout — flat list aligned to the section title (no inset). The
|
|
// previous `margin: 0 18px` came from the card chrome; now that each
|
|
// section is a flat block, rows sit at the same gutter as the title.
|
|
.setting-item {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1.15fr) minmax(220px, 340px);
|
|
gap: 14px;
|
|
margin: 0;
|
|
padding: 18px 0;
|
|
border-top: 1px solid var(--settings-item-border);
|
|
background: transparent;
|
|
|
|
&:first-of-type {
|
|
border-top: 0;
|
|
padding-top: 8px;
|
|
}
|
|
|
|
&:last-of-type {
|
|
margin-bottom: 0;
|
|
}
|
|
}
|
|
|
|
.settings-group .setting-item:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.setting-item__meta {
|
|
h4 {
|
|
margin: 0;
|
|
font-size: 0.95rem;
|
|
font-weight: 650;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
p {
|
|
margin: 3px 0 0;
|
|
color: var(--settings-muted);
|
|
font-size: 0.84rem;
|
|
line-height: 1.45;
|
|
}
|
|
}
|
|
|
|
.setting-item__control {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
align-items: center;
|
|
gap: 12px;
|
|
min-height: 40px;
|
|
|
|
mat-form-field,
|
|
.w-full {
|
|
width: min(100%, 340px);
|
|
}
|
|
}
|
|
|
|
.setting-item__toggle {
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.setting-item--full-control {
|
|
grid-template-columns: 1fr;
|
|
gap: 12px;
|
|
}
|
|
|
|
.setting-item--full-control .setting-item__control {
|
|
justify-content: stretch;
|
|
}
|
|
|
|
.app-update-panel__control {
|
|
align-items: stretch;
|
|
justify-content: space-between;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.app-update-status {
|
|
min-width: min(100%, 260px);
|
|
display: flex;
|
|
flex: 1 1 280px;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
color: var(--settings-muted);
|
|
font-size: 0.84rem;
|
|
line-height: 1.45;
|
|
|
|
strong {
|
|
color: var(--app-body-color);
|
|
font-size: 0.9rem;
|
|
font-weight: 650;
|
|
}
|
|
|
|
progress {
|
|
width: min(100%, 320px);
|
|
height: 8px;
|
|
accent-color: var(--app-selection-color);
|
|
}
|
|
}
|
|
|
|
.app-update-actions {
|
|
display: flex;
|
|
flex: 0 1 auto;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
|
|
button {
|
|
app-region: no-drag;
|
|
}
|
|
}
|
|
|
|
.setting-item--external-player-arguments {
|
|
grid-template-columns: 1fr;
|
|
gap: 10px;
|
|
|
|
.setting-item__control {
|
|
justify-content: stretch;
|
|
align-items: stretch;
|
|
}
|
|
|
|
mat-form-field {
|
|
width: 100%;
|
|
}
|
|
|
|
textarea {
|
|
min-height: 86px;
|
|
resize: vertical;
|
|
font-family:
|
|
'JetBrains Mono', 'SFMono-Regular', Menlo, Monaco, Consolas,
|
|
monospace;
|
|
font-size: 0.84rem;
|
|
line-height: 1.45;
|
|
}
|
|
}
|
|
|
|
// Compact segmented control — replaces the previous 3 buttons each ~78px
|
|
// tall. Per the UX audit, "Light/Dark/System" was reading as the most
|
|
// important setting on the page because of its sheer footprint. Now the
|
|
// row reads at the same weight as the other compact rows (Language,
|
|
// Startup behavior, etc.), restoring the visual hierarchy.
|
|
.theme-switcher {
|
|
width: min(100%, 360px);
|
|
display: inline-flex;
|
|
padding: 3px;
|
|
gap: 2px;
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--mat-sys-surface-container-low) 80%,
|
|
transparent
|
|
);
|
|
border: 1px solid
|
|
color-mix(in srgb, var(--mat-sys-outline-variant) 60%, transparent);
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.theme-switcher__option {
|
|
flex: 1 1 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
min-height: 30px;
|
|
padding: 4px 12px;
|
|
border: 0;
|
|
border-radius: 7px;
|
|
background: transparent;
|
|
color: var(--mat-sys-on-surface-variant);
|
|
font: inherit;
|
|
font-size: 0.82rem;
|
|
font-weight: 500;
|
|
letter-spacing: 0;
|
|
cursor: pointer;
|
|
transition:
|
|
background 0.16s ease,
|
|
color 0.16s ease,
|
|
box-shadow 0.16s ease;
|
|
|
|
mat-icon {
|
|
width: 16px;
|
|
height: 16px;
|
|
font-size: 16px;
|
|
line-height: 16px;
|
|
}
|
|
|
|
&:hover {
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--mat-sys-surface-container-highest) 70%,
|
|
transparent
|
|
);
|
|
color: var(--mat-sys-on-surface);
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline: none;
|
|
box-shadow: 0 0 0 2px
|
|
color-mix(in srgb, var(--app-selection-color) 50%, transparent);
|
|
}
|
|
}
|
|
|
|
.theme-switcher__option--selected {
|
|
background: var(--app-widget-bg, var(--mat-sys-surface));
|
|
color: var(--app-selection-color);
|
|
font-weight: 600;
|
|
box-shadow:
|
|
0 1px 2px rgba(0, 0, 0, 0.18),
|
|
inset 0 0 0 1px
|
|
color-mix(in srgb, var(--app-selection-border) 36%, transparent);
|
|
}
|
|
|
|
.epg-sources {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.epg-sources-empty {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
text-align: center;
|
|
gap: 8px;
|
|
padding: 24px 16px 16px;
|
|
color: var(--mat-sys-on-surface-variant);
|
|
|
|
.epg-sources-empty__icon {
|
|
font-size: 40px;
|
|
width: 40px;
|
|
height: 40px;
|
|
opacity: 0.55;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.epg-sources-empty__title {
|
|
margin: 0;
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
color: var(--mat-sys-on-surface);
|
|
}
|
|
|
|
.epg-sources-empty__hint {
|
|
margin: 0;
|
|
font-size: 0.825rem;
|
|
line-height: 1.5;
|
|
max-width: 360px;
|
|
opacity: 0.75;
|
|
}
|
|
}
|
|
|
|
.epg-source-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
|
|
.w-full {
|
|
width: 100%;
|
|
}
|
|
|
|
app-epg-source-status {
|
|
flex-shrink: 0;
|
|
}
|
|
}
|
|
|
|
.inline-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
|
|
.button-spinner {
|
|
display: inline-block;
|
|
margin-right: 6px;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.inline-actions__button {
|
|
min-width: 118px;
|
|
}
|
|
|
|
.inline-actions__button--busy {
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--app-selection-surface) 74%,
|
|
transparent
|
|
);
|
|
color: var(--app-selection-color);
|
|
box-shadow:
|
|
inset 0 0 0 1px
|
|
color-mix(in srgb, var(--app-selection-border) 48%, transparent),
|
|
0 8px 18px -18px var(--settings-group-active-glow);
|
|
}
|
|
|
|
.inline-actions__label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.remote-control-urls {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: 6px;
|
|
}
|
|
|
|
.remote-control-url-item {
|
|
width: min(100%, 340px);
|
|
}
|
|
|
|
.url-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
gap: 4px;
|
|
}
|
|
|
|
.remote-control-url {
|
|
font-family:
|
|
'JetBrains Mono', 'SFMono-Regular', Menlo, Monaco, Consolas, monospace;
|
|
color: inherit;
|
|
text-decoration: none;
|
|
font-size: 0.84rem;
|
|
|
|
&:hover {
|
|
text-decoration: underline;
|
|
}
|
|
}
|
|
|
|
.qr-code-container {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.version-block {
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
|
|
span {
|
|
font-weight: 700;
|
|
}
|
|
|
|
small {
|
|
margin-top: 4px;
|
|
color: var(--mat-sys-on-surface-variant);
|
|
text-align: right;
|
|
}
|
|
}
|
|
|
|
.link-cards {
|
|
width: 100%;
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 10px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.link-card {
|
|
box-sizing: border-box;
|
|
display: grid;
|
|
grid-template-columns: 40px minmax(0, 1fr) 20px;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 12px 14px;
|
|
border: 1px solid
|
|
color-mix(in srgb, var(--mat-sys-outline-variant) 70%, transparent);
|
|
border-radius: 16px;
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--mat-sys-surface-container-low) 88%,
|
|
transparent
|
|
);
|
|
color: var(--mat-sys-on-surface);
|
|
text-decoration: none;
|
|
transition:
|
|
border-color 0.18s ease,
|
|
background 0.18s ease,
|
|
transform 0.18s ease,
|
|
box-shadow 0.18s ease;
|
|
|
|
&:hover {
|
|
transform: translateY(-1px);
|
|
border-color: color-mix(
|
|
in srgb,
|
|
var(--app-selection-border) 54%,
|
|
var(--mat-sys-outline-variant)
|
|
);
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--mat-sys-surface-container-high) 92%,
|
|
transparent
|
|
);
|
|
box-shadow: 0 10px 20px -22px rgba(15, 23, 42, 0.5);
|
|
|
|
.link-card__arrow {
|
|
opacity: 1;
|
|
transform: translate(2px, -2px);
|
|
}
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline: none;
|
|
box-shadow:
|
|
0 0 0 2px var(--mat-sys-surface),
|
|
0 0 0 4px
|
|
color-mix(in srgb, var(--app-selection-color) 40%, transparent);
|
|
}
|
|
}
|
|
|
|
.link-card__glyph {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 12px;
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--mat-sys-surface-container-high) 82%,
|
|
transparent
|
|
);
|
|
color: var(--mat-sys-on-surface-variant);
|
|
|
|
svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
mat-icon {
|
|
font-size: 20px;
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
}
|
|
|
|
.link-card--github .link-card__glyph {
|
|
background: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent);
|
|
color: var(--mat-sys-on-surface);
|
|
}
|
|
|
|
.link-card--blog .link-card__glyph {
|
|
background: color-mix(in srgb, #ff7849 18%, transparent);
|
|
color: #ff8a5b;
|
|
}
|
|
|
|
.link-card--telegram .link-card__glyph {
|
|
background: color-mix(in srgb, #229ed9 22%, transparent);
|
|
color: #38b0e3;
|
|
}
|
|
|
|
.link-card__text {
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
|
|
strong {
|
|
font-size: 0.92rem;
|
|
font-weight: 650;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
span {
|
|
color: var(--settings-muted);
|
|
font-size: 0.78rem;
|
|
line-height: 1.35;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
}
|
|
}
|
|
|
|
.link-card__arrow {
|
|
font-size: 18px;
|
|
width: 18px;
|
|
height: 18px;
|
|
color: var(--settings-muted);
|
|
opacity: 0.6;
|
|
transition:
|
|
opacity 0.18s ease,
|
|
transform 0.18s ease;
|
|
}
|
|
|
|
.settings-note {
|
|
margin: 12px 18px 18px;
|
|
padding: 10px 12px;
|
|
border-radius: 14px;
|
|
font-size: 0.84rem;
|
|
background: color-mix(in srgb, var(--mat-sys-primary) 8%, transparent);
|
|
border: 1px solid
|
|
color-mix(in srgb, var(--mat-sys-primary) 30%, transparent);
|
|
line-height: 1.45;
|
|
|
|
a {
|
|
color: inherit;
|
|
font-weight: 700;
|
|
}
|
|
}
|
|
|
|
.danger-zone {
|
|
margin-top: 8px;
|
|
margin-bottom: 18px;
|
|
padding: 14px 16px;
|
|
background: var(--settings-item-danger-bg);
|
|
border: 1px solid var(--settings-item-danger-border);
|
|
border-radius: 16px;
|
|
grid-template-columns: 1fr;
|
|
gap: 14px;
|
|
}
|
|
|
|
.danger-zone__header {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
|
|
.setting-item__meta {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
}
|
|
|
|
.danger-zone__summary {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
margin: 0;
|
|
}
|
|
|
|
.danger-zone__summary-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-height: 34px;
|
|
padding: 4px 6px 4px 12px;
|
|
border-radius: 12px;
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--mat-sys-surface-container-high) 78%,
|
|
transparent
|
|
);
|
|
border: 1px solid
|
|
color-mix(in srgb, var(--settings-item-danger-border) 60%, transparent);
|
|
font-size: 0.82rem;
|
|
line-height: 1.2;
|
|
color: var(--mat-sys-on-surface);
|
|
|
|
.danger-zone__summary-icon {
|
|
font-size: 18px;
|
|
width: 18px;
|
|
height: 18px;
|
|
color: var(--mat-sys-on-surface-variant);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.danger-zone__summary-label {
|
|
font-weight: 500;
|
|
letter-spacing: 0.01em;
|
|
}
|
|
|
|
.danger-zone__summary-count {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 26px;
|
|
height: 22px;
|
|
padding: 0 8px;
|
|
border-radius: 999px;
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--mat-sys-on-surface) 10%,
|
|
transparent
|
|
);
|
|
color: var(--mat-sys-on-surface);
|
|
font-size: 0.78rem;
|
|
font-weight: 700;
|
|
font-variant-numeric: tabular-nums;
|
|
margin-left: 2px;
|
|
}
|
|
}
|
|
|
|
.danger-zone__progress {
|
|
margin: 0;
|
|
color: var(--mat-sys-on-surface-variant);
|
|
font-size: 0.84rem;
|
|
}
|
|
|
|
.danger-zone__button {
|
|
min-width: 138px;
|
|
flex-shrink: 0;
|
|
align-self: flex-start;
|
|
}
|
|
|
|
.danger-zone__button--busy {
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--settings-item-danger-border) 28%,
|
|
transparent
|
|
);
|
|
}
|
|
|
|
// Unsaved-changes bar — only present while the form is dirty, so unlike the
|
|
// previous always-visible footer Save button it doubles as the "you edited
|
|
// something on another section page" reminder. Sticky rather than fixed so
|
|
// it stays inside the settings column and never covers the shell rail.
|
|
.unsaved-bar {
|
|
position: sticky;
|
|
bottom: calc(
|
|
var(--settings-action-bar-offset) + var(--settings-safe-bottom)
|
|
);
|
|
margin-left: 0;
|
|
margin-right: 0;
|
|
// Stretched to the full layout width, so its own inline padding would be
|
|
// added on top and push the settings page into a horizontal scroll.
|
|
box-sizing: border-box;
|
|
z-index: 100;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
padding: 12px 18px;
|
|
border-top: 1px solid var(--settings-action-bar-border);
|
|
border-left: 0;
|
|
border-right: 0;
|
|
border-bottom: 0;
|
|
border-radius: 0;
|
|
background: var(--settings-action-bar-bg);
|
|
backdrop-filter: blur(12px);
|
|
box-shadow: var(--settings-action-bar-shadow);
|
|
animation: unsaved-bar-enter 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
@keyframes unsaved-bar-enter {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(8px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.unsaved-bar__message {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: var(--settings-muted);
|
|
font-size: 0.88rem;
|
|
|
|
mat-icon {
|
|
font-size: 18px;
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
}
|
|
|
|
.unsaved-bar__actions {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-left: auto;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.settings-save-button.mat-mdc-button-base {
|
|
// Use the unified blue primary (--app-selection-color) instead of the
|
|
// old hand-rolled #3b82f6 gradient that didn't match the rest of the
|
|
// app's CTAs after the color unification commit.
|
|
background: var(--app-selection-color);
|
|
color: var(--app-selection-on-color, #fff);
|
|
font-weight: 500;
|
|
padding: 0 20px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 4px 14px
|
|
color-mix(in srgb, var(--app-selection-color) 28%, transparent);
|
|
transition:
|
|
transform 0.2s ease,
|
|
box-shadow 0.2s ease,
|
|
opacity 0.2s ease;
|
|
|
|
mat-icon {
|
|
margin-right: 6px;
|
|
}
|
|
|
|
&:hover:not([disabled]) {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 6px 18px
|
|
color-mix(in srgb, var(--app-selection-color) 38%, transparent);
|
|
}
|
|
|
|
&:active:not([disabled]) {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
&[disabled] {
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--mat-sys-on-surface) 12%,
|
|
transparent
|
|
);
|
|
color: color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent);
|
|
box-shadow: none;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 1100px) {
|
|
.settings-layout {
|
|
max-width: 100%;
|
|
padding: 0;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 860px) {
|
|
.settings-container {
|
|
height: auto;
|
|
padding: 12px 12px calc(18px + var(--settings-safe-bottom));
|
|
}
|
|
|
|
.settings-page-header {
|
|
padding: 12px 14px;
|
|
border-radius: 16px;
|
|
}
|
|
|
|
.setting-item {
|
|
grid-template-columns: 1fr;
|
|
gap: 12px;
|
|
margin: 0 14px;
|
|
padding: 12px 0;
|
|
}
|
|
|
|
.setting-item__control {
|
|
justify-content: flex-start;
|
|
|
|
mat-form-field,
|
|
.w-full,
|
|
.epg-sources,
|
|
.remote-control-url-item {
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
.theme-switcher {
|
|
width: 100%;
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.remote-control-urls,
|
|
.version-block {
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.link-cards {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.support-btn {
|
|
flex: 1 1 100%;
|
|
}
|
|
|
|
.url-row {
|
|
justify-content: flex-start;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.unsaved-bar {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
border-radius: 16px;
|
|
}
|
|
|
|
.unsaved-bar__actions {
|
|
flex-direction: column-reverse;
|
|
align-items: stretch;
|
|
margin-left: 0;
|
|
|
|
button {
|
|
width: 100%;
|
|
}
|
|
}
|
|
}
|