8c977d6624
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).
559 lines
12 KiB
CSS
559 lines
12 KiB
CSS
/* ═══════════════════════════════════════════════════
|
|
PDGA Ratings — Shared Design System
|
|
═══════════════════════════════════════════════════ */
|
|
|
|
/* Fonts loaded via <link> in layout.ejs for parallel loading */
|
|
|
|
:root {
|
|
/* ── 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);
|
|
|
|
--accent: #4f5fd8;
|
|
--accent-soft: color-mix(in oklab, var(--accent) 12%, white);
|
|
--accent-text: color-mix(in oklab, var(--accent) 92%, black);
|
|
|
|
--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);
|
|
|
|
--radius: 14px;
|
|
--radius-sm: 10px;
|
|
|
|
--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);
|
|
|
|
--font-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
|
|
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
|
|
|
/* 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: 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);
|
|
|
|
--transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
/* ── Reset & Base ─────────────────────────────── */
|
|
|
|
*, *::before, *::after {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: var(--font-sans);
|
|
font-size: 14px;
|
|
line-height: 1.45;
|
|
font-feature-settings: "ss01", "cv11";
|
|
margin: 0;
|
|
padding: 0;
|
|
background: var(--bg);
|
|
color: var(--ink);
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
.mono {
|
|
font-family: var(--font-mono);
|
|
font-feature-settings: "tnum", "zero";
|
|
}
|
|
|
|
/* ── Topbar ───────────────────────────────────── */
|
|
|
|
.topbar {
|
|
position: sticky;
|
|
top: 0;
|
|
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);
|
|
}
|
|
|
|
.topbar__inner {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 14px 32px;
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.topbar__brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.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;
|
|
color: #fff;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.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;
|
|
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;
|
|
color: var(--ink);
|
|
}
|
|
|
|
.topbar__divider {
|
|
width: 1px;
|
|
height: 22px;
|
|
background: var(--line);
|
|
}
|
|
|
|
.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 ────────────────────────────────── */
|
|
|
|
.container {
|
|
max-width: 960px;
|
|
margin: 0 auto;
|
|
padding: 28px 24px 60px;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: 26px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
letter-spacing: -0.03em;
|
|
margin: 0 0 24px 0;
|
|
}
|
|
|
|
/* ── Loading ──────────────────────────────────── */
|
|
|
|
.loading {
|
|
text-align: center;
|
|
padding: 40px 20px;
|
|
font-size: 15px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* ── Tables ───────────────────────────────────── */
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: separate;
|
|
border-spacing: 0;
|
|
font-size: 14px;
|
|
}
|
|
|
|
thead {
|
|
position: sticky;
|
|
top: 56px;
|
|
z-index: 10;
|
|
}
|
|
|
|
th {
|
|
padding: 10px 16px;
|
|
text-align: left;
|
|
font-weight: 600;
|
|
font-size: 11px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: var(--text-secondary);
|
|
background: var(--surface-2);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
th:first-child {
|
|
border-radius: var(--radius-md) 0 0 0;
|
|
}
|
|
|
|
th:last-child {
|
|
border-radius: 0 var(--radius-md) 0 0;
|
|
}
|
|
|
|
td {
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--border-light);
|
|
vertical-align: middle;
|
|
}
|
|
|
|
tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.expandable-row {
|
|
cursor: pointer;
|
|
transition: background var(--transition);
|
|
}
|
|
|
|
.expandable-row:hover {
|
|
background: var(--accent-subtle);
|
|
}
|
|
|
|
.expanded-content {
|
|
display: none;
|
|
}
|
|
|
|
.expanded-content td {
|
|
padding: 0;
|
|
background: var(--surface-2);
|
|
border-top: 2px solid var(--accent);
|
|
}
|
|
|
|
.expanded-cell {
|
|
padding: 20px !important;
|
|
}
|
|
|
|
/* ── Links ────────────────────────────────────── */
|
|
|
|
a {
|
|
color: var(--accent);
|
|
text-decoration: none;
|
|
transition: color var(--transition);
|
|
}
|
|
|
|
a:hover {
|
|
color: var(--accent-hover);
|
|
}
|
|
|
|
/* ── Buttons ──────────────────────────────────── */
|
|
|
|
.btn {
|
|
padding: 8px 16px;
|
|
background: var(--accent);
|
|
color: white;
|
|
border: none;
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
font-family: var(--font-sans);
|
|
transition: background var(--transition), transform var(--transition), box-shadow var(--transition);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.btn:hover {
|
|
background: var(--accent-hover);
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.btn:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
.btn:disabled {
|
|
background: var(--text-muted);
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
box-shadow: none;
|
|
}
|
|
|
|
/* ── Card Section ─────────────────────────────── */
|
|
|
|
.card-section {
|
|
background: var(--surface-1);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
padding: 24px;
|
|
margin-bottom: 28px;
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.card-section h3 {
|
|
margin: 0 0 14px 0;
|
|
color: var(--text-primary);
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
text-align: center;
|
|
}
|
|
|
|
.card-section-form {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
/* ── Inputs ───────────────────────────────────── */
|
|
|
|
.input {
|
|
padding: 9px 14px;
|
|
font-size: 14px;
|
|
font-family: var(--font-sans);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
outline: none;
|
|
background: var(--surface-1);
|
|
color: var(--text-primary);
|
|
transition: border-color var(--transition), box-shadow var(--transition);
|
|
}
|
|
|
|
.input:focus {
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px var(--accent-subtle);
|
|
}
|
|
|
|
.input::placeholder {
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.card-section {
|
|
padding: 16px;
|
|
}
|
|
|
|
.card-section-form {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.card-section-form .input {
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
/* ── Refresh Icons ────────────────────────────── */
|
|
|
|
.refresh-icon {
|
|
cursor: pointer;
|
|
opacity: 0.4;
|
|
margin-left: 8px;
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
transition: all var(--transition);
|
|
padding: 4px;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.refresh-icon:hover {
|
|
opacity: 1;
|
|
color: var(--accent);
|
|
background: var(--accent-subtle);
|
|
}
|
|
|
|
.refresh-icon.spinning {
|
|
animation: spin 0.8s linear infinite;
|
|
color: var(--accent);
|
|
opacity: 1;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* ── Responsive ───────────────────────────────── */
|
|
|
|
@media (max-width: 768px) {
|
|
.container {
|
|
padding: 16px 12px 40px;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: 22px;
|
|
}
|
|
|
|
table {
|
|
font-size: 13px;
|
|
}
|
|
|
|
th, td {
|
|
padding: 10px 8px;
|
|
}
|
|
|
|
th {
|
|
font-size: 10px;
|
|
}
|
|
|
|
.mobile-hide {
|
|
display: none;
|
|
}
|
|
|
|
thead {
|
|
top: 56px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
th, td {
|
|
padding: 8px 6px;
|
|
}
|
|
}
|