mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
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>
This commit is contained in:
1 parent
f856226dc3
commit
eac1dfa09e
45 files changed
+801
-488
No files matched your search
@@ -37,6 +37,9 @@ jobs:
|
||||
CI: true
|
||||
NX_TASKS_RUNNER_DYNAMIC_OUTPUT: false
|
||||
|
||||
- name: Check for new --mat-sys-* token usages
|
||||
run: node tools/styles/check-mat-sys-usage.mjs
|
||||
|
||||
unit-and-typecheck:
|
||||
name: Unit Tests and Typechecks
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -210,6 +210,11 @@ are baselined in `tools/eslint/max-lines-baseline.mjs`; regenerate the baseline
|
||||
with `node tools/eslint/generate-max-lines-baseline.mjs` after splitting a file.
|
||||
Never add new files to the baseline.
|
||||
|
||||
The CI lint job also runs `pnpm run lint:styles`
|
||||
(`tools/styles/check-mat-sys-usage.mjs`), which fails on new `var(--mat-sys-*)`
|
||||
references — those tokens are never emitted by the current theme; use the
|
||||
`--app-*` design tokens instead (see `docs/architecture/theme-design-tokens.md`).
|
||||
|
||||
## Architecture
|
||||
|
||||
### Monorepo Structure (Nx Workspace)
|
||||
@@ -477,6 +482,15 @@ This rule exists to keep the codebase navigable and reviewable. A 150-line file
|
||||
|
||||
---
|
||||
|
||||
**Design Tokens (Theming)**:
|
||||
|
||||
- The app theme (light + dark) lives in `apps/web/src/m3-theme.scss` (older Material M3 API: `mat.define-theme` + `mat.all-component-themes`). It defines the canonical `--app-*` design tokens for both themes.
|
||||
- **Never reference `--mat-sys-*` tokens in styles** — this theme setup never emits Material system tokens, so such references are silent no-ops (invalid at computed-value time). Use the `--app-*` tokens instead.
|
||||
- CI fails on new `var(--mat-sys-*)` usages: `pnpm run lint:styles` (`tools/styles/check-mat-sys-usage.mjs`; legacy usages are baselined in `tools/styles/mat-sys-baseline.mjs`, which must only shrink).
|
||||
- Token mapping and screenshot-driven migration protocol: `docs/architecture/theme-design-tokens.md`.
|
||||
|
||||
---
|
||||
|
||||
**Angular Coding Standards**:
|
||||
|
||||
This project uses modern Angular signal-based APIs and patterns. **ALWAYS** use the following:
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
--settings-action-bar-space: 0px;
|
||||
--settings-page-header-bg: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-low) 94%,
|
||||
var(--app-widget-bg) 94%,
|
||||
rgba(255, 255, 255, 0.98)
|
||||
);
|
||||
--settings-page-header-border: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-outline-variant) 72%,
|
||||
var(--app-separator) 72%,
|
||||
transparent
|
||||
);
|
||||
--settings-page-header-shadow:
|
||||
@@ -29,12 +29,12 @@
|
||||
0 1px 0 rgba(255, 255, 255, 0.68) inset;
|
||||
--settings-group-bg: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface) 96%,
|
||||
var(--app-content-bg) 96%,
|
||||
rgba(255, 255, 255, 0.96)
|
||||
);
|
||||
--settings-group-border: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-outline-variant) 78%,
|
||||
var(--app-separator) 78%,
|
||||
transparent
|
||||
);
|
||||
--settings-group-shadow: 0 14px 28px -32px rgba(15, 23, 42, 0.14);
|
||||
@@ -59,42 +59,38 @@
|
||||
--settings-item-border: var(--app-separator);
|
||||
--settings-item-danger-bg: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-error) 4%,
|
||||
var(--app-error-color) 4%,
|
||||
rgba(255, 255, 255, 0.96)
|
||||
);
|
||||
--settings-item-danger-border: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-error) 14%,
|
||||
var(--app-error-color) 14%,
|
||||
transparent
|
||||
);
|
||||
--settings-action-bar-bg: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-low) 94%,
|
||||
var(--app-widget-bg) 94%,
|
||||
rgba(255, 255, 255, 0.9)
|
||||
);
|
||||
--settings-action-bar-border: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-outline-variant) 62%,
|
||||
var(--app-separator) 62%,
|
||||
transparent
|
||||
);
|
||||
--settings-action-bar-shadow: 0 10px 26px -26px rgba(15, 23, 42, 0.22);
|
||||
--settings-muted: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-on-surface) 64%,
|
||||
transparent
|
||||
);
|
||||
--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(--mat-sys-surface-container) 92%,
|
||||
var(--app-widget-bg) 92%,
|
||||
rgba(12, 16, 24, 0.94)
|
||||
);
|
||||
--settings-page-header-border: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-outline-variant) 38%,
|
||||
var(--app-separator) 38%,
|
||||
transparent
|
||||
);
|
||||
--settings-page-header-shadow:
|
||||
@@ -102,7 +98,7 @@
|
||||
0 16px 26px -34px rgba(0, 0, 0, 0.4);
|
||||
--settings-group-bg: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container) 94%,
|
||||
var(--app-widget-bg) 94%,
|
||||
rgba(10, 14, 22, 0.82)
|
||||
);
|
||||
--settings-group-border: rgba(255, 255, 255, 0.08);
|
||||
@@ -127,26 +123,22 @@
|
||||
0 10px 22px -28px var(--settings-group-active-glow);
|
||||
--settings-item-danger-bg: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-error) 8%,
|
||||
var(--app-error-color) 8%,
|
||||
rgba(24, 10, 12, 0.86)
|
||||
);
|
||||
--settings-item-danger-border: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-error) 24%,
|
||||
var(--app-error-color) 24%,
|
||||
transparent
|
||||
);
|
||||
--settings-action-bar-bg: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container) 92%,
|
||||
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: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-on-surface) 68%,
|
||||
transparent
|
||||
);
|
||||
--settings-muted: var(--app-muted-color);
|
||||
}
|
||||
|
||||
.settings-container {
|
||||
@@ -171,11 +163,7 @@
|
||||
color-mix(in srgb, #ffb347 14%, transparent) 0%,
|
||||
transparent 60%
|
||||
),
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-low) 92%,
|
||||
transparent
|
||||
);
|
||||
color-mix(in srgb, var(--app-widget-bg) 92%, transparent);
|
||||
overflow: hidden;
|
||||
|
||||
&::after {
|
||||
@@ -272,7 +260,7 @@
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
box-shadow:
|
||||
0 0 0 2px var(--mat-sys-surface),
|
||||
0 0 0 2px var(--app-content-bg),
|
||||
0 0 0 4px
|
||||
color-mix(
|
||||
in srgb,
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
}
|
||||
|
||||
.settings-delete-dialog__summary {
|
||||
@@ -42,14 +42,9 @@
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--mat-sys-outline) 46%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--app-separator) 46%, transparent);
|
||||
border-radius: 14px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container) 84%,
|
||||
transparent
|
||||
);
|
||||
background: color-mix(in srgb, var(--app-widget-bg) 84%, transparent);
|
||||
|
||||
strong {
|
||||
font-size: 1rem;
|
||||
@@ -68,7 +63,7 @@
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
font-size: 18px;
|
||||
color: var(--mat-sys-primary);
|
||||
color: var(--app-selection-color);
|
||||
}
|
||||
|
||||
.settings-delete-dialog__list {
|
||||
@@ -86,7 +81,7 @@
|
||||
var(--app-selection-surface) 32%,
|
||||
transparent
|
||||
);
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
}
|
||||
|
||||
.settings-delete-dialog__actions {
|
||||
|
||||
@@ -278,13 +278,8 @@
|
||||
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);
|
||||
background: color-mix(in srgb, var(--app-widget-bg) 80%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--app-separator) 60%, transparent);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@@ -299,7 +294,7 @@
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
background: transparent;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
font: inherit;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
@@ -320,10 +315,10 @@
|
||||
&:hover {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-highest) 70%,
|
||||
var(--app-card-hover-bg) 70%,
|
||||
transparent
|
||||
);
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@@ -334,7 +329,7 @@
|
||||
}
|
||||
|
||||
.theme-switcher__option--selected {
|
||||
background: var(--app-widget-bg, var(--mat-sys-surface));
|
||||
background: var(--app-widget-bg);
|
||||
color: var(--app-selection-color);
|
||||
font-weight: 600;
|
||||
box-shadow:
|
||||
@@ -358,7 +353,7 @@
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
padding: 24px 16px 16px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
|
||||
.epg-sources-empty__icon {
|
||||
font-size: 40px;
|
||||
@@ -372,7 +367,7 @@
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
.epg-sources-empty__hint {
|
||||
@@ -479,7 +474,7 @@
|
||||
|
||||
small {
|
||||
margin-top: 4px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
@@ -499,15 +494,10 @@
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--mat-sys-outline-variant) 70%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--app-separator) 70%, transparent);
|
||||
border-radius: 16px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-low) 88%,
|
||||
transparent
|
||||
);
|
||||
color: var(--mat-sys-on-surface);
|
||||
background: color-mix(in srgb, var(--app-widget-bg) 88%, transparent);
|
||||
color: var(--app-heading-color);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
border-color 0.18s ease,
|
||||
@@ -520,11 +510,11 @@
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--app-selection-border) 54%,
|
||||
var(--mat-sys-outline-variant)
|
||||
var(--app-separator)
|
||||
);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-high) 92%,
|
||||
var(--app-widget-header-bg) 92%,
|
||||
transparent
|
||||
);
|
||||
box-shadow: 0 10px 20px -22px rgba(15, 23, 42, 0.5);
|
||||
@@ -538,7 +528,7 @@
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
box-shadow:
|
||||
0 0 0 2px var(--mat-sys-surface),
|
||||
0 0 0 2px var(--app-content-bg),
|
||||
0 0 0 4px
|
||||
color-mix(in srgb, var(--app-selection-color) 40%, transparent);
|
||||
}
|
||||
@@ -553,10 +543,10 @@
|
||||
border-radius: 12px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-high) 82%,
|
||||
var(--app-widget-header-bg) 82%,
|
||||
transparent
|
||||
);
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
@@ -571,8 +561,8 @@
|
||||
}
|
||||
|
||||
.link-card--github .link-card__glyph {
|
||||
background: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent);
|
||||
color: var(--mat-sys-on-surface);
|
||||
background: color-mix(in srgb, var(--app-heading-color) 10%, transparent);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
.link-card--blog .link-card__glyph {
|
||||
@@ -625,9 +615,9 @@
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
font-size: 0.84rem;
|
||||
background: color-mix(in srgb, var(--mat-sys-primary) 8%, transparent);
|
||||
background: color-mix(in srgb, var(--app-selection-color) 8%, transparent);
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--mat-sys-primary) 30%, transparent);
|
||||
color-mix(in srgb, var(--app-selection-color) 30%, transparent);
|
||||
line-height: 1.45;
|
||||
|
||||
a {
|
||||
@@ -675,20 +665,20 @@
|
||||
border-radius: 12px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-high) 78%,
|
||||
var(--app-widget-header-bg) 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);
|
||||
color: var(--app-heading-color);
|
||||
|
||||
.danger-zone__summary-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -707,10 +697,10 @@
|
||||
border-radius: 999px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-on-surface) 10%,
|
||||
var(--app-heading-color) 10%,
|
||||
transparent
|
||||
);
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
@@ -720,7 +710,7 @@
|
||||
|
||||
.danger-zone__progress {
|
||||
margin: 0;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
@@ -812,10 +802,10 @@
|
||||
&[disabled] {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-on-surface) 12%,
|
||||
var(--app-heading-color) 12%,
|
||||
transparent
|
||||
);
|
||||
color: color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent);
|
||||
color: color-mix(in srgb, var(--app-heading-color) 38%, transparent);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,13 @@ html {
|
||||
// Live / on-air accent (broadcast indicator)
|
||||
--app-live-color: #d12c2c;
|
||||
|
||||
// Error / destructive accent (M3 error role)
|
||||
--app-error-color: #ba1a1a;
|
||||
|
||||
// Secondary cyan accent (M3 "tertiary" role): radio badges,
|
||||
// external-playback + success states
|
||||
--app-accent-color: #0e7490;
|
||||
|
||||
// Content area
|
||||
--app-content-shadow: -2px 4px 12px rgba(0, 0, 0, 0.06);
|
||||
|
||||
@@ -164,6 +171,13 @@ html {
|
||||
// Live / on-air accent (broadcast indicator)
|
||||
--app-live-color: #ff8f8f;
|
||||
|
||||
// Error / destructive accent (M3 error role)
|
||||
--app-error-color: #ffb4ab;
|
||||
|
||||
// Secondary cyan accent (M3 "tertiary" role): radio badges,
|
||||
// external-playback + success states
|
||||
--app-accent-color: #5cd6ff;
|
||||
|
||||
// Content area
|
||||
--app-content-shadow: -4px 8px 20px rgba(0, 0, 0, 0.18);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
border: 1px solid transparent;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
@@ -54,13 +54,13 @@
|
||||
min-width: 2ch;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&:hover:not(.selected):not(.active):not(.is-active) {
|
||||
background: var(--app-selection-surface);
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
&.selected,
|
||||
|
||||
@@ -154,7 +154,7 @@ textarea,
|
||||
|
||||
// ─── Dark Theme ───────────────────────────────────────────────────────────────
|
||||
.dark-theme {
|
||||
background: var(--mat-sys-surface) !important;
|
||||
background: var(--app-shell-bg) !important;
|
||||
|
||||
// Refined scrollbar for dark mode
|
||||
::-webkit-scrollbar {
|
||||
|
||||
+26
-22
@@ -13,28 +13,32 @@ module.exports = {
|
||||
sans: ['Inter', 'Roboto', 'Helvetica Neue', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
// Surface colors wired to Material sys vars so Tailwind
|
||||
// utilities automatically respect the active theme
|
||||
surface: 'var(--mat-sys-surface)',
|
||||
'surface-low': 'var(--mat-sys-surface-container-low)',
|
||||
'surface-mid': 'var(--mat-sys-surface-container)',
|
||||
'surface-high': 'var(--mat-sys-surface-container-high)',
|
||||
'on-surface': 'var(--mat-sys-on-surface)',
|
||||
'on-surface-variant': 'var(--mat-sys-on-surface-variant)',
|
||||
outline: 'var(--mat-sys-outline)',
|
||||
'outline-variant': 'var(--mat-sys-outline-variant)',
|
||||
primary: 'var(--mat-sys-primary)',
|
||||
'on-primary': 'var(--mat-sys-on-primary)',
|
||||
'primary-container': 'var(--mat-sys-primary-container)',
|
||||
'on-primary-container': 'var(--mat-sys-on-primary-container)',
|
||||
secondary: 'var(--mat-sys-secondary)',
|
||||
'secondary-container': 'var(--mat-sys-secondary-container)',
|
||||
'on-secondary-container':
|
||||
'var(--mat-sys-on-secondary-container)',
|
||||
tertiary: 'var(--mat-sys-tertiary)',
|
||||
'tertiary-container': 'var(--mat-sys-tertiary-container)',
|
||||
error: 'var(--mat-sys-error)',
|
||||
'error-container': 'var(--mat-sys-error-container)',
|
||||
// Surface colors wired to the --app-* design tokens
|
||||
// (m3-theme.scss) so Tailwind utilities automatically respect
|
||||
// the active theme. Never wire these to --mat-sys-* vars: the
|
||||
// current Material theme setup does not emit system tokens
|
||||
// (see docs/architecture/theme-design-tokens.md).
|
||||
surface: 'var(--app-content-bg)',
|
||||
'surface-low': 'var(--app-widget-bg)',
|
||||
'surface-mid': 'var(--app-widget-bg)',
|
||||
'surface-high': 'var(--app-widget-header-bg)',
|
||||
'on-surface': 'var(--app-heading-color)',
|
||||
'on-surface-variant': 'var(--app-body-color)',
|
||||
outline: 'var(--app-separator)',
|
||||
'outline-variant': 'var(--app-separator)',
|
||||
primary: 'var(--app-selection-color)',
|
||||
'on-primary': 'var(--app-selection-on-color)',
|
||||
'primary-container': 'var(--app-selection-surface)',
|
||||
'on-primary-container': 'var(--app-selection-color)',
|
||||
secondary: 'var(--app-body-color)',
|
||||
'secondary-container': 'var(--app-hover-overlay)',
|
||||
'on-secondary-container': 'var(--app-heading-color)',
|
||||
tertiary: 'var(--app-accent-color)',
|
||||
'tertiary-container':
|
||||
'color-mix(in srgb, var(--app-accent-color) 15%, transparent)',
|
||||
error: 'var(--app-error-color)',
|
||||
'error-container':
|
||||
'color-mix(in srgb, var(--app-error-color) 12%, transparent)',
|
||||
},
|
||||
borderRadius: {
|
||||
widget: '14px',
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# Theme design tokens: `--app-*` is canonical, `--mat-sys-*` is a no-op
|
||||
|
||||
## TL;DR
|
||||
|
||||
- The only CSS design tokens that exist at runtime are the `--app-*` custom
|
||||
properties defined (for light and dark) in `apps/web/src/m3-theme.scss`.
|
||||
- `var(--mat-sys-*)` references **do not work anywhere in this app**. The
|
||||
theme uses Angular Material's older M3 API (`mat.define-theme` +
|
||||
`mat.all-component-themes` / `mat.all-component-colors`), which emits
|
||||
_component_ tokens with literal values but never emits the `--mat-sys-*`
|
||||
_system_ tokens. Verified empirically via CDP on 2026-07-12: no stylesheet
|
||||
rule defines any `--mat-sys-*` variable.
|
||||
- CI fails when new `var(--mat-sys-*)` references are added
|
||||
(`tools/styles/check-mat-sys-usage.mjs`); legacy usages are grandfathered
|
||||
in `tools/styles/mat-sys-baseline.mjs`, which must only shrink.
|
||||
|
||||
## What a `--mat-sys-*` reference actually does today
|
||||
|
||||
Because the token is never defined, any declaration containing
|
||||
`var(--mat-sys-x)` without a fallback becomes _invalid at computed-value
|
||||
time_, which behaves like `unset`:
|
||||
|
||||
| Declaration shape | Actual rendering |
|
||||
| ------------------------------------------------------------------ | -------------------------------------------------------------- |
|
||||
| `color: var(--mat-sys-on-surface-variant)` | inherited text color |
|
||||
| `background: var(--mat-sys-surface-container)` | transparent |
|
||||
| `background: color-mix(..., var(--mat-sys-x) 12%, ...)` | transparent |
|
||||
| `border: 1px solid var(--mat-sys-outline-variant)` | **no border at all** |
|
||||
| `border-color: var(--mat-sys-primary)` (width/style set elsewhere) | `currentColor` border |
|
||||
| `--local-var: ... var(--mat-sys-x) ...` | `--local-var` becomes invalid too, cascading to every consumer |
|
||||
| `var(--app-x, var(--mat-sys-y))` | `--app-x` (fallback is dead but harmless) |
|
||||
|
||||
This means the app's accepted visual design was produced _with_ these
|
||||
no-ops. Making the tokens suddenly resolve (e.g. by migrating the theme to
|
||||
`mat.theme()`, available since Material v19) would flip hundreds of sites at
|
||||
once: surfaces, borders, and dividers that are currently invisible would
|
||||
appear app-wide with M3 azure-palette values that were never part of the
|
||||
hand-tuned design. That is why the fix strategy is **(b) replace usages
|
||||
incrementally with `--app-*` tokens**, not (a) emit the system tokens.
|
||||
|
||||
Every migration step must be screenshot-verified in both themes (see
|
||||
"Migration protocol" below) because replacing a no-op is a deliberate visual
|
||||
change: the borders/backgrounds the original author intended will start
|
||||
rendering.
|
||||
|
||||
## Token mapping for migration
|
||||
|
||||
When migrating a file, map by _role in context_, not mechanically:
|
||||
|
||||
| `--mat-sys-*` token | Replacement |
|
||||
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `on-surface` (text) | `--app-heading-color` (emphasis/hover text), `--app-body-color` (regular copy) |
|
||||
| `on-surface` (inside `color-mix` overlays) | `--app-on-surface` |
|
||||
| `on-surface-variant` | `--app-muted-color` (icons, captions, placeholders, meta), `--app-body-color` (secondary copy) |
|
||||
| `outline-variant`, `outline` | `--app-separator` (hairlines/dividers), `--app-widget-header-border` (faintest) |
|
||||
| `surface` | `--app-content-bg` (page-level), `--app-widget-bg` (component surface) |
|
||||
| `surface-container`, `-low` | `--app-widget-bg`; for chips/pills on a widget surface prefer `color-mix(in srgb, var(--app-on-surface) 6%, transparent)` |
|
||||
| `surface-container-high`, `-highest` | `--app-widget-header-bg` (raised surface), `--app-card-hover-bg` (hover state) |
|
||||
| `primary` | `--app-selection-color` |
|
||||
| `on-primary` | `--app-selection-on-color` |
|
||||
| `primary-container` | `--app-selection-surface` (`-strong` for emphasis) |
|
||||
| `error`, `error-container` | `--app-error-color`; surfaces via `color-mix(in srgb, var(--app-error-color) N%, transparent)` |
|
||||
| `tertiary`, `tertiary-container` | `--app-accent-color` (cyan accent: radio badge, external-playback and success states); surfaces via `color-mix` |
|
||||
|
||||
Dead-fallback usages `var(--app-x, var(--mat-sys-y))` are always safe to
|
||||
simplify to `var(--app-x)` — the fallback can never be taken (and if
|
||||
`--app-x` were undefined, the fallback would be invalid anyway, so behavior
|
||||
is identical in every case).
|
||||
|
||||
## Migration protocol
|
||||
|
||||
1. Replace the file's `var(--mat-sys-*)` references using the table above.
|
||||
2. Run the app (`pnpm nx serve electron-backend`, CDP on 9222) and
|
||||
screenshot the affected views in **both** light and dark themes; compare
|
||||
against pre-change screenshots. Newly appearing borders/backgrounds must
|
||||
look intentional and consistent with the `--app-*` design language.
|
||||
3. Regenerate the baseline: `node tools/styles/generate-mat-sys-baseline.mjs`
|
||||
and commit the shrunken `tools/styles/mat-sys-baseline.mjs`.
|
||||
4. `pnpm run lint:styles` must pass.
|
||||
|
||||
## Guard
|
||||
|
||||
- `pnpm run lint:styles` → `tools/styles/check-mat-sys-usage.mjs` scans
|
||||
`apps/` and `libs/` (`.scss`, `.css`, `.ts`, `.html`, `.js`) and fails if
|
||||
any file has more `var(--mat-sys-` references than its baseline entry.
|
||||
- CI runs it in the Lint job (`.github/workflows/ci.yml`).
|
||||
- Never add new entries or raise counts in the baseline.
|
||||
+36
-39
@@ -15,7 +15,7 @@
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: color-mix(in srgb, var(--app-on-surface) 5%, transparent);
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--mat-sys-outline-variant) 36%, transparent);
|
||||
color-mix(in srgb, var(--app-separator) 36%, transparent);
|
||||
app-region: no-drag;
|
||||
|
||||
&:hover {
|
||||
@@ -26,7 +26,7 @@
|
||||
);
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-outline-variant) 64%,
|
||||
var(--app-separator) 64%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
@@ -39,7 +39,7 @@
|
||||
);
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-outline-variant) 72%,
|
||||
var(--app-separator) 72%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
@@ -68,7 +68,7 @@
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
|
||||
.playlist-info {
|
||||
@@ -89,7 +89,7 @@
|
||||
font-size: 0.74em;
|
||||
line-height: 1.3;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,14 +105,15 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
// Body-tier so the refresh control reads as active, not disabled.
|
||||
color: var(--app-body-color);
|
||||
transition:
|
||||
color 0.15s ease,
|
||||
background 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: var(--mat-sys-on-surface);
|
||||
background: var(--mat-sys-surface-container);
|
||||
color: var(--app-heading-color);
|
||||
background: var(--app-hover-overlay);
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-button-touch-target {
|
||||
@@ -155,7 +156,7 @@
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
margin-left: 4px;
|
||||
|
||||
@@ -165,7 +166,7 @@
|
||||
}
|
||||
|
||||
::ng-deep .playlist-switcher-menu.mat-mdc-menu-panel {
|
||||
--mat-menu-container-color: var(--mat-sys-surface-container-high);
|
||||
--mat-menu-container-color: var(--app-widget-header-bg);
|
||||
width: min(
|
||||
var(--playlist-switcher-overlay-width, 400px),
|
||||
calc(100vw - 24px)
|
||||
@@ -180,7 +181,7 @@
|
||||
) !important;
|
||||
margin-top: -1px !important;
|
||||
border-radius: 0 0 12px 12px !important;
|
||||
border: 1px solid var(--mat-sys-outline) !important;
|
||||
border: 1px solid var(--app-separator) !important;
|
||||
border-top: 0 !important;
|
||||
overflow: hidden !important;
|
||||
background: var(--mat-menu-container-color) !important;
|
||||
@@ -225,14 +226,14 @@
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
opacity: 0.8;
|
||||
padding: 0;
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
opacity: 1;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
@@ -260,15 +261,15 @@
|
||||
}
|
||||
|
||||
.search-container:focus-within {
|
||||
border-color: var(--mat-sys-primary);
|
||||
background: var(--mat-sys-surface-container-highest);
|
||||
border-color: var(--app-selection-color);
|
||||
background: var(--app-card-hover-bg);
|
||||
}
|
||||
|
||||
.search-container .search-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
|
||||
.search-container .search-input {
|
||||
@@ -284,7 +285,7 @@
|
||||
}
|
||||
|
||||
.search-container .search-input::placeholder {
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
|
||||
.search-container .clear-btn {
|
||||
@@ -313,13 +314,9 @@
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--mat-sys-outline-variant) 72%, transparent);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-high) 78%,
|
||||
transparent
|
||||
);
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color-mix(in srgb, var(--app-separator) 72%, transparent);
|
||||
background: var(--app-hover-overlay);
|
||||
color: var(--app-muted-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
@@ -328,8 +325,8 @@
|
||||
transition: all 0.16s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--mat-sys-surface-container);
|
||||
color: var(--mat-sys-on-surface);
|
||||
background: var(--app-hover-overlay);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
@@ -338,7 +335,7 @@
|
||||
--app-selection-surface-strong,
|
||||
var(--app-selection-surface)
|
||||
);
|
||||
color: var(--app-selection-color, var(--mat-sys-primary));
|
||||
color: var(--app-selection-color);
|
||||
/* box-shadow:
|
||||
inset 0 0 0 1px
|
||||
color-mix(in srgb, var(--app-selection-color) 18%, transparent),
|
||||
@@ -348,7 +345,7 @@
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--app-selection-border, var(--mat-sys-primary));
|
||||
outline: 2px solid var(--app-selection-border);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
}
|
||||
@@ -409,7 +406,7 @@
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
flex-shrink: 0;
|
||||
color: var(--mat-sys-on-surface-variant);padding: 0;
|
||||
color: var(--app-muted-color);padding: 0;
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
@@ -429,7 +426,7 @@
|
||||
::ng-deep .playlist-item-action-menu {
|
||||
.danger,
|
||||
.danger mat-icon {
|
||||
color: var(--mat-sys-error, #f44336);
|
||||
color: var(--app-error-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,7 +458,7 @@
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
@@ -524,8 +521,8 @@
|
||||
font-size: 12px !important;
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
color: var(--mat-sys-primary);
|
||||
background-color: var(--mat-menu-container-color, var(--mat-sys-surface));
|
||||
color: var(--app-selection-color);
|
||||
background-color: var(--mat-menu-container-color);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -553,7 +550,7 @@
|
||||
font-size: 0.73em;
|
||||
line-height: 1.25;
|
||||
letter-spacing: 0.01em;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
@@ -562,7 +559,7 @@
|
||||
white-space: nowrap;
|
||||
|
||||
.separator {
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,7 +570,7 @@
|
||||
justify-content: center;
|
||||
padding: 32px 16px;
|
||||
gap: 12px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
|
||||
mat-icon {
|
||||
font-size: 36px;
|
||||
@@ -599,7 +596,7 @@
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
font: inherit;
|
||||
font-size: 0.86em;
|
||||
font-weight: 500;
|
||||
@@ -609,8 +606,8 @@
|
||||
color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--mat-sys-surface-container);
|
||||
color: var(--mat-sys-on-surface);
|
||||
background: var(--app-hover-overlay);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
|
||||
+1
-5
@@ -87,11 +87,7 @@ const EPG_URL_PATTERN = /^\s*(http|https|file):\/\/[^ "]+\s*$/;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
border: 1px solid
|
||||
var(
|
||||
--app-widget-header-border,
|
||||
var(--mat-sys-outline-variant)
|
||||
);
|
||||
border: 1px solid var(--app-widget-header-border);
|
||||
border-radius: 8px;
|
||||
background: var(--mat-sys-surface-container-low);
|
||||
}
|
||||
|
||||
+3
-6
@@ -73,7 +73,7 @@
|
||||
flex-shrink: 0;
|
||||
min-height: 32px;
|
||||
border-radius: 999px;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-body-color);
|
||||
--mdc-text-button-label-text-color: currentColor;
|
||||
--mat-text-button-state-layer-color: currentColor;
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-body-color);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
@@ -174,10 +174,7 @@ mat-paginator {
|
||||
flex-shrink: 0;
|
||||
// Shrink paginator text to fit header height comfortably
|
||||
--mat-paginator-container-size: 40px;
|
||||
--mat-paginator-enabled-icon-color: var(
|
||||
--app-body-color,
|
||||
var(--mat-sys-on-surface-variant)
|
||||
);
|
||||
--mat-paginator-enabled-icon-color: var(--app-body-color);
|
||||
--mat-paginator-disabled-icon-color: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
overflow: hidden;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high)) 56%,
|
||||
var(--app-body-color, var(--mat-sys-on-surface-variant)) 44%
|
||||
var(--app-widget-header-bg) 56%,
|
||||
var(--app-body-color) 44%
|
||||
);
|
||||
|
||||
&::after {
|
||||
@@ -23,7 +23,7 @@
|
||||
transparent 8%,
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--app-heading-color, var(--mat-sys-on-surface)) 18%,
|
||||
var(--app-heading-color) 18%,
|
||||
transparent
|
||||
)
|
||||
44%,
|
||||
@@ -57,7 +57,7 @@
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
padding: 24px 16px 12px;
|
||||
border-bottom: 1px solid var(--mat-sys-outline-variant);
|
||||
border-bottom: 1px solid var(--app-separator);
|
||||
|
||||
&-left {
|
||||
display: flex;
|
||||
@@ -92,8 +92,8 @@
|
||||
}
|
||||
|
||||
&__badge {
|
||||
background: var(--mat-sys-primary);
|
||||
color: var(--mat-sys-on-primary);
|
||||
background: var(--app-selection-color);
|
||||
color: var(--app-selection-on-color);
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
@@ -108,19 +108,19 @@
|
||||
padding: 7px 10px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-high) 86%,
|
||||
var(--app-widget-header-bg) 86%,
|
||||
transparent
|
||||
);
|
||||
border: 1px solid var(--mat-sys-outline-variant);
|
||||
border: 1px solid var(--app-separator);
|
||||
border-radius: 8px;
|
||||
box-shadow: inset 0 1px 0
|
||||
color-mix(in srgb, var(--mat-sys-on-surface) 8%, transparent);
|
||||
color-mix(in srgb, var(--app-on-surface) 8%, transparent);
|
||||
|
||||
mat-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
font-size: 16px;
|
||||
color: var(--mat-sys-primary);
|
||||
color: var(--app-selection-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@
|
||||
'JetBrains Mono', 'SFMono-Regular', Menlo, Monaco, Consolas,
|
||||
monospace;
|
||||
font-size: 0.78rem;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -143,7 +143,7 @@
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
|
||||
mat-icon {
|
||||
font-size: 4rem;
|
||||
@@ -158,7 +158,7 @@
|
||||
font-size: 1.05rem;
|
||||
font-weight: 520;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
span {
|
||||
@@ -198,7 +198,7 @@
|
||||
font-size: 0.86rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
&__skeleton-item {
|
||||
@@ -212,10 +212,10 @@
|
||||
border-radius: 14px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-bg, var(--mat-sys-surface-container-high)) 88%,
|
||||
var(--mat-sys-surface-container) 12%
|
||||
var(--app-widget-bg) 88%,
|
||||
var(--app-widget-header-bg) 12%
|
||||
);
|
||||
border: 1px solid var(--app-separator, var(--mat-sys-outline-variant));
|
||||
border: 1px solid var(--app-separator);
|
||||
box-shadow: var(--app-widget-shadow, none);
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@
|
||||
width: 64px;
|
||||
height: 90px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--app-separator, var(--mat-sys-outline-variant));
|
||||
border: 1px solid var(--app-separator);
|
||||
}
|
||||
|
||||
&__skeleton-info {
|
||||
@@ -281,7 +281,7 @@
|
||||
border-top: 1px solid
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--app-separator, var(--mat-sys-outline-variant)) 72%,
|
||||
var(--app-separator) 72%,
|
||||
transparent
|
||||
);
|
||||
|
||||
@@ -300,12 +300,9 @@
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
min-width: 0;
|
||||
background: var(
|
||||
--app-widget-bg,
|
||||
var(--mat-sys-surface-container-high)
|
||||
);
|
||||
background: var(--app-widget-bg);
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--app-separator, var(--mat-sys-outline-variant));
|
||||
border: 1px solid var(--app-separator);
|
||||
box-shadow: var(--app-widget-shadow, none);
|
||||
transition:
|
||||
background 0.18s ease,
|
||||
@@ -317,7 +314,7 @@
|
||||
cursor: pointer;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary);
|
||||
outline: 2px solid var(--app-selection-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
@@ -341,14 +338,11 @@
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
background: var(
|
||||
--app-widget-header-bg,
|
||||
var(--mat-sys-surface-container-high)
|
||||
);
|
||||
background: var(--app-widget-header-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--app-separator, var(--mat-sys-outline-variant));
|
||||
border: 1px solid var(--app-separator);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
@@ -360,7 +354,7 @@
|
||||
font-size: 2rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
@@ -377,7 +371,7 @@
|
||||
&-title {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
@@ -399,7 +393,7 @@
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
}
|
||||
|
||||
&-status {
|
||||
@@ -409,10 +403,7 @@
|
||||
font-weight: 500;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
background: var(
|
||||
--app-widget-header-bg,
|
||||
var(--mat-sys-surface-container-high)
|
||||
);
|
||||
background: var(--app-widget-header-bg);
|
||||
|
||||
mat-icon {
|
||||
font-size: 1.1rem;
|
||||
@@ -431,16 +422,16 @@
|
||||
}
|
||||
|
||||
&.status-failed {
|
||||
color: var(--mat-sys-error);
|
||||
color: var(--app-error-color);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-error) 12%,
|
||||
var(--app-error-color) 12%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
&.status-queued {
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-body-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,11 +445,11 @@
|
||||
}
|
||||
|
||||
&-error {
|
||||
color: var(--mat-sys-error);
|
||||
color: var(--app-error-color);
|
||||
font-size: 0.8rem;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-error) 12%,
|
||||
var(--app-error-color) 12%,
|
||||
transparent
|
||||
);
|
||||
padding: 2px 8px;
|
||||
@@ -475,7 +466,7 @@
|
||||
border-top: 1px solid
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--app-separator, var(--mat-sys-outline-variant)) 72%,
|
||||
var(--app-separator) 72%,
|
||||
transparent
|
||||
);
|
||||
|
||||
@@ -514,8 +505,8 @@
|
||||
}
|
||||
|
||||
&__episode-badge {
|
||||
background: var(--mat-sys-primary);
|
||||
color: var(--mat-sys-on-primary);
|
||||
background: var(--app-selection-color);
|
||||
color: var(--app-selection-on-color);
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.7rem;
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
.content-header {
|
||||
padding: 8px 12px;
|
||||
min-height: 48px;
|
||||
background: var(--app-widget-header-bg, var(--mat-sys-surface-container-high));
|
||||
background: var(--app-widget-header-bg);
|
||||
border-bottom: 1px solid var(--app-separator);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -59,7 +59,7 @@ mat-card:hover {
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
border-radius: 10px;
|
||||
|
||||
mat-icon {
|
||||
@@ -101,7 +101,7 @@ mat-card:hover {
|
||||
overflow: hidden;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.35;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-body-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@
|
||||
gap: 14px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-body-color);
|
||||
|
||||
img {
|
||||
width: min(100%, var(--error-view-illustration-size));
|
||||
@@ -69,7 +69,7 @@
|
||||
font-size: var(--error-view-title-size);
|
||||
font-weight: var(--error-view-title-weight);
|
||||
line-height: 1.12;
|
||||
color: var(--app-heading-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-heading-color);
|
||||
letter-spacing: var(--error-view-title-spacing);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
@@ -78,17 +78,3 @@ nav.flex .portal-rail-link.active {
|
||||
inset 0 0 0 1px var(--app-selection-border),
|
||||
0 10px 20px -16px var(--app-selection-glow);
|
||||
}
|
||||
|
||||
nav.flex .portal-rail-link.is-active::after,
|
||||
nav.flex .portal-rail-link.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
border-radius: 999px;
|
||||
background: var(--app-selection-color);
|
||||
box-shadow: 0 0 10px var(--app-selection-glow);
|
||||
}
|
||||
@@ -15,13 +15,13 @@
|
||||
overflow: hidden;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high)) 84%,
|
||||
var(--app-widget-bg, var(--mat-sys-surface-container)) 16%
|
||||
var(--app-widget-header-bg) 84%,
|
||||
var(--app-widget-bg) 16%
|
||||
);
|
||||
border: 1px solid
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-border, var(--mat-sys-outline-variant)) 94%,
|
||||
var(--app-widget-header-border) 94%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
@@ -35,8 +35,8 @@
|
||||
border-radius: 999px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high)) 56%,
|
||||
var(--app-body-color, var(--mat-sys-on-surface-variant)) 44%
|
||||
var(--app-widget-header-bg) 56%,
|
||||
var(--app-body-color) 44%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
border-radius: 999px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high)) 52%,
|
||||
var(--app-body-color, var(--mat-sys-on-surface-variant)) 48%
|
||||
var(--app-widget-header-bg) 52%,
|
||||
var(--app-body-color) 48%
|
||||
);
|
||||
|
||||
&--primary {
|
||||
@@ -87,8 +87,8 @@
|
||||
flex-shrink: 0;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high)) 60%,
|
||||
var(--app-body-color, var(--mat-sys-on-surface-variant)) 40%
|
||||
var(--app-widget-header-bg) 60%,
|
||||
var(--app-body-color) 40%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,8 +99,8 @@
|
||||
flex-shrink: 0;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high)) 84%,
|
||||
var(--app-widget-bg, var(--mat-sys-surface-container)) 16%
|
||||
var(--app-widget-header-bg) 84%,
|
||||
var(--app-widget-bg) 16%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@
|
||||
flex-shrink: 0;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high)) 60%,
|
||||
var(--app-body-color, var(--mat-sys-on-surface-variant)) 40%
|
||||
var(--app-widget-header-bg) 60%,
|
||||
var(--app-body-color) 40%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
transparent 10%,
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--app-heading-color, var(--mat-sys-on-surface)) 20%,
|
||||
var(--app-heading-color) 20%,
|
||||
transparent
|
||||
)
|
||||
46%,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:host {
|
||||
display: block;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-body-color);
|
||||
}
|
||||
|
||||
// ─── Hero header ────────────────────────────────────────────────────────────
|
||||
@@ -11,7 +11,7 @@
|
||||
gap: 18px;
|
||||
background:
|
||||
var(--app-accent-gradient),
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high));
|
||||
var(--app-widget-header-bg);
|
||||
border-bottom: 1px solid var(--app-widget-header-border, rgba(255, 255, 255, 0.05));
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
color: var(--app-eyebrow-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-eyebrow-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
@@ -36,8 +36,8 @@
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--app-provider-xtream, var(--mat-sys-primary));
|
||||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--app-provider-xtream, var(--mat-sys-primary)) 14%, transparent);
|
||||
background: var(--app-provider-xtream);
|
||||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--app-provider-xtream) 14%, transparent);
|
||||
}
|
||||
|
||||
.account-dialog__eyebrow-divider {
|
||||
@@ -61,12 +61,12 @@
|
||||
line-height: 1.2;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--app-heading-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
.account-dialog__subtitle {
|
||||
margin: 4px 0 0;
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--app-separator);
|
||||
background: var(--mat-sys-surface-container);
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface));
|
||||
background: var(--app-hover-overlay);
|
||||
color: var(--app-body-color);
|
||||
font-size: 0.76rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
@@ -111,14 +111,14 @@
|
||||
.account-dialog__loading-copy {
|
||||
margin: 0;
|
||||
max-width: 60ch;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-body-color);
|
||||
}
|
||||
|
||||
.account-dialog__message {
|
||||
padding: 10px 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--app-separator);
|
||||
background: var(--mat-sys-surface-container);
|
||||
background: var(--app-hover-overlay);
|
||||
line-height: 1.5;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
@@ -136,7 +136,7 @@
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--app-widget-border, transparent);
|
||||
background: var(--app-widget-bg, var(--mat-sys-surface-container));
|
||||
background: var(--app-widget-bg);
|
||||
box-shadow: var(--app-widget-shadow, none);
|
||||
// Subtle inset highlight matching widget-shell pattern
|
||||
outline: 1px solid rgba(255, 255, 255, 0.04);
|
||||
@@ -150,8 +150,8 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
background: color-mix(in srgb, var(--app-provider-xtream, var(--mat-sys-primary)) 12%, transparent);
|
||||
color: var(--app-provider-xtream, var(--mat-sys-primary));
|
||||
background: color-mix(in srgb, var(--app-provider-xtream) 12%, transparent);
|
||||
color: var(--app-provider-xtream);
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
@@ -161,7 +161,7 @@
|
||||
}
|
||||
|
||||
.account-stat__label {
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
@@ -172,7 +172,7 @@
|
||||
font-size: 1.3rem;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--app-heading-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
.account-stat__meter {
|
||||
@@ -187,14 +187,14 @@
|
||||
display: block;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: var(--app-provider-xtream, var(--mat-sys-primary));
|
||||
background: var(--app-provider-xtream);
|
||||
}
|
||||
|
||||
// ─── Content area ───────────────────────────────────────────────────────────
|
||||
.account-dialog__content {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: var(--app-widget-bg, var(--mat-sys-surface));
|
||||
background: var(--app-widget-bg);
|
||||
}
|
||||
|
||||
.account-dialog__grid {
|
||||
@@ -210,7 +210,7 @@
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--app-widget-border, transparent);
|
||||
background: var(--app-widget-bg, var(--mat-sys-surface-container-low));
|
||||
background: var(--app-widget-bg);
|
||||
box-shadow: var(--app-widget-shadow, none);
|
||||
outline: 1px solid rgba(255, 255, 255, 0.04);
|
||||
outline-offset: -1px;
|
||||
@@ -226,12 +226,12 @@
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--app-heading-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
.account-panel__eyebrow {
|
||||
margin: 0;
|
||||
color: var(--app-eyebrow-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-eyebrow-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
@@ -261,7 +261,7 @@
|
||||
|
||||
.account-detail-row dt {
|
||||
margin: 0;
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
@@ -270,7 +270,7 @@
|
||||
|
||||
.account-detail-row dd {
|
||||
margin: 0;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-body-color);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
word-break: break-word;
|
||||
@@ -282,7 +282,7 @@
|
||||
}
|
||||
|
||||
.account-detail-row__value--accent {
|
||||
color: var(--app-provider-xtream, var(--mat-sys-primary));
|
||||
color: var(--app-provider-xtream);
|
||||
}
|
||||
|
||||
.account-detail-row__value--positive {
|
||||
@@ -301,7 +301,7 @@
|
||||
|
||||
.account-chip-section__label {
|
||||
margin: 0;
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
@@ -320,9 +320,9 @@
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid color-mix(in srgb, var(--app-provider-xtream, var(--mat-sys-primary)) 16%, transparent);
|
||||
background: color-mix(in srgb, var(--app-provider-xtream, var(--mat-sys-primary)) 8%, transparent);
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface));
|
||||
border: 1px solid color-mix(in srgb, var(--app-provider-xtream) 16%, transparent);
|
||||
background: color-mix(in srgb, var(--app-provider-xtream) 8%, transparent);
|
||||
color: var(--app-body-color);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
@@ -335,8 +335,8 @@
|
||||
|
||||
.account-chip--muted {
|
||||
border-color: var(--app-separator);
|
||||
background: var(--mat-sys-surface-container);
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
background: var(--app-hover-overlay);
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
|
||||
// ─── Error state ────────────────────────────────────────────────────────────
|
||||
@@ -347,7 +347,7 @@
|
||||
padding: 36px 20px;
|
||||
border-radius: 16px;
|
||||
border: 1px dashed var(--app-separator);
|
||||
background: var(--app-widget-bg, var(--mat-sys-surface-container-low));
|
||||
background: var(--app-widget-bg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@
|
||||
|
||||
.account-dialog__error-state p {
|
||||
max-width: 52ch;
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
line-height: 1.5;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
@@ -370,8 +370,8 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 14px;
|
||||
background: color-mix(in srgb, var(--mat-sys-error) 12%, transparent);
|
||||
color: var(--mat-sys-error);
|
||||
background: color-mix(in srgb, var(--app-error-color) 12%, transparent);
|
||||
color: var(--app-error-color);
|
||||
}
|
||||
|
||||
// ─── Skeleton loading ───────────────────────────────────────────────────────
|
||||
@@ -394,7 +394,7 @@
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
color-mix(in srgb, var(--app-heading-color, var(--mat-sys-on-surface)) 10%, transparent),
|
||||
color-mix(in srgb, var(--app-heading-color) 10%, transparent),
|
||||
transparent
|
||||
);
|
||||
animation: account-dialog-shimmer 1.4s infinite;
|
||||
@@ -426,7 +426,7 @@
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
border-top: 1px solid var(--app-widget-header-border, rgba(255, 255, 255, 0.05));
|
||||
background: var(--app-widget-bg, var(--mat-sys-surface));
|
||||
background: var(--app-widget-bg);
|
||||
}
|
||||
|
||||
.account-dialog__close {
|
||||
|
||||
+13
-13
@@ -27,10 +27,10 @@
|
||||
|
||||
.selection-info {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
|
||||
strong {
|
||||
color: var(--mat-sys-primary);
|
||||
color: var(--app-selection-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,17 +50,17 @@
|
||||
padding: 0 10px;
|
||||
min-height: 36px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--app-search-border, var(--mat-sys-outline-variant));
|
||||
background: var(--app-search-bg, var(--mat-sys-surface-container));
|
||||
border: 1px solid var(--app-search-border);
|
||||
background: var(--app-search-bg);
|
||||
cursor: text;
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--mat-sys-primary);
|
||||
border-color: var(--app-selection-color);
|
||||
box-shadow: 0 0 0 3px
|
||||
color-mix(in srgb, var(--mat-sys-primary) 12%, transparent);
|
||||
color-mix(in srgb, var(--app-selection-color) 12%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -77,14 +77,14 @@
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
font: inherit;
|
||||
font-size: 0.84rem;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
flex: 1;
|
||||
min-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--mat-sys-outline-variant);
|
||||
border: 1px solid var(--app-separator);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@@ -126,11 +126,11 @@
|
||||
transition: background-color 0.12s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--mat-sys-surface-container);
|
||||
background-color: var(--app-hover-overlay);
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid var(--mat-sys-surface-container);
|
||||
border-bottom: 1px solid var(--app-widget-header-border);
|
||||
}
|
||||
|
||||
mat-checkbox {
|
||||
@@ -149,7 +149,7 @@
|
||||
.empty-message {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
|
||||
+1
-4
@@ -61,10 +61,7 @@ mat-paginator {
|
||||
background: transparent !important;
|
||||
flex-shrink: 0;
|
||||
--mat-paginator-container-size: 40px;
|
||||
--mat-paginator-enabled-icon-color: var(
|
||||
--app-body-color,
|
||||
var(--mat-sys-on-surface-variant)
|
||||
);
|
||||
--mat-paginator-enabled-icon-color: var(--app-body-color);
|
||||
--mat-paginator-disabled-icon-color: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--app-widget-header-bg, var(--mat-sys-surface-container-high));
|
||||
background: var(--app-widget-header-bg);
|
||||
border-bottom: 1px solid var(--app-separator);
|
||||
app-region: drag;
|
||||
}
|
||||
|
||||
+34
-34
@@ -1,6 +1,6 @@
|
||||
:host {
|
||||
display: block;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-body-color);
|
||||
}
|
||||
|
||||
// ─── Hero header ────────────────────────────────────────────────────────────
|
||||
@@ -11,7 +11,7 @@
|
||||
gap: 18px;
|
||||
background:
|
||||
var(--app-accent-gradient),
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high));
|
||||
var(--app-widget-header-bg);
|
||||
border-bottom: 1px solid var(--app-widget-header-border, rgba(255, 255, 255, 0.05));
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
color: var(--app-eyebrow-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-eyebrow-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
@@ -36,8 +36,8 @@
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--mat-sys-primary);
|
||||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--mat-sys-primary) 14%, transparent);
|
||||
background: var(--app-selection-color);
|
||||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--app-selection-color) 14%, transparent);
|
||||
}
|
||||
|
||||
.channel-dialog__headline-row {
|
||||
@@ -59,7 +59,7 @@
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
object-fit: contain;
|
||||
background: var(--mat-sys-surface-container);
|
||||
background: var(--app-widget-bg);
|
||||
border: 1px solid var(--app-separator);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -70,12 +70,12 @@
|
||||
line-height: 1.2;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--app-heading-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
.channel-dialog__subtitle {
|
||||
margin: 4px 0 0;
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
@@ -96,8 +96,8 @@
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--app-separator);
|
||||
background: var(--mat-sys-surface-container);
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface));
|
||||
background: var(--app-widget-bg);
|
||||
color: var(--app-body-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
@@ -111,15 +111,15 @@
|
||||
}
|
||||
|
||||
.channel-dialog__status-pill--radio {
|
||||
border-color: color-mix(in srgb, var(--mat-sys-tertiary) 30%, transparent);
|
||||
background: color-mix(in srgb, var(--mat-sys-tertiary) 12%, transparent);
|
||||
color: var(--mat-sys-tertiary);
|
||||
border-color: color-mix(in srgb, var(--app-accent-color) 30%, transparent);
|
||||
background: color-mix(in srgb, var(--app-accent-color) 12%, transparent);
|
||||
color: var(--app-accent-color);
|
||||
}
|
||||
|
||||
.channel-dialog__status-pill--playback {
|
||||
border-color: color-mix(in srgb, var(--mat-sys-primary) 30%, transparent);
|
||||
background: color-mix(in srgb, var(--mat-sys-primary) 12%, transparent);
|
||||
color: var(--mat-sys-primary);
|
||||
border-color: color-mix(in srgb, var(--app-selection-color) 30%, transparent);
|
||||
background: color-mix(in srgb, var(--app-selection-color) 12%, transparent);
|
||||
color: var(--app-selection-color);
|
||||
}
|
||||
|
||||
// ─── Hero stat cards ────────────────────────────────────────────────────────
|
||||
@@ -135,7 +135,7 @@
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--app-widget-border, transparent);
|
||||
background: var(--app-widget-bg, var(--mat-sys-surface-container));
|
||||
background: var(--app-widget-bg);
|
||||
box-shadow: var(--app-widget-shadow, none);
|
||||
outline: 1px solid rgba(255, 255, 255, 0.04);
|
||||
outline-offset: -1px;
|
||||
@@ -148,8 +148,8 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
background: color-mix(in srgb, var(--mat-sys-primary) 12%, transparent);
|
||||
color: var(--mat-sys-primary);
|
||||
background: color-mix(in srgb, var(--app-selection-color) 12%, transparent);
|
||||
color: var(--app-selection-color);
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
@@ -159,7 +159,7 @@
|
||||
}
|
||||
|
||||
.channel-stat__label {
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
@@ -170,7 +170,7 @@
|
||||
font-size: 1.1rem;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--app-heading-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-heading-color);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -181,7 +181,7 @@
|
||||
}
|
||||
|
||||
&--empty {
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-size: 0.88rem;
|
||||
@@ -192,7 +192,7 @@
|
||||
.channel-dialog__content {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: var(--app-widget-bg, var(--mat-sys-surface));
|
||||
background: var(--app-widget-bg);
|
||||
}
|
||||
|
||||
.channel-dialog__grid {
|
||||
@@ -209,7 +209,7 @@
|
||||
padding: 10px 10px 10px 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--app-separator);
|
||||
background: var(--mat-sys-surface-container);
|
||||
background: var(--app-widget-bg);
|
||||
|
||||
code {
|
||||
flex: 1;
|
||||
@@ -217,7 +217,7 @@
|
||||
font-family: 'JetBrains Mono', 'Roboto Mono', monospace;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.5;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-body-color);
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
@@ -229,7 +229,7 @@
|
||||
height: 32px;
|
||||
--mdc-icon-button-state-layer-size: 32px;
|
||||
--mdc-icon-button-icon-size: 18px;
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
|
||||
// ─── Detail panels ──────────────────────────────────────────────────────────
|
||||
@@ -239,7 +239,7 @@
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--app-widget-border, transparent);
|
||||
background: var(--app-widget-bg, var(--mat-sys-surface-container-low));
|
||||
background: var(--app-widget-bg);
|
||||
box-shadow: var(--app-widget-shadow, none);
|
||||
outline: 1px solid rgba(255, 255, 255, 0.04);
|
||||
outline-offset: -1px;
|
||||
@@ -256,12 +256,12 @@
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--app-heading-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
.channel-panel__eyebrow {
|
||||
margin: 0;
|
||||
color: var(--app-eyebrow-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-eyebrow-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
@@ -291,7 +291,7 @@
|
||||
|
||||
.channel-detail-row dt {
|
||||
margin: 0;
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
@@ -300,7 +300,7 @@
|
||||
|
||||
.channel-detail-row dd {
|
||||
margin: 0;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-body-color);
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
word-break: break-word;
|
||||
@@ -313,7 +313,7 @@
|
||||
}
|
||||
|
||||
.channel-detail-row__value--empty {
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@
|
||||
|
||||
.channel-chip-section__label {
|
||||
margin: 0;
|
||||
color: var(--app-eyebrow-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-eyebrow-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
@@ -342,7 +342,7 @@
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
border-top: 1px solid var(--app-widget-header-border, rgba(255, 255, 255, 0.05));
|
||||
background: var(--app-widget-bg, var(--mat-sys-surface));
|
||||
background: var(--app-widget-bg);
|
||||
}
|
||||
|
||||
.channel-dialog__close {
|
||||
|
||||
+14
-14
@@ -18,10 +18,10 @@
|
||||
|
||||
.selection-info {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
|
||||
strong {
|
||||
color: var(--mat-sys-primary);
|
||||
color: var(--app-selection-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,17 +41,17 @@
|
||||
padding: 0 10px;
|
||||
min-height: 36px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--app-search-border, var(--mat-sys-outline-variant));
|
||||
background: var(--app-search-bg, var(--mat-sys-surface-container));
|
||||
border: 1px solid var(--app-search-border);
|
||||
background: var(--app-search-bg);
|
||||
cursor: text;
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--mat-sys-primary);
|
||||
border-color: var(--app-selection-color);
|
||||
box-shadow: 0 0 0 3px
|
||||
color-mix(in srgb, var(--mat-sys-primary) 12%, transparent);
|
||||
color-mix(in srgb, var(--app-selection-color) 12%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
font-size: 18px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -70,12 +70,12 @@
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
font: inherit;
|
||||
font-size: 0.84rem;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
flex: 1;
|
||||
min-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--mat-sys-outline-variant);
|
||||
border: 1px solid var(--app-separator);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@@ -117,11 +117,11 @@
|
||||
transition: background-color 0.12s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--mat-sys-surface-container);
|
||||
background-color: var(--app-hover-overlay);
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid var(--mat-sys-surface-container);
|
||||
border-bottom: 1px solid var(--app-widget-header-border);
|
||||
}
|
||||
|
||||
mat-checkbox {
|
||||
@@ -139,12 +139,12 @@
|
||||
|
||||
.item-count {
|
||||
font-size: 0.8rem;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
+6
-6
@@ -16,7 +16,7 @@
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: var(--app-content-bg, var(--mat-sys-surface));
|
||||
background: var(--app-content-bg);
|
||||
}
|
||||
|
||||
.groups-nav-panel {
|
||||
@@ -27,7 +27,7 @@
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
background: var(--app-rail-bg, var(--mat-sys-surface-container-low));
|
||||
background: var(--app-rail-bg);
|
||||
padding: 12px 10px 10px 12px;
|
||||
box-sizing: border-box;
|
||||
container-type: inline-size;
|
||||
@@ -96,8 +96,8 @@
|
||||
padding: 0 10px;
|
||||
min-height: 36px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--app-search-border, var(--mat-sys-outline-variant));
|
||||
background: var(--app-search-bg, var(--mat-sys-surface-container));
|
||||
border: 1px solid var(--app-search-border);
|
||||
background: var(--app-search-bg);
|
||||
cursor: text;
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
@@ -194,7 +194,7 @@
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 24px 12px;
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
|
||||
&__icon {
|
||||
width: 28px;
|
||||
@@ -243,7 +243,7 @@
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border-right: none;
|
||||
background: var(--app-content-bg, var(--mat-sys-surface));
|
||||
background: var(--app-content-bg);
|
||||
border-top-left-radius: 16px;
|
||||
}
|
||||
|
||||
|
||||
+6
-10
@@ -61,7 +61,7 @@
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: var(--app-content-bg, var(--mat-sys-surface));
|
||||
background: var(--app-content-bg);
|
||||
}
|
||||
|
||||
.groups-loading-nav {
|
||||
@@ -73,7 +73,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: var(--app-rail-bg, var(--mat-sys-surface-container-low));
|
||||
background: var(--app-rail-bg);
|
||||
padding: 12px 10px 10px 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -100,15 +100,11 @@
|
||||
border-radius: 12px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high)) 88%,
|
||||
var(--app-widget-bg, var(--mat-sys-surface-container)) 12%
|
||||
var(--app-widget-header-bg) 88%,
|
||||
var(--app-widget-bg) 12%
|
||||
);
|
||||
border: 1px solid
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-border, var(--mat-sys-outline-variant)) 96%,
|
||||
transparent
|
||||
);
|
||||
color-mix(in srgb, var(--app-widget-header-border) 96%, transparent);
|
||||
}
|
||||
|
||||
.groups-loading-nav__label,
|
||||
@@ -135,7 +131,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: var(--app-content-bg, var(--mat-sys-surface));
|
||||
background: var(--app-content-bg);
|
||||
border-top-left-radius: 16px;
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
font-size: 64px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
color: var(--app-selection-color, var(--mat-sys-primary));
|
||||
color: var(--app-selection-color);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
@@ -205,8 +205,8 @@
|
||||
// Play / Pause uses the unified app primary, not the station's dominant color
|
||||
// — same blue as every other CTA so radio doesn't feel like a different app.
|
||||
.play-btn {
|
||||
--mdc-fab-container-color: var(--app-selection-color, var(--mat-sys-primary));
|
||||
--mat-fab-container-color: var(--app-selection-color, var(--mat-sys-primary));
|
||||
--mdc-fab-container-color: var(--app-selection-color);
|
||||
--mat-fab-container-color: var(--app-selection-color);
|
||||
--mdc-fab-icon-color: #fff;
|
||||
--mat-fab-foreground-color: #fff;
|
||||
--mat-fab-icon-color: #fff;
|
||||
@@ -243,10 +243,10 @@
|
||||
|
||||
.vol-slider {
|
||||
flex: 1;
|
||||
--mdc-slider-active-track-color: var(--app-selection-color, var(--mat-sys-primary));
|
||||
--mdc-slider-handle-color: var(--app-selection-color, var(--mat-sys-primary));
|
||||
--mat-slider-active-track-color: var(--app-selection-color, var(--mat-sys-primary));
|
||||
--mat-slider-handle-color: var(--app-selection-color, var(--mat-sys-primary));
|
||||
--mdc-slider-active-track-color: var(--app-selection-color);
|
||||
--mdc-slider-handle-color: var(--app-selection-color);
|
||||
--mat-slider-active-track-color: var(--app-selection-color);
|
||||
--mat-slider-handle-color: var(--app-selection-color);
|
||||
--mdc-slider-inactive-track-color: var(--mat-sys-outline-variant);
|
||||
--mdc-slider-handle-width: 12px;
|
||||
--mdc-slider-handle-height: 12px;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
padding: $padding;
|
||||
min-height: $min-height;
|
||||
flex-shrink: 0;
|
||||
background: var(--app-widget-header-bg, var(--mat-sys-surface-container-high));
|
||||
background: var(--app-widget-header-bg);
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
@if $sticky {
|
||||
@@ -38,7 +38,7 @@
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--app-heading-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-heading-color);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
$font-size: 0.78rem,
|
||||
$font-weight: 500,
|
||||
$padding: 2px 8px,
|
||||
$color: var(--app-body-color, var(--mat-sys-on-surface-variant)),
|
||||
$color: var(--app-body-color),
|
||||
$background: var(--mat-sys-surface-container)
|
||||
) {
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
.sidebar-header {
|
||||
padding: 8px 12px;
|
||||
background: var(--app-widget-header-bg, var(--mat-sys-surface-container-high));
|
||||
background: var(--app-widget-header-bg);
|
||||
border-bottom: 1px solid var(--app-separator);
|
||||
app-region: drag;
|
||||
display: flex;
|
||||
|
||||
+5
-5
@@ -16,13 +16,13 @@
|
||||
border-radius: 12px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-selection-surface, var(--mat-sys-surface-container)) 72%,
|
||||
var(--app-selection-surface) 72%,
|
||||
transparent
|
||||
);
|
||||
border: 1px solid
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--app-selection-border, var(--mat-sys-outline-variant)) 70%,
|
||||
var(--app-selection-border) 70%,
|
||||
transparent
|
||||
);
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
@@ -52,8 +52,8 @@
|
||||
border-radius: 999px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high)) 52%,
|
||||
var(--app-body-color, var(--mat-sys-on-surface-variant)) 48%
|
||||
var(--app-widget-header-bg) 52%,
|
||||
var(--app-body-color) 48%
|
||||
);
|
||||
|
||||
&::after {
|
||||
@@ -65,7 +65,7 @@
|
||||
transparent 8%,
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--app-heading-color, var(--mat-sys-on-surface)) 18%,
|
||||
var(--app-heading-color) 18%,
|
||||
transparent
|
||||
)
|
||||
44%,
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@
|
||||
gap: 14px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-body-color);
|
||||
|
||||
img {
|
||||
width: min(100%, var(--error-view-illustration-size));
|
||||
@@ -68,7 +68,7 @@
|
||||
font-size: var(--error-view-title-size);
|
||||
font-weight: var(--error-view-title-weight);
|
||||
line-height: 1.12;
|
||||
color: var(--app-heading-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-heading-color);
|
||||
letter-spacing: var(--error-view-title-spacing);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
+20
-29
@@ -31,7 +31,7 @@
|
||||
font-size: 0.94rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,14 +54,14 @@
|
||||
}
|
||||
|
||||
&--active mat-icon {
|
||||
color: var(--mat-sys-primary);
|
||||
color: var(--app-selection-color);
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@
|
||||
margin-top: 5px;
|
||||
padding: 2px 0;
|
||||
border-radius: 999px;
|
||||
background: var(--mat-sys-surface-container);
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
background: var(--app-hover-overlay);
|
||||
color: var(--app-muted-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
@@ -87,7 +87,7 @@
|
||||
.context-header p {
|
||||
margin: 4px 0 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
|
||||
// ─── Search Bar ──────────────────────────────────────────────────────────────
|
||||
@@ -99,8 +99,8 @@
|
||||
padding: 0 10px;
|
||||
min-height: 36px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--app-search-border, var(--mat-sys-outline-variant));
|
||||
background: var(--app-search-bg, var(--mat-sys-surface-container));
|
||||
border: 1px solid var(--app-search-border);
|
||||
background: var(--app-search-bg);
|
||||
flex-shrink: 0;
|
||||
cursor: text;
|
||||
transition:
|
||||
@@ -108,9 +108,9 @@
|
||||
box-shadow 0.15s ease;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--mat-sys-primary);
|
||||
border-color: var(--app-selection-color);
|
||||
box-shadow: 0 0 0 3px
|
||||
color-mix(in srgb, var(--mat-sys-primary) 12%, transparent);
|
||||
color-mix(in srgb, var(--app-selection-color) 12%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -127,14 +127,14 @@
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
font: inherit;
|
||||
font-size: 0.84rem;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
// ─── Divider ──────────────────────────────────────────────────────────────────
|
||||
.context-divider {
|
||||
height: 1px;
|
||||
background: var(--mat-sys-outline-variant);
|
||||
background: var(--app-separator);
|
||||
margin: 0 0 6px;
|
||||
opacity: 0.6;
|
||||
flex-shrink: 0;
|
||||
@@ -197,15 +197,11 @@ app-workspace-context-category-view {
|
||||
border-radius: 12px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high)) 88%,
|
||||
var(--app-widget-bg, var(--mat-sys-surface-container)) 12%
|
||||
var(--app-widget-header-bg) 88%,
|
||||
var(--app-widget-bg) 12%
|
||||
);
|
||||
border: 1px solid
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-border, var(--mat-sys-outline-variant)) 96%,
|
||||
transparent
|
||||
);
|
||||
color-mix(in srgb, var(--app-widget-header-border) 96%, transparent);
|
||||
}
|
||||
|
||||
.context-skeleton__dot,
|
||||
@@ -215,8 +211,8 @@ app-workspace-context-category-view {
|
||||
overflow: hidden;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-widget-header-bg, var(--mat-sys-surface-container-high)) 52%,
|
||||
var(--app-body-color, var(--mat-sys-on-surface-variant)) 48%
|
||||
var(--app-widget-header-bg) 52%,
|
||||
var(--app-body-color) 48%
|
||||
);
|
||||
|
||||
&::after {
|
||||
@@ -226,12 +222,7 @@ app-workspace-context-category-view {
|
||||
background: linear-gradient(
|
||||
100deg,
|
||||
transparent 8%,
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--app-heading-color, var(--mat-sys-on-surface)) 18%,
|
||||
transparent
|
||||
)
|
||||
44%,
|
||||
color-mix(in srgb, var(--app-heading-color) 18%, transparent) 44%,
|
||||
transparent 74%
|
||||
);
|
||||
transform: translateX(-120%);
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
.context-panel {
|
||||
border-right: 1px solid var(--mat-sys-outline-variant);
|
||||
background: var(--app-rail-bg, var(--mat-sys-surface-container-low));
|
||||
background: var(--app-rail-bg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
+37
-35
@@ -9,8 +9,8 @@
|
||||
gap: 12px;
|
||||
height: 56px;
|
||||
padding: 0 24px;
|
||||
border-bottom: 1px solid var(--mat-sys-outline-variant);
|
||||
background: var(--app-header-bg, var(--mat-sys-surface-container-low));
|
||||
border-bottom: 1px solid var(--app-widget-header-border);
|
||||
background: var(--app-header-bg);
|
||||
app-region: drag;
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
max-width: 560px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--app-search-border, var(--mat-sys-outline-variant));
|
||||
background: var(--app-search-bg, var(--mat-sys-surface-container));
|
||||
border: 1px solid var(--app-search-border);
|
||||
background: var(--app-search-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -39,16 +39,16 @@
|
||||
app-region: no-drag;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--mat-sys-primary);
|
||||
border-color: var(--app-selection-color);
|
||||
box-shadow: 0 0 0 3px
|
||||
color-mix(in srgb, var(--mat-sys-primary) 12%, transparent);
|
||||
color-mix(in srgb, var(--app-selection-color) 12%, transparent);
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -60,14 +60,10 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-high) 76%,
|
||||
transparent
|
||||
);
|
||||
color: var(--app-muted-color);
|
||||
background: var(--app-hover-overlay);
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--mat-sys-outline-variant) 82%, transparent);
|
||||
color-mix(in srgb, var(--app-separator) 82%, transparent);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
@@ -79,19 +75,19 @@
|
||||
}
|
||||
|
||||
.search-chip--scope {
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
.search-chip--status {
|
||||
color: var(--app-selection-color, var(--mat-sys-primary));
|
||||
color: var(--app-selection-color);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-selection-surface, var(--mat-sys-primary-container)) 70%,
|
||||
var(--app-selection-surface) 70%,
|
||||
transparent
|
||||
);
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--app-selection-border, var(--mat-sys-primary)) 52%,
|
||||
var(--app-selection-border) 52%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
@@ -101,7 +97,7 @@
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
font: inherit;
|
||||
font-size: 0.875rem;
|
||||
|
||||
@@ -111,7 +107,7 @@
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
@@ -124,14 +120,10 @@
|
||||
min-width: 36px;
|
||||
padding: 0 6px;
|
||||
margin-right: -2px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-high) 76%,
|
||||
transparent
|
||||
);
|
||||
color: var(--app-muted-color);
|
||||
background: var(--app-hover-overlay);
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--mat-sys-outline-variant) 70%, transparent);
|
||||
color-mix(in srgb, var(--app-separator) 70%, transparent);
|
||||
border-radius: 5px;
|
||||
font-family: inherit;
|
||||
font-size: 0.7rem;
|
||||
@@ -146,17 +138,17 @@
|
||||
border-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--mat-sys-surface-container-highest);
|
||||
color: var(--mat-sys-on-surface);
|
||||
background: var(--app-card-hover-bg);
|
||||
color: var(--app-heading-color);
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-outline-variant) 100%,
|
||||
var(--app-separator) 100%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary);
|
||||
outline: 2px solid var(--app-selection-color);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
}
|
||||
@@ -170,15 +162,26 @@
|
||||
|
||||
button,
|
||||
a {
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
// Body-tier (not muted) so these read as active controls, not
|
||||
// disabled. Hover brightens to --app-heading-color below.
|
||||
color: var(--app-body-color);
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
color 0.15s ease,
|
||||
background 0.15s ease;
|
||||
|
||||
// Suppress Material's circular state-layer overlay so only the
|
||||
// rounded-rectangle hover/active states below render. Keeps the
|
||||
// header consistent with the rail, nav items, and the .is-active
|
||||
// state (all rounded rectangles). The click ripple is left intact —
|
||||
// it is clipped to the button's 8px radius.
|
||||
::ng-deep .mat-mdc-button-persistent-ripple {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--mat-sys-on-surface);
|
||||
background: var(--mat-sys-surface-container);
|
||||
color: var(--app-heading-color);
|
||||
background: var(--app-hover-overlay);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
@@ -202,7 +205,6 @@
|
||||
.header-actions--trailing {
|
||||
margin-left: auto;
|
||||
padding-left: 8px;
|
||||
border-left: 1px solid var(--mat-sys-outline-variant);
|
||||
app-region: no-drag;
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -45,7 +45,7 @@
|
||||
font-size: 1.55rem;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--app-heading-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,13 +85,13 @@
|
||||
.workspace-loading-overlay__phase {
|
||||
font-size: 1rem;
|
||||
font-weight: 560;
|
||||
color: var(--app-body-color, var(--mat-sys-on-surface));
|
||||
color: var(--app-body-color);
|
||||
}
|
||||
|
||||
.workspace-loading-overlay__detail {
|
||||
max-width: 32ch;
|
||||
justify-self: center;
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
justify-self: center;
|
||||
line-height: 1.45;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
|
||||
color: var(--app-muted-color);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
|
||||
-14
@@ -83,17 +83,3 @@
|
||||
color: var(--rail-link-active-icon, var(--app-selection-color));
|
||||
}
|
||||
}
|
||||
|
||||
.portal-rail-link.is-active::after,
|
||||
.portal-rail-link.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
border-radius: 999px;
|
||||
background: var(--rail-link-active-icon, var(--app-selection-color));
|
||||
box-shadow: 0 0 10px var(--rail-link-active-glow, var(--app-selection-glow));
|
||||
}
|
||||
+2
-28
@@ -13,8 +13,8 @@
|
||||
box-sizing: border-box;
|
||||
padding: 10px 8px;
|
||||
border-right: 1px solid
|
||||
var(--app-rail-border, var(--mat-sys-outline-variant));
|
||||
background: var(--app-rail-bg, var(--mat-sys-surface-container-low));
|
||||
var(--app-rail-border);
|
||||
background: var(--app-rail-bg);
|
||||
app-region: drag;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
@@ -83,19 +83,6 @@
|
||||
mat-icon {
|
||||
color: var(--rail-link-active-icon);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 18px;
|
||||
border-radius: 999px;
|
||||
background: var(--rail-link-active-icon);
|
||||
box-shadow: 0 0 10px var(--rail-link-active-glow);
|
||||
}
|
||||
}
|
||||
|
||||
.brand {
|
||||
@@ -208,19 +195,6 @@
|
||||
background: var(--app-hover-overlay);
|
||||
color: var(--app-on-surface);
|
||||
}
|
||||
|
||||
&.is-active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
border-radius: 999px;
|
||||
background: var(--app-selection-color);
|
||||
box-shadow: 0 0 10px var(--app-selection-glow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: var(--app-shell-bg, var(--mat-sys-surface));
|
||||
background: var(--app-shell-bg);
|
||||
color: var(--mat-sys-on-surface);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ app-workspace-shell-header {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
background: var(--app-rail-bg, var(--mat-sys-surface-container-low));
|
||||
background: var(--app-rail-bg);
|
||||
|
||||
&.single-pane {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -53,14 +53,14 @@ app-workspace-shell-context-sidebar {
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
padding: 0;
|
||||
background: var(--app-content-bg, var(--mat-sys-surface));
|
||||
background: var(--app-content-bg);
|
||||
}
|
||||
|
||||
.workspace-content {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
background: var(--app-content-bg, var(--mat-sys-surface));
|
||||
background: var(--app-content-bg);
|
||||
border-radius: 16px 0 0 0;
|
||||
box-shadow: -4px 8px 20px var(--app-content-shadow, rgba(0, 0, 0, 0.18));
|
||||
}
|
||||
|
||||
+4
-4
@@ -32,20 +32,20 @@
|
||||
font-size: 0.94rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--mat-sys-on-surface);
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
}
|
||||
|
||||
.context-header p {
|
||||
margin: 4px 0 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
}
|
||||
|
||||
// ─── Divider ──────────────────────────────────────────────────────────────────
|
||||
.context-divider {
|
||||
height: 1px;
|
||||
background: var(--mat-sys-outline-variant);
|
||||
background: var(--app-separator);
|
||||
margin: 0 0 6px;
|
||||
opacity: 0.6;
|
||||
flex-shrink: 0;
|
||||
@@ -97,6 +97,6 @@
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
color: var(--app-muted-color);
|
||||
opacity: 0.85;
|
||||
}
|
||||
@@ -61,6 +61,7 @@
|
||||
"release:artwork:generate": "tsx tools/release/generate-marketing-artwork.ts --generate",
|
||||
"release:artwork:validate": "tsx tools/release/generate-marketing-artwork.ts --validate",
|
||||
"lint": "nx run-many --target=lint --all",
|
||||
"lint:styles": "node tools/styles/check-mat-sys-usage.mjs",
|
||||
"build": "nx build electron-backend"
|
||||
},
|
||||
"private": true,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Fails when new `var(--mat-sys-*)` references are added anywhere in
|
||||
* apps/ or libs/. The current Material theme (m3-theme.scss, older M3 API)
|
||||
* never emits `--mat-sys-*` system tokens, so such references are silent
|
||||
* no-ops: the declaration is invalid at computed-value time and falls back
|
||||
* to inherited/initial values. Use the `--app-*` design tokens from
|
||||
* apps/web/src/m3-theme.scss instead.
|
||||
*
|
||||
* Pre-existing usages are grandfathered in tools/styles/mat-sys-baseline.mjs
|
||||
* (regenerate with `node tools/styles/generate-mat-sys-baseline.mjs` after
|
||||
* migrating a file — the baseline should only shrink).
|
||||
*
|
||||
* Usage: node tools/styles/check-mat-sys-usage.mjs
|
||||
*/
|
||||
|
||||
import process from 'node:process';
|
||||
|
||||
import { matSysBaseline } from './mat-sys-baseline.mjs';
|
||||
import { scanMatSysUsages } from './mat-sys-usage.shared.mjs';
|
||||
|
||||
const usages = scanMatSysUsages(process.cwd());
|
||||
|
||||
const violations = usages.filter(
|
||||
({ file, count }) => count > (matSysBaseline.get(file) ?? 0)
|
||||
);
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error(
|
||||
'New var(--mat-sys-*) references detected. These tokens are never\n' +
|
||||
'emitted by the current Material theme setup and resolve to\n' +
|
||||
'nothing — use the --app-* design tokens from\n' +
|
||||
'apps/web/src/m3-theme.scss instead\n' +
|
||||
'(see docs/architecture/theme-design-tokens.md).\n'
|
||||
);
|
||||
for (const { file, count } of violations) {
|
||||
const baselined = matSysBaseline.get(file) ?? 0;
|
||||
console.error(` ${file}: ${count} usages (baseline: ${baselined})`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const stale = [...matSysBaseline.keys()].filter(
|
||||
(file) => !usages.some((usage) => usage.file === file)
|
||||
);
|
||||
const shrunk = usages.filter(
|
||||
({ file, count }) => count < (matSysBaseline.get(file) ?? 0)
|
||||
);
|
||||
if (stale.length > 0 || shrunk.length > 0) {
|
||||
console.log(
|
||||
'Baseline is stale (files were migrated). Consider running\n' +
|
||||
'`node tools/styles/generate-mat-sys-baseline.mjs` to shrink it.'
|
||||
);
|
||||
}
|
||||
|
||||
console.log('No new var(--mat-sys-*) references. OK.');
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Regenerates tools/styles/mat-sys-baseline.mjs: the per-file counts of
|
||||
* legacy `var(--mat-sys-*)` references that predate the guard enforced by
|
||||
* tools/styles/check-mat-sys-usage.mjs. These tokens are never emitted by
|
||||
* the current Material theme setup, so every reference is a silent no-op.
|
||||
* The list should only shrink: migrate a file to `--app-*` tokens, rerun
|
||||
* this generator, and commit the result. Never add new entries.
|
||||
*
|
||||
* Usage: node tools/styles/generate-mat-sys-baseline.mjs
|
||||
*/
|
||||
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
|
||||
import { scanMatSysUsages } from './mat-sys-usage.shared.mjs';
|
||||
|
||||
const workspaceRoot = process.cwd();
|
||||
const usages = scanMatSysUsages(workspaceRoot);
|
||||
|
||||
const banner = `// Generated by tools/styles/generate-mat-sys-baseline.mjs — do not edit by hand.
|
||||
// Files with legacy var(--mat-sys-*) references that predate the guard.
|
||||
// These tokens are never emitted by the current theme (m3-theme.scss), so
|
||||
// every reference is a silent no-op. Use the --app-* design tokens instead
|
||||
// (see docs/architecture/theme-design-tokens.md). This list should only
|
||||
// shrink: migrate a file, rerun the generator, and commit the result.
|
||||
`;
|
||||
|
||||
const body = usages
|
||||
.map(({ file, count }) => ` ['${file}', ${count}],`)
|
||||
.join('\n');
|
||||
|
||||
writeFileSync(
|
||||
path.join(workspaceRoot, 'tools/styles/mat-sys-baseline.mjs'),
|
||||
`${banner}export const matSysBaseline = new Map([\n${body}\n]);\n`
|
||||
);
|
||||
|
||||
const total = usages.reduce((sum, { count }) => sum + count, 0);
|
||||
console.log(`Baselined ${total} usages across ${usages.length} files.`);
|
||||
@@ -0,0 +1,158 @@
|
||||
// Generated by tools/styles/generate-mat-sys-baseline.mjs — do not edit by hand.
|
||||
// Files with legacy var(--mat-sys-*) references that predate the guard.
|
||||
// These tokens are never emitted by the current theme (m3-theme.scss), so
|
||||
// every reference is a silent no-op. Use the --app-* design tokens instead
|
||||
// (see docs/architecture/theme-design-tokens.md). This list should only
|
||||
// shrink: migrate a file, rerun the generator, and commit the result.
|
||||
export const matSysBaseline = new Map([
|
||||
['apps/web/src/styles.scss', 2],
|
||||
[
|
||||
'libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.scss',
|
||||
6,
|
||||
],
|
||||
[
|
||||
'libs/playlist/shared/ui/src/lib/recent-playlists/playlist-info/playlist-info.component.ts',
|
||||
3,
|
||||
],
|
||||
[
|
||||
'libs/playlist/shared/ui/src/lib/recent-playlists/playlist-item/playlist-item.component.scss',
|
||||
4,
|
||||
],
|
||||
[
|
||||
'libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.scss',
|
||||
5,
|
||||
],
|
||||
[
|
||||
'libs/portal/shared/ui/src/lib/components/favorites-layout/favorites-layout.component.scss',
|
||||
5,
|
||||
],
|
||||
[
|
||||
'libs/portal/shared/ui/src/lib/components/global-favorites-list/global-favorites-list.component.scss',
|
||||
1,
|
||||
],
|
||||
[
|
||||
'libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.scss',
|
||||
2,
|
||||
],
|
||||
[
|
||||
'libs/portal/shared/ui/src/lib/components/portal-empty-state/portal-empty-state.component.scss',
|
||||
1,
|
||||
],
|
||||
[
|
||||
'libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-page.component.scss',
|
||||
3,
|
||||
],
|
||||
[
|
||||
'libs/portal/shared/ui/src/lib/components/unified-collection/unified-grid-tab.component.scss',
|
||||
1,
|
||||
],
|
||||
[
|
||||
'libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.scss',
|
||||
6,
|
||||
],
|
||||
[
|
||||
'libs/portal/shared/ui/src/lib/navigation/portal-rail-links.component.scss',
|
||||
1,
|
||||
],
|
||||
['libs/portal/shared/ui/src/lib/styles/_skeleton.scss', 1],
|
||||
[
|
||||
'libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.scss',
|
||||
3,
|
||||
],
|
||||
[
|
||||
'libs/portal/xtream/feature/src/lib/portal-channels-list/portal-channels-list.component.scss',
|
||||
1,
|
||||
],
|
||||
[
|
||||
'libs/portal/xtream/feature/src/lib/search-results/search-results.component.scss',
|
||||
5,
|
||||
],
|
||||
[
|
||||
'libs/portal/xtream/feature/src/lib/xtream-cached-offline-notice.component.ts',
|
||||
2,
|
||||
],
|
||||
[
|
||||
'libs/ui/components/src/lib/channel-list-container/all-channels-view/all-channels-view.component.scss',
|
||||
2,
|
||||
],
|
||||
[
|
||||
'libs/ui/components/src/lib/channel-list-container/channel-list-container.component.scss',
|
||||
2,
|
||||
],
|
||||
[
|
||||
'libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.scss',
|
||||
6,
|
||||
],
|
||||
[
|
||||
'libs/ui/components/src/lib/channel-list-loading-state/channel-list-loading-state.component.scss',
|
||||
1,
|
||||
],
|
||||
[
|
||||
'libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.scss',
|
||||
14,
|
||||
],
|
||||
[
|
||||
'libs/ui/components/src/lib/season-container/season-container.component.scss',
|
||||
24,
|
||||
],
|
||||
[
|
||||
'libs/ui/components/src/lib/window-controls/window-controls.component.scss',
|
||||
1,
|
||||
],
|
||||
['libs/ui/playback/src/lib/audio-player/audio-player.component.scss', 14],
|
||||
[
|
||||
'libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.scss',
|
||||
25,
|
||||
],
|
||||
[
|
||||
'libs/ui/playback/src/lib/portal-inline-player/series-playback-navigation-controls.component.scss',
|
||||
4,
|
||||
],
|
||||
['libs/ui/styles/_panel-header.scss', 1],
|
||||
['libs/ui/styles/_portal-layout.scss', 1],
|
||||
['libs/ui/styles/_portal-sidebar.scss', 4],
|
||||
[
|
||||
'libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.scss',
|
||||
2,
|
||||
],
|
||||
[
|
||||
'libs/workspace/shell/feature/src/lib/global-favorites/global-favorites-list.component.scss',
|
||||
1,
|
||||
],
|
||||
[
|
||||
'libs/workspace/shell/feature/src/lib/workspace-command-palette/workspace-command-palette.component.scss',
|
||||
12,
|
||||
],
|
||||
[
|
||||
'libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-category-view.component.scss',
|
||||
1,
|
||||
],
|
||||
[
|
||||
'libs/workspace/shell/feature/src/lib/workspace-context-panel/workspace-context-panel.component.scss',
|
||||
1,
|
||||
],
|
||||
[
|
||||
'libs/workspace/shell/feature/src/lib/workspace-context-panel/workspace-settings-context-panel.component.scss',
|
||||
1,
|
||||
],
|
||||
[
|
||||
'libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-context-sidebar/workspace-shell-context-sidebar.component.scss',
|
||||
1,
|
||||
],
|
||||
[
|
||||
'libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-import-overlay/workspace-shell-import-overlay.component.scss',
|
||||
1,
|
||||
],
|
||||
[
|
||||
'libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-rail-links/workspace-shell-rail-links.component.scss',
|
||||
3,
|
||||
],
|
||||
[
|
||||
'libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-rail/workspace-shell-rail.component.scss',
|
||||
5,
|
||||
],
|
||||
[
|
||||
'libs/workspace/shell/feature/src/lib/workspace-shell/workspace-shell.component.scss',
|
||||
1,
|
||||
],
|
||||
]);
|
||||
@@ -0,0 +1,48 @@
|
||||
// Shared scanner for the --mat-sys-* usage guard.
|
||||
//
|
||||
// The Angular Material theme 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 `--mat-sys-*` system tokens. Every
|
||||
// `var(--mat-sys-*)` reference therefore fails at computed-value time and
|
||||
// silently falls back to inherited/initial values. New styles must use the
|
||||
// `--app-*` design tokens defined in m3-theme.scss instead.
|
||||
//
|
||||
// See docs/architecture/theme-design-tokens.md for the token mapping.
|
||||
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
export const SCAN_ROOTS = ['apps', 'libs'];
|
||||
export const SCAN_EXTENSIONS = ['.scss', '.css', '.ts', '.html', '.js'];
|
||||
const SKIP_DIRS = new Set(['node_modules', 'dist', '.nx', 'coverage']);
|
||||
const USAGE_PATTERN = /var\(--mat-sys-/g;
|
||||
|
||||
function collectFiles(dir, results) {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
if (entry.isDirectory()) {
|
||||
if (!SKIP_DIRS.has(entry.name)) {
|
||||
collectFiles(path.join(dir, entry.name), results);
|
||||
}
|
||||
} else if (SCAN_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
|
||||
results.push(path.join(dir, entry.name));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/** Returns a sorted array of { file, count } for files with usages. */
|
||||
export function scanMatSysUsages(workspaceRoot) {
|
||||
return SCAN_ROOTS.flatMap((root) =>
|
||||
collectFiles(path.join(workspaceRoot, root), [])
|
||||
)
|
||||
.map((filePath) => ({
|
||||
file: path
|
||||
.relative(workspaceRoot, filePath)
|
||||
.split(path.sep)
|
||||
.join('/'),
|
||||
count: (readFileSync(filePath, 'utf8').match(USAGE_PATTERN) || [])
|
||||
.length,
|
||||
}))
|
||||
.filter(({ count }) => count > 0)
|
||||
.sort((a, b) => a.file.localeCompare(b.file));
|
||||
}
|
||||
Reference in new issue
Block a user