Design overhaul, stats page, internal notes

Design: Onest+Unbounded font pairing (Cyrillic-verified, replacing plain
Golos Text), manual light/dark toggle wired to the data-theme cascade
already built in MVP-1, refreshed palette + new chart-mark tokens
(CVD-validated via the dataviz skill's validator), colored avatar
initials, per-channel colored badges, favicon, nicer empty states.

Stats: /stats page — status/channel breakdown, 14-day ticket volume,
avg first-response time, per-agent open-ticket workload. Colors and
chart forms follow the dataviz skill's procedure (categorical order
re-stepped to clear the CVD adjacency check in both themes).

Internal notes: messages.visibility ("public"/"internal") column.
Agent-only notes never reach customer-facing reads (getTicketForCustomer)
or the customer SSE stream (/api/portal/events, reused by the widget) —
fixed at both the initial-fetch and live-delivery layers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
This commit is contained in:
ogrechkoandClaude Sonnet 5 committed 2026-07-26 14:38:52 +00:00
1 parent 4efce5f307
commit 9f1413f0b9
23 files changed
+1315 -64

No files matched your search

+98 -11
View File
@@ -2,6 +2,7 @@
@theme {
--font-sans: var(--font-sans), ui-sans-serif, system-ui, sans-serif;
--font-display: var(--font-display), var(--font-sans), ui-sans-serif, sans-serif;
--font-mono: var(--font-mono), ui-monospace, monospace;
--color-bg: var(--bg);
@@ -30,25 +31,41 @@
--color-success-soft: var(--success-soft);
--color-success-soft-text: var(--success-soft-text);
--color-info: var(--info);
--color-info-soft: var(--info-soft);
--color-info-soft-text: var(--info-soft-text);
--color-rose: var(--rose);
--color-rose-soft: var(--rose-soft);
--color-rose-soft-text: var(--rose-soft-text);
/* Chart marks — validated categorical set (dataviz skill), distinct from
the softer *-soft badge tokens which are too light for dark-mode marks. */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-muted: var(--chart-muted);
--radius-sm: var(--radius-sm-val);
--radius-md: var(--radius-md-val);
--radius-lg: var(--radius-lg-val);
}
:root {
--bg: #fafaf9;
--bg: #f8f7fb;
--surface: #ffffff;
--surface-hover: #f6f5ff;
--border: #e8e6f5;
--border-strong: #d6d1ee;
--text: #191524;
--text-muted: #6c6685;
--text-faint: #a29cc0;
--surface-hover: #f2effa;
--border: #e5e0f2;
--border-strong: #cec4e8;
--text: #171325;
--text-muted: #635d7a;
--text-faint: #9891ad;
--accent: #7c3aed;
--accent-hover: #6d28d9;
--accent-soft: #f3edff;
--accent-soft-text: #6d28d9;
--accent: #6d28d9;
--accent-hover: #5b21b6;
--accent-soft: #f0e8fe;
--accent-soft-text: #5b21b6;
--danger: #dc2626;
--danger-soft: #fef2f2;
@@ -62,6 +79,22 @@
--success-soft: #ecfdf9;
--success-soft-text: #0f766e;
--info: #2563eb;
--info-soft: #eff6ff;
--info-soft-text: #1d4ed8;
--rose: #e11d48;
--rose-soft: #fff1f2;
--rose-soft-text: #be123c;
/* Chart marks — fixed categorical order, CVD-validated (accent/success/
info/warning); reorder only after re-running the validator. */
--chart-1: #6d28d9;
--chart-2: #0d9488;
--chart-3: #2563eb;
--chart-4: #d97706;
--chart-muted: var(--border-strong);
--radius-sm-val: 8px;
--radius-md-val: 12px;
--radius-lg-val: 18px;
@@ -97,6 +130,22 @@
--success-soft: #0f2320;
--success-soft-text: #5eead4;
--info: #60a5fa;
--info-soft: #12203a;
--info-soft-text: #93c5fd;
--rose: #fb7185;
--rose-soft: #2a1420;
--rose-soft-text: #fda4af;
/* Darker/more saturated than the badge tokens above — validated
separately for the dark-mode lightness band (L 0.48–0.67). */
--chart-1: #8b5cf6;
--chart-2: #0d9488;
--chart-3: #3b82f6;
--chart-4: #d97706;
--chart-muted: var(--border-strong);
color-scheme: dark;
}
}
@@ -128,9 +177,46 @@
--success-soft: #0f2320;
--success-soft-text: #5eead4;
--info: #60a5fa;
--info-soft: #12203a;
--info-soft-text: #93c5fd;
--rose: #fb7185;
--rose-soft: #2a1420;
--rose-soft-text: #fda4af;
--chart-1: #8b5cf6;
--chart-2: #0d9488;
--chart-3: #3b82f6;
--chart-4: #d97706;
--chart-muted: var(--border-strong);
color-scheme: dark;
}
/* Theme-toggle icon: both render always, CSS picks which shows — same
data-theme cascade the palette above uses, so it never needs JS state. */
.theme-toggle-btn .theme-icon-sun {
display: none;
}
.theme-toggle-btn .theme-icon-moon {
display: inline;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .theme-toggle-btn .theme-icon-sun {
display: inline;
}
:root:not([data-theme="light"]) .theme-toggle-btn .theme-icon-moon {
display: none;
}
}
:root[data-theme="dark"] .theme-toggle-btn .theme-icon-sun {
display: inline;
}
:root[data-theme="dark"] .theme-toggle-btn .theme-icon-moon {
display: none;
}
* {
border-color: var(--border);
}
@@ -175,6 +261,7 @@ body {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
.btn {