lots of styling changes and some random fixes
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, onDestroy, tick } from 'svelte';
|
import { onMount, onDestroy, tick } from 'svelte';
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
data
|
data
|
||||||
@@ -127,11 +128,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Runners-up, responsive grid -->
|
<!-- Runners-up, responsive grid -->
|
||||||
{#if leaderboard.length > 1}
|
{#if leaderboard.length > 1}
|
||||||
<div class="runners-grid" style="--runner-count:{Math.min(leaderboard.length - 1, 5)}">
|
<div class="runners-grid" style="--runner-count:{Math.min(leaderboard.length - 1, 5)}">
|
||||||
{#each leaderboard.slice(1) as team, i (team.name)}
|
{#each leaderboard.slice(1) as team, i (team.name)}
|
||||||
<a
|
<a
|
||||||
href="/"
|
href="/"
|
||||||
|
animate:flip={{ duration: 300 }}
|
||||||
class="score-box runner"
|
class="score-box runner"
|
||||||
style="--c:{team.color}"
|
style="--c:{team.color}"
|
||||||
aria-label="{team.name}, {ordinal(i + 2)} place, {team.points} points"
|
aria-label="{team.name}, {ordinal(i + 2)} place, {team.points} points"
|
||||||
@@ -169,6 +171,7 @@
|
|||||||
<div class="events">
|
<div class="events">
|
||||||
{#each eventTable as event (event.id)}
|
{#each eventTable as event (event.id)}
|
||||||
<div
|
<div
|
||||||
|
animate:flip={{ duration: 300 }}
|
||||||
class="event-card"
|
class="event-card"
|
||||||
class:ongoing-event={event.state == 1}
|
class:ongoing-event={event.state == 1}
|
||||||
class:completed-event={event.state == 2}
|
class:completed-event={event.state == 2}
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Sort players: placed first (ascending), unplaced last
|
|
||||||
if (data && data[0] && data[0].registeredPlayers) {
|
if (data && data[0] && data[0].registeredPlayers) {
|
||||||
data[0].registeredPlayers.forEach((bracket: any) => {
|
data[0].registeredPlayers.forEach((bracket: any) => {
|
||||||
bracket.items.sort((a: any, b: any) => {
|
bracket.items.sort((a: any, b: any) => {
|
||||||
@@ -63,99 +62,83 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
eventEndpoint = new EventSource('/api/registeredEvents');
|
eventEndpoint = new EventSource('/api/registeredEvents');
|
||||||
|
|
||||||
// eventEndpoint.onmessage = (e) => {
|
|
||||||
// const eventData = JSON.parse(e.data);
|
|
||||||
// console.log(eventData);
|
|
||||||
// };
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await eventDataPromise}
|
{#await eventDataPromise}
|
||||||
<div>loading</div>
|
<div class="page-container"><div>loading</div></div>
|
||||||
{:then eventData}
|
{:then eventData}
|
||||||
{@const event = eventData[0]}
|
{@const event = eventData[0]}
|
||||||
{console.log(event)}
|
<div class="page-container">
|
||||||
<div class="flex justify-center">
|
<div class="event-card" class:ongoing-event={event.state == 1} class:completed-event={event.state == 2}
|
||||||
<div class="w-full flex-col px-[5vw] text-center">
|
style={event.state == 2 ? `--event-color: ${event.winner?.color ?? 'currentColor'}` : ''}>
|
||||||
<div
|
<div class="event-header">
|
||||||
style:background-color={event.state === 1
|
<span class="event-name goldman">{event.name}</span>
|
||||||
? 'color-mix(in srgb, #fe640b 18%, transparent)'
|
<span class="event-division">{event.division}</span>
|
||||||
: event.state === 2
|
{#if event.state == 1}
|
||||||
? 'color-mix(in srgb, #a6e3a1 18%, transparent)'
|
<span class="event-status goldman">ONGOING</span>
|
||||||
: ''}
|
{:else if event.state == 2}
|
||||||
class="align-text-middle my-7 h-10 w-full rounded-2xl border-2 border-solid border-ctp-surface1"
|
<span class="event-status goldman">FINISHED</span>
|
||||||
>
|
|
||||||
{event.name} - {event.division}
|
|
||||||
{#if event.state == 1}- ONGOING
|
|
||||||
{:else if event.state == 2}- FINISHED
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#each event.registeredPlayers as bracket, bi}
|
|
||||||
{#if bi > 0}
|
|
||||||
<div class="bracket-sep" aria-hidden="true"></div>
|
|
||||||
{/if}
|
|
||||||
<div class="bracket-row">
|
|
||||||
<div class="brackets-name flex items-center">
|
|
||||||
<span class="brackets-name-text align-text-middle">{bracket.name}</span>
|
|
||||||
<div class="bracket-vertical-sep"></div>
|
|
||||||
</div>
|
|
||||||
{#each bracket.items as player}
|
|
||||||
<div class="player-box" style="--c:{player.teamColor}">
|
|
||||||
<div class="player-ghost" aria-hidden="true">
|
|
||||||
<svg viewBox="0 0.1 100 0.6" preserveAspectRatio="none" class="ghost-svg">
|
|
||||||
<text
|
|
||||||
x="0"
|
|
||||||
y="0.7"
|
|
||||||
font-size="1"
|
|
||||||
dominant-baseline="auto"
|
|
||||||
textLength="100"
|
|
||||||
lengthAdjust="spacingAndGlyphs"
|
|
||||||
font-family="'Black Ops One',system-ui">{player.firstName}</text
|
|
||||||
>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="player-name-wrap" use:marquee>
|
|
||||||
<span class=" text-xl">
|
|
||||||
{player.firstName}
|
|
||||||
{player.lastName}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{#if player.placement !== 0}
|
|
||||||
<div class="player-placement goldman">{ordinal(player.placement)}</div>
|
|
||||||
<div class="resultContainer flex justify-center">
|
|
||||||
{#each player.playerScores as score}
|
|
||||||
<div class="mx-5 my-1 rounded border-2" style="border-color:{player.teamColor}">
|
|
||||||
Run {score.resultIndex + 1}: {score.result}{event.resultPresets[0].unit}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="player-placement-gap"></div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if data.user}
|
|
||||||
<div class="mt-10 flex w-full justify-center">
|
|
||||||
<a
|
|
||||||
class="flex justify-center rounded border-2 border-solid border-white bg-ctp-surface2 p-4"
|
|
||||||
href="/event/scoring/{eventId}">Score This Event</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/await}
|
|
||||||
|
|
||||||
<style>
|
<div class="brackets px-[5vw]">
|
||||||
.resultContainer {
|
{#each event.registeredPlayers as bracket, bi}
|
||||||
flex-direction: column;
|
{#if bi > 0}
|
||||||
}
|
<div class="bracket-sep" aria-hidden="true"></div>
|
||||||
@media (max-width: 479px) {
|
{/if}
|
||||||
.resultContainer {
|
<div class="bracket-row">
|
||||||
flex-direction: column;
|
<div class="brackets-name flex items-center">
|
||||||
}
|
<span class="brackets-name-text align-text-middle">{bracket.name}</span>
|
||||||
}
|
<div class="bracket-vertical-sep"></div>
|
||||||
</style>
|
</div>
|
||||||
|
{#each bracket.items as player}
|
||||||
|
<div class="player-box" style="--c:{player.teamColor}">
|
||||||
|
<div class="player-ghost" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0.1 100 0.6" preserveAspectRatio="none" class="ghost-svg">
|
||||||
|
<text
|
||||||
|
x="0"
|
||||||
|
y="0.7"
|
||||||
|
font-size="1"
|
||||||
|
dominant-baseline="auto"
|
||||||
|
textLength="100"
|
||||||
|
lengthAdjust="spacingAndGlyphs"
|
||||||
|
font-family="'Black Ops One',system-ui">{player.firstName}</text
|
||||||
|
>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="player-name-wrap" use:marquee>
|
||||||
|
<a href="/stats/player/{player.id}" class="marquee-inner">
|
||||||
|
{player.firstName}
|
||||||
|
{player.lastName}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{#if player.placement !== 0}
|
||||||
|
<div class="player-placement goldman">{ordinal(player.placement)}</div>
|
||||||
|
<div class="flex flex-col justify-center">
|
||||||
|
{#each player.playerScores as score}
|
||||||
|
<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}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="player-placement-gap"></div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if data.user}
|
||||||
|
<div class="mt-10 flex w-full justify-center">
|
||||||
|
<a
|
||||||
|
class="flex justify-center rounded border-2 border-solid border-white bg-ctp-surface2 p-4"
|
||||||
|
href="/event/scoring/{eventId}">Score This Event</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
@@ -17,6 +17,13 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -51,7 +58,7 @@
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
text-decoration: none;
|
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 {
|
.score-box:hover {
|
||||||
filter: brightness(1.15);
|
filter: brightness(1.15);
|
||||||
@@ -65,10 +72,12 @@
|
|||||||
aspect-ratio: 2 / 1;
|
aspect-ratio: 2 / 1;
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
|
transition: aspect-ratio 0.3s ease, border-width 0.3s ease, min-height 0.3s ease;
|
||||||
}
|
}
|
||||||
.runner {
|
.runner {
|
||||||
aspect-ratio: 4 / 1;
|
aspect-ratio: 4 / 1;
|
||||||
min-height: 56px;
|
min-height: 56px;
|
||||||
|
transition: aspect-ratio 0.3s ease, min-height 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ghost SVG fills cell, text sits at bottom */
|
/* Ghost SVG fills cell, text sits at bottom */
|
||||||
@@ -90,40 +99,83 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0 14px;
|
padding: 0 14px;
|
||||||
gap: 8px;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.score-meta {
|
.score-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.score-rank {
|
.score-rank {
|
||||||
font-size: 10px;
|
font-size: clamp(14px, 2.5vw, 17px);
|
||||||
letter-spacing: 1.5px;
|
letter-spacing: 1.2px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
opacity: 0.5;
|
opacity: 0.85;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-shadow: 0 0 6px rgb(0 0 0 / 50%);
|
||||||
}
|
}
|
||||||
.score-name {
|
.score-name {
|
||||||
font-size: clamp(11px, 3vw, 20px);
|
font-size: clamp(20px, 5.5vw, 40px);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
.score-pts {
|
.score-pts {
|
||||||
font-size: clamp(20px, 5.5vw, 40px);
|
font-size: clamp(20px, 5.5vw, 40px);
|
||||||
letter-spacing: 2px;
|
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 {
|
.winner .score-name {
|
||||||
font-size: clamp(16px, 5vw, 32px);
|
font-size: clamp(34px, 9vw, 64px);
|
||||||
|
max-width: 70%;
|
||||||
}
|
}
|
||||||
.winner .score-pts {
|
.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 */
|
/* Responsive runners-up: 1 col <480px, 2 cols 480-699px, 4 cols ≥700px */
|
||||||
@@ -131,6 +183,7 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
transition: gap 0.3s ease;
|
||||||
}
|
}
|
||||||
@media (min-width: 480px) {
|
@media (min-width: 480px) {
|
||||||
.runners-grid {
|
.runners-grid {
|
||||||
@@ -256,17 +309,20 @@
|
|||||||
.brackets {
|
.brackets {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
transition: gap 0.3s ease, align-items 0.3s ease;
|
||||||
}
|
}
|
||||||
.bracket-sep {
|
.bracket-sep {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: color-mix(in srgb, currentColor 10%, transparent);
|
background: color-mix(in srgb, currentColor 10%, transparent);
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
|
transition: height 0.3s ease, width 0.3s ease, margin 0.3s ease;
|
||||||
}
|
}
|
||||||
.bracket-row {
|
.bracket-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 7px 9px;
|
padding: 7px 9px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
transition: gap 0.3s ease, padding 0.3s ease, min-width 0.3s ease, flex 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Player boxes */
|
/* Player boxes */
|
||||||
@@ -283,6 +339,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 52px;
|
min-height: 52px;
|
||||||
|
transition: max-width 0.3s ease, flex 0.3s ease, min-height 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile: single column layout */
|
/* Mobile: single column layout */
|
||||||
@@ -384,3 +441,76 @@
|
|||||||
.player-placement-gap {
|
.player-placement-gap {
|
||||||
height: 50px;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,36 +67,45 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="page-container">
|
||||||
<h2 class="mb-4 text-lg font-bold">Manual Ledger Entry</h2>
|
<h2 class="section-label">Manual Ledger Entry</h2>
|
||||||
<div class="mb-4 flex flex-wrap gap-4 items-end">
|
<div class="card">
|
||||||
<div>
|
<div class="flex flex-wrap gap-4 items-end">
|
||||||
<label class="block text-sm mb-1">Event</label>
|
|
||||||
<select bind:value={selectedEventId} class="border rounded px-2 py-1">
|
|
||||||
<option value="">Select event...</option>
|
|
||||||
{#each data.events as event (event.id)}
|
|
||||||
<option value={event.id}>{event.name} — {event.division}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
{#each teamNames as team}
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm mb-1">{team}</label>
|
<label class="block text-sm mb-1">Event</label>
|
||||||
<input
|
<select bind:value={selectedEventId}>
|
||||||
type="number"
|
<option value="">Select event...</option>
|
||||||
value={inputPoints[team] ?? '0'}
|
{#each data.events as event (event.id)}
|
||||||
oninput={(e) => { inputPoints[team] = (e.target as HTMLInputElement).value; }}
|
<option value={event.id}>{event.name} — {event.division}</option>
|
||||||
class="border rounded px-2 py-1 w-20"
|
{/each}
|
||||||
/>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{#each teamNames as team}
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm mb-1">{team}</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={inputPoints[team] ?? '0'}
|
||||||
|
oninput={(e) => { inputPoints[team] = (e.target as HTMLInputElement).value; }}
|
||||||
|
style="width: 5rem;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<button onclick={submitEntry} disabled={!selectedEventId || submitting}>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-label" style="padding-bottom: 4px;">
|
||||||
|
Ledger Entries
|
||||||
<button
|
<button
|
||||||
onclick={submitEntry}
|
onclick={() => loadEntries(!showAll)}
|
||||||
disabled={!selectedEventId || submitting}
|
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;"
|
||||||
class="border rounded px-4 py-1 bg-green-700 text-white disabled:opacity-50"
|
|
||||||
>
|
>
|
||||||
Submit
|
{showAll ? 'Show 100 most recent' : 'Load all entries'}
|
||||||
</button>
|
</button>
|
||||||
|
<span class="text-sm opacity-60" style="margin-left: 0.5rem;">{entries.length} entries</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table data={entries} maxHeight="none">
|
<Table data={entries} maxHeight="none">
|
||||||
@@ -121,14 +130,4 @@
|
|||||||
<td class="px-2">{formatTime(entry.timestamp)}</td>
|
<td class="px-2">{formatTime(entry.timestamp)}</td>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
<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>
|
|
||||||
@@ -4,87 +4,39 @@
|
|||||||
export let form: ActionData;
|
export let form: ActionData;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="auth-card">
|
<div class="page-container">
|
||||||
{#if data.user}
|
<div class="card" style="max-width: 360px; margin: 3rem auto;">
|
||||||
<div class="align-center flex h-full w-full flex-col text-center">
|
{#if data.user}
|
||||||
<h1>Already logged in</h1>
|
<div class="align-center flex h-full w-full flex-col text-center">
|
||||||
<p>You're signed in as <strong>{data.user.username}</strong></p>
|
<h1>Already logged in</h1>
|
||||||
<a href="/">Go to home</a>
|
<p>You're signed in as <strong>{data.user.username}</strong></p>
|
||||||
<form method="POST" action="/api/logout">
|
<a href="/">Go to home</a>
|
||||||
<button type="submit">Log out</button>
|
<form method="POST" action="/api/logout">
|
||||||
</form>
|
<button type="submit">Log out</button>
|
||||||
</div>
|
</form>
|
||||||
{:else}
|
</div>
|
||||||
<h1>Log in</h1>
|
{:else}
|
||||||
|
<h1>Log in</h1>
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<label>
|
<label>
|
||||||
Username
|
Username
|
||||||
<input class="text-black" name="username" type="text" autocomplete="username" required />
|
<input name="username" type="text" autocomplete="username" required />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Password
|
Password
|
||||||
<input
|
<input name="password" type="password" autocomplete="current-password" required />
|
||||||
class="text-black"
|
</label>
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
autocomplete="current-password"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{#if form?.message}
|
{#if form?.message}
|
||||||
<p class="error">{form.message}</p>
|
<p class="error">{form.message}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button type="submit">Log in</button>
|
<button type="submit">Log in</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<p class="switch">No account? <a href="/register">Sign up</a></p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- <p class="switch">No account? <a href="/signup">Sign up</a></p> -->
|
|
||||||
</div>
|
</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;
|
|
||||||
}
|
|
||||||
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>
|
|
||||||
@@ -4,85 +4,33 @@
|
|||||||
export let form: ActionData;
|
export let form: ActionData;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="auth-card">
|
<div class="page-container">
|
||||||
<h1>Sign up</h1>
|
<div class="card" style="max-width: 360px; margin: 3rem auto;">
|
||||||
|
<h1>Sign up</h1>
|
||||||
|
|
||||||
{#if data.disabled}
|
{#if data.disabled}
|
||||||
<p class="error">Registration is currently disabled by an administrator.</p>
|
<p class="error">Registration is currently disabled by an administrator.</p>
|
||||||
{:else}
|
{:else}
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<label>
|
<label>
|
||||||
Username
|
Username
|
||||||
<input name="username" class="text-black" type="text" autocomplete="username" required />
|
<input name="username" type="text" autocomplete="username" required />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Password
|
Password
|
||||||
<input
|
<input name="password" type="password" autocomplete="new-password" minlength="8" required />
|
||||||
class="text-black"
|
<small>At least 8 characters</small>
|
||||||
name="password"
|
</label>
|
||||||
type="password"
|
|
||||||
autocomplete="new-password"
|
|
||||||
minlength="8"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<small>At least 8 characters</small>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{#if form?.message}
|
{#if form?.message}
|
||||||
<p class="error">{form.message}</p>
|
<p class="error">{form.message}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button type="submit">Create account</button>
|
<button type="submit">Create account</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p class="switch">Already have an account? <a href="/login">Log in</a></p>
|
<p class="switch">Already have an account? <a href="/login">Log in</a></p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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>
|
|
||||||
@@ -5,21 +5,21 @@
|
|||||||
let { data }: { data: PageData } = $props();
|
let { data }: { data: PageData } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="page-container">
|
||||||
<h2 class="mb-4 text-lg font-bold">Settings</h2>
|
<h2 class="section-label">Settings</h2>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<form method="POST" action="?/toggleRegistration" use:enhance>
|
<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">
|
<div class="flex-1">
|
||||||
<p class="font-semibold">Disable Registration</p>
|
<p class="font-semibold">Disable Registration</p>
|
||||||
<p class="text-sm opacity-70">Prevents new users from signing up via /register</p>
|
<p class="text-sm opacity-70">Prevents new users from signing up via /register</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="border rounded px-4 py-1 {data.disableRegistration
|
style={data.disableRegistration
|
||||||
? 'bg-red-700 text-white'
|
? 'background: color-mix(in srgb, var(--ctp-mocha-red) 30%, transparent); border-color: var(--ctp-mocha-red);'
|
||||||
: 'bg-green-700 text-white'}"
|
: 'background: color-mix(in srgb, var(--ctp-mocha-green) 30%, transparent); border-color: var(--ctp-mocha-green);'}
|
||||||
>
|
>
|
||||||
{data.disableRegistration ? 'Disabled' : 'Enabled'}
|
{data.disableRegistration ? 'Disabled' : 'Enabled'}
|
||||||
</button>
|
</button>
|
||||||
@@ -27,20 +27,20 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form method="POST" action="?/toggleLeaderboardLock" use:enhance>
|
<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">
|
<div class="flex-1">
|
||||||
<p class="font-semibold">Require Login for Leaderboard</p>
|
<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>
|
<p class="text-sm opacity-70">Non-logged-in users will be redirected to /login</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="border rounded px-4 py-1 {data.requireLoginForLeaderboard
|
style={data.requireLoginForLeaderboard
|
||||||
? 'bg-red-700 text-white'
|
? 'background: color-mix(in srgb, var(--ctp-mocha-red) 30%, transparent); border-color: var(--ctp-mocha-red);'
|
||||||
: 'bg-green-700 text-white'}"
|
: 'background: color-mix(in srgb, var(--ctp-mocha-green) 30%, transparent); border-color: var(--ctp-mocha-green);'}
|
||||||
>
|
>
|
||||||
{data.requireLoginForLeaderboard ? 'Locked' : 'Public'}
|
{data.requireLoginForLeaderboard ? 'Locked' : 'Public'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,53 +23,32 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await data.playerInfo}
|
{#await data.playerInfo}
|
||||||
<div>Loading...</div>
|
<div class="page-container"><div>Loading...</div></div>
|
||||||
{:then playerInfo}
|
{:then playerInfo}
|
||||||
{console.log(playerInfo)}
|
<div class="page-container">
|
||||||
<div class="mt-5 flex justify-center">
|
<div class="flex justify-center">
|
||||||
<div
|
<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}"
|
style="--c:{playerInfo.teamInfo.color}"
|
||||||
>
|
>
|
||||||
<div class="player-ghost" aria-hidden="true">
|
<div class="player-ghost" aria-hidden="true">
|
||||||
<svg viewBox="0 0.1 100 0.6" preserveAspectRatio="none" class="ghost-svg">
|
<svg viewBox="0 0.1 100 0.6" preserveAspectRatio="none" class="ghost-svg">
|
||||||
<text
|
<text
|
||||||
x="0"
|
x="0"
|
||||||
y="0.7"
|
y="0.7"
|
||||||
font-size="1"
|
font-size="1"
|
||||||
dominant-baseline="auto"
|
dominant-baseline="auto"
|
||||||
textLength="100"
|
textLength="100"
|
||||||
lengthAdjust="spacingAndGlyphs"
|
lengthAdjust="spacingAndGlyphs"
|
||||||
font-family="'Black Ops One',system-ui">{playerInfo.firstName}</text
|
font-family="'Black Ops One',system-ui">{playerInfo.firstName}</text
|
||||||
>
|
>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class=" goldman player-name-wrap text-5xl" use:marquee>
|
<div class="goldman player-name-wrap text-5xl" use:marquee>
|
||||||
{playerInfo.firstName}
|
{playerInfo.firstName}
|
||||||
{playerInfo.lastName}
|
{playerInfo.lastName}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/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>
|
|
||||||
Reference in New Issue
Block a user