lots of styling changes and some random fixes

This commit is contained in:
2026-07-05 13:15:08 +01:00
parent f93f407d66
commit ac4f02f597
8 changed files with 340 additions and 346 deletions

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { onMount, onDestroy, tick } from 'svelte';
import { flip } from 'svelte/animate';
let {
data
@@ -132,6 +133,7 @@
{#each leaderboard.slice(1) as team, i (team.name)}
<a
href="/"
animate:flip={{ duration: 300 }}
class="score-box runner"
style="--c:{team.color}"
aria-label="{team.name}, {ordinal(i + 2)} place, {team.points} points"
@@ -169,6 +171,7 @@
<div class="events">
{#each eventTable as event (event.id)}
<div
animate:flip={{ duration: 300 }}
class="event-card"
class:ongoing-event={event.state == 1}
class:completed-event={event.state == 2}

View File

@@ -44,7 +44,6 @@
const data = await response.json();
// Sort players: placed first (ascending), unplaced last
if (data && data[0] && data[0].registeredPlayers) {
data[0].registeredPlayers.forEach((bracket: any) => {
bracket.items.sort((a: any, b: any) => {
@@ -63,34 +62,27 @@
onMount(() => {
eventEndpoint = new EventSource('/api/registeredEvents');
// eventEndpoint.onmessage = (e) => {
// const eventData = JSON.parse(e.data);
// console.log(eventData);
// };
});
</script>
{#await eventDataPromise}
<div>loading</div>
<div class="page-container"><div>loading</div></div>
{:then eventData}
{@const event = eventData[0]}
{console.log(event)}
<div class="flex justify-center">
<div class="w-full flex-col px-[5vw] text-center">
<div
style:background-color={event.state === 1
? 'color-mix(in srgb, #fe640b 18%, transparent)'
: event.state === 2
? 'color-mix(in srgb, #a6e3a1 18%, transparent)'
: ''}
class="align-text-middle my-7 h-10 w-full rounded-2xl border-2 border-solid border-ctp-surface1"
>
{event.name} - {event.division}
{#if event.state == 1}- ONGOING
{:else if event.state == 2}- FINISHED
<div class="page-container">
<div class="event-card" class:ongoing-event={event.state == 1} class:completed-event={event.state == 2}
style={event.state == 2 ? `--event-color: ${event.winner?.color ?? 'currentColor'}` : ''}>
<div class="event-header">
<span class="event-name goldman">{event.name}</span>
<span class="event-division">{event.division}</span>
{#if event.state == 1}
<span class="event-status goldman">ONGOING</span>
{:else if event.state == 2}
<span class="event-status goldman">FINISHED</span>
{/if}
</div>
<div class="brackets px-[5vw]">
{#each event.registeredPlayers as bracket, bi}
{#if bi > 0}
<div class="bracket-sep" aria-hidden="true"></div>
@@ -116,16 +108,16 @@
</svg>
</div>
<div class="player-name-wrap" use:marquee>
<span class=" text-xl">
<a href="/stats/player/{player.id}" class="marquee-inner">
{player.firstName}
{player.lastName}
</span>
</a>
</div>
{#if player.placement !== 0}
<div class="player-placement goldman">{ordinal(player.placement)}</div>
<div class="resultContainer flex justify-center">
<div class="flex flex-col justify-center">
{#each player.playerScores as score}
<div class="mx-5 my-1 rounded border-2" style="border-color:{player.teamColor}">
<div class="mx-1 my-0.5 rounded border px-1 text-xs" style="border-color:{player.teamColor}">
Run {score.resultIndex + 1}: {score.result}{event.resultPresets[0].unit}
</div>
{/each}
@@ -139,6 +131,7 @@
{/each}
</div>
</div>
{#if data.user}
<div class="mt-10 flex w-full justify-center">
<a
@@ -147,15 +140,5 @@
>
</div>
{/if}
</div>
{/await}
<style>
.resultContainer {
flex-direction: column;
}
@media (max-width: 479px) {
.resultContainer {
flex-direction: column;
}
}
</style>

View File

@@ -17,6 +17,13 @@
flex-direction: column;
}
.page-container {
max-width: 1000px;
margin: 0 auto;
width: 100%;
padding: 14px;
}
.page {
display: flex;
flex-direction: column;
@@ -51,7 +58,7 @@
grid-template-columns: 1fr;
grid-template-rows: 1fr;
text-decoration: none;
transition: filter 0.15s ease;
transition: aspect-ratio 0.3s ease, border-width 0.3s ease, min-height 0.3s ease, filter 0.15s ease;
}
.score-box:hover {
filter: brightness(1.15);
@@ -65,10 +72,12 @@
aspect-ratio: 2 / 1;
border-width: 3px;
min-height: 80px;
transition: aspect-ratio 0.3s ease, border-width 0.3s ease, min-height 0.3s ease;
}
.runner {
aspect-ratio: 4 / 1;
min-height: 56px;
transition: aspect-ratio 0.3s ease, min-height 0.3s ease;
}
/* Ghost SVG fills cell, text sits at bottom */
@@ -90,40 +99,83 @@
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 14px;
gap: 8px;
min-width: 0;
}
.score-meta {
display: flex;
flex-direction: column;
min-width: 0;
flex: 1;
overflow: hidden;
}
.score-rank {
font-size: 10px;
letter-spacing: 1.5px;
font-size: clamp(14px, 2.5vw, 17px);
letter-spacing: 1.2px;
text-transform: uppercase;
opacity: 0.5;
opacity: 0.85;
line-height: 1;
margin-bottom: 3px;
margin-bottom: 2px;
white-space: nowrap;
text-shadow: 0 0 6px rgb(0 0 0 / 50%);
}
.score-name {
font-size: clamp(11px, 3vw, 20px);
font-size: clamp(20px, 5.5vw, 40px);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.score-pts {
font-size: clamp(20px, 5.5vw, 40px);
letter-spacing: 2px;
flex-shrink: 0;
position: absolute;
right: 14px;
top: 50%;
transform: translateY(-50%);
z-index: 2;
text-shadow: 0 0 8px rgb(0 0 0 / 80%), 0 0 4px rgb(0 0 0 / 60%);
}
.winner .score-meta {
flex-direction: column;
align-items: flex-start;
}
.winner .score-rank {
font-size: clamp(16px, 3vw, 20px);
opacity: 0.85;
margin-bottom: 3px;
}
.winner .score-name {
font-size: clamp(16px, 5vw, 32px);
font-size: clamp(34px, 9vw, 64px);
max-width: 70%;
}
.winner .score-pts {
font-size: clamp(26px, 7.5vw, 52px);
font-size: clamp(34px, 9vw, 64px);
}
/* Smallest screens: one-line for runners, bigger text to fill height */
@media (max-width: 479px) {
.score-meta {
flex-direction: row;
align-items: center;
gap: 10px;
}
.score-rank {
margin-bottom: 0;
}
.score-name {
font-size: clamp(18px, 5vw, 28px);
}
.score-pts {
font-size: clamp(22px, 6vw, 34px);
}
.winner .score-name {
font-size: clamp(26px, 7vw, 42px);
max-width: 60%;
}
.winner .score-pts {
font-size: clamp(28px, 8vw, 48px);
}
}
/* Responsive runners-up: 1 col <480px, 2 cols 480-699px, 4 cols ≥700px */
@@ -131,6 +183,7 @@
display: grid;
gap: 10px;
grid-template-columns: 1fr;
transition: gap 0.3s ease;
}
@media (min-width: 480px) {
.runners-grid {
@@ -256,17 +309,20 @@
.brackets {
display: flex;
flex-direction: column;
transition: gap 0.3s ease, align-items 0.3s ease;
}
.bracket-sep {
height: 1px;
background: color-mix(in srgb, currentColor 10%, transparent);
margin: 0 10px;
transition: height 0.3s ease, width 0.3s ease, margin 0.3s ease;
}
.bracket-row {
display: flex;
gap: 6px;
padding: 7px 9px;
flex-wrap: wrap;
transition: gap 0.3s ease, padding 0.3s ease, min-width 0.3s ease, flex 0.3s ease;
}
/* Player boxes */
@@ -283,6 +339,7 @@
display: flex;
flex-direction: column;
min-height: 52px;
transition: max-width 0.3s ease, flex 0.3s ease, min-height 0.3s ease;
}
/* Mobile: single column layout */
@@ -384,3 +441,76 @@
.player-placement-gap {
height: 50px;
}
/* Shared card pattern for auth, settings, etc. */
.card {
border-radius: 12px;
border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.card h1 {
font-size: 1.25rem;
font-weight: 700;
margin: 0;
}
.card label {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.85rem;
opacity: 0.8;
}
.card input,
.card select {
padding: 0.5rem;
border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
border-radius: 6px;
background: color-mix(in srgb, currentColor 6%, transparent);
color: inherit;
font-size: 0.9rem;
}
.card input:focus,
.card select:focus {
outline: none;
border-color: currentColor;
}
.card button {
padding: 0.5rem 1rem;
border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
border-radius: 6px;
background: color-mix(in srgb, currentColor 10%, transparent);
color: inherit;
cursor: pointer;
font-size: 0.9rem;
transition: filter 0.15s ease;
}
.card button:hover {
filter: brightness(1.15);
}
.card button:disabled {
opacity: 0.4;
cursor: default;
}
.card .error {
color: var(--ctp-mocha-red, #f38ba8);
font-size: 0.85rem;
margin: 0;
}
.card .switch {
font-size: 0.85rem;
text-align: center;
opacity: 0.7;
}
.card .switch a {
color: inherit;
}
.card .switch a:hover {
text-decoration: underline;
}
.card small {
opacity: 0.6;
font-size: 0.8rem;
}

View File

@@ -67,12 +67,13 @@
});
</script>
<div class="p-4">
<h2 class="mb-4 text-lg font-bold">Manual Ledger Entry</h2>
<div class="mb-4 flex flex-wrap gap-4 items-end">
<div class="page-container">
<h2 class="section-label">Manual Ledger Entry</h2>
<div class="card">
<div class="flex flex-wrap gap-4 items-end">
<div>
<label class="block text-sm mb-1">Event</label>
<select bind:value={selectedEventId} class="border rounded px-2 py-1">
<select bind:value={selectedEventId}>
<option value="">Select event...</option>
{#each data.events as event (event.id)}
<option value={event.id}>{event.name} {event.division}</option>
@@ -86,18 +87,26 @@
type="number"
value={inputPoints[team] ?? '0'}
oninput={(e) => { inputPoints[team] = (e.target as HTMLInputElement).value; }}
class="border rounded px-2 py-1 w-20"
style="width: 5rem;"
/>
</div>
{/each}
<button
onclick={submitEntry}
disabled={!selectedEventId || submitting}
class="border rounded px-4 py-1 bg-green-700 text-white disabled:opacity-50"
>
<button onclick={submitEntry} disabled={!selectedEventId || submitting}>
Submit
</button>
</div>
</div>
<div class="section-label" style="padding-bottom: 4px;">
Ledger Entries
<button
onclick={() => loadEntries(!showAll)}
style="margin-left: 0.5rem; padding: 0.15rem 0.5rem; font-size: 0.75rem; border: 1px solid color-mix(in srgb, currentColor 18%, transparent); border-radius: 4px; background: transparent; color: inherit; cursor: pointer;"
>
{showAll ? 'Show 100 most recent' : 'Load all entries'}
</button>
<span class="text-sm opacity-60" style="margin-left: 0.5rem;">{entries.length} entries</span>
</div>
<Table data={entries} maxHeight="none">
{#snippet header()}
@@ -121,14 +130,4 @@
<td class="px-2">{formatTime(entry.timestamp)}</td>
{/snippet}
</Table>
<div class="mt-4">
<button
onclick={() => loadEntries(!showAll)}
class="border rounded px-4 py-1"
>
{showAll ? 'Show 100 most recent' : 'Load all entries'}
</button>
<span class="ml-2 text-sm">{entries.length} entries shown</span>
</div>
</div>

View File

@@ -4,7 +4,8 @@
export let form: ActionData;
</script>
<div class="auth-card">
<div class="page-container">
<div class="card" style="max-width: 360px; margin: 3rem auto;">
{#if data.user}
<div class="align-center flex h-full w-full flex-col text-center">
<h1>Already logged in</h1>
@@ -20,18 +21,12 @@
<form method="POST">
<label>
Username
<input class="text-black" name="username" type="text" autocomplete="username" required />
<input name="username" type="text" autocomplete="username" required />
</label>
<label>
Password
<input
class="text-black"
name="password"
type="password"
autocomplete="current-password"
required
/>
<input name="password" type="password" autocomplete="current-password" required />
</label>
{#if form?.message}
@@ -40,51 +35,8 @@
<button type="submit">Log in</button>
</form>
<p class="switch">No account? <a href="/register">Sign up</a></p>
{/if}
<!-- <p class="switch">No account? <a href="/signup">Sign up</a></p> -->
</div>
<style>
.auth-card {
max-width: 320px;
margin: 4rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
}
form {
display: flex;
flex-direction: column;
gap: 1rem;
}
label {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.9rem;
}
input {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 0.6rem;
border: none;
border-radius: 4px;
background: #333;
color: #fff;
cursor: pointer;
}
.error {
color: #c0392b;
font-size: 0.85rem;
margin: 0;
}
.switch {
margin-top: 1rem;
font-size: 0.85rem;
text-align: center;
}
</style>
</div>

View File

@@ -4,7 +4,8 @@
export let form: ActionData;
</script>
<div class="auth-card">
<div class="page-container">
<div class="card" style="max-width: 360px; margin: 3rem auto;">
<h1>Sign up</h1>
{#if data.disabled}
@@ -13,19 +14,12 @@
<form method="POST">
<label>
Username
<input name="username" class="text-black" type="text" autocomplete="username" required />
<input name="username" type="text" autocomplete="username" required />
</label>
<label>
Password
<input
class="text-black"
name="password"
type="password"
autocomplete="new-password"
minlength="8"
required
/>
<input name="password" type="password" autocomplete="new-password" minlength="8" required />
<small>At least 8 characters</small>
</label>
@@ -38,51 +32,5 @@
<p class="switch">Already have an account? <a href="/login">Log in</a></p>
{/if}
</div>
</div>
<style>
.auth-card {
max-width: 320px;
margin: 4rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
}
form {
display: flex;
flex-direction: column;
gap: 1rem;
}
label {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.9rem;
}
input {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
}
small {
color: #888;
}
button {
padding: 0.6rem;
border: none;
border-radius: 4px;
background: #333;
color: #fff;
cursor: pointer;
}
.error {
color: #c0392b;
font-size: 0.85rem;
margin: 0;
}
.switch {
margin-top: 1rem;
font-size: 0.85rem;
text-align: center;
}
</style>

View File

@@ -5,21 +5,21 @@
let { data }: { data: PageData } = $props();
</script>
<div class="p-4">
<h2 class="mb-4 text-lg font-bold">Settings</h2>
<div class="page-container">
<h2 class="section-label">Settings</h2>
<div class="flex flex-col gap-4">
<form method="POST" action="?/toggleRegistration" use:enhance>
<div class="flex items-center gap-4 border rounded p-4">
<div class="card" style="flex-direction: row; align-items: center;">
<div class="flex-1">
<p class="font-semibold">Disable Registration</p>
<p class="text-sm opacity-70">Prevents new users from signing up via /register</p>
</div>
<button
type="submit"
class="border rounded px-4 py-1 {data.disableRegistration
? 'bg-red-700 text-white'
: 'bg-green-700 text-white'}"
style={data.disableRegistration
? 'background: color-mix(in srgb, var(--ctp-mocha-red) 30%, transparent); border-color: var(--ctp-mocha-red);'
: 'background: color-mix(in srgb, var(--ctp-mocha-green) 30%, transparent); border-color: var(--ctp-mocha-green);'}
>
{data.disableRegistration ? 'Disabled' : 'Enabled'}
</button>
@@ -27,16 +27,16 @@
</form>
<form method="POST" action="?/toggleLeaderboardLock" use:enhance>
<div class="flex items-center gap-4 border rounded p-4">
<div class="card" style="flex-direction: row; align-items: center;">
<div class="flex-1">
<p class="font-semibold">Require Login for Leaderboard</p>
<p class="text-sm opacity-70">Non-logged-in users will be redirected to /login</p>
</div>
<button
type="submit"
class="border rounded px-4 py-1 {data.requireLoginForLeaderboard
? 'bg-red-700 text-white'
: 'bg-green-700 text-white'}"
style={data.requireLoginForLeaderboard
? 'background: color-mix(in srgb, var(--ctp-mocha-red) 30%, transparent); border-color: var(--ctp-mocha-red);'
: 'background: color-mix(in srgb, var(--ctp-mocha-green) 30%, transparent); border-color: var(--ctp-mocha-green);'}
>
{data.requireLoginForLeaderboard ? 'Locked' : 'Public'}
</button>

View File

@@ -23,12 +23,12 @@
</script>
{#await data.playerInfo}
<div>Loading...</div>
<div class="page-container"><div>Loading...</div></div>
{:then playerInfo}
{console.log(playerInfo)}
<div class="mt-5 flex justify-center">
<div class="page-container">
<div class="flex justify-center">
<div
class="player-single-box aspect-square w-full max-w-[95vw] justify-center md:aspect-2/1 lg:aspect-2/1"
class="player-box aspect-square w-full max-w-[95vw] md:aspect-2/1 lg:aspect-2/1"
style="--c:{playerInfo.teamInfo.color}"
>
<div class="player-ghost" aria-hidden="true">
@@ -44,32 +44,11 @@
>
</svg>
</div>
<div class=" goldman player-name-wrap text-5xl" use:marquee>
<div class="goldman player-name-wrap text-5xl" use:marquee>
{playerInfo.firstName}
{playerInfo.lastName}
</div>
</div>
</div>
</div>
{/await}
<style>
.player-single-box {
flex: 1 1 0;
position: relative;
overflow: hidden;
border-radius: 8px;
border: 1.5px solid var(--c);
color: var(--c);
background: color-mix(in srgb, var(--c) 10%, transparent);
padding: 5px 7px 5px;
display: flex;
flex-direction: column;
}
.player-name-wrap {
position: relative;
z-index: 1;
text-decoration: none;
overflow: hidden;
white-space: nowrap;
}
</style>