diff --git a/public/css/courses.css b/public/css/courses.css index 4c30da0..672364c 100644 --- a/public/css/courses.css +++ b/public/css/courses.css @@ -1,125 +1,113 @@ -/* Course page styles */ +/* ═══════════════════════════════════════════════════ + Courses Page + ═══════════════════════════════════════════════════ */ -.container { - max-width: 1000px; -} - -.nav-links { - text-align: center; - margin-bottom: 20px; -} - -.nav-links a { - margin: 0 15px; - color: #007bff; - text-decoration: none; - font-weight: bold; -} - -.nav-links a:hover { - text-decoration: underline; -} +/* ── Controls ─────────────────────────────────── */ .controls { - text-align: right; + display: flex; + justify-content: flex-end; margin-bottom: 20px; } -.btn { - margin-left: 10px; -} - -.search-container { - margin-bottom: 20px; - text-align: center; -} - -.search-input { - width: 100%; - max-width: 400px; - padding: 10px 15px; - font-size: 16px; - border: 2px solid #ddd; - border-radius: 4px; - outline: none; - transition: border-color 0.2s; -} - -.search-input:focus { - border-color: #007bff; -} +/* ── Search ───────────────────────────────────── */ .search-results-info { text-align: center; margin: 10px 0; - color: #666; - font-size: 14px; + color: var(--text-muted); + font-size: 13px; } +/* ── Layouts ──────────────────────────────────── */ + .layouts-container { - padding: 15px; + padding: 16px; +} + +.layouts-container h4 { + margin: 0 0 12px 0; + font-size: 13px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-secondary); } .layout-item { - padding: 10px; - margin: 5px 0; - background-color: white; - border-radius: 4px; - border: 1px solid #dee2e6; + padding: 12px 14px; + margin: 4px 0; + background: var(--surface-1); + border-radius: var(--radius-sm); + border: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; + transition: border-color var(--transition), box-shadow var(--transition); +} + +.layout-item:hover { + border-color: var(--accent-border); + box-shadow: var(--shadow-sm); } .layout-name { - font-weight: bold; - color: #333; + font-weight: 600; + color: var(--text-primary); + font-size: 14px; } .layout-par { - color: #007bff; - font-weight: bold; + color: var(--accent); + font-weight: 700; + font-size: 14px; + font-variant-numeric: tabular-nums; + white-space: nowrap; } .no-layouts { text-align: center; - color: #999; + color: var(--text-muted); font-style: italic; - padding: 20px; + padding: 24px; + font-size: 13px; } +/* ── Inactive Layouts Accordion ───────────────── */ + .inactive-layouts-accordion { - margin-top: 15px; - border: 1px solid #dee2e6; - border-radius: 4px; - background-color: #f8f9fa; + margin-top: 16px; + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--surface-2); + overflow: hidden; } .accordion-header { - padding: 12px 15px; + padding: 12px 16px; cursor: pointer; user-select: none; display: flex; justify-content: space-between; align-items: center; - background-color: #e9ecef; - border-radius: 4px; - transition: background-color 0.2s; + background: var(--surface-3); + transition: background var(--transition); } .accordion-header:hover { - background-color: #dee2e6; + background: var(--border); } .accordion-header-text { font-weight: 600; - color: #6c757d; - font-size: 14px; + color: var(--text-secondary); + font-size: 13px; } .accordion-icon { - transition: transform 0.3s; - color: #6c757d; + transition: transform 0.3s ease; + color: var(--text-muted); + font-size: 12px; } .accordion-icon.expanded { @@ -130,15 +118,16 @@ max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; - padding: 0 10px; + padding: 0 12px; } .accordion-content.expanded { max-height: 2000px; - padding: 10px; + padding: 12px; transition: max-height 0.5s ease-in; } .layout-item.inactive { - opacity: 0.7; + opacity: 0.6; + border-style: dashed; } diff --git a/public/css/players.css b/public/css/players.css index 100b61b..145c32e 100644 --- a/public/css/players.css +++ b/public/css/players.css @@ -1,32 +1,50 @@ -/* Player ratings page styles */ +/* ═══════════════════════════════════════════════════ + Players Page + ═══════════════════════════════════════════════════ */ + +/* ── Add Player Button ─────────────────────────── */ + +.btn-add { + background: var(--green); +} + +.btn-add:hover { + background: #059669; +} + +/* ── Progress ─────────────────────────────────── */ .progress-container { width: 100%; - background-color: #f0f0f0; - border-radius: 10px; + background: var(--surface-3); + border-radius: var(--radius-xl); padding: 3px; margin: 20px 0; + overflow: hidden; } .progress-bar { width: 0%; - height: 30px; - background-color: #007bff; - border-radius: 8px; + height: 26px; + background: linear-gradient(135deg, var(--accent), var(--accent-hover)); + border-radius: var(--radius-xl); text-align: center; - line-height: 30px; + line-height: 26px; color: white; - font-weight: bold; - transition: width 0.3s ease; + font-weight: 600; + font-size: 12px; + transition: width 0.4s ease; } .progress-text { text-align: center; - margin: 10px 0; - font-size: 16px; - color: #666; + margin: 8px 0; + font-size: 13px; + color: var(--text-secondary); } +/* ── Mobile helpers ───────────────────────────── */ + .mobile-only { display: none; } @@ -37,108 +55,44 @@ } .player-name { - font-weight: bold; - } - - .chart-container { - height: 250px; - margin: 5px 0; - } - - .chart-title { - font-size: 14px; + font-weight: 600; } } -.chart-container { - width: 100%; - height: 300px; - margin: 10px 0; - border: 1px solid #ddd; - border-radius: 4px; - background: white; -} - -.chart-title { - text-align: center; - font-weight: bold; - margin-bottom: 10px; - color: #333; -} - -.loading-chart { - display: flex; - justify-content: center; - align-items: center; - height: 200px; - color: #666; -} - -.chart-tooltip { - position: fixed; - background-color: rgba(0, 0, 0, 0.9); - color: white; - padding: 8px 12px; - border-radius: 4px; - font-size: 12px; - pointer-events: none; - z-index: 10000; - display: none; - white-space: nowrap; - box-shadow: 0 2px 8px rgba(0,0,0,0.3); - border: 1px solid rgba(255,255,255,0.2); -} +/* ── Rating values ────────────────────────────── */ .rating { - font-weight: bold; - color: #007bff; + font-weight: 700; + color: var(--accent); + font-variant-numeric: tabular-nums; } -.std-dev-tooltip { - position: absolute; - background-color: rgba(0, 0, 0, 0.9); - color: white; - padding: 8px 12px; - border-radius: 4px; - font-size: 12px; - pointer-events: none; - z-index: 10000; - display: none; - white-space: nowrap; - box-shadow: 0 2px 8px rgba(0,0,0,0.3); - border: 1px solid rgba(255,255,255,0.2); - font-weight: normal; -} - -.predicted-value { - position: relative; - display: inline-block; +.rating-value { + font-variant-numeric: tabular-nums; } .pdga-number { - color: #6c757d; - font-size: 14px; -} - -.difference { - font-weight: bold; + color: var(--text-muted); + font-size: 13px; + font-variant-numeric: tabular-nums; } .positive { - color: #28a745; + color: var(--green); } .negative { - color: #dc3545; + color: var(--red); } .neutral { - color: #6c757d; + color: var(--text-muted); } .rating-change { - font-weight: bold; - font-size: 14px; + font-weight: 600; + font-size: 13px; + font-variant-numeric: tabular-nums; } .refresh-section { @@ -146,19 +100,95 @@ align-items: center; } +.predicted-value { + position: relative; + display: inline-block; + font-variant-numeric: tabular-nums; +} + +.difference { + font-weight: 600; +} + +/* ── Chart ────────────────────────────────────── */ + +.chart-container { + width: 100%; + height: 300px; + margin: 10px 0; + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--surface-1); + overflow: hidden; +} + +.chart-title { + text-align: center; + font-weight: 600; + font-size: 14px; + margin-bottom: 10px; + color: var(--text-primary); +} + +.loading-chart { + display: flex; + justify-content: center; + align-items: center; + height: 200px; + color: var(--text-muted); + font-size: 13px; +} + +.chart-tooltip { + position: fixed; + background: var(--navy-900); + color: var(--text-inverse); + padding: 8px 12px; + border-radius: var(--radius-sm); + font-size: 12px; + font-family: var(--font-mono); + pointer-events: none; + z-index: 10000; + display: none; + white-space: nowrap; + box-shadow: var(--shadow-lg); +} + +/* ── Tooltips ─────────────────────────────────── */ + +.std-dev-tooltip { + position: absolute; + background: var(--navy-900); + color: var(--text-inverse); + padding: 6px 10px; + border-radius: var(--radius-sm); + font-size: 12px; + font-family: var(--font-mono); + pointer-events: none; + z-index: 10000; + display: none; + white-space: nowrap; + box-shadow: var(--shadow-lg); + font-weight: 400; +} + +/* ── Debug Icon ───────────────────────────────── */ + .debug-icon:hover { opacity: 1 !important; - color: #007bff !important; - transform: scale(1.1); + color: var(--accent) !important; } +/* ── Debug Modal ──────────────────────────────── */ + .debug-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; - background-color: rgba(0, 0, 0, 0.5); + background: rgba(15, 23, 42, 0.5); + backdrop-filter: blur(4px); z-index: 10001; display: none; justify-content: center; @@ -166,96 +196,59 @@ } .debug-content { - background: white; - border-radius: 8px; - padding: 20px; - max-width: 600px; + background: var(--surface-1); + border-radius: var(--radius-lg); + padding: 24px; + max-width: 640px; + width: 90%; max-height: 80vh; overflow-y: auto; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + box-shadow: var(--shadow-overlay); + position: relative; } .debug-header { - font-weight: bold; - font-size: 18px; - margin-bottom: 15px; - color: #333; - border-bottom: 2px solid #007bff; - padding-bottom: 10px; + font-weight: 700; + font-size: 16px; + margin-bottom: 16px; + color: var(--text-primary); + padding-bottom: 12px; + border-bottom: 1px solid var(--border); } .debug-log { - font-family: 'Courier New', monospace; - background-color: #f8f9fa; - border: 1px solid #e9ecef; - border-radius: 4px; - padding: 15px; + font-family: var(--font-mono); + background: var(--surface-2); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 16px; font-size: 12px; - line-height: 1.4; + line-height: 1.6; white-space: pre-line; - color: #495057; + color: var(--text-primary); } .debug-close { position: absolute; - top: 10px; - right: 15px; - font-size: 24px; - color: #999; + top: 12px; + right: 16px; + font-size: 22px; + color: var(--text-muted); cursor: pointer; background: none; border: none; + padding: 4px; + border-radius: var(--radius-sm); + transition: color var(--transition), background var(--transition); + line-height: 1; } .debug-close:hover { - color: #333; + color: var(--text-primary); + background: var(--surface-3); } -.add-player-section { - background-color: #f8f9fa; - border: 2px solid #007bff; - border-radius: 8px; - padding: 20px; - margin-bottom: 30px; - text-align: center; -} - -.add-player-section h3 { - margin-top: 0; - margin-bottom: 15px; - color: #333; - font-size: 18px; -} - -.add-player-form { - display: flex; - justify-content: center; - align-items: center; - gap: 10px; - flex-wrap: wrap; -} - -.pdga-input { - padding: 10px 15px; - font-size: 16px; - border: 2px solid #ddd; - border-radius: 4px; - outline: none; - width: 250px; - transition: border-color 0.2s; -} - -.pdga-input:focus { - border-color: #007bff; -} - -.btn-add { - background-color: #28a745; -} - -.btn-add:hover { - background-color: #218838; -} +/* ── Add Player Modal ─────────────────────────── */ .modal { position: fixed; @@ -263,7 +256,8 @@ left: 0; width: 100%; height: 100%; - background-color: rgba(0, 0, 0, 0.5); + background: rgba(15, 23, 42, 0.5); + backdrop-filter: blur(4px); z-index: 10001; display: none; justify-content: center; @@ -271,78 +265,86 @@ } .modal-content { - background: white; - border-radius: 8px; + background: var(--surface-1); + border-radius: var(--radius-lg); padding: 0; - max-width: 500px; + max-width: 480px; width: 90%; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + box-shadow: var(--shadow-overlay); position: relative; + overflow: hidden; } .modal-header { - font-weight: bold; - font-size: 20px; - padding: 20px; - color: #333; - border-bottom: 2px solid #007bff; + font-weight: 700; + font-size: 17px; + padding: 20px 24px; + color: var(--text-primary); + border-bottom: 1px solid var(--border); } .modal-body { - padding: 20px; - font-size: 16px; - color: #495057; + padding: 24px; + font-size: 14px; + color: var(--text-secondary); line-height: 1.6; } .modal-footer { - padding: 15px 20px; - border-top: 1px solid #e9ecef; + padding: 16px 24px; + border-top: 1px solid var(--border); display: flex; justify-content: flex-end; - gap: 10px; + gap: 8px; + background: var(--surface-2); } .modal-close { position: absolute; - top: 10px; - right: 15px; - font-size: 28px; - color: #999; + top: 12px; + right: 16px; + font-size: 24px; + color: var(--text-muted); cursor: pointer; background: none; border: none; line-height: 1; + padding: 4px; + border-radius: var(--radius-sm); + transition: color var(--transition), background var(--transition); } .modal-close:hover { - color: #333; + color: var(--text-primary); + background: var(--surface-3); } .btn-cancel { - background-color: #6c757d; + background: var(--surface-3); + color: var(--text-primary); } .btn-cancel:hover { - background-color: #5a6268; + background: var(--border); } .btn-confirm { - background-color: #28a745; + background: var(--green); } .btn-confirm:hover { - background-color: #218838; + background: #059669; } +/* ── Responsive ───────────────────────────────── */ + @media (max-width: 768px) { - .add-player-section { - padding: 15px; + .chart-container { + height: 250px; + margin: 5px 0; } - .add-player-form { - flex-direction: column; - } - .pdga-input { - width: 100%; + + .chart-title { + font-size: 13px; } } diff --git a/public/css/shared.css b/public/css/shared.css index 2a0a42f..2a02b57 100644 --- a/public/css/shared.css +++ b/public/css/shared.css @@ -1,163 +1,421 @@ -/* Shared styles for PDGA Ratings app */ +/* ═══════════════════════════════════════════════════ + 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'); + +:root { + /* Color system */ + --surface-0: #f0f2f5; + --surface-1: #ffffff; + --surface-2: #f8f9fb; + --surface-3: #eef0f4; + + --navy-900: #0f172a; + --navy-800: #1e293b; + --navy-700: #334155; + --navy-600: #475569; + + --text-primary: #0f172a; + --text-secondary: #64748b; + --text-muted: #94a3b8; + --text-inverse: #f8fafc; + + --accent: #3b82f6; + --accent-hover: #2563eb; + --accent-subtle: rgba(59, 130, 246, 0.08); + --accent-border: rgba(59, 130, 246, 0.2); + + --green: #10b981; + --green-subtle: rgba(16, 185, 129, 0.1); + --red: #ef4444; + --red-subtle: rgba(239, 68, 68, 0.1); + --amber: #f59e0b; + + --border: #e2e8f0; + --border-light: #f1f5f9; + + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --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-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); +} + +/* ── Reset & Base ─────────────────────────────── */ + +*, *::before, *::after { + box-sizing: border-box; +} body { - font-family: Arial, sans-serif; + font-family: var(--font-sans); margin: 0; - padding: 20px; - background-color: #f5f5f5; + padding: 0; + background-color: var(--surface-0); + color: var(--text-primary); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + line-height: 1.5; } -@media (max-width: 768px) { - body { - padding: 10px; - } +/* ── App Shell ────────────────────────────────── */ + +.app-header { + background: var(--navy-900); + color: var(--text-inverse); + padding: 0 24px; + position: sticky; + top: 0; + z-index: 100; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); } -.container { - max-width: 800px; +.header-inner { + max-width: 960px; margin: 0 auto; - background: white; - padding: 30px; - border-radius: 8px; - box-shadow: 0 2px 10px rgba(0,0,0,0.1); - overflow-x: auto; + display: flex; + align-items: center; + justify-content: space-between; + height: 56px; } -@media (max-width: 768px) { - .container { - padding: 15px; - border-radius: 0; - box-shadow: none; - } +.app-logo { + font-size: 17px; + font-weight: 700; + letter-spacing: -0.02em; + color: var(--text-inverse); + text-decoration: none; + display: flex; + align-items: center; + gap: 8px; } -h1 { - color: #333; - text-align: center; - margin-bottom: 30px; -} - -@media (max-width: 768px) { - h1 { - font-size: 24px; - margin-bottom: 20px; - } -} - -.loading { - text-align: center; - padding: 20px; - font-size: 18px; - color: #666; -} - -table { - width: 100%; - border-collapse: collapse; - margin-top: 20px; +.app-logo .logo-icon { + width: 28px; + height: 28px; + background: var(--accent); + border-radius: var(--radius-sm); + display: flex; + align-items: center; + justify-content: center; font-size: 14px; } -th, td { - padding: 12px; - text-align: left; - border-bottom: 1px solid #ddd; +.app-nav { + display: flex; + gap: 2px; } -@media (max-width: 768px) { - table { - font-size: 12px; - } - th, td { - padding: 8px 4px; - } - .mobile-hide { - display: none; - } +.app-nav a { + padding: 6px 14px; + color: var(--text-muted); + text-decoration: none; + font-size: 14px; + font-weight: 500; + border-radius: var(--radius-sm); + transition: color var(--transition), background var(--transition); +} + +.app-nav a:hover { + color: var(--text-inverse); + background: rgba(255, 255, 255, 0.08); +} + +.app-nav a.active { + color: var(--text-inverse); + background: rgba(255, 255, 255, 0.12); +} + +/* ── 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 { - background-color: #f8f9fa; - font-weight: bold; - color: #495057; + 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); } -tr:hover { - background-color: #f5f5f5; +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-color: #e3f2fd; + background: var(--accent-subtle); } .expanded-content { display: none; - background-color: #f8f9fa; - border-top: 2px solid #007bff; } .expanded-content td { - padding: 20px; + padding: 0; + background: var(--surface-2); + border-top: 2px solid var(--accent); } +.expanded-cell { + padding: 20px !important; +} + +/* ── Links ────────────────────────────────────── */ + a { - color: #007bff; + color: var(--accent); text-decoration: none; + transition: color var(--transition); } a:hover { - text-decoration: underline; + color: var(--accent-hover); } +/* ── Buttons ──────────────────────────────────── */ + .btn { padding: 8px 16px; - background-color: #007bff; + background: var(--accent); color: white; border: none; - border-radius: 4px; + border-radius: var(--radius-sm); cursor: pointer; - font-size: 14px; + 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-color: #0056b3; + background: var(--accent-hover); + box-shadow: var(--shadow-sm); +} + +.btn:active { + transform: scale(0.98); } .btn:disabled { - background-color: #6c757d; + 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.6; + opacity: 0.4; margin-left: 8px; - font-size: 13px; - color: #6c757d; - transition: all 0.2s ease; - padding: 2px; - border-radius: 3px; + font-size: 12px; + color: var(--text-secondary); + transition: all var(--transition); + padding: 4px; + border-radius: var(--radius-sm); } .refresh-icon:hover { opacity: 1; - color: #007bff; - background-color: rgba(0, 123, 255, 0.1); - transform: scale(1.1); + color: var(--accent); + background: var(--accent-subtle); } .refresh-icon.spinning { - animation: spin 1s linear infinite; - color: #007bff; + animation: spin 0.8s linear infinite; + color: var(--accent); opacity: 1; - background-color: rgba(0, 123, 255, 0.1); } @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + to { transform: rotate(360deg); } +} + +/* ── Responsive ───────────────────────────────── */ + +@media (max-width: 768px) { + .container { + padding: 16px 12px 40px; + } + + .page-title { + 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; + } + + th, td { + padding: 10px 8px; + } + + th { + font-size: 10px; + } + + .mobile-hide { + display: none; + } + + thead { + top: 48px; + } +} + +@media (max-width: 480px) { + th, td { + padding: 8px 6px; + } } diff --git a/public/js/chart.js b/public/js/chart.js index 7f7da21..abbf382 100644 --- a/public/js/chart.js +++ b/public/js/chart.js @@ -1,121 +1,150 @@ function createRatingChart(container, history) { - const width = container.clientWidth - 40; - const height = 250; - const margin = { top: 20, right: 30, bottom: 40, left: 60 }; + const width = container.clientWidth; + const height = 280; + const margin = { top: 24, right: 20, bottom: 44, left: 56 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; - + const pdgaNumber = container.id.replace('chart-', ''); const tooltip = document.getElementById(`tooltip-${pdgaNumber}`); - + const ratings = history.map(h => h.rating); const minRating = Math.min(...ratings) - 10; const maxRating = Math.max(...ratings) + 10; - + const xStep = chartWidth / (history.length - 1 || 1); const yScale = chartHeight / (maxRating - minRating); - - let svg = ``; - - svg += ``; - svg += ``; - - for (let i = 0; i <= 5; i++) { - const y = margin.top + (i * chartHeight / 5); - const rating = Math.round(maxRating - (i * (maxRating - minRating) / 5)); - svg += ``; - svg += `${rating}`; - } - - let pathData = ''; + + // Build points array const points = []; - + let pathData = ''; + history.forEach((point, i) => { const x = margin.left + (i * xStep); const y = margin.top + ((maxRating - point.rating) * yScale); - points.push({ x, y, rating: point.rating, date: point.displayDate }); - - if (i === 0) { - pathData += `M ${x} ${y}`; - } else { - pathData += ` L ${x} ${y}`; - } + pathData += i === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`; }); - - svg += ``; - + + // Area fill path (close to bottom) + const areaPath = pathData + + ` L ${points[points.length - 1].x} ${margin.top + chartHeight}` + + ` L ${points[0].x} ${margin.top + chartHeight} Z`; + + let svg = ``; + + // Defs: gradient + glow + svg += ` + + + + + + + + + `; + + // Background + svg += ``; + + // Grid lines + const gridCount = 5; + for (let i = 0; i <= gridCount; i++) { + const y = margin.top + (i * chartHeight / gridCount); + const rating = Math.round(maxRating - (i * (maxRating - minRating) / gridCount)); + + svg += ``; + svg += `${rating}`; + } + + // Area fill + svg += ``; + + // Line + svg += ``; + + // Data points points.forEach((point, i) => { - svg += ``; - svg += ``; + svg += ``; + svg += ``; }); - + + // X-axis labels const labelStep = Math.max(1, Math.floor(history.length / 6)); history.forEach((point, i) => { if (i % labelStep === 0 || i === history.length - 1) { const x = margin.left + (i * xStep); const date = new Date(point.date).toLocaleDateString('en-US', { month: 'short', year: '2-digit' }); - svg += `${date}`; + svg += `${date}`; } }); - - svg += `Rating`; - + + // Y-axis label + svg += `Rating`; + svg += ''; - - container.innerHTML = svg; - + + container.textContent = ''; + container.insertAdjacentHTML('beforeend', svg); + setTimeout(() => { - const svgElement = document.getElementById(`svg-${pdgaNumber}`); - - if (svgElement) { - const hoverAreas = svgElement.querySelectorAll('.hover-area'); - const dataPoints = svgElement.querySelectorAll('.data-point'); - - let currentTooltip = null; - let tooltipTimeout = null; - - hoverAreas.forEach((area, i) => { - area.addEventListener('mouseenter', (e) => { - if (tooltipTimeout) { - clearTimeout(tooltipTimeout); - tooltipTimeout = null; - } - - if (currentTooltip !== null && currentTooltip !== i) { - dataPoints[currentTooltip].setAttribute('r', '4'); - dataPoints[currentTooltip].setAttribute('fill', '#007bff'); - } - - currentTooltip = i; - const point = points[i]; - tooltip.innerHTML = `${point.date}
Rating: ${point.rating}`; - tooltip.style.display = 'block'; + const svgElement = document.getElementById(`svg-${pdgaNumber}`) || container.querySelector('svg'); + if (!svgElement) return; + + const hoverAreas = svgElement.querySelectorAll('.hover-area'); + const dataPoints = svgElement.querySelectorAll('.data-point'); + + let currentTooltip = null; + let tooltipTimeout = null; + + hoverAreas.forEach((area, i) => { + area.addEventListener('mouseenter', (e) => { + if (tooltipTimeout) { + clearTimeout(tooltipTimeout); + tooltipTimeout = null; + } + + if (currentTooltip !== null && currentTooltip !== i) { + dataPoints[currentTooltip].setAttribute('r', '3.5'); + dataPoints[currentTooltip].setAttribute('fill', '#3b82f6'); + } + + currentTooltip = i; + const point = points[i]; + + tooltip.textContent = ''; + const strong = document.createElement('strong'); + strong.textContent = point.date; + tooltip.appendChild(strong); + tooltip.appendChild(document.createElement('br')); + tooltip.appendChild(document.createTextNode('Rating: ' + point.rating)); + + tooltip.style.display = 'block'; + tooltip.style.left = `${e.clientX + 15}px`; + tooltip.style.top = `${e.clientY - 35}px`; + + dataPoints[i].setAttribute('r', '6'); + dataPoints[i].setAttribute('fill', '#2563eb'); + }); + + area.addEventListener('mousemove', (e) => { + if (currentTooltip === i) { tooltip.style.left = `${e.clientX + 15}px`; tooltip.style.top = `${e.clientY - 35}px`; - - dataPoints[i].setAttribute('r', '6'); - dataPoints[i].setAttribute('fill', '#0056b3'); - }); - - area.addEventListener('mousemove', (e) => { - if (currentTooltip === i) { - tooltip.style.left = `${e.clientX + 15}px`; - tooltip.style.top = `${e.clientY - 35}px`; - } - }); - - area.addEventListener('mouseleave', () => { - if (currentTooltip === i) { - tooltipTimeout = setTimeout(() => { - tooltip.style.display = 'none'; - dataPoints[i].setAttribute('r', '4'); - dataPoints[i].setAttribute('fill', '#007bff'); - currentTooltip = null; - }, 100); - } - }); + } }); - } + + area.addEventListener('mouseleave', () => { + if (currentTooltip === i) { + tooltipTimeout = setTimeout(() => { + tooltip.style.display = 'none'; + dataPoints[i].setAttribute('r', '3.5'); + dataPoints[i].setAttribute('fill', '#3b82f6'); + currentTooltip = null; + }, 100); + } + }); + }); }, 100); } diff --git a/views/pages/courses.ejs b/views/pages/courses.ejs index 91acf63..0fe9cc3 100644 --- a/views/pages/courses.ejs +++ b/views/pages/courses.ejs @@ -1,21 +1,22 @@ <% var body = ` -
- -
- -
- +
+

Find Courses

+
+ + +
@@ -29,4 +30,4 @@ cssFiles: ['courses.css'], jsFiles: ['courses.js'], body: body -}) %> +}) %> \ No newline at end of file diff --git a/views/pages/index.ejs b/views/pages/index.ejs index 8b4a9f9..9b1ab80 100644 --- a/views/pages/index.ejs +++ b/views/pages/index.ejs @@ -1,23 +1,24 @@ <% var body = ` -
+

Add Yourself to Tracked Players

-
+
-
- Load All - +
+ Load All +