feat(ui): Velocity rollout — page polish + lime-as-text contrast fixes

Roll the re-skin across the remaining surfaces and fix the readability
regressions the lime accent introduced (lime works as a fill/border but is
unreadable as text on light):

- --m-c-rule is now a soft divider, so page panels/tables get tidy outlines
  instead of a mess of black hairlines; the brutalist weight stays on cards,
  nav, sections and inputs (which reference ink directly).
- New --m-c-warning (amber) for medium severity, keeping the low→medium→high
  gradient legible; applied to SeverityBadge, AnomalyCard, feed stat.
- Interactive/link/highlight text (Home CTA + links, Journal/Backtest/Compare
  buttons, KPI + evidence values) moved off lime to the readable --m-c-info
  blue; Home first-run CTA is now a filled-lime brutalist button; odds-up
  delta → positive green; rate arrow → neutral.
- Results winner colours → tokens (positive / info) + Velocity-aligned tints.

CSS-only — build clean, all 568 tests green.
This commit is contained in:
2026-05-29 15:04:15 +03:00
parent 5d79911c12
commit 1e4dddbbad
15 changed files with 33 additions and 31 deletions
+2 -2
View File
@@ -77,7 +77,7 @@
outline-offset: 2px; outline-offset: 2px;
} }
.m-anomaly-card--high { border-left-color: var(--m-c-anomaly); } .m-anomaly-card--high { border-left-color: var(--m-c-anomaly); }
.m-anomaly-card--medium { border-left-color: var(--m-c-accent); } .m-anomaly-card--medium { border-left-color: var(--m-c-warning); }
.m-anomaly-card--low { border-left-color: var(--m-c-ink-soft); } .m-anomaly-card--low { border-left-color: var(--m-c-ink-soft); }
.m-anomaly-card__head { .m-anomaly-card__head {
@@ -132,7 +132,7 @@
color: var(--m-c-ink-soft); color: var(--m-c-ink-soft);
font-size: 0.875rem; font-size: 0.875rem;
} }
.m-anomaly-card__rate-arrow { color: var(--m-c-accent); font-size: 0.875rem; } .m-anomaly-card__rate-arrow { color: var(--m-c-ink-soft); font-size: 0.875rem; }
.m-anomaly-card__rate-post { .m-anomaly-card__rate-post {
color: var(--m-c-ink); color: var(--m-c-ink);
font-weight: 600; font-weight: 600;
@@ -143,7 +143,7 @@
justify-content: center; justify-content: center;
font-family: var(--m-font-display); font-family: var(--m-font-display);
font-size: 1.75rem; font-size: 1.75rem;
color: var(--m-c-accent); color: var(--m-c-info);
} }
.m-evidence__row { .m-evidence__row {
display: grid; display: grid;
+1 -1
View File
@@ -60,7 +60,7 @@
color: var(--m-c-ink-soft); color: var(--m-c-ink-soft);
transition: color 220ms ease; transition: color 220ms ease;
} }
.m-odds.is-up .m-odds__delta { color: var(--m-c-accent); } .m-odds.is-up .m-odds__delta { color: var(--m-c-positive); }
.m-odds.is-down .m-odds__delta { color: var(--m-c-anomaly); } .m-odds.is-down .m-odds__delta { color: var(--m-c-anomaly); }
.m-odds.is-flat .m-odds__delta { color: var(--m-c-ink-soft); } .m-odds.is-flat .m-odds__delta { color: var(--m-c-ink-soft); }
@@ -2,7 +2,7 @@
SeverityBadge — small uppercase pill encoding an anomaly's severity bucket. SeverityBadge — small uppercase pill encoding an anomaly's severity bucket.
The High variant is signal-red (`--m-c-anomaly`) and pulses to draw the eye The High variant is signal-red (`--m-c-anomaly`) and pulses to draw the eye
on the feed page. Medium uses the editorial amber accent. Low is a muted on the feed page. Medium uses an amber warning tone. Low is a muted
neutral so it does not compete with higher severities. neutral so it does not compete with higher severities.
The component is presentational only — callers compute the severity (via The component is presentational only — callers compute the severity (via
@@ -56,8 +56,8 @@
background: color-mix(in srgb, var(--m-c-ink-soft) 8%, transparent); background: color-mix(in srgb, var(--m-c-ink-soft) 8%, transparent);
} }
.m-severity--medium { .m-severity--medium {
color: var(--m-c-accent); color: var(--m-c-warning);
background: color-mix(in srgb, var(--m-c-accent) 12%, transparent); background: color-mix(in srgb, var(--m-c-warning) 12%, transparent);
} }
.m-severity--high { .m-severity--high {
color: var(--m-c-anomaly); color: var(--m-c-anomaly);
@@ -200,7 +200,7 @@
font-feature-settings: var(--m-num-feature); font-feature-settings: var(--m-num-feature);
} }
.m-anomaly-feed__stat--high dd { color: var(--m-c-anomaly); } .m-anomaly-feed__stat--high dd { color: var(--m-c-anomaly); }
.m-anomaly-feed__stat--medium dd { color: var(--m-c-accent); } .m-anomaly-feed__stat--medium dd { color: var(--m-c-warning); }
.m-anomaly-feed__list { .m-anomaly-feed__list {
display: grid; display: grid;
@@ -452,8 +452,8 @@
.m-backtest__submit { .m-backtest__submit {
gap: var(--m-space-2); gap: var(--m-space-2);
padding: 8px 16px; padding: 8px 16px;
border-color: var(--m-c-accent); border-color: var(--m-c-info);
color: var(--m-c-accent); color: var(--m-c-info);
font-family: var(--m-font-mono); font-family: var(--m-font-mono);
font-size: 0.75rem; font-size: 0.75rem;
text-transform: uppercase; text-transform: uppercase;
@@ -553,7 +553,7 @@
letter-spacing: 0.14em; letter-spacing: 0.14em;
padding: 8px 16px; padding: 8px 16px;
} }
.m-backtest__preset-save-btn:not(:disabled):hover { border-color: var(--m-c-accent); color: var(--m-c-accent); } .m-backtest__preset-save-btn:not(:disabled):hover { border-color: var(--m-c-info); color: var(--m-c-info); }
.m-backtest__preset-save-btn:disabled { opacity: 0.6; cursor: progress; } .m-backtest__preset-save-btn:disabled { opacity: 0.6; cursor: progress; }
@@media (prefers-reduced-motion: reduce) { @@media (prefers-reduced-motion: reduce) {
.m-backtest__preset, .m-backtest__preset-del, .m-backtest__preset-save-btn { transition: none; } .m-backtest__preset, .m-backtest__preset-del, .m-backtest__preset-save-btn { transition: none; }
@@ -748,7 +748,7 @@
transition: color 120ms ease, border-color 120ms ease; transition: color 120ms ease, border-color 120ms ease;
} }
.m-backtest__open:hover { .m-backtest__open:hover {
color: var(--m-c-accent); color: var(--m-c-info);
border-bottom-color: var(--m-c-ink); border-bottom-color: var(--m-c-ink);
} }
@@ -617,7 +617,7 @@
transition: color 120ms ease, border-color 120ms ease; transition: color 120ms ease, border-color 120ms ease;
} }
.m-insights__open:hover { .m-insights__open:hover {
color: var(--m-c-accent); color: var(--m-c-info);
border-bottom-color: var(--m-c-ink); border-bottom-color: var(--m-c-ink);
} }
@@ -220,7 +220,7 @@
color: var(--m-c-ink); border-bottom: 1px solid var(--m-c-accent); padding-bottom: 1px; color: var(--m-c-ink); border-bottom: 1px solid var(--m-c-accent); padding-bottom: 1px;
transition: color 120ms ease, border-color 120ms ease; transition: color 120ms ease, border-color 120ms ease;
} }
.m-paper__open:hover { color: var(--m-c-accent); border-bottom-color: var(--m-c-ink); } .m-paper__open:hover { color: var(--m-c-info); border-bottom-color: var(--m-c-ink); }
.m-list-empty { .m-list-empty {
display: grid; place-content: center; gap: var(--m-space-3); padding: var(--m-space-7); display: grid; place-content: center; gap: var(--m-space-3); padding: var(--m-space-7);
@@ -139,7 +139,7 @@
.m-cmp__form-hint { font-size: 0.75rem; color: var(--m-c-ink-soft); } .m-cmp__form-hint { font-size: 0.75rem; color: var(--m-c-ink-soft); }
.m-cmp__form-actions { display: flex; align-items: end; } .m-cmp__form-actions { display: flex; align-items: end; }
.m-cmp__run { .m-cmp__run {
gap: var(--m-space-2); padding: 8px 16px; border-color: var(--m-c-accent); color: var(--m-c-accent); gap: var(--m-space-2); padding: 8px 16px; border-color: var(--m-c-info); color: var(--m-c-info);
font-family: var(--m-font-mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.14em; font-family: var(--m-font-mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.14em;
} }
.m-cmp__run:not(:disabled):hover { background: var(--m-c-accent); color: var(--m-c-paper); } .m-cmp__run:not(:disabled):hover { background: var(--m-c-accent); color: var(--m-c-paper); }
+4 -4
View File
@@ -43,7 +43,7 @@
{ {
@* First-run: nothing captured yet. Make the next step unmissable. *@ @* First-run: nothing captured yet. Make the next step unmissable. *@
<div data-test="home-empty" style="display: grid; gap: var(--m-space-4); padding: var(--m-space-4) 0;"> <div data-test="home-empty" style="display: grid; gap: var(--m-space-4); padding: var(--m-space-4) 0;">
<span class="m-mono" style="font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.16em; color: var(--m-c-accent);"> <span class="m-mono" style="font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.16em; color: var(--m-c-info);">
@L["Home.Empty.Heading"] @L["Home.Empty.Heading"]
</span> </span>
<p style="color: var(--m-c-ink-soft); max-width: 48ch; margin: 0;"> <p style="color: var(--m-c-ink-soft); max-width: 48ch; margin: 0;">
@@ -51,7 +51,7 @@
</p> </p>
<div> <div>
<a href="/settings" data-test="home-empty-cta" <a href="/settings" data-test="home-empty-cta"
style="display: inline-flex; align-items: center; gap: 8px; padding: 10px 18px; border: 1px solid var(--m-c-accent); color: var(--m-c-accent); font-family: var(--m-font-mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.12em; text-decoration: none;"> style="display: inline-flex; align-items: center; gap: 8px; padding: 10px 18px; background: var(--m-c-accent); color: var(--m-c-on-accent); border: 2px solid var(--m-c-ink); border-radius: var(--m-radius-md); box-shadow: var(--m-shadow-hard-sm); font-family: var(--m-font-mono); font-weight: 700; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.12em; text-decoration: none;">
@L["Home.Empty.Cta"] → @L["Home.Empty.Cta"] →
</a> </a>
</div> </div>
@@ -62,7 +62,7 @@
@* Capturing, but the detector hasn't flagged anything yet. *@ @* Capturing, but the detector hasn't flagged anything yet. *@
<div data-test="home-no-signals" style="display: grid; gap: var(--m-space-3); padding: var(--m-space-3) 0; border-top: 1px solid var(--m-c-rule);"> <div data-test="home-no-signals" style="display: grid; gap: var(--m-space-3); padding: var(--m-space-3) 0; border-top: 1px solid var(--m-c-rule);">
<p style="color: var(--m-c-ink-soft); margin: 0;">@L["Home.NoSignals"]</p> <p style="color: var(--m-c-ink-soft); margin: 0;">@L["Home.NoSignals"]</p>
<a href="/anomalies" style="font-family: var(--m-font-mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.12em; color: var(--m-c-accent); text-decoration: none;"> <a href="/anomalies" style="font-family: var(--m-font-mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.12em; color: var(--m-c-info); text-decoration: none;">
@L["Home.ViewAll"] → @L["Home.ViewAll"] →
</a> </a>
</div> </div>
@@ -92,7 +92,7 @@
</div> </div>
<div style="margin-top: var(--m-space-5); padding-top: var(--m-space-3); border-top: 1px solid var(--m-c-rule);"> <div style="margin-top: var(--m-space-5); padding-top: var(--m-space-3); border-top: 1px solid var(--m-c-rule);">
<a href="/anomalies" style="font-family: var(--m-font-mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.12em; color: var(--m-c-accent); text-decoration: none;"> <a href="/anomalies" style="font-family: var(--m-font-mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.12em; color: var(--m-c-info); text-decoration: none;">
@L["Home.ViewAll"] → @L["Home.ViewAll"] →
</a> </a>
</div> </div>
+2 -2
View File
@@ -579,8 +579,8 @@
gap: var(--m-space-3); gap: var(--m-space-3);
} }
.m-journal__submit { .m-journal__submit {
border-color: var(--m-c-accent); border-color: var(--m-c-info);
color: var(--m-c-accent); color: var(--m-c-info);
} }
.m-journal__submit:not(:disabled):hover { .m-journal__submit:not(:disabled):hover {
background: var(--m-c-accent); background: var(--m-c-accent);
@@ -156,11 +156,11 @@
border-radius: var(--m-radius-xs); border-radius: var(--m-radius-xs);
border: 1px solid var(--m-c-rule); border: 1px solid var(--m-c-rule);
} }
.m-result-winner--side1 { background: rgba(34,197,94,0.10); color: #15803d; border-color: rgba(34,197,94,0.30); } .m-result-winner--side1 { background: rgba(31,158,61,0.10); color: var(--m-c-positive); border-color: rgba(31,158,61,0.32); }
.m-result-winner--side2 { background: rgba(59,130,246,0.10); color: #1d4ed8; border-color: rgba(59,130,246,0.30); } .m-result-winner--side2 { background: rgba(36,75,255,0.10); color: var(--m-c-info); border-color: rgba(36,75,255,0.32); }
.m-result-winner--draw { background: rgba(120,113,108,0.10); color: var(--m-c-ink-soft); } .m-result-winner--draw { background: rgba(120,113,108,0.10); color: var(--m-c-ink-soft); }
[data-theme="dark"] .m-result-winner--side1 { color: #4ade80; background: rgba(34,197,94,0.15); } [data-theme="dark"] .m-result-winner--side1 { background: rgba(74,222,128,0.16); }
[data-theme="dark"] .m-result-winner--side2 { color: #93c5fd; background: rgba(59,130,246,0.15); } [data-theme="dark"] .m-result-winner--side2 { background: rgba(111,139,255,0.16); }
</style> </style>
@code { @code {
@@ -198,8 +198,8 @@
border-radius: var(--m-radius-xs); border-radius: var(--m-radius-xs);
border: 1px solid var(--m-c-rule); border: 1px solid var(--m-c-rule);
} }
.m-result-winner--side1 { background: rgba(34,197,94,0.10); color: #15803d; border-color: rgba(34,197,94,0.30); } .m-result-winner--side1 { background: rgba(31,158,61,0.10); color: var(--m-c-positive); border-color: rgba(31,158,61,0.32); }
.m-result-winner--side2 { background: rgba(59,130,246,0.10); color: #1d4ed8; border-color: rgba(59,130,246,0.30); } .m-result-winner--side2 { background: rgba(36,75,255,0.10); color: var(--m-c-info); border-color: rgba(36,75,255,0.32); }
.m-result-winner--draw { background: rgba(120,113,108,0.10); color: var(--m-c-ink-soft); } .m-result-winner--draw { background: rgba(120,113,108,0.10); color: var(--m-c-ink-soft); }
</style> </style>
@@ -516,7 +516,7 @@
{ {
if (_filter.SortKey != key) return; if (_filter.SortKey != key) return;
builder.OpenElement(0, "span"); builder.OpenElement(0, "span");
builder.AddAttribute(1, "style", "margin-left: 6px; color: var(--m-c-accent);"); builder.AddAttribute(1, "style", "margin-left: 6px; color: var(--m-c-info);");
builder.AddContent(2, _filter.SortDescending ? "▼" : "▲"); builder.AddContent(2, _filter.SortDescending ? "▼" : "▲");
builder.CloseElement(); builder.CloseElement();
}; };
+5 -3
View File
@@ -49,13 +49,14 @@
--m-c-paper: #fffef8; --m-c-paper: #fffef8;
--m-c-paper-2: #f3f1e9; --m-c-paper-2: #f3f1e9;
--m-c-paper-3: #e7e3d6; --m-c-paper-3: #e7e3d6;
--m-c-rule: #0a0a0a; --m-c-rule: #d8d4c4; /* soft divider — brutalist weight lives on cards/inputs, not page hairlines */
--m-c-accent: #c6f400; /* acid lime */ --m-c-accent: #c6f400; /* acid lime */
--m-c-accent-soft: #b3dd00; --m-c-accent-soft: #b3dd00;
--m-c-on-accent: #0a0a0a; /* ink on lime — lime is a light hue */ --m-c-on-accent: #0a0a0a; /* ink on lime — lime is a light hue */
--m-c-anomaly: #ff3b30; --m-c-anomaly: #ff3b30;
--m-c-positive: #1f9e3d; --m-c-positive: #1f9e3d;
--m-c-info: #244bff; /* electric blue */ --m-c-info: #244bff; /* electric blue — interactive / highlight / link text */
--m-c-warning: #c2680a; /* amber — medium severity + warnings (lime is unreadable as text) */
/* Tabular numerals for everywhere odds/scores appear */ /* Tabular numerals for everywhere odds/scores appear */
--m-num-feature: "tnum" 1, "lnum" 1; --m-num-feature: "tnum" 1, "lnum" 1;
@@ -69,13 +70,14 @@
--m-c-paper: #1e1c15; --m-c-paper: #1e1c15;
--m-c-paper-2: #141310; --m-c-paper-2: #141310;
--m-c-paper-3: #2a2820; --m-c-paper-3: #2a2820;
--m-c-rule: #f5f3ea; --m-c-rule: #3a3730;
--m-c-accent: #c6f400; --m-c-accent: #c6f400;
--m-c-accent-soft: #aacc00; --m-c-accent-soft: #aacc00;
--m-c-on-accent: #0a0a0a; --m-c-on-accent: #0a0a0a;
--m-c-anomaly: #ff5a4f; --m-c-anomaly: #ff5a4f;
--m-c-positive: #4ade80; --m-c-positive: #4ade80;
--m-c-info: #6f8bff; --m-c-info: #6f8bff;
--m-c-warning: #ffb24d;
/* Hard shadow goes pure-black on dark; the light border defines the block. */ /* Hard shadow goes pure-black on dark; the light border defines the block. */
--m-shadow-hard: 5px 5px 0 #000000; --m-shadow-hard: 5px 5px 0 #000000;
--m-shadow-hard-sm: 3px 3px 0 #000000; --m-shadow-hard-sm: 3px 3px 0 #000000;