diff --git a/bot/app/web/frontend/src/lib/components/patterns/admin/AdminRevenueChart.svelte b/bot/app/web/frontend/src/lib/components/patterns/admin/AdminRevenueChart.svelte index 8bd3798..1f50900 100644 --- a/bot/app/web/frontend/src/lib/components/patterns/admin/AdminRevenueChart.svelte +++ b/bot/app/web/frontend/src/lib/components/patterns/admin/AdminRevenueChart.svelte @@ -24,7 +24,8 @@ function readCssColor(name, fallback) { if (typeof document === "undefined") return fallback; - const raw = getComputedStyle(document.documentElement).getPropertyValue(name).trim(); + const scope = hostEl || document.documentElement; + const raw = getComputedStyle(scope).getPropertyValue(name).trim(); return raw || fallback; } @@ -65,7 +66,11 @@ const muted = readCssColor("--admin-muted", "#9aa7a2"); const border = readCssColor("--admin-border", "rgba(255,255,255,0.12)"); const accent = readCssColor("--accent", "#00fe7a"); - const lineStroke = readCssColor("--admin-text", "#e8f0ec"); + const lineStroke = readCssColor( + "--admin-chart-stroke", + readCssColor("--admin-text", "#e8f0ec"), + ); + const lineFill = readCssColor("--admin-chart-fill", "rgba(120, 140, 132, 0.14)"); return { width: w, @@ -94,7 +99,7 @@ stroke: lineStroke, width: 2, cap: "round", - fill: "rgba(120, 140, 132, 0.14)", + fill: lineFill, }, ], axes: [ diff --git a/bot/app/web/frontend/src/lib/webapp/themeStyle.js b/bot/app/web/frontend/src/lib/webapp/themeStyle.js index c415cd0..15fe42d 100644 --- a/bot/app/web/frontend/src/lib/webapp/themeStyle.js +++ b/bot/app/web/frontend/src/lib/webapp/themeStyle.js @@ -12,6 +12,21 @@ const TOKEN_TO_CSS_VAR = { muted: "--muted", dim: "--dim", danger: "--danger", + danger_text: "--danger-text", + danger_soft: "--danger-soft", + danger_border: "--danger-border", + success: "--success", + success_text: "--success-text", + success_soft: "--success-soft", + success_border: "--success-border", + warning: "--warning", + warning_text: "--warning-text", + warning_soft: "--warning-soft", + warning_border: "--warning-border", + info: "--info", + info_text: "--info-text", + info_soft: "--info-soft", + info_border: "--info-border", blue: "--blue", radius: "--radius", font_sans: "--font-sans", diff --git a/bot/app/web/frontend/src/styles/admin-controls.css b/bot/app/web/frontend/src/styles/admin-controls.css index e69029f..3f22104 100644 --- a/bot/app/web/frontend/src/styles/admin-controls.css +++ b/bot/app/web/frontend/src/styles/admin-controls.css @@ -113,7 +113,7 @@ .admin-btn.admin-btn-primary { background: var(--accent); - color: #02110a; + color: var(--accent-contrast); border-color: color-mix(in srgb, var(--accent) 70%, #000); font-weight: 600; } @@ -129,31 +129,31 @@ } .admin-btn.admin-btn-ghost:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.04); + background: var(--surface-hover); color: var(--admin-text); } .admin-btn.admin-btn-danger { - background: color-mix(in srgb, #ff5757 80%, #000); + background: color-mix(in srgb, var(--danger) 80%, #000); color: #fff; - border-color: color-mix(in srgb, #ff5757 60%, #000); + border-color: color-mix(in srgb, var(--danger) 60%, #000); font-weight: 600; } .admin-btn.admin-btn-danger:hover:not(:disabled) { - background: #ff5757; - border-color: #ff5757; + background: var(--danger); + border-color: var(--danger); } .admin-btn.admin-btn-danger-soft { - color: #ffb4b4; + color: var(--danger-text); background: var(--admin-surface-2); - border-color: color-mix(in srgb, #ff6b6b 30%, transparent); + border-color: var(--danger-border); } .admin-btn.admin-btn-danger-soft:hover:not(:disabled) { - background: color-mix(in srgb, #ff6b6b 14%, var(--admin-surface)); - border-color: color-mix(in srgb, #ff6b6b 50%, transparent); + background: var(--danger-soft); + border-color: color-mix(in srgb, var(--danger) 50%, transparent); } .admin-btn.admin-btn-icon { @@ -183,21 +183,21 @@ } .admin-badge.admin-badge-success { - border-color: color-mix(in srgb, var(--accent) 36%, transparent); - color: var(--accent); - background: color-mix(in srgb, var(--accent) 12%, var(--admin-surface)); + border-color: var(--success-border); + color: var(--success-text); + background: var(--success-soft); } .admin-badge.admin-badge-danger { - border-color: color-mix(in srgb, #ff6b6b 32%, transparent); - color: #ffb4b4; - background: color-mix(in srgb, #ff6b6b 12%, var(--admin-surface)); + border-color: var(--danger-border); + color: var(--danger-text); + background: var(--danger-soft); } .admin-badge.admin-badge-warning { - border-color: color-mix(in srgb, #ffd166 32%, transparent); - color: #ffd166; - background: color-mix(in srgb, #ffd166 12%, var(--admin-surface)); + border-color: var(--warning-border); + color: var(--warning-text); + background: var(--warning-soft); } .admin-badge.admin-badge-muted { @@ -270,7 +270,7 @@ .admin-extend-control .admin-btn.admin-btn-primary { background: var(--accent); - color: #02110a; + color: var(--accent-contrast); border-color: color-mix(in srgb, var(--accent) 70%, #000); font-weight: 600; } diff --git a/bot/app/web/frontend/src/styles/admin.css b/bot/app/web/frontend/src/styles/admin.css index 821ab64..df35dd5 100644 --- a/bot/app/web/frontend/src/styles/admin.css +++ b/bot/app/web/frontend/src/styles/admin.css @@ -546,11 +546,11 @@ } .admin-revenue-kpi-growth.is-up { - color: color-mix(in srgb, #3ecf8e 92%, var(--admin-text)); + color: var(--success-text); } .admin-revenue-kpi-growth.is-down { - color: color-mix(in srgb, #ff6b6b 88%, var(--admin-text)); + color: var(--danger-text); } .admin-revenue-chart { @@ -2088,11 +2088,11 @@ } .admin-traffic-card-premium { - border-color: color-mix(in srgb, var(--accent) 30%, var(--admin-border)); + border-color: var(--info-border); } .admin-traffic-card-warning { - border-color: color-mix(in srgb, #ffb454 46%, var(--admin-border)); + border-color: var(--warning-border); } .admin-traffic-head, @@ -2134,11 +2134,11 @@ } .admin-traffic-bar-premium span { - background: linear-gradient(90deg, #66e3ff, var(--accent)); + background: linear-gradient(90deg, var(--info), color-mix(in srgb, var(--info) 64%, #ffffff)); } .admin-traffic-card-warning .admin-traffic-bar span { - background: linear-gradient(90deg, #ffb454, #ff6b6b); + background: linear-gradient(90deg, var(--warning), var(--danger)); } .admin-traffic-meta { @@ -2656,8 +2656,8 @@ } .admin-switch-root[data-state="checked"] { - background: color-mix(in srgb, var(--accent) 80%, var(--admin-elev)); - border-color: color-mix(in srgb, var(--accent) 50%, transparent); + background: color-mix(in srgb, var(--success) 82%, var(--admin-elev)); + border-color: color-mix(in srgb, var(--success) 52%, transparent); } .admin-switch-root:focus-visible { @@ -3080,9 +3080,9 @@ } .admin-danger-zone { - border: 1px solid color-mix(in srgb, #ff6b6b 32%, transparent); + border: 1px solid var(--danger-border); border-radius: 12px; - background: color-mix(in srgb, #ff6b6b 6%, var(--admin-surface)); + background: var(--danger-soft); padding: 14px 16px; display: flex; flex-direction: column; @@ -3099,13 +3099,13 @@ .admin-danger-zone-head strong { font-size: 13px; font-weight: 700; - color: #ffb4b4; + color: var(--danger-text); text-transform: uppercase; letter-spacing: 0.04em; } .admin-danger-zone-head small { - color: color-mix(in srgb, #ffb4b4 70%, var(--admin-muted)); + color: color-mix(in srgb, var(--danger-text) 70%, var(--admin-muted)); font-size: 12px; line-height: 1.45; } diff --git a/bot/app/web/frontend/src/styles/base.css b/bot/app/web/frontend/src/styles/base.css index 6a5b977..a0015cf 100644 --- a/bot/app/web/frontend/src/styles/base.css +++ b/bot/app/web/frontend/src/styles/base.css @@ -24,6 +24,21 @@ --muted: #a9b4b0; --dim: #68736f; --danger: #ff6b6b; + --danger-text: #ffb4b4; + --danger-soft: color-mix(in srgb, var(--danger) 12%, var(--panel)); + --danger-border: color-mix(in srgb, var(--danger) 34%, var(--border)); + --success: #3ecf8e; + --success-text: #7ff0b9; + --success-soft: color-mix(in srgb, var(--success) 12%, var(--panel)); + --success-border: color-mix(in srgb, var(--success) 34%, var(--border)); + --warning: #ffd166; + --warning-text: #ffd166; + --warning-soft: color-mix(in srgb, var(--warning) 12%, var(--panel)); + --warning-border: color-mix(in srgb, var(--warning) 34%, var(--border)); + --info: #2d9cff; + --info-text: #8ed0ff; + --info-soft: color-mix(in srgb, var(--info) 12%, var(--panel)); + --info-border: color-mix(in srgb, var(--info) 34%, var(--border)); --blue: #2d9cff; --radius: 8px; --accent-contrast: #03100a; diff --git a/bot/app/web/frontend/src/styles/components.css b/bot/app/web/frontend/src/styles/components.css index 62df485..bc75e07 100644 --- a/bot/app/web/frontend/src/styles/components.css +++ b/bot/app/web/frontend/src/styles/components.css @@ -171,15 +171,15 @@ } .admin-cn-badge-destructive { - background: color-mix(in srgb, var(--danger) 16%, var(--admin-surface)); - border-color: color-mix(in srgb, var(--danger) 35%, var(--admin-border)); - color: color-mix(in srgb, var(--danger) 92%, var(--admin-text)); + background: var(--danger-soft); + border-color: var(--danger-border); + color: var(--danger-text); } .admin-cn-badge-success { - background: color-mix(in srgb, var(--accent) 14%, var(--admin-surface)); - border-color: color-mix(in srgb, var(--accent) 32%, var(--admin-border)); - color: color-mix(in srgb, var(--accent) 88%, var(--admin-text)); + background: var(--success-soft); + border-color: var(--success-border); + color: var(--success-text); } .admin-cn-badge-muted { diff --git a/bot/app/web/frontend/src/styles/webapp.css b/bot/app/web/frontend/src/styles/webapp.css index 9b7d6dd..62a9001 100644 --- a/bot/app/web/frontend/src/styles/webapp.css +++ b/bot/app/web/frontend/src/styles/webapp.css @@ -574,8 +574,8 @@ a { .device-disconnect-button, .device-danger-button { - border-color: color-mix(in srgb, var(--danger) 62%, var(--border)); - color: #ffb8b8; + border-color: var(--danger-border); + color: var(--danger-text); } .devices-empty-card { @@ -957,7 +957,7 @@ a { .tariff-warning-card { justify-content: flex-start; - color: var(--warning, #ffd166); + color: var(--warning-text); } .confirm-summary-card { @@ -1067,10 +1067,10 @@ a { .field-error-tooltip { z-index: 120; max-width: 280px; - border: 1px solid color-mix(in srgb, var(--danger) 60%, var(--border)); + border: 1px solid var(--danger-border); border-radius: 8px; - background: #2a1010; - color: #ffd8d8; + background: var(--danger-soft); + color: var(--danger-text); padding: 8px 10px; font-size: 12px; line-height: 1.3; @@ -1286,16 +1286,16 @@ a { .settings-row-linked { grid-template-columns: 28px minmax(0, 1fr); - border-color: color-mix(in srgb, var(--accent) 38%, var(--border)); - background: color-mix(in srgb, var(--accent) 11%, var(--surface-muted)); + border-color: var(--success-border); + background: var(--success-soft); } .settings-row-linked > svg:first-child { - color: var(--accent); + color: var(--success-text); } .settings-row-linked strong { - color: var(--accent); + color: var(--success-text); } .emoji-flag { @@ -1772,7 +1772,7 @@ a { } .status-line { - color: var(--accent); + color: var(--success-text); font-size: 12px; line-height: 1.4; } @@ -1800,7 +1800,7 @@ a { bottom: 16px; z-index: 120; max-width: min(420px, calc(100vw - 32px)); - border: 1px solid color-mix(in srgb, var(--accent) 46%, var(--border)); + border: 1px solid var(--border-strong); border-radius: var(--radius); background: color-mix(in srgb, var(--panel) 92%, transparent); box-shadow: var(--shadow-popover); diff --git a/bot/app/web/themes/light/style.css b/bot/app/web/themes/light/style.css index 1317e33..25f6148 100644 --- a/bot/app/web/themes/light/style.css +++ b/bot/app/web/themes/light/style.css @@ -11,6 +11,21 @@ --muted: #475569; --dim: #64748b; --danger: #dc2626; + --danger-text: #b91c1c; + --danger-soft: color-mix(in srgb, var(--danger) 9%, var(--panel)); + --danger-border: color-mix(in srgb, var(--danger) 34%, var(--border)); + --success: #16a34a; + --success-text: #166534; + --success-soft: color-mix(in srgb, var(--success) 10%, var(--panel)); + --success-border: color-mix(in srgb, var(--success) 34%, var(--border)); + --warning: #d97706; + --warning-text: #92400e; + --warning-soft: color-mix(in srgb, var(--warning) 11%, var(--panel)); + --warning-border: color-mix(in srgb, var(--warning) 34%, var(--border)); + --info: #2563eb; + --info-text: #1d4ed8; + --info-soft: color-mix(in srgb, var(--info) 9%, var(--panel)); + --info-border: color-mix(in srgb, var(--info) 30%, var(--border)); --blue: #2563eb; --radius: 8px; --accent-contrast: #ffffff; @@ -22,9 +37,9 @@ --overlay-scrim: rgba(15, 23, 42, 0.34); --nav-bg: rgba(255, 255, 255, 0.88); --rail-bg: rgba(255, 255, 255, 0.72); - --shadow-soft: 0 16px 38px rgba(15, 23, 42, 0.08); - --shadow-strong: 0 24px 64px rgba(15, 23, 42, 0.16); - --shadow-popover: 0 18px 34px rgba(15, 23, 42, 0.14); + --shadow-soft: 0 6px 18px rgba(15, 23, 42, 0.06); + --shadow-strong: 0 18px 44px rgba(15, 23, 42, 0.12); + --shadow-popover: 0 14px 28px rgba(15, 23, 42, 0.12); --inset-highlight: rgba(255, 255, 255, 0.75); --admin-bg: #f7f8fb; --admin-surface: #ffffff; @@ -34,7 +49,33 @@ --admin-border-strong: rgba(15, 23, 42, 0.18); --admin-text: #0f172a; --admin-muted: #64748b; - --admin-dim: #94a3b8; + --admin-dim: #64748b; + --admin-chart-stroke: #065f46; + --admin-chart-fill: rgba(6, 95, 70, 0.22); +} + +.theme-key-light .ui-spinner, +.theme-key-light .brand-mark-spinner { + color: inherit; +} + +.theme-key-light .telegram-button-spinner { + border-color: rgba(255, 255, 255, 0.35); + border-top-color: #ffffff; +} + +.theme-key-light .btn-primary, +.theme-key-light .admin-btn.admin-btn-primary, +.theme-key-light .admin-extend-control .admin-btn.admin-btn-primary { + background: color-mix(in srgb, var(--accent) 50%, #000000); + border-color: color-mix(in srgb, var(--accent) 42%, #000000); + color: #ffffff; +} + +.theme-key-light .btn-primary:hover:not(:disabled), +.theme-key-light .admin-btn.admin-btn-primary:hover:not(:disabled), +.theme-key-light .admin-extend-control .admin-btn.admin-btn-primary:hover:not(:disabled) { + background: color-mix(in srgb, var(--accent) 52%, #000000); } .theme-key-light.app-shell { @@ -44,3 +85,47 @@ .theme-key-light .phone-screen { background: var(--bg); } + +/* Flatten Settings rows: no gradient sheen, no inset highlight that reads as a 3D bevel */ +.theme-key-light .settings-row { + background: var(--panel); + box-shadow: none; +} + +.theme-key-light .settings-row-linked { + background: var(--success-soft); +} + +/* Avatar/profile card: bigger lift, but rows below have an opaque background and + stack above, so the shadow stays visually under them instead of bleeding through. */ +.theme-key-light .settings-profile { + box-shadow: + 0 10px 24px rgba(15, 23, 42, 0.10), + inset 0 1px 0 var(--inset-highlight); +} + +.theme-key-light .settings-links-block { + position: relative; + z-index: 1; +} + +/* Slightly stronger axis/grid contrast for the revenue chart on a light surface */ +.theme-key-light .admin-revenue-svg-frame { + background: #ffffff; +} + +/* Bonus section: drop accent color from body strongs; only the bonus-system heading + and explicitly-accent card headings stay tinted — and they use the same darkened + accent technique as .btn-primary on light, so they remain readable on white. */ +.theme-key-light .bonus-card strong { + color: var(--text); +} + +.theme-key-light .bonus-card-head strong, +.theme-key-light .card-heading-accent { + color: color-mix(in srgb, var(--accent) 50%, #000000); +} + +.theme-key-light .bonus-card-head > svg { + color: color-mix(in srgb, var(--accent) 50%, #000000); +} diff --git a/bot/app/web/themes/light/theme.json b/bot/app/web/themes/light/theme.json index a228433..9713516 100644 --- a/bot/app/web/themes/light/theme.json +++ b/bot/app/web/themes/light/theme.json @@ -9,7 +9,7 @@ "use_primary_accent": true, "use_in_admin": true, "css_file": "style.css", - "assets_version": 1, + "assets_version": 2, "tokens": { "color_scheme": "light" } diff --git a/config/webapp_themes_config.py b/config/webapp_themes_config.py index 8346c5e..e6cc7f7 100644 --- a/config/webapp_themes_config.py +++ b/config/webapp_themes_config.py @@ -33,6 +33,21 @@ class ThemeTokens(BaseModel): muted: Optional[str] = None dim: Optional[str] = None danger: Optional[str] = None + danger_text: Optional[str] = None + danger_soft: Optional[str] = None + danger_border: Optional[str] = None + success: Optional[str] = None + success_text: Optional[str] = None + success_soft: Optional[str] = None + success_border: Optional[str] = None + warning: Optional[str] = None + warning_text: Optional[str] = None + warning_soft: Optional[str] = None + warning_border: Optional[str] = None + info: Optional[str] = None + info_text: Optional[str] = None + info_soft: Optional[str] = None + info_border: Optional[str] = None blue: Optional[str] = None radius: Optional[str] = None font_sans: Optional[str] = None @@ -350,7 +365,7 @@ def _builtin_theme_assets_need_refresh(key: str, target_dir: Path) -> bool: except OSError: return True if key == "light": - return ".theme-key-light.app-shell" not in style + return "--success-text" not in style or ".theme-key-light.app-shell" not in style if key == "ascii": return ( ".theme-key-ascii" not in style