feat: add async tour system

Players can create tours with selected courses/layouts and a date range.
Others join via a 6-character tour code, play the courses, and report
their total strokes. Live leaderboard with points and +/- par display.

Includes: database schema, model, service, routes, views, and styling.
This commit is contained in:
Samuel Enocsson
2026-03-19 22:47:54 +01:00
parent d567c4bca9
commit 2ccb018bdf
14 changed files with 2489 additions and 0 deletions
+188
View File
@@ -0,0 +1,188 @@
/* Tour Form */
.tour-form {
display: flex;
flex-direction: column;
gap: 16px;
max-width: 600px;
margin: 0 auto;
}
.form-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.form-group label {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.form-row {
display: flex;
gap: 12px;
}
.form-row .form-group {
flex: 1;
}
/* Course Selector */
.course-entry {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 8px;
}
.course-entry select {
flex: 1;
}
.btn-secondary {
background: var(--surface-2);
color: var(--text-secondary);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--surface-3);
color: var(--text-primary);
}
.btn-remove {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 6px;
border-radius: var(--radius-sm);
font-size: 14px;
transition: color var(--transition), background var(--transition);
}
.btn-remove:hover {
color: var(--red);
background: var(--red-subtle);
}
/* Tour Header */
.tour-header {
margin-bottom: 24px;
}
.tour-meta {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
.tour-dates,
.tour-code-label {
font-size: 14px;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 6px;
}
/* Badges */
.badge {
display: inline-block;
padding: 3px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.badge-active {
background: var(--green-subtle);
color: var(--green);
}
.badge-finished {
background: var(--surface-3);
color: var(--text-secondary);
}
.badge-upcoming {
background: var(--accent-subtle);
color: var(--accent);
}
/* Tour Code Display */
.tour-created-message {
text-align: center;
color: var(--text-secondary);
margin: 0 0 12px 0;
}
.tour-code-display {
text-align: center;
padding: 16px;
background: var(--surface-2);
border-radius: var(--radius-md);
border: 1px solid var(--border);
}
.tour-code-display a {
font-family: var(--font-mono);
font-size: 16px;
font-weight: 500;
}
/* Leaderboard */
.strokes {
font-weight: 600;
font-family: var(--font-mono);
}
.under-par {
color: var(--green);
font-size: 12px;
font-weight: 600;
}
.over-par {
color: var(--red);
font-size: 12px;
font-weight: 600;
}
.even-par {
color: var(--text-muted);
font-size: 12px;
font-weight: 600;
}
/* Responsive */
@media (max-width: 768px) {
.form-row {
flex-direction: column;
}
.course-entry {
flex-direction: column;
}
.course-entry select {
width: 100%;
}
.tour-meta {
gap: 8px;
}
}