a627c360c3
Cross-cutting polish that surfaced while Phase 8 was being implemented:
* Invariant-culture formatting on every decimal ToString("0.00" / "0.##")
(OddsCell, OddsTimeline, SeverityBadge, AnomalyEvidence,
Pages/Events/Detail) — previously the comma/dot decimal separator
switched with the locale and broke tabular alignment + tests.
* LocaleState constructor no longer mutates process-wide ambient culture;
apply only happens through Set(...). Stops parallel bUnit test runs
leaking ru-RU into each other's threads.
* MainLayout: drawer-open state now offsets main content / footer / appbar
by the drawer width (248px) so the sidebar no longer overlaps content.
Mobile breakpoint (≤720px) keeps the original full-width layout.
* wwwroot/index.html (Marathon.UI RCL): switched from Plotly CDN to the
bundled "_content/Plotly.Blazor/plotly-2.35.3.min.js" — works offline
and matches the Plotly.Blazor 5.4.1 version pin.
* Marathon.Hosts.WpfBlazor/wwwroot/index.html: host-level page that
BlazorWebView's HostPage attribute resolves to. Same Plotly bundle,
no autostart="false" (BlazorWebView auto-starts).
107 lines
3.2 KiB
Plaintext
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-accent); }
|
|
.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 }
|
|
}
|