feat(ui): Velocity redesign — foundations (tokens, theme, chrome)

Re-skin the app into the "Velocity" neo-brutalist direction: acid-lime
accent, hard offset shadows, slammed uppercase headlines, rounded
brutalist blocks. Light "paper" chassis + a warm-charcoal dark mode, both
keeping the lime accent and a constant black appbar; the light/dark toggle
is preserved.

- Type is Cyrillic-complete (the mockup's Anton/DM Sans/Space Mono are not,
  and this product is Russian-first): Oswald (display) + Manrope (body) +
  JetBrains Mono (numerals), loaded via the existing Google Fonts CDN.
- app.css: Velocity token palette (light+dark) + brutalist shared
  components (cards, kicker chips, stat blocks, nav, segmented, sections,
  anomaly badge); diagonal-hatch texture; electric-blue focus rings.
- MarathonTheme + Tokens.cs: Mud palette / typography / radius retargeted
  so every Mud component follows; appbar black, drawer flipped to paper.
- MainLayout + NavBody chrome: black appbar, lime capture chip, paper
  drawer with brutalist nav blocks (fixes the old white-on-paper brand).
- OddsTimeline + SportIcon recoloured to the Velocity palette (chart lines
  now read on both themes; dropped the off-brand purple sport hue).

Foundations only — build clean, all 568 tests green. Page-level polish
(Results winner colours, bespoke page styles) rolls out next.
This commit is contained in:
2026-05-29 14:47:42 +03:00
parent 0683e348ba
commit 5d79911c12
9 changed files with 325 additions and 278 deletions
@@ -8,17 +8,17 @@
<!-- Prevent flash of unthemed content -->
<style>
html, body { background: #f5f4ef; margin: 0; }
html, body { background: #f3f1e9; margin: 0; }
@media (prefers-color-scheme: dark) {
html, body { background: #0c0a09; }
html, body { background: #141310; }
}
</style>
<!-- Fonts: IBM Plex Sans / Serif / Mono + JetBrains Mono. Full Cyrillic coverage. -->
<!-- Fonts: Oswald (display) + Manrope (body) + JetBrains Mono (numerals). Full Cyrillic coverage. -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Serif:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500&family=JetBrains+Mono:wght@400;500;600&display=swap"
href="https://fonts.googleapis.com/css2?family=Oswald:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700;800&display=swap"
rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
@@ -26,13 +26,13 @@
</head>
<body>
<div id="app">
<div style="padding: 64px; font-family: 'IBM Plex Serif', Georgia, serif; color: #475569;">
<span style="font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: #d97706;">Booting</span>
<div style="font-size: 32px; font-weight: 300; margin-top: 8px;">Marathon Odds Lab</div>
<div style="padding: 64px; font-family: 'Manrope', system-ui, sans-serif; color: #6b6757;">
<span style="font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: #0a0a0a; background: #c6f400; border: 2px solid #0a0a0a; border-radius: 8px; padding: 4px 9px;">Booting</span>
<div style="font-family: 'Oswald', sans-serif; font-size: 42px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.01em; margin-top: 14px; color: #0a0a0a;">Marathon Odds Lab</div>
</div>
</div>
<div id="blazor-error-ui" style="display:none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; padding: 12px 24px; background: #dc2626; color: #fafaf7; font-family: 'IBM Plex Sans', sans-serif;">
<div id="blazor-error-ui" style="display:none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; padding: 12px 24px; background: #ff3b30; color: #fffef8; font-family: 'Manrope', system-ui, sans-serif;">
<span>An unhandled error has occurred.</span>
<a href="" class="reload" style="color: #fff; text-decoration: underline; margin-left: 12px;">Reload</a>
<a class="dismiss" style="float: right; cursor: pointer; padding: 0 8px;">×</a>
+11 -9
View File
@@ -3,11 +3,11 @@
@inject AnomalyBrowsingState AnomalyState
<nav class="m-nav" aria-label="primary">
<div style="padding: var(--m-space-5) var(--m-space-4) var(--m-space-3); border-bottom: 1px solid rgba(231,229,228,0.10);">
<div style="font-family: var(--m-font-display); font-size: 1.25rem; color: #fafaf7;">
<span style="color: var(--m-c-accent);">M</span>arathon
<div style="padding: var(--m-space-5) var(--m-space-4) var(--m-space-4); border-bottom: 2px solid var(--m-c-ink);">
<div style="font-family: var(--m-font-display); font-weight: 700; font-size: 1.5rem; text-transform: uppercase; letter-spacing: 0.01em; color: var(--m-c-ink);">
<span style="background: var(--m-c-accent); color: var(--m-c-on-accent); padding: 0 5px; border-radius: 5px;">M</span>arathon
</div>
<div style="font-family: var(--m-font-mono); font-size: 0.6875rem; letter-spacing: 0.18em; text-transform: uppercase; color: rgba(231,229,228,0.55); margin-top: 4px;">
<div style="font-family: var(--m-font-mono); font-size: 0.625rem; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: var(--m-c-ink-soft); margin-top: 4px;">
Odds Lab · v0.1
</div>
</div>
@@ -78,18 +78,20 @@
<style>
.m-nav__badge {
margin-left: auto;
min-width: 18px;
box-sizing: border-box;
min-width: 20px;
padding: 0 6px;
background: var(--m-c-anomaly);
color: #ffffff;
font-family: var(--m-font-mono);
font-size: 0.625rem;
font-weight: 600;
font-weight: 700;
letter-spacing: 0.05em;
line-height: 18px;
height: 18px;
line-height: 16px;
height: 20px;
text-align: center;
border-radius: var(--m-radius-xs);
border: 2px solid var(--m-c-ink);
border-radius: var(--m-radius-sm);
animation: m-pulse 1.6s ease-in-out infinite;
}
@@media (prefers-reduced-motion: reduce) {
+13 -13
View File
@@ -152,9 +152,9 @@
var times = points.Select(p => (object)p.At.UtcDateTime).ToList();
return new List<ITrace>
{
BuildSeries("Win 1", "#0f172a", points.Select(p => (object?)p.Win1Rate).ToList(), times),
BuildSeries("Draw", "#d97706", points.Select(p => (object?)p.DrawRate).ToList(), times),
BuildSeries("Win 2", "#dc2626", points.Select(p => (object?)p.Win2Rate).ToList(), times),
BuildSeries("Win 1", "#244bff", points.Select(p => (object?)p.Win1Rate).ToList(), times),
BuildSeries("Draw", "#ff8a00", points.Select(p => (object?)p.DrawRate).ToList(), times),
BuildSeries("Win 2", "#ff3b30", points.Select(p => (object?)p.Win2Rate).ToList(), times),
};
}
@@ -178,13 +178,13 @@
{
AutoSize = true,
Margin = new Plotly.Blazor.LayoutLib.Margin { L = 56, R = 24, T = 24, B = 48 },
PaperBgColor = dark ? "#1c1917" : "#fafaf7",
PlotBgColor = dark ? "#0c0a09" : "#fafaf7",
PaperBgColor = dark ? "#1e1c15" : "#fffef8",
PlotBgColor = dark ? "#141310" : "#fffef8",
Font = new Plotly.Blazor.LayoutLib.Font
{
Family = "JetBrains Mono, IBM Plex Mono, Consolas, monospace",
Size = 11,
Color = dark ? "#f5f5f4" : "#0f172a",
Color = dark ? "#f5f3ea" : "#0a0a0a",
},
XAxis = new List<XAxis>
{
@@ -192,14 +192,14 @@
{
Title = new Plotly.Blazor.LayoutLib.XAxisLib.Title { Text = string.Empty },
ShowGrid = true,
GridColor = dark ? "#292524" : "#e7e5e4",
GridColor = dark ? "#2a2820" : "#e7e3d6",
ZeroLine = false,
LineColor = dark ? "#292524" : "#e7e5e4",
LineColor = dark ? "#2a2820" : "#e7e3d6",
TickFont = new Plotly.Blazor.LayoutLib.XAxisLib.TickFont
{
Family = "JetBrains Mono, IBM Plex Mono, Consolas, monospace",
Size = 10,
Color = dark ? "#a8a29e" : "#475569",
Color = dark ? "#9c9784" : "#6b6757",
},
},
},
@@ -209,14 +209,14 @@
{
Title = new Plotly.Blazor.LayoutLib.YAxisLib.Title { Text = string.Empty },
ShowGrid = true,
GridColor = dark ? "#292524" : "#e7e5e4",
GridColor = dark ? "#2a2820" : "#e7e3d6",
ZeroLine = false,
LineColor = dark ? "#292524" : "#e7e5e4",
LineColor = dark ? "#2a2820" : "#e7e3d6",
TickFont = new Plotly.Blazor.LayoutLib.YAxisLib.TickFont
{
Family = "JetBrains Mono, IBM Plex Mono, Consolas, monospace",
Size = 10,
Color = dark ? "#a8a29e" : "#475569",
Color = dark ? "#9c9784" : "#6b6757",
},
},
},
@@ -232,7 +232,7 @@
{
Family = "JetBrains Mono, IBM Plex Mono, Consolas, monospace",
Size = 11,
Color = dark ? "#e7e5e4" : "#1e293b",
Color = dark ? "#d8d4c6" : "#26241e",
},
},
},
+4 -4
View File
@@ -20,10 +20,10 @@
flex: 0 0 auto;
}
.m-sport svg { width: 100%; height: 100%; display: block; }
.m-sport[data-sport="6"] { color: #d97706; }
.m-sport[data-sport="11"] { color: #15803d; }
.m-sport[data-sport="22723"] { color: #0369a1; }
.m-sport[data-sport="43658"] { color: #6d28d9; }
.m-sport[data-sport="6"] { color: #ff8a00; } /* basketball — amber */
.m-sport[data-sport="11"] { color: #1f9e3d; } /* football — green */
.m-sport[data-sport="22723"] { color: #0d9488; } /* tennis — teal */
.m-sport[data-sport="43658"] { color: #244bff; } /* hockey — electric blue */
[data-theme="dark"] .m-sport { filter: brightness(1.1); }
</style>
+10 -12
View File
@@ -27,8 +27,8 @@
<div class="m-appbar__tools m-rise m-rise-2">
<span class="m-capture-pill" data-test="capture-pill"
aria-label="@L["Scraping.Aria"]" title="@L["Scraping.Aria"]"
style="display:inline-flex; align-items:center; gap:7px; font-family:var(--m-font-mono); font-size:0.6875rem; text-transform:uppercase; letter-spacing:0.12em; color:@(Capturing ? "var(--m-c-positive)" : "var(--m-c-ink-soft)");">
<span style="width:8px; height:8px; border-radius:50%; background:@(Capturing ? "var(--m-c-positive)" : "var(--m-c-ink-soft)");"></span>
style="display:inline-flex; align-items:center; gap:7px; font-family:var(--m-font-mono); font-size:0.6875rem; font-weight:700; text-transform:uppercase; letter-spacing:0.1em; padding:6px 10px; border-radius:8px; border:2px solid @(Capturing ? "#0a0a0a" : "rgba(255,254,248,0.4)"); background:@(Capturing ? "var(--m-c-accent)" : "transparent"); color:@(Capturing ? "var(--m-c-on-accent)" : "rgba(255,254,248,0.72)");">
<span style="width:8px; height:8px; border-radius:50%; background:@(Capturing ? "#0a0a0a" : "rgba(255,254,248,0.72)");"></span>
@(Capturing ? L["Scraping.On"] : L["Scraping.Off"])
</span>
<LocaleSwitcher />
@@ -43,7 +43,7 @@
ClipMode="DrawerClipMode.Always"
Elevation="0"
Width="248px"
Color="Color.Dark">
Color="Color.Surface">
<NavBody />
</MudDrawer>
@@ -58,7 +58,7 @@
<footer class="m-footer">
<span class="m-kicker">Marathon Odds Lab</span>
<span style="font-family: var(--m-font-mono); font-size: 0.6875rem; color: var(--m-c-ink-soft); letter-spacing: 0.16em; text-transform: uppercase;">
Phase 5 · Editorial-Quant · v0.1
Velocity · v0.1
</span>
</footer>
</div>
@@ -76,8 +76,9 @@
align-items: center;
gap: var(--m-space-3);
padding: 0 clamp(var(--m-space-3), 2vw, var(--m-space-5));
border-bottom: 1px solid var(--m-c-rule);
background: var(--m-c-paper);
border-bottom: 2px solid var(--m-c-accent);
background: #0a0a0a;
color: #fffef8;
z-index: 10;
}
@@ -116,15 +117,12 @@
align-items: center;
justify-content: space-between;
padding: 0 clamp(var(--m-space-3), 2vw, var(--m-space-5));
border-top: 1px solid var(--m-c-rule);
border-top: 2px solid var(--m-c-ink);
background: var(--m-c-paper);
}
[data-theme="dark"] .m-appbar,
[data-theme="dark"] .m-footer {
background: var(--m-c-paper-2);
border-color: var(--m-c-rule);
}
/* The appbar is a constant black bar in both themes (set above); only the
footer follows the paper/charcoal surface token. */
</style>
@code {
+103 -97
View File
@@ -6,12 +6,14 @@ 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.
/// Aesthetic direction: "Velocity" — neo-brutalist sportsbook. Acid lime
/// accent, hard offset shadows, slammed uppercase headlines. Pairs Oswald
/// (Cyrillic-capable condensed display) and Manrope (body) with JetBrains
/// Mono for tabular numerals — the mockup's Anton/DM Sans/Space Mono lack
/// Cyrillic, which this Russian-first product requires. Anomaly accent is a
/// load-bearing signal red so the anomaly visual language hangs off
/// <c>palette.Error</c> without coupling to a hard-coded hex. Light "paper"
/// chassis with a warm-charcoal dark mode; the black appbar is constant.
/// </summary>
public static class MarathonTheme
{
@@ -27,103 +29,105 @@ public static class MarathonTheme
};
// ------------------------------------------------------------------
// Palettes — single accent (amber #d97706), single signal (red #ef4444)
// on a deep navy/parchment chassis. No purple gradients, no cliche.
// Palettes — acid lime accent + signal red on a "paper" chassis
// (warm-charcoal in dark). Primary is the boldest structural colour
// per mode: ink in light, lime in dark. The appbar is black in both.
// ------------------------------------------------------------------
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",
Primary = "#0a0a0a", // ink — brutalist black actions
PrimaryContrastText = "#fffef8",
Secondary = "#26241e", // warm ink
SecondaryContrastText = "#fffef8",
Tertiary = "#c6f400", // acid lime accent
TertiaryContrastText = "#0a0a0a",
Info = "#244bff", // electric blue
Success = "#1f9e3d",
Warning = "#ff8a00",
Error = "#ff3b30", // anomaly signal
ErrorContrastText = "#fffef8",
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",
Black = "#0a0a0a",
White = "#fffef8",
Surface = "#fffef8", // card paper
Background = "#f3f1e9", // page paper
BackgroundGray = "#e7e3d6",
DrawerBackground = "#fffef8", // paper drawer — brutalist light nav
DrawerText = "#26241e",
DrawerIcon = "#6b6757",
AppbarBackground = "#0a0a0a", // constant black bar
AppbarText = "#fffef8",
TextPrimary = "#0f172a",
TextSecondary = "#475569",
TextDisabled = "#94a3b8",
ActionDefault = "#334155",
ActionDisabled = "#cbd5e1",
ActionDisabledBackground = "#e2e8f0",
TextPrimary = "#0a0a0a",
TextSecondary = "#6b6757",
TextDisabled = "#a8a293",
ActionDefault = "#26241e",
ActionDisabled = "#cbc7b8",
ActionDisabledBackground = "#e7e3d6",
LinesDefault = "#e7e5e4",
LinesInputs = "#cbd5e1",
TableLines = "#e7e5e4",
TableStriped = "#f5f4ef",
TableHover = "#ebe9e1",
Divider = "#e7e5e4",
DividerLight = "#f1f5f9",
LinesDefault = "#d8d4c4",
LinesInputs = "#0a0a0a", // bold black input borders
TableLines = "#e7e3d6",
TableStriped = "#f3f1e9",
TableHover = "#e7e3d6",
Divider = "#d8d4c4",
DividerLight = "#ebe7da",
OverlayDark = new MudColor("#0f172a99").Value,
OverlayLight = new MudColor("#fafaf7cc").Value,
OverlayDark = new MudColor("#0a0a0a99").Value,
OverlayLight = new MudColor("#fffef8cc").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",
Primary = "#c6f400", // lime, promoted in dark mode
PrimaryContrastText = "#0a0a0a",
Secondary = "#d8d4c6",
SecondaryContrastText = "#0a0a0a",
Tertiary = "#c6f400",
TertiaryContrastText = "#0a0a0a",
Info = "#6f8bff",
Success = "#4ade80",
Warning = "#fbbf24",
Error = "#f87171", // anomaly signal — softened for dark
ErrorContrastText = "#0c0a09",
Warning = "#ffae42",
Error = "#ff5a4f", // anomaly signal — softened for dark
ErrorContrastText = "#0a0a0a",
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",
Black = "#0a0a0a",
White = "#fffef8",
Surface = "#1e1c15", // warm charcoal card
Background = "#141310", // near-black warm
BackgroundGray = "#2a2820",
DrawerBackground = "#1e1c15",
DrawerText = "#d8d4c6",
DrawerIcon = "#9c9784",
AppbarBackground = "#0a0a0a", // constant black bar
AppbarText = "#f5f3ea",
TextPrimary = "#f5f5f4",
TextSecondary = "#a8a29e",
TextPrimary = "#f5f3ea",
TextSecondary = "#9c9784",
TextDisabled = "#57534e",
ActionDefault = "#a8a29e",
ActionDisabled = "#44403c",
ActionDisabledBackground = "#1c1917",
ActionDefault = "#d8d4c6",
ActionDisabled = "#57534e",
ActionDisabledBackground = "#2a2820",
LinesDefault = "#292524",
LinesInputs = "#44403c",
TableLines = "#292524",
TableStriped = "#1c1917",
TableHover = "#292524",
Divider = "#292524",
DividerLight = "#1c1917",
LinesDefault = "#3a3730",
LinesInputs = "#f5f3ea", // light input borders on charcoal
TableLines = "#2a2820",
TableStriped = "#1e1c15",
TableHover = "#2a2820",
Divider = "#3a3730",
DividerLight = "#2a2820",
OverlayDark = new MudColor("#0c0a09cc").Value,
OverlayLight = new MudColor("#fafaf722").Value,
OverlayDark = new MudColor("#141310cc").Value,
OverlayLight = new MudColor("#f5f3ea22").Value,
};
// ------------------------------------------------------------------
// Typography — IBM Plex Sans / JetBrains Mono / IBM Plex Serif (display)
// All have full Cyrillic coverage. Numerals are tabular.
// Typography — Oswald (condensed display) / Manrope (body) /
// JetBrains Mono (numerals). All have full Cyrillic coverage, which
// the Velocity mockup's Anton/DM Sans/Space Mono do not. Numerals 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[] DisplayStack = { "Oswald", "PT Sans Narrow", "Arial Narrow", "sans-serif" };
private static readonly string[] BodyStack = { "Manrope", "Segoe UI", "system-ui", "sans-serif" };
private static readonly string[] MonoStack = { "JetBrains Mono", "IBM Plex Mono", "Fira Code", "Consolas", "monospace" };
private static readonly Typography MarathonTypography = new()
@@ -139,26 +143,29 @@ public static class MarathonTheme
H1 = new H1
{
FontFamily = DisplayStack,
FontWeight = 300,
FontSize = "clamp(2.25rem, 4vw, 3.5rem)",
LineHeight = 1.05,
LetterSpacing = "-0.022em",
FontWeight = 700,
FontSize = "clamp(2.5rem, 5vw, 4rem)",
LineHeight = 0.98,
LetterSpacing = "0.005em",
TextTransform = "uppercase",
},
H2 = new H2
{
FontFamily = DisplayStack,
FontWeight = 400,
FontSize = "clamp(1.75rem, 2.5vw, 2.25rem)",
LineHeight = 1.15,
LetterSpacing = "-0.018em",
FontWeight = 700,
FontSize = "clamp(1.875rem, 3vw, 2.5rem)",
LineHeight = 1.02,
LetterSpacing = "0.005em",
TextTransform = "uppercase",
},
H3 = new H3
{
FontFamily = DisplayStack,
FontWeight = 500,
FontSize = "1.5rem",
LineHeight = 1.25,
LetterSpacing = "-0.012em",
FontWeight = 600,
FontSize = "1.625rem",
LineHeight = 1.1,
LetterSpacing = "0.01em",
TextTransform = "uppercase",
},
H4 = new H4
{
@@ -242,12 +249,11 @@ public static class MarathonTheme
};
// ------------------------------------------------------------------
// Layout — sharp corners, narrow drawer. The aesthetic earns its
// authority through restraint.
// Layout — rounded brutalist blocks, narrow drawer.
// ------------------------------------------------------------------
private static readonly LayoutProperties LayoutProps = new()
{
DefaultBorderRadius = "2px",
DefaultBorderRadius = "10px",
AppbarHeight = "60px",
DrawerWidthLeft = "248px",
DrawerWidthRight = "248px",
+10 -10
View File
@@ -9,14 +9,14 @@ 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 const string AnomalySignal = "#ff3b30";
public const string AnomalySignalDark = "#ff5a4f";
public const string Accent = "#c6f400"; // acid lime
public const string AccentDark = "#c6f400";
public const string InkPrimary = "#0a0a0a";
public const string Parchment = "#fffef8";
public const string ParchmentDeep = "#f3f1e9";
public const string InkDeep = "#141310";
}
public static class Spacing
@@ -31,8 +31,8 @@ public static class Tokens
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 DisplayStack = "\"Oswald\", \"PT Sans Narrow\", \"Arial Narrow\", sans-serif";
public const string BodyStack = "\"Manrope\", \"Segoe UI\", system-ui, sans-serif";
public const string MonoStack = "\"JetBrains Mono\", \"IBM Plex Mono\", \"Fira Code\", Consolas, monospace";
}
}
+158 -117
View File
@@ -1,9 +1,13 @@
/* ===================================================================
Marathon — Editorial-Quant design system
Marathon — "Velocity" design system
------------------------------------------------------------------
Inspiration: long-form data journalism (FT, Quartz), terminal
instruments (Bloomberg), and Belarusian / Soviet print typography.
The aesthetic is confident, dense, and serif-led on display surfaces.
Neo-brutalist sportsbook. Acid lime, hard offset shadows, slammed
uppercase headlines. Cyrillic-complete type: Oswald (display) +
Manrope (body) + JetBrains Mono (numerals / labels) — the Velocity
mockup's Anton/DM Sans/Space Mono have no Cyrillic, which this
Russian-first product needs.
Light "paper" chassis + a warm-charcoal dark mode; both keep the
lime accent and the hard ink/black shadow signature.
=================================================================== */
:root {
@@ -18,50 +22,63 @@
--m-space-8: 64px;
--m-space-9: 96px;
/* ----- Radius — sharp by default, soft variants for inputs ----- */
/* ----- Radius — rounded brutalist blocks ----- */
--m-radius-sharp: 0;
--m-radius-xs: 2px;
--m-radius-sm: 4px;
--m-radius-md: 6px;
--m-radius-lg: 10px;
--m-radius-xs: 6px;
--m-radius-sm: 8px;
--m-radius-md: 10px;
--m-radius-lg: 14px;
/* ----- Typography ----- */
--m-font-display: "IBM Plex Serif", "PT Serif", Georgia, serif;
--m-font-body: "IBM Plex Sans", "PT Sans", system-ui, sans-serif;
/* ----- Brutalist borders + hard offset shadows ----- */
--m-border-w: 2px;
--m-border-w-bold: 3px;
--m-shadow-hard: 5px 5px 0 var(--m-c-ink);
--m-shadow-hard-sm: 3px 3px 0 var(--m-c-ink);
--m-shadow-hard-accent: 5px 5px 0 var(--m-c-accent);
--m-shadow-hard-anomaly: 5px 5px 0 var(--m-c-anomaly);
/* ----- Typography (all Cyrillic-complete) ----- */
--m-font-display: "Oswald", "PT Sans Narrow", "Arial Narrow", sans-serif;
--m-font-body: "Manrope", "Segoe UI", system-ui, sans-serif;
--m-font-mono: "JetBrains Mono", "IBM Plex Mono", "Fira Code", Consolas, monospace;
/* ----- Colors — light (parchment) chassis ----- */
--m-c-ink: #0f172a;
--m-c-ink-2: #1e293b;
--m-c-ink-soft: #475569;
--m-c-paper: #fafaf7;
--m-c-paper-2: #f5f4ef;
--m-c-paper-3: #ebe9e1;
--m-c-rule: #e7e5e4;
--m-c-accent: #d97706;
--m-c-accent-soft: #f59e0b;
--m-c-anomaly: #dc2626;
--m-c-positive: #15803d;
--m-c-info: #0369a1;
/* ----- Colors — light "paper" chassis ----- */
--m-c-ink: #0a0a0a;
--m-c-ink-2: #26241e;
--m-c-ink-soft: #6b6757;
--m-c-paper: #fffef8;
--m-c-paper-2: #f3f1e9;
--m-c-paper-3: #e7e3d6;
--m-c-rule: #0a0a0a;
--m-c-accent: #c6f400; /* acid lime */
--m-c-accent-soft: #b3dd00;
--m-c-on-accent: #0a0a0a; /* ink on lime — lime is a light hue */
--m-c-anomaly: #ff3b30;
--m-c-positive: #1f9e3d;
--m-c-info: #244bff; /* electric blue */
/* Tabular numerals for everywhere odds/scores appear */
--m-num-feature: "tnum" 1, "lnum" 1, "ss01" 1;
--m-num-feature: "tnum" 1, "lnum" 1;
}
/* Dark theme overrides (applied via class on <html> or via MudThemeProvider) */
/* Dark theme overrides — warm charcoal, lime retained */
.mud-theme-dark, [data-theme="dark"] {
--m-c-ink: #f5f5f4;
--m-c-ink-2: #e7e5e4;
--m-c-ink-soft: #a8a29e;
--m-c-paper: #1c1917;
--m-c-paper-2: #0c0a09;
--m-c-paper-3: #292524;
--m-c-rule: #292524;
--m-c-accent: #fbbf24;
--m-c-accent-soft: #fcd34d;
--m-c-anomaly: #f87171;
--m-c-ink: #f5f3ea;
--m-c-ink-2: #d8d4c6;
--m-c-ink-soft: #9c9784;
--m-c-paper: #1e1c15;
--m-c-paper-2: #141310;
--m-c-paper-3: #2a2820;
--m-c-rule: #f5f3ea;
--m-c-accent: #c6f400;
--m-c-accent-soft: #aacc00;
--m-c-on-accent: #0a0a0a;
--m-c-anomaly: #ff5a4f;
--m-c-positive: #4ade80;
--m-c-info: #38bdf8;
--m-c-info: #6f8bff;
/* Hard shadow goes pure-black on dark; the light border defines the block. */
--m-shadow-hard: 5px 5px 0 #000000;
--m-shadow-hard-sm: 3px 3px 0 #000000;
}
/* ===================================================================
@@ -81,19 +98,13 @@ html, body {
}
body {
/* Subtle paper grain — 1px mottled noise, rendered cheaply via SVG. */
background-image:
radial-gradient(circle at 25% 12%, rgba(217, 119, 6, 0.035), transparent 45%),
radial-gradient(circle at 88% 78%, rgba(15, 23, 42, 0.040), transparent 50%),
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.045 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
/* Subtle diagonal hatch — the Velocity texture. */
background-image: repeating-linear-gradient(135deg, transparent 0 22px, rgba(10, 10, 10, 0.025) 22px 24px);
background-attachment: fixed;
}
.mud-theme-dark body, [data-theme="dark"] body {
background-image:
radial-gradient(circle at 25% 12%, rgba(251, 191, 36, 0.045), transparent 45%),
radial-gradient(circle at 88% 78%, rgba(56, 189, 248, 0.030), transparent 50%),
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.025 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
background-image: repeating-linear-gradient(135deg, transparent 0 22px, rgba(255, 255, 255, 0.022) 22px 24px);
}
#app {
@@ -120,61 +131,71 @@ body {
}
/* ===================================================================
Editorial markers — kicker label + serif display lockup
Editorial markers — lime kicker chip + slammed display lockup
=================================================================== */
.m-kicker {
font-family: var(--m-font-mono);
text-transform: uppercase;
letter-spacing: 0.18em;
letter-spacing: 0.14em;
font-size: 0.6875rem;
color: var(--m-c-accent);
font-weight: 500;
font-weight: 700;
color: var(--m-c-on-accent);
background: var(--m-c-accent);
border: var(--m-border-w) solid var(--m-c-ink);
border-radius: var(--m-radius-md);
padding: 5px 10px;
display: inline-block;
padding-bottom: var(--m-space-1);
border-bottom: 1px solid var(--m-c-accent);
}
/* A tilted variant for hero kickers — opt-in so it never skews chrome. */
.m-kicker--tilt {
transform: rotate(-1.5deg);
}
.m-display {
font-family: var(--m-font-display);
font-weight: 300;
letter-spacing: -0.022em;
line-height: 1.05;
font-weight: 700;
letter-spacing: 0.005em;
line-height: 0.98;
text-transform: uppercase;
color: var(--m-c-ink);
}
.m-rule {
border: 0;
border-top: 1px solid var(--m-c-rule);
border-top: var(--m-border-w) solid var(--m-c-ink);
margin: var(--m-space-5) 0;
}
.m-rule--double {
border: 0;
border-top: 3px double var(--m-c-rule);
border-top: var(--m-border-w-bold) double var(--m-c-ink);
margin: var(--m-space-5) 0;
}
/* ===================================================================
Cards — paper-like, borders not shadows
Cards — brutalist blocks: thick border + hard offset shadow
=================================================================== */
.m-card {
background: var(--m-c-paper);
border: 1px solid var(--m-c-rule);
border-radius: var(--m-radius-xs);
border: var(--m-border-w-bold) solid var(--m-c-ink);
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow-hard);
padding: var(--m-space-5);
position: relative;
}
/* Emphasis variants recolour the hard shadow rather than a hidden left rule. */
.m-card--accented {
border-left: 3px solid var(--m-c-accent);
box-shadow: var(--m-shadow-hard-accent);
}
.m-card--anomaly {
border-left: 3px solid var(--m-c-anomaly);
box-shadow: var(--m-shadow-hard-anomaly);
}
/* ===================================================================
Stat block — large number, mono, kicker on top
Stat block — slammed Oswald number, lime tick, mono label
=================================================================== */
.m-stat {
display: flex;
@@ -183,26 +204,37 @@ body {
}
.m-stat__value {
font-family: var(--m-font-mono);
font-family: var(--m-font-display);
font-feature-settings: var(--m-num-feature);
font-size: clamp(2rem, 4vw, 3rem);
font-weight: 500;
font-size: clamp(2.25rem, 4.5vw, 3.25rem);
font-weight: 700;
line-height: 1;
color: var(--m-c-ink);
letter-spacing: -0.02em;
letter-spacing: 0.01em;
}
.m-stat__value::after {
content: "";
display: block;
width: 42px;
height: 5px;
margin-top: var(--m-space-2);
background: var(--m-c-accent);
}
.m-stat__label {
font-family: var(--m-font-body);
font-size: 0.8125rem;
font-family: var(--m-font-mono);
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--m-c-ink-soft);
text-transform: none;
letter-spacing: 0;
}
.m-stat__delta {
font-family: var(--m-font-mono);
font-size: 0.75rem;
font-weight: 700;
color: var(--m-c-positive);
}
@@ -239,16 +271,16 @@ body {
}
/* ===================================================================
Focus rings — deliberate, accent, never invisible
Focus rings — electric blue for contrast on lime/paper and charcoal
=================================================================== */
:focus-visible {
outline: 2px solid var(--m-c-accent);
outline: var(--m-border-w) solid var(--m-c-info);
outline-offset: 2px;
}
.mud-button:focus-visible,
.mud-icon-button:focus-visible {
outline: 2px solid var(--m-c-accent);
outline: var(--m-border-w) solid var(--m-c-info);
outline-offset: 2px;
}
@@ -292,10 +324,12 @@ body {
.m-brand__mark {
font-family: var(--m-font-display);
font-weight: 500;
font-size: 1.375rem;
letter-spacing: -0.02em;
font-weight: 700;
font-size: 1.5rem;
letter-spacing: 0.01em;
line-height: 1;
text-transform: uppercase;
color: inherit;
}
.m-brand__mark::first-letter {
@@ -304,47 +338,55 @@ body {
.m-brand__dateline {
font-family: var(--m-font-mono);
font-size: 0.6875rem;
font-size: 0.625rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.16em;
color: var(--m-c-ink-soft);
border-left: 1px solid var(--m-c-rule);
color: var(--m-c-accent);
border-left: var(--m-border-w) solid currentColor;
padding-left: var(--m-space-3);
}
/* ===================================================================
Drawer — narrow, dark, mono labels
Drawer — paper sidebar, brutalist nav blocks
=================================================================== */
.m-nav__group {
padding: var(--m-space-3) var(--m-space-4);
padding: var(--m-space-4) var(--m-space-3) var(--m-space-2);
font-family: var(--m-font-mono);
font-size: 0.6875rem;
font-size: 0.625rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.18em;
color: rgba(231, 229, 228, 0.55);
color: var(--m-c-ink-soft);
}
.m-nav__link {
display: flex;
align-items: center;
gap: var(--m-space-3);
padding: var(--m-space-3) var(--m-space-4);
color: rgba(231, 229, 228, 0.85);
padding: 10px 12px;
color: var(--m-c-ink-2);
text-decoration: none;
font-size: 0.9375rem;
border-left: 2px solid transparent;
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
font-family: var(--m-font-body);
font-weight: 700;
font-size: 0.875rem;
border: var(--m-border-w) solid transparent;
border-radius: var(--m-radius-md);
transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease, background 120ms ease, color 120ms ease;
}
.m-nav__link:hover {
background: rgba(217, 119, 6, 0.10);
color: #ffffff;
border-color: var(--m-c-ink);
transform: translate(-1px, -1px);
box-shadow: var(--m-shadow-hard-sm);
color: var(--m-c-ink);
}
.m-nav__link.active {
color: #ffffff;
background: rgba(217, 119, 6, 0.14);
border-left-color: var(--m-c-accent);
background: var(--m-c-accent);
color: var(--m-c-on-accent);
border-color: var(--m-c-ink);
box-shadow: var(--m-shadow-hard-sm);
}
.m-nav__link .mud-icon-root { font-size: 1.1rem; }
@@ -354,8 +396,8 @@ body {
=================================================================== */
.m-segmented {
display: inline-flex;
border: 1px solid var(--m-c-rule);
border-radius: var(--m-radius-xs);
border: var(--m-border-w) solid var(--m-c-ink);
border-radius: var(--m-radius-md);
overflow: hidden;
background: var(--m-c-paper);
}
@@ -367,15 +409,16 @@ body {
padding: 6px 12px;
font-family: var(--m-font-mono);
font-size: 0.6875rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.14em;
letter-spacing: 0.12em;
color: var(--m-c-ink-soft);
cursor: pointer;
transition: background 120ms ease, color 120ms ease;
}
.m-segmented__btn + .m-segmented__btn {
border-left: 1px solid var(--m-c-rule);
border-left: var(--m-border-w) solid var(--m-c-ink);
}
.m-segmented__btn:hover {
@@ -383,21 +426,17 @@ body {
}
.m-segmented__btn.is-active {
background: var(--m-c-ink);
color: var(--m-c-paper);
}
.mud-theme-dark .m-segmented__btn.is-active,
[data-theme="dark"] .m-segmented__btn.is-active {
background: var(--m-c-accent);
color: var(--m-c-paper-2);
color: var(--m-c-on-accent);
}
/* ===================================================================
Settings page — section ledger
=================================================================== */
.m-section {
border: 1px solid var(--m-c-rule);
border: var(--m-border-w-bold) solid var(--m-c-ink);
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow-hard);
background: var(--m-c-paper);
margin-bottom: var(--m-space-5);
}
@@ -407,16 +446,17 @@ body {
align-items: baseline;
justify-content: space-between;
padding: var(--m-space-4) var(--m-space-5);
border-bottom: 1px solid var(--m-c-rule);
background: var(--m-c-paper-2);
border-bottom: var(--m-border-w) solid var(--m-c-ink);
background: var(--m-c-paper-3);
}
.m-section__head h2 {
margin: 0;
font-family: var(--m-font-display);
font-weight: 400;
font-size: 1.25rem;
letter-spacing: -0.012em;
font-weight: 700;
font-size: 1.375rem;
text-transform: uppercase;
letter-spacing: 0.01em;
}
.m-section__body {
@@ -450,15 +490,16 @@ body {
display: inline-flex;
align-items: center;
gap: var(--m-space-2);
padding: 2px 8px;
background: rgba(220, 38, 38, 0.10);
padding: 3px 9px;
background: var(--m-c-paper);
color: var(--m-c-anomaly);
border: 1px solid currentColor;
border-radius: var(--m-radius-xs);
border: var(--m-border-w) solid currentColor;
border-radius: var(--m-radius-sm);
font-family: var(--m-font-mono);
font-size: 0.6875rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.14em;
letter-spacing: 0.12em;
}
.m-anomaly__pulse {
+8 -8
View File
@@ -8,17 +8,17 @@
<!-- Prevent flash of unthemed content -->
<style>
html, body { background: #f5f4ef; margin: 0; }
html, body { background: #f3f1e9; margin: 0; }
@media (prefers-color-scheme: dark) {
html, body { background: #0c0a09; }
html, body { background: #141310; }
}
</style>
<!-- Fonts: IBM Plex Sans / Serif / Mono + JetBrains Mono. Full Cyrillic coverage. -->
<!-- Fonts: Oswald (display) + Manrope (body) + JetBrains Mono (numerals). Full Cyrillic coverage. -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Serif:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500&family=JetBrains+Mono:wght@400;500;600&display=swap"
href="https://fonts.googleapis.com/css2?family=Oswald:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700;800&display=swap"
rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
@@ -26,13 +26,13 @@
</head>
<body>
<div id="app">
<div style="padding: 64px; font-family: 'IBM Plex Serif', Georgia, serif; color: #475569;">
<span style="font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: #d97706;">Booting</span>
<div style="font-size: 32px; font-weight: 300; margin-top: 8px;">Marathon Odds Lab</div>
<div style="padding: 64px; font-family: 'Manrope', system-ui, sans-serif; color: #6b6757;">
<span style="font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: #0a0a0a; background: #c6f400; border: 2px solid #0a0a0a; border-radius: 8px; padding: 4px 9px;">Booting</span>
<div style="font-family: 'Oswald', sans-serif; font-size: 42px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.01em; margin-top: 14px; color: #0a0a0a;">Marathon Odds Lab</div>
</div>
</div>
<div id="blazor-error-ui" style="display:none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; padding: 12px 24px; background: #dc2626; color: #fafaf7; font-family: 'IBM Plex Sans', sans-serif;">
<div id="blazor-error-ui" style="display:none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; padding: 12px 24px; background: #ff3b30; color: #fffef8; font-family: 'Manrope', system-ui, sans-serif;">
<span>An unhandled error has occurred.</span>
<a href="" class="reload" style="color: #fff; text-decoration: underline; margin-left: 12px;">Reload</a>
<a class="dismiss" style="float: right; cursor: pointer; padding: 0 8px;">×</a>