diff --git a/src/Marathon.Hosts.WpfBlazor/wwwroot/index.html b/src/Marathon.Hosts.WpfBlazor/wwwroot/index.html new file mode 100644 index 0000000..162d2ce --- /dev/null +++ b/src/Marathon.Hosts.WpfBlazor/wwwroot/index.html @@ -0,0 +1,48 @@ + + + + + + Marathon — Odds Lab + + + + + + + + + + + + + + +
+
+ Booting +
Marathon Odds Lab
+
+
+ + + + + + + + + + + diff --git a/src/Marathon.UI/Components/AnomalyEvidence.razor b/src/Marathon.UI/Components/AnomalyEvidence.razor index f06cc3f..e72d16c 100644 --- a/src/Marathon.UI/Components/AnomalyEvidence.razor +++ b/src/Marathon.UI/Components/AnomalyEvidence.razor @@ -217,7 +217,7 @@ builder.OpenElement(12, "span"); builder.AddAttribute(13, "class", "m-evidence__rate"); - builder.AddContent(14, rate is { } r ? r.ToString("0.00") : "—"); + builder.AddContent(14, rate is { } r ? r.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture) : "—"); builder.CloseElement(); builder.CloseElement(); diff --git a/src/Marathon.UI/Components/OddsCell.razor b/src/Marathon.UI/Components/OddsCell.razor index 74561c0..9846531 100644 --- a/src/Marathon.UI/Components/OddsCell.razor +++ b/src/Marathon.UI/Components/OddsCell.razor @@ -87,7 +87,9 @@ [Parameter] public string AriaPrefix { get; set; } = "Odds"; [Parameter] public string EmptyPlaceholder { get; set; } = "—"; - private string Formatted => Rate is { } r ? r.ToString("0.00") : EmptyPlaceholder; + private string Formatted => Rate is { } r + ? r.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture) + : EmptyPlaceholder; private Trend Direction { diff --git a/src/Marathon.UI/Components/OddsTimeline.razor b/src/Marathon.UI/Components/OddsTimeline.razor index 988df3d..2d41aa5 100644 --- a/src/Marathon.UI/Components/OddsTimeline.razor +++ b/src/Marathon.UI/Components/OddsTimeline.razor @@ -238,5 +238,6 @@ }, }; - private static string FormatRate(decimal? r) => r is { } v ? v.ToString("0.00") : "—"; + private static string FormatRate(decimal? r) => + r is { } v ? v.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture) : "—"; } diff --git a/src/Marathon.UI/Components/SeverityBadge.razor b/src/Marathon.UI/Components/SeverityBadge.razor index 8a98fa6..73ab2ac 100644 --- a/src/Marathon.UI/Components/SeverityBadge.razor +++ b/src/Marathon.UI/Components/SeverityBadge.razor @@ -19,7 +19,7 @@ @Label @if (ShowScore && Score is { } s) { - + } diff --git a/src/Marathon.UI/MainLayout.razor b/src/Marathon.UI/MainLayout.razor index 4225975..2c575f4 100644 --- a/src/Marathon.UI/MainLayout.razor +++ b/src/Marathon.UI/MainLayout.razor @@ -83,6 +83,25 @@ min-height: 0; } + /* MudDrawer is positioned fixed/absolute by Mud's CSS — push main content + right by the drawer's width (248px) so the sidebar doesn't overlap. + Same shift on the appbar tools row so the brand isn't covered. */ + .m-app-frame.is-drawer-open .m-main, + .m-app-frame.is-drawer-open .m-footer { + padding-left: 248px; + transition: padding-left 200ms ease; + } + .m-app-frame.is-drawer-open .m-appbar { + padding-left: calc(248px + clamp(var(--m-space-3), 2vw, var(--m-space-5))); + transition: padding-left 200ms ease; + } + @@media (max-width: 720px) { + /* On narrow viewports the drawer becomes a temporary overlay anyway. */ + .m-app-frame.is-drawer-open .m-main, + .m-app-frame.is-drawer-open .m-footer, + .m-app-frame.is-drawer-open .m-appbar { padding-left: 0; } + } + .m-footer { display: flex; align-items: center; diff --git a/src/Marathon.UI/Pages/Events/Detail.razor b/src/Marathon.UI/Pages/Events/Detail.razor index 292e9aa..4f25fa6 100644 --- a/src/Marathon.UI/Pages/Events/Detail.razor +++ b/src/Marathon.UI/Pages/Events/Detail.razor @@ -105,7 +105,7 @@ @BetTypeLabel(row.Type) @SideLabel(row.Side) @FormatThreshold(row.Threshold) - @row.Rate.ToString("0.00") + @row.Rate.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture) } @@ -334,6 +334,8 @@ _ => s.ToString(), }; - private static string FormatRate(decimal? r) => r is { } v ? v.ToString("0.00") : "—"; - private static string FormatThreshold(decimal? v) => v is { } x ? x.ToString("0.##") : "—"; + private static string FormatRate(decimal? r) => + r is { } v ? v.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture) : "—"; + private static string FormatThreshold(decimal? v) => + v is { } x ? x.ToString("0.##", System.Globalization.CultureInfo.InvariantCulture) : "—"; } diff --git a/src/Marathon.UI/Services/LocaleState.cs b/src/Marathon.UI/Services/LocaleState.cs index 12c4fcd..23a2111 100644 --- a/src/Marathon.UI/Services/LocaleState.cs +++ b/src/Marathon.UI/Services/LocaleState.cs @@ -15,23 +15,35 @@ public sealed class LocaleState public static readonly IReadOnlyList Supported = new[] { Russian, English }; - private CultureInfo _culture = CultureInfo.GetCultureInfo(Russian); + private CultureInfo _culture; + + public LocaleState() + { + // Initialise the field only — do NOT mutate process-wide ambient culture + // here. Production startup (App.xaml.cs) always calls Set(...) right after + // resolving this service, which applies ambient culture explicitly. Doing + // it here as well leaks ru-RU across parallel test runs whenever any + // bUnit test class instantiates MarathonTestContext. + _culture = CultureInfo.GetCultureInfo(Russian); + } public CultureInfo Culture { get => _culture; private set { + // Always re-apply ambient cultures even when the value matches — + // App.xaml.cs's startup call uses the same default the constructor + // initialized to, so an equality short-circuit here would leave the + // actual thread CurrentCulture stuck on the system locale. + ApplyAmbientCulture(value); + if (string.Equals(_culture.Name, value.Name, StringComparison.OrdinalIgnoreCase)) { return; } _culture = value; - CultureInfo.DefaultThreadCurrentCulture = value; - CultureInfo.DefaultThreadCurrentUICulture = value; - CultureInfo.CurrentCulture = value; - CultureInfo.CurrentUICulture = value; OnChange?.Invoke(); } } @@ -47,4 +59,12 @@ public sealed class LocaleState Culture = CultureInfo.GetCultureInfo(cultureName); } + + private static void ApplyAmbientCulture(CultureInfo c) + { + CultureInfo.DefaultThreadCurrentCulture = c; + CultureInfo.DefaultThreadCurrentUICulture = c; + CultureInfo.CurrentCulture = c; + CultureInfo.CurrentUICulture = c; + } } diff --git a/src/Marathon.UI/wwwroot/index.html b/src/Marathon.UI/wwwroot/index.html index fd229df..ebc4eb2 100644 --- a/src/Marathon.UI/wwwroot/index.html +++ b/src/Marathon.UI/wwwroot/index.html @@ -38,8 +38,8 @@ × - - + +