feat: shared visual layer + redesigned topbar (#4)
Introduce new design token set (paper/ink/line/accent + radius/shadow) with backward-compat aliases for legacy --surface/--navy/--text names. Swap DM Sans for Plus Jakarta Sans, add JetBrains Mono with tabular numerics. Replace .app-header with sticky .topbar partial (brand + segmented nav + Next update / Last refresh meta + Refresh all button). Add POST /api/refresh-all that runs refreshAllPlayersInDB() with an in-memory mutex and returns the rendered topbar so HTMX can swap it in. "Next update" is computed as first Tuesday of next month (approximation of PDGA's monthly cycle). "Last refresh" derives from MAX(players.last_updated).
This commit is contained in:
+225
-88
@@ -2,52 +2,75 @@
|
||||
PDGA Ratings — Shared Design System
|
||||
═══════════════════════════════════════════════════ */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=JetBrains+Mono:wght@400;500&display=swap');
|
||||
/* Fonts loaded via <link> in layout.ejs for parallel loading */
|
||||
|
||||
:root {
|
||||
/* Color system */
|
||||
--surface-0: #f0f2f5;
|
||||
--surface-1: #ffffff;
|
||||
--surface-2: #f8f9fb;
|
||||
--surface-3: #eef0f4;
|
||||
/* ── New design tokens ─────────────────────────── */
|
||||
--bg: oklch(0.985 0.005 250);
|
||||
--paper: #ffffff;
|
||||
--paper-2: oklch(0.975 0.006 250);
|
||||
--ink: oklch(0.22 0.02 260);
|
||||
--ink-2: oklch(0.42 0.015 260);
|
||||
--ink-3: oklch(0.60 0.012 260);
|
||||
--line: oklch(0.93 0.008 260);
|
||||
--line-2: oklch(0.88 0.010 260);
|
||||
--hover: oklch(0.965 0.012 260);
|
||||
|
||||
--navy-900: #0f172a;
|
||||
--navy-800: #1e293b;
|
||||
--navy-700: #334155;
|
||||
--navy-600: #475569;
|
||||
--accent: #4f5fd8;
|
||||
--accent-soft: color-mix(in oklab, var(--accent) 12%, white);
|
||||
--accent-text: color-mix(in oklab, var(--accent) 92%, black);
|
||||
|
||||
--text-primary: #0f172a;
|
||||
--text-secondary: #64748b;
|
||||
--text-muted: #94a3b8;
|
||||
--text-inverse: #f8fafc;
|
||||
--up: oklch(0.55 0.15 150);
|
||||
--up-soft: oklch(0.94 0.04 150);
|
||||
--down: oklch(0.58 0.18 25);
|
||||
--down-soft: oklch(0.95 0.04 25);
|
||||
|
||||
--accent: #3b82f6;
|
||||
--accent-hover: #2563eb;
|
||||
--accent-subtle: rgba(59, 130, 246, 0.08);
|
||||
--accent-border: rgba(59, 130, 246, 0.2);
|
||||
--radius: 14px;
|
||||
--radius-sm: 10px;
|
||||
|
||||
--green: #10b981;
|
||||
--green-subtle: rgba(16, 185, 129, 0.1);
|
||||
--red: #ef4444;
|
||||
--red-subtle: rgba(239, 68, 68, 0.1);
|
||||
--amber: #f59e0b;
|
||||
--shadow-card: 0 1px 0 oklch(0.92 0.01 260), 0 1px 2px oklch(0.85 0.01 260 / 0.30);
|
||||
--shadow-pop: 0 1px 2px oklch(0.80 0.01 260 / 0.18), 0 14px 40px -10px oklch(0.50 0.02 260 / 0.18);
|
||||
|
||||
--border: #e2e8f0;
|
||||
--border-light: #f1f5f9;
|
||||
--font-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
||||
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 14px;
|
||||
/* legacy token aliases — remove as components migrate */
|
||||
--surface-0: var(--bg);
|
||||
--surface-1: var(--paper);
|
||||
--surface-2: var(--paper-2);
|
||||
--surface-3: var(--paper-2);
|
||||
|
||||
--text-primary: var(--ink);
|
||||
--text-secondary: var(--ink-2);
|
||||
--text-muted: var(--ink-3);
|
||||
--text-inverse: var(--paper);
|
||||
|
||||
--navy-900: var(--ink);
|
||||
--navy-800: var(--ink);
|
||||
--navy-700: var(--ink-2);
|
||||
--navy-600: var(--ink-2);
|
||||
|
||||
--border: var(--line);
|
||||
--border-light: var(--line-2);
|
||||
|
||||
--green: var(--up);
|
||||
--green-subtle: var(--up-soft);
|
||||
--red: var(--down);
|
||||
--red-subtle: var(--down-soft);
|
||||
|
||||
--accent-hover: color-mix(in oklab, var(--accent) 85%, black);
|
||||
--accent-subtle: var(--accent-soft);
|
||||
--accent-border: color-mix(in oklab, var(--accent) 40%, transparent);
|
||||
|
||||
--radius-md: var(--radius-sm);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: 20px;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||
--shadow-md: 0 4px 12px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.04);
|
||||
--shadow-lg: 0 8px 30px rgba(15, 23, 42, 0.08), 0 2px 6px rgba(15, 23, 42, 0.04);
|
||||
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||
--shadow-md: var(--shadow-card);
|
||||
--shadow-lg: var(--shadow-pop);
|
||||
--shadow-overlay: 0 20px 60px rgba(15, 23, 42, 0.15), 0 4px 12px rgba(15, 23, 42, 0.08);
|
||||
|
||||
--font-sans: 'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'SF Mono', monospace;
|
||||
|
||||
--transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@@ -59,81 +82,208 @@
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 14px;
|
||||
line-height: 1.45;
|
||||
font-feature-settings: "ss01", "cv11";
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--surface-0);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ── App Shell ────────────────────────────────── */
|
||||
.mono {
|
||||
font-family: var(--font-mono);
|
||||
font-feature-settings: "tnum", "zero";
|
||||
}
|
||||
|
||||
.app-header {
|
||||
background: var(--navy-900);
|
||||
color: var(--text-inverse);
|
||||
padding: 0 24px;
|
||||
/* ── Topbar ───────────────────────────────────── */
|
||||
|
||||
.topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
z-index: 50;
|
||||
background: color-mix(in oklab, var(--paper) 92%, transparent);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
.topbar__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 56px;
|
||||
gap: 16px;
|
||||
padding: 14px 32px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.app-logo {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--text-inverse);
|
||||
text-decoration: none;
|
||||
.topbar__brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.app-logo .logo-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: var(--accent);
|
||||
border-radius: var(--radius-sm);
|
||||
.topbar__brand-mark {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, var(--accent), color-mix(in oklab, var(--accent) 70%, black));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.app-nav {
|
||||
.topbar__brand-mark svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.topbar__brand-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.topbar__brand-title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.topbar__brand-sub {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.topbar__nav {
|
||||
display: flex;
|
||||
background: var(--paper-2);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 4px;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.app-nav a {
|
||||
padding: 6px 14px;
|
||||
color: var(--text-muted);
|
||||
.topbar__nav a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
padding: 0 14px;
|
||||
border-radius: 7px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--ink-2);
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
transition: color 120ms ease, background 120ms ease;
|
||||
}
|
||||
|
||||
.topbar__nav a:hover {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.topbar__nav a.active {
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
.topbar__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.topbar__meta-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.topbar__meta-label {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.topbar__meta-value {
|
||||
font-family: var(--font-mono);
|
||||
font-feature-settings: "tnum", "zero";
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: color var(--transition), background var(--transition);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.app-nav a:hover {
|
||||
color: var(--text-inverse);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
.topbar__divider {
|
||||
width: 1px;
|
||||
height: 22px;
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
.app-nav a.active {
|
||||
color: var(--text-inverse);
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
.topbar__refresh {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-family: var(--font-sans);
|
||||
cursor: pointer;
|
||||
transition: background 120ms ease, border-color 120ms ease;
|
||||
}
|
||||
|
||||
.topbar__refresh:hover:not(:disabled) {
|
||||
background: var(--hover);
|
||||
border-color: color-mix(in oklab, var(--line) 60%, var(--ink-3));
|
||||
}
|
||||
|
||||
.topbar__refresh:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.topbar__refresh-spinner {
|
||||
display: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: 2px solid var(--line);
|
||||
border-top-color: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: topbar-spin 0.7s linear infinite;
|
||||
}
|
||||
|
||||
.topbar__refresh.htmx-request .topbar__refresh-spinner {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.topbar__refresh.htmx-request .topbar__refresh-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes topbar-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@media (max-width: 880px) {
|
||||
.topbar__inner { padding: 10px 16px; gap: 10px; }
|
||||
.topbar__meta-item, .topbar__divider { display: none; }
|
||||
.topbar__refresh-label { display: none; }
|
||||
.topbar__nav a { padding: 0 10px; font-size: 12px; }
|
||||
.topbar__brand-sub { display: none; }
|
||||
}
|
||||
|
||||
/* ── Container ────────────────────────────────── */
|
||||
@@ -380,19 +530,6 @@ a:hover {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.app-logo {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.app-nav a {
|
||||
padding: 5px 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
table {
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -410,7 +547,7 @@ a:hover {
|
||||
}
|
||||
|
||||
thead {
|
||||
top: 48px;
|
||||
top: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user