Files
iptvnator/apps/web/src/_settings-theme.scss
T
4grayandClaude Fable 5 eac1dfa09e fix(theme): replace no-op --mat-sys-* tokens with --app-* design tokens
The Material theme setup in apps/web/src/m3-theme.scss uses the older M3
API (mat.define-theme + mat.all-component-themes), which emits component
tokens but never emits the --mat-sys-* system tokens. As a result ~540
var(--mat-sys-*) references across the app were silent no-ops: the
declarations were invalid at computed-value time and fell back to
inherited/initial values, so many backgrounds, borders and dividers never
rendered and several text/icon colors sat at the wrong contrast.

Rather than migrate to mat.theme() (which would flip every no-op at once
with M3 azure-palette values foreign to the hand-tuned design), remap the
high-visibility usages to the existing --app-* design tokens defined for
both themes in m3-theme.scss, verified screenshot-driven in light and dark
via Electron CDP.

Changes:
- Add --app-error-color and --app-accent-color tokens (light + dark).
- Fix high-visibility surfaces: settings area, workspace shell header,
  playlist switcher, downloads, channel-details dialog, account-info,
  group/category management dialogs, sources context/filters panels,
  nav-list, and the .dark-theme app background.
- Point the Tailwind color palette at --app-* tokens instead of --mat-sys-*.
- Strip ~150 dead var(--app-x, var(--mat-sys-y)) fallbacks repo-wide (the
  fallback can never be taken).
- UI polish surfaced by the remap: suppress Material's circular icon-button
  state layer in the header (keep the rounded-rectangle hover), remove the
  orphan divider before the download button, remove the redundant right-edge
  accent bar on active rail items (the filled chip is enough), and raise the
  header/switcher control icons from the muted tier to --app-body-color so
  they no longer read as disabled.

Guard against regressions:
- tools/styles/check-mat-sys-usage.mjs fails on any new var(--mat-sys-*)
  reference; legacy usages are grandfathered in a shrink-only baseline
  (tools/styles/mat-sys-baseline.mjs). Wired into the CI Lint job and
  exposed as `pnpm run lint:styles`.

Docs:
- docs/architecture/theme-design-tokens.md documents why the tokens are
  no-ops, the --app-* mapping table, and the screenshot-driven migration
  protocol. CLAUDE.md updated with the token policy and the new lint check.

Scrollbar-related --mat-sys-* usages are intentionally left untouched here;
they belong to the separate branch claude/iptvnator-dark-scrollbar-2600aa.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 22:31:46 +02:00

325 lines
9.0 KiB
SCSS

