WIP(initial-implementation): parallel batch P2/P3/P5 — code complete, unreviewed
Snapshot of the parallel batch (Phases 2 + 3 + 5) at session pause. Solution does
NOT build cleanly yet — known cross-phase compile issues remain to be resolved
before review. See plans/initial-implementation/PLAN.md "Resume Notes" section
for the exact tomorrow-morning action list.
Phase 2 (Storage):
- Repository interfaces in Marathon.Application/Abstractions
- DateRange, ExportKind, StorageOptions in Marathon.Application/Storage
- EF Core 8 + SQLite (WAL) persistence: 7 entities + configurations + 4 repos
- Hand-written InitialCreate migration (dotnet ef blocked by parallel work)
- ClosedXML ExcelExporter with exact customer-spec wide columns
- PersistenceModule.AddMarathonPersistence DI extension
- Round-trip + export tests (cannot run yet — see cross-phase issues)
Phase 3 (Scraping):
- IOddsScraper, IBetPlacer in Marathon.Application/Abstractions
- ScrapingOptions in Marathon.Infrastructure/Configuration
- MarathonbetScraper with 4 parsers (Upcoming, Live, EventOdds, Results)
- Helpers: ServerTimeProvider, PeriodScopeMapper, OutcomeCodeMapper, MoscowDateParser
- UserAgentRotatorHandler + Polly v8 resilience pipeline
- ScrapingModule.AddMarathonScraping DI extension
- GlobalUsings.cs aliases for EventId / Configuration disambiguation
- Parser tests with trimmed HTML fixtures
- ScrapeResultsAsync interim no-op (Phase 8 will replace via watch-list polling)
Phase 5 (UI shell — killed mid-final-verify, assumed ~95%):
- Marathon.UI populated: MainLayout, App.razor, Pages (Home, Settings),
Components, Theme (MarathonTheme.cs + Tokens.cs + app.css), Resources
(SharedResource.{cs,ru.resx,en.resx}), Services (ISettingsWriter), wwwroot
- WPF host: App.xaml(.cs), MainWindow.xaml(.cs), Marathon.Hosts.WpfBlazor.csproj
with Microsoft.AspNetCore.Components.WebView.Wpf + MudBlazor + Serilog
- appsettings.json + appsettings.Development.json with all sections wired
- bUnit tests: MainLayoutTests, LocaleSwitcherTests, ThemeToggleTests,
JsonSettingsWriterTests + Support helpers
Cross-phase issues to resolve at next session:
1. Phase 2 repository classes are 'internal' — Phase 3's tests can't reference
them. Fix: add InternalsVisibleTo to Marathon.Infrastructure.csproj.
2. Phase 5: LocalizationOptions namespace ambiguity (AspNetCore vs Extensions).
3. Phase 5: WpfBlazor Serilog API mismatch.
Reviewer has NOT run on this batch. Move to Phase 4 only after build is green
and a combined parallel-batch reviewer passes.
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
using MudBlazor;
|
||||
using MudBlazor.Utilities;
|
||||
|
||||
namespace Marathon.UI.Theme;
|
||||
|
||||
/// <summary>
|
||||
/// The Marathon design system, expressed as a MudBlazor theme.
|
||||
///
|
||||
/// Aesthetic direction: editorial-quant. Inspired by Bloomberg terminals,
|
||||
/// FT.com long-reads, and Quartz dashboards. Confident, information-dense,
|
||||
/// reveals patterns. Pairs IBM Plex Sans (Cyrillic-capable display + body)
|
||||
/// with JetBrains Mono for tabular numerals. Anomaly accent is a load-bearing
|
||||
/// signal red so Phase 7 can hang the entire anomaly visual language off
|
||||
/// <c>palette.Error</c> without coupling to a hard-coded hex.
|
||||
/// </summary>
|
||||
public static class MarathonTheme
|
||||
{
|
||||
/// <summary>The full theme — both light and dark palettes plus typography.</summary>
|
||||
public static MudTheme Build() => new()
|
||||
{
|
||||
PaletteLight = LightPalette,
|
||||
PaletteDark = DarkPalette,
|
||||
Typography = MarathonTypography,
|
||||
LayoutProperties = LayoutProps,
|
||||
Shadows = MarathonShadows,
|
||||
ZIndex = new ZIndex(),
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Palettes — single accent (amber #d97706), single signal (red #ef4444)
|
||||
// on a deep navy/parchment chassis. No purple gradients, no cliche.
|
||||
// ------------------------------------------------------------------
|
||||
private static readonly PaletteLight LightPalette = new()
|
||||
{
|
||||
Primary = "#0f172a", // deep navy / ink
|
||||
PrimaryContrastText = "#fafaf7",
|
||||
Secondary = "#334155", // slate
|
||||
SecondaryContrastText = "#fafaf7",
|
||||
Tertiary = "#d97706", // amber accent
|
||||
TertiaryContrastText = "#1c1917",
|
||||
Info = "#0369a1",
|
||||
Success = "#15803d",
|
||||
Warning = "#b45309",
|
||||
Error = "#dc2626", // anomaly signal
|
||||
ErrorContrastText = "#fff7ed",
|
||||
|
||||
Black = "#1c1917",
|
||||
White = "#fafaf7",
|
||||
Surface = "#fafaf7", // warm parchment
|
||||
Background = "#f5f4ef", // a half-step warmer than surface
|
||||
BackgroundGray = "#ebe9e1",
|
||||
DrawerBackground = "#0f172a", // dark drawer on light app — editorial contrast
|
||||
DrawerText = "#e7e5e4",
|
||||
DrawerIcon = "#d6d3d1",
|
||||
AppbarBackground = "#fafaf7",
|
||||
AppbarText = "#0f172a",
|
||||
|
||||
TextPrimary = "#0f172a",
|
||||
TextSecondary = "#475569",
|
||||
TextDisabled = "#94a3b8",
|
||||
ActionDefault = "#334155",
|
||||
ActionDisabled = "#cbd5e1",
|
||||
ActionDisabledBackground = "#e2e8f0",
|
||||
|
||||
LinesDefault = "#e7e5e4",
|
||||
LinesInputs = "#cbd5e1",
|
||||
TableLines = "#e7e5e4",
|
||||
TableStriped = "#f5f4ef",
|
||||
TableHover = "#ebe9e1",
|
||||
Divider = "#e7e5e4",
|
||||
DividerLight = "#f1f5f9",
|
||||
|
||||
OverlayDark = new MudColor("#0f172a99").Value,
|
||||
OverlayLight = new MudColor("#fafaf7cc").Value,
|
||||
};
|
||||
|
||||
private static readonly PaletteDark DarkPalette = new()
|
||||
{
|
||||
Primary = "#fbbf24", // amber, promoted in dark mode
|
||||
PrimaryContrastText = "#0c0a09",
|
||||
Secondary = "#94a3b8",
|
||||
SecondaryContrastText = "#0c0a09",
|
||||
Tertiary = "#fbbf24",
|
||||
TertiaryContrastText = "#0c0a09",
|
||||
Info = "#38bdf8",
|
||||
Success = "#4ade80",
|
||||
Warning = "#fbbf24",
|
||||
Error = "#f87171", // anomaly signal — softened for dark
|
||||
ErrorContrastText = "#0c0a09",
|
||||
|
||||
Black = "#0c0a09",
|
||||
White = "#fafaf7",
|
||||
Surface = "#1c1917", // ink-stained paper
|
||||
Background = "#0c0a09", // near-black
|
||||
BackgroundGray = "#1c1917",
|
||||
DrawerBackground = "#0c0a09",
|
||||
DrawerText = "#e7e5e4",
|
||||
DrawerIcon = "#a8a29e",
|
||||
AppbarBackground = "#0c0a09",
|
||||
AppbarText = "#fafaf7",
|
||||
|
||||
TextPrimary = "#f5f5f4",
|
||||
TextSecondary = "#a8a29e",
|
||||
TextDisabled = "#57534e",
|
||||
ActionDefault = "#a8a29e",
|
||||
ActionDisabled = "#44403c",
|
||||
ActionDisabledBackground = "#1c1917",
|
||||
|
||||
LinesDefault = "#292524",
|
||||
LinesInputs = "#44403c",
|
||||
TableLines = "#292524",
|
||||
TableStriped = "#1c1917",
|
||||
TableHover = "#292524",
|
||||
Divider = "#292524",
|
||||
DividerLight = "#1c1917",
|
||||
|
||||
OverlayDark = new MudColor("#0c0a09cc").Value,
|
||||
OverlayLight = new MudColor("#fafaf722").Value,
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Typography — IBM Plex Sans / JetBrains Mono / IBM Plex Serif (display)
|
||||
// All have full Cyrillic coverage. Numerals are tabular.
|
||||
// ------------------------------------------------------------------
|
||||
private static readonly string[] DisplayStack = { "IBM Plex Serif", "PT Serif", "Georgia", "serif" };
|
||||
private static readonly string[] BodyStack = { "IBM Plex Sans", "PT Sans", "system-ui", "sans-serif" };
|
||||
private static readonly string[] MonoStack = { "JetBrains Mono", "IBM Plex Mono", "Fira Code", "Consolas", "monospace" };
|
||||
|
||||
private static readonly Typography MarathonTypography = new()
|
||||
{
|
||||
Default = new Default
|
||||
{
|
||||
FontFamily = BodyStack,
|
||||
FontWeight = 400,
|
||||
FontSize = "0.9375rem", // 15px — denser than MUD default 16
|
||||
LineHeight = 1.55,
|
||||
LetterSpacing = "0",
|
||||
},
|
||||
H1 = new H1
|
||||
{
|
||||
FontFamily = DisplayStack,
|
||||
FontWeight = 300,
|
||||
FontSize = "clamp(2.25rem, 4vw, 3.5rem)",
|
||||
LineHeight = 1.05,
|
||||
LetterSpacing = "-0.022em",
|
||||
},
|
||||
H2 = new H2
|
||||
{
|
||||
FontFamily = DisplayStack,
|
||||
FontWeight = 400,
|
||||
FontSize = "clamp(1.75rem, 2.5vw, 2.25rem)",
|
||||
LineHeight = 1.15,
|
||||
LetterSpacing = "-0.018em",
|
||||
},
|
||||
H3 = new H3
|
||||
{
|
||||
FontFamily = DisplayStack,
|
||||
FontWeight = 500,
|
||||
FontSize = "1.5rem",
|
||||
LineHeight = 1.25,
|
||||
LetterSpacing = "-0.012em",
|
||||
},
|
||||
H4 = new H4
|
||||
{
|
||||
FontFamily = BodyStack,
|
||||
FontWeight = 600,
|
||||
FontSize = "1.25rem",
|
||||
LineHeight = 1.3,
|
||||
LetterSpacing = "-0.005em",
|
||||
},
|
||||
H5 = new H5
|
||||
{
|
||||
FontFamily = BodyStack,
|
||||
FontWeight = 600,
|
||||
FontSize = "1.0625rem",
|
||||
LineHeight = 1.35,
|
||||
},
|
||||
H6 = new H6
|
||||
{
|
||||
FontFamily = BodyStack,
|
||||
FontWeight = 600,
|
||||
FontSize = "0.9375rem",
|
||||
LineHeight = 1.4,
|
||||
LetterSpacing = "0.02em",
|
||||
},
|
||||
Subtitle1 = new Subtitle1
|
||||
{
|
||||
FontFamily = BodyStack,
|
||||
FontWeight = 500,
|
||||
FontSize = "0.9375rem",
|
||||
LineHeight = 1.5,
|
||||
},
|
||||
Subtitle2 = new Subtitle2
|
||||
{
|
||||
FontFamily = BodyStack,
|
||||
FontWeight = 500,
|
||||
FontSize = "0.8125rem",
|
||||
LineHeight = 1.5,
|
||||
LetterSpacing = "0.01em",
|
||||
},
|
||||
Body1 = new Body1
|
||||
{
|
||||
FontFamily = BodyStack,
|
||||
FontWeight = 400,
|
||||
FontSize = "0.9375rem",
|
||||
LineHeight = 1.55,
|
||||
},
|
||||
Body2 = new Body2
|
||||
{
|
||||
FontFamily = BodyStack,
|
||||
FontWeight = 400,
|
||||
FontSize = "0.8125rem",
|
||||
LineHeight = 1.5,
|
||||
},
|
||||
Button = new Button
|
||||
{
|
||||
FontFamily = BodyStack,
|
||||
FontWeight = 500,
|
||||
FontSize = "0.8125rem",
|
||||
LineHeight = 1.4,
|
||||
LetterSpacing = "0.06em",
|
||||
TextTransform = "uppercase",
|
||||
},
|
||||
Caption = new Caption
|
||||
{
|
||||
FontFamily = MonoStack,
|
||||
FontWeight = 400,
|
||||
FontSize = "0.75rem",
|
||||
LineHeight = 1.4,
|
||||
LetterSpacing = "0.04em",
|
||||
TextTransform = "uppercase",
|
||||
},
|
||||
Overline = new Overline
|
||||
{
|
||||
FontFamily = MonoStack,
|
||||
FontWeight = 500,
|
||||
FontSize = "0.6875rem",
|
||||
LineHeight = 1.4,
|
||||
LetterSpacing = "0.18em",
|
||||
TextTransform = "uppercase",
|
||||
},
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Layout — sharp corners, narrow drawer. The aesthetic earns its
|
||||
// authority through restraint.
|
||||
// ------------------------------------------------------------------
|
||||
private static readonly LayoutProperties LayoutProps = new()
|
||||
{
|
||||
DefaultBorderRadius = "2px",
|
||||
AppbarHeight = "60px",
|
||||
DrawerWidthLeft = "248px",
|
||||
DrawerWidthRight = "248px",
|
||||
DrawerMiniWidthLeft = "60px",
|
||||
DrawerMiniWidthRight = "60px",
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Shadows — flat by default, one accent shadow for floating panels.
|
||||
// Override only the slots Mud actually uses; keep first/last as-is.
|
||||
// ------------------------------------------------------------------
|
||||
private static readonly Shadow MarathonShadows = new()
|
||||
{
|
||||
Elevation = new[]
|
||||
{
|
||||
"none",
|
||||
"0 1px 0 0 rgba(15,23,42,0.06)",
|
||||
"0 1px 2px 0 rgba(15,23,42,0.08)",
|
||||
"0 2px 4px -1px rgba(15,23,42,0.10)",
|
||||
"0 4px 8px -2px rgba(15,23,42,0.12)",
|
||||
"0 6px 14px -4px rgba(15,23,42,0.14)",
|
||||
"0 8px 18px -6px rgba(15,23,42,0.16)",
|
||||
"0 10px 22px -8px rgba(15,23,42,0.18)",
|
||||
"0 12px 28px -10px rgba(15,23,42,0.20)",
|
||||
"0 14px 32px -12px rgba(15,23,42,0.22)",
|
||||
"0 16px 36px -14px rgba(15,23,42,0.24)",
|
||||
"0 18px 40px -16px rgba(15,23,42,0.26)",
|
||||
"0 20px 44px -18px rgba(15,23,42,0.28)",
|
||||
"0 22px 48px -20px rgba(15,23,42,0.30)",
|
||||
"0 24px 52px -22px rgba(15,23,42,0.32)",
|
||||
"0 26px 56px -24px rgba(15,23,42,0.34)",
|
||||
"0 28px 60px -26px rgba(15,23,42,0.36)",
|
||||
"0 30px 64px -28px rgba(15,23,42,0.38)",
|
||||
"0 32px 68px -30px rgba(15,23,42,0.40)",
|
||||
"0 34px 72px -32px rgba(15,23,42,0.42)",
|
||||
"0 36px 76px -34px rgba(15,23,42,0.44)",
|
||||
"0 38px 80px -36px rgba(15,23,42,0.46)",
|
||||
"0 40px 84px -38px rgba(15,23,42,0.48)",
|
||||
"0 42px 88px -40px rgba(15,23,42,0.50)",
|
||||
"0 44px 92px -42px rgba(15,23,42,0.52)",
|
||||
"0 46px 96px -44px rgba(15,23,42,0.54)",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Marathon.UI.Theme;
|
||||
|
||||
/// <summary>
|
||||
/// Design tokens exposed to C# code (e.g. for chart colors, custom shapes,
|
||||
/// Razor components that need to reach beyond the MudTheme palette).
|
||||
/// Mirrors the values declared as CSS variables in <c>wwwroot/app.css</c>.
|
||||
/// </summary>
|
||||
public static class Tokens
|
||||
{
|
||||
public static class Colors
|
||||
{
|
||||
public const string AnomalySignal = "#dc2626";
|
||||
public const string AnomalySignalDark = "#f87171";
|
||||
public const string Accent = "#d97706";
|
||||
public const string AccentDark = "#fbbf24";
|
||||
public const string InkPrimary = "#0f172a";
|
||||
public const string Parchment = "#fafaf7";
|
||||
public const string ParchmentDeep = "#f5f4ef";
|
||||
public const string InkDeep = "#0c0a09";
|
||||
}
|
||||
|
||||
public static class Spacing
|
||||
{
|
||||
public const string Xs = "4px";
|
||||
public const string Sm = "8px";
|
||||
public const string Md = "16px";
|
||||
public const string Lg = "24px";
|
||||
public const string Xl = "40px";
|
||||
public const string Xxl = "64px";
|
||||
}
|
||||
|
||||
public static class Typography
|
||||
{
|
||||
public const string DisplayStack = "\"IBM Plex Serif\", \"PT Serif\", Georgia, serif";
|
||||
public const string BodyStack = "\"IBM Plex Sans\", \"PT Sans\", system-ui, sans-serif";
|
||||
public const string MonoStack = "\"JetBrains Mono\", \"IBM Plex Mono\", \"Fira Code\", Consolas, monospace";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user