Files
maraphon-app/src/Marathon.UI/Components/OddsCell.razor
T
alexei.dolgolyov 1e4dddbbad 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.
2026-05-29 15:04:15 +03:00

107 lines
3.2 KiB
Plaintext

@*
OddsCell — tabular mono rendering of a decimal odds rate, with an inline
direction marker (▲ amber rising, ▼ red falling, em-dash unchanged) when
the value differs from a previous render. Drives the visual indicator
that Phase 6 promises for live-list odds movement.
Rate is the current value. Previous (optional) is the prior value the
caller wants compared against. Caller decides what "previous" means
(last refresh / last snapshot) and tracks it in its own state.
*@
@{
var direction = Direction;
var directionGlyph = direction switch
{
Trend.Up => "▲", // ▲
Trend.Down => "▼", // ▼
_ => "—", // em-dash
};
var directionClass = direction switch
{
Trend.Up => "is-up",
Trend.Down => "is-down",
_ => "is-flat",
};
var ariaTrend = direction switch
{
Trend.Up => "rising",
Trend.Down => "falling",
_ => "unchanged",
};
}
<span class="m-odds @directionClass" aria-label="@AriaPrefix @Formatted (@ariaTrend)" data-trend="@ariaTrend">
<span class="m-odds__value m-mono" data-numeric>@Formatted</span>
@if (ShowTrend)
{
<span class="m-odds__delta" aria-hidden="true">@directionGlyph</span>
}
</span>
<style>
.m-odds {
display: inline-flex;
align-items: baseline;
gap: 6px;
min-width: 56px;
white-space: nowrap;
}
.m-odds__value {
font-feature-settings: "tnum" 1, "lnum" 1;
font-weight: 500;
color: var(--m-c-ink);
font-size: 0.9375rem;
}
.m-odds__delta {
font-family: var(--m-font-mono);
font-size: 0.6875rem;
line-height: 1;
color: var(--m-c-ink-soft);
transition: color 220ms ease;
}
.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-flat .m-odds__delta { color: var(--m-c-ink-soft); }
.m-odds.is-up .m-odds__value,
.m-odds.is-down .m-odds__value {
animation: m-odds-flash 1200ms ease-out 1;
}
@@keyframes m-odds-flash {
0% { background: color-mix(in srgb, currentColor 14%, transparent); }
100% { background: transparent; }
}
@@media (prefers-reduced-motion: reduce) {
.m-odds.is-up .m-odds__value,
.m-odds.is-down .m-odds__value { animation: none; }
}
</style>
@code {
[Parameter] public decimal? Rate { get; set; }
[Parameter] public decimal? Previous { get; set; }
[Parameter] public bool ShowTrend { get; set; } = true;
[Parameter] public string AriaPrefix { get; set; } = "Odds";
[Parameter] public string EmptyPlaceholder { get; set; } = "—";
private string Formatted => Rate is { } r
? r.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
: EmptyPlaceholder;
private Trend Direction
{
get
{
if (Rate is not { } r || Previous is not { } p) return Trend.Flat;
if (r > p) return Trend.Up;
if (r < p) return Trend.Down;
return Trend.Flat;
}
}
private enum Trend { Flat, Up, Down }
}