.settings-container {
--settings-safe-bottom: env(safe-area-inset-bottom, 0px);
// Action bar is a full-width sticky footer, so it sits flush against
// the bottom edge of the scroll container — no hover offset. The
// previous 24px gap was tuned for the old floating-chip layout where
// the bar visually hovered above the bottom.
--settings-action-bar-offset: 0px;
// 0 by default — the action bar is now the last child of the form
// and acts as the natural bottom edge, so no padding-bottom is
// needed below it. The previous 112px/80px reservation existed to
// push content above the OLD floating-chip layout, but in the new
// sticky-footer model that just added dead scrollable space below
// the last setting row. The bar's own padding (12px) and the
// section title bottom-borders already provide visual breathing
// room when scrolled to the end.
--settings-action-bar-space: 0px;
--settings-page-header-bg: color-mix(
in srgb,
var(--app-widget-bg) 94%,
rgba(255, 255, 255, 0.98)
);
--settings-page-header-border: color-mix(
in srgb,
var(--app-separator) 72%,
transparent
);
--settings-page-header-shadow:
0 10px 20px -28px rgba(15, 23, 42, 0.16),
0 1px 0 rgba(255, 255, 255, 0.68) inset;
--settings-group-bg: color-mix(
in srgb,
var(--app-content-bg) 96%,
rgba(255, 255, 255, 0.96)
);
--settings-group-border: color-mix(
in srgb,
var(--app-separator) 78%,
transparent
);
--settings-group-shadow: 0 14px 28px -32px rgba(15, 23, 42, 0.14);
--settings-group-active-bg: color-mix(
in srgb,
var(--app-selection-color) 2%,
rgba(255, 255, 255, 0.98)
);
--settings-group-active-ring: color-mix(
in srgb,
var(--app-selection-border) 68%,
var(--settings-group-border)
);
--settings-group-active-glow: color-mix(
in srgb,
var(--app-selection-glow) 28%,
transparent
);
--settings-group-active-shadow:
inset 0 0 0 1px var(--settings-group-active-ring),
0 12px 24px -28px var(--settings-group-active-glow);
--settings-item-border: var(--app-separator);
--settings-item-danger-bg: color-mix(
in srgb,
var(--app-error-color) 4%,
rgba(255, 255, 255, 0.96)
);
--settings-item-danger-border: color-mix(
in srgb,
var(--app-error-color) 14%,
transparent
);
--settings-action-bar-bg: color-mix(
in srgb,
var(--app-widget-bg) 94%,
rgba(255, 255, 255, 0.9)
);
--settings-action-bar-border: color-mix(
in srgb,
var(--app-separator) 62%,
transparent
);
--settings-action-bar-shadow: 0 10px 26px -26px rgba(15, 23, 42, 0.22);
--settings-muted: var(--app-muted-color);
}
:where(.dark-theme, [data-theme='dark'], .mat-app-background.dark)
.settings-container {
--settings-page-header-bg: color-mix(
in srgb,
var(--app-widget-bg) 92%,
rgba(12, 16, 24, 0.94)
);
--settings-page-header-border: color-mix(
in srgb,
var(--app-separator) 38%,
transparent
);
--settings-page-header-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.05),
0 16px 26px -34px rgba(0, 0, 0, 0.4);
--settings-group-bg: color-mix(
in srgb,
var(--app-widget-bg) 94%,
rgba(10, 14, 22, 0.82)
);
--settings-group-border: rgba(255, 255, 255, 0.08);
--settings-group-shadow: 0 16px 28px -32px rgba(0, 0, 0, 0.42);
--settings-group-active-bg: color-mix(
in srgb,
var(--app-selection-surface) 24%,
rgba(255, 255, 255, 0.015)
);
--settings-group-active-ring: color-mix(
in srgb,
var(--app-selection-border) 54%,
rgba(255, 255, 255, 0.08)
);
--settings-group-active-glow: color-mix(
in srgb,
var(--app-selection-glow) 22%,
transparent
);
--settings-group-active-shadow:
inset 0 0 0 1px var(--settings-group-active-ring),
0 10px 22px -28px var(--settings-group-active-glow);
--settings-item-danger-bg: color-mix(
in srgb,
var(--app-error-color) 8%,
rgba(24, 10, 12, 0.86)
);
--settings-item-danger-border: color-mix(
in srgb,
var(--app-error-color) 24%,
transparent
);
--settings-action-bar-bg: color-mix(
in srgb,
var(--app-widget-bg) 92%,
transparent
);
--settings-action-bar-border: rgba(255, 255, 255, 0.08);
--settings-action-bar-shadow: 0 8px 30px rgba(0, 0, 0, 0.18);
--settings-muted: var(--app-muted-color);
}
.settings-container {
.support-card {
width: 100%;
box-sizing: border-box;
position: relative;
display: flex;
flex-direction: column;
gap: 14px;
padding: 18px;
border-radius: 18px;
border: 1px solid color-mix(in srgb, #ff4d6d 26%, transparent);
background:
radial-gradient(
120% 140% at 0% 0%,
color-mix(in srgb, #ff4d6d 16%, transparent) 0%,
transparent 55%
),
radial-gradient(
120% 140% at 100% 100%,
color-mix(in srgb, #ffb347 14%, transparent) 0%,
transparent 60%
),
color-mix(in srgb, var(--app-widget-bg) 92%, transparent);
overflow: hidden;
&::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(
80% 100% at 50% -10%,
color-mix(in srgb, #ff4d6d 12%, transparent) 0%,
transparent 70%
);
opacity: 0.8;
}
}
.support-card__intro {
position: relative;
display: flex;
align-items: flex-start;
gap: 14px;
z-index: 1;
}
.support-card__emblem {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border-radius: 14px;
background: linear-gradient(135deg, #ff4d6d, #ff8b5e);
color: #fff;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.35),
0 10px 18px -10px rgba(255, 77, 109, 0.55);
mat-icon {
font-size: 22px;
width: 22px;
height: 22px;
}
}
.support-card__copy {
min-width: 0;
flex: 1 1 auto;
h4 {
margin: 0;
font-size: 1rem;
font-weight: 700;
letter-spacing: -0.01em;
}
p {
margin: 4px 0 0;
color: var(--settings-muted);
font-size: 0.86rem;
line-height: 1.5;
max-width: 64ch;
}
}
.support-card__actions {
position: relative;
z-index: 1;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.support-btn {
flex: 1 1 220px;
display: inline-flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
border-radius: 14px;
border: 1px solid transparent;
text-decoration: none;
color: #fff;
transition:
transform 0.18s ease,
box-shadow 0.18s ease,
filter 0.18s ease;
&:hover {
transform: translateY(-1px);
filter: brightness(1.04);
}
&:focus-visible {
outline: none;
box-shadow:
0 0 0 2px var(--app-content-bg),
0 0 0 4px
color-mix(
in srgb,
var(--app-selection-color) 40%,
transparent
);
}
}
.support-btn__glyph {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.18);
color: #fff;
flex: 0 0 auto;
svg {
width: 18px;
height: 18px;
}
mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}
}
.support-btn__text {
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
strong {
font-size: 0.92rem;
font-weight: 700;
letter-spacing: -0.01em;
}
small {
color: rgba(255, 255, 255, 0.82);
font-size: 0.75rem;
line-height: 1.3;
}
}
.support-btn--sponsors {
background: linear-gradient(135deg, #d4337a, #ff4d6d);
box-shadow: 0 12px 22px -18px rgba(212, 51, 122, 0.7);
}
.support-btn--kofi {
background: linear-gradient(135deg, #13c3ff, #29abe0);
box-shadow: 0 12px 22px -18px rgba(19, 195, 255, 0.7);
}
}