added log file uploading and match results

This commit is contained in:
2026-02-21 22:25:21 +11:00
parent 6439bf782b
commit 680ba3fe50
20 changed files with 2595 additions and 61 deletions

View File

@@ -9,6 +9,7 @@
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace;
--spacing: 0.25rem;
--breakpoint-md: 48rem;
--breakpoint-lg: 64rem;
--breakpoint-xl: 80rem;
--breakpoint-2xl: 96rem;
@@ -38,6 +39,7 @@
--text-6xl--line-height: 1;
--text-9xl: 8rem;
--text-9xl--line-height: 1;
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
@@ -50,6 +52,7 @@
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--animate-spin: spin 1s linear infinite;
--blur-sm: 8px;
--default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
--default-font-family: var(--font-sans);
@@ -214,6 +217,9 @@
.collapse {
visibility: collapse;
}
.invisible {
visibility: hidden;
}
.visible {
visibility: visible;
}
@@ -261,6 +267,9 @@
.top-20 {
top: calc(var(--spacing) * 20);
}
.top-full {
top: 100%;
}
.right-0 {
right: calc(var(--spacing) * 0);
}
@@ -451,6 +460,9 @@
.h-16 {
height: calc(var(--spacing) * 16);
}
.h-\[calc\(100\%-3rem\)\] {
height: calc(100% - 3rem);
}
.h-full {
height: 100%;
}
@@ -514,6 +526,12 @@
.w-48 {
width: calc(var(--spacing) * 48);
}
.w-56 {
width: calc(var(--spacing) * 56);
}
.w-72 {
width: calc(var(--spacing) * 72);
}
.w-80 {
width: calc(var(--spacing) * 80);
}
@@ -559,6 +577,9 @@
.max-w-screen-lg {
max-width: var(--breakpoint-lg);
}
.max-w-screen-md {
max-width: var(--breakpoint-md);
}
.max-w-screen-xl {
max-width: var(--breakpoint-xl);
}
@@ -610,6 +631,9 @@
.cursor-grab {
cursor: grab;
}
.cursor-help {
cursor: help;
}
.cursor-not-allowed {
cursor: not-allowed;
}
@@ -622,6 +646,12 @@
.resize-none {
resize: none;
}
.list-inside {
list-style-position: inside;
}
.list-disc {
list-style-type: disc;
}
.appearance-none {
appearance: none;
}
@@ -667,6 +697,9 @@
.gap-1 {
gap: calc(var(--spacing) * 1);
}
.gap-1\.5 {
gap: calc(var(--spacing) * 1.5);
}
.gap-2 {
gap: calc(var(--spacing) * 2);
}
@@ -682,6 +715,13 @@
.gap-8 {
gap: calc(var(--spacing) * 8);
}
.space-y-0\.5 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 0.5) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 0.5) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-1 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
@@ -1036,6 +1076,9 @@
.px-2 {
padding-inline: calc(var(--spacing) * 2);
}
.px-2\.5 {
padding-inline: calc(var(--spacing) * 2.5);
}
.px-3 {
padding-inline: calc(var(--spacing) * 3);
}
@@ -1054,6 +1097,9 @@
.py-1 {
padding-block: calc(var(--spacing) * 1);
}
.py-1\.5 {
padding-block: calc(var(--spacing) * 1.5);
}
.py-2 {
padding-block: calc(var(--spacing) * 2);
}
@@ -1150,6 +1196,10 @@
--tw-leading: calc(var(--spacing) * 6);
line-height: calc(var(--spacing) * 6);
}
.leading-none {
--tw-leading: 1;
line-height: 1;
}
.leading-relaxed {
--tw-leading: var(--leading-relaxed);
line-height: var(--leading-relaxed);
@@ -1158,6 +1208,10 @@
--tw-font-weight: var(--font-weight-bold);
font-weight: var(--font-weight-bold);
}
.font-light {
--tw-font-weight: var(--font-weight-light);
font-weight: var(--font-weight-light);
}
.font-medium {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
@@ -1214,6 +1268,12 @@
.text-red {
color: var(--red);
}
.text-red\/60 {
color: var(--red);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--red) 60%, transparent);
}
}
.text-red\/80 {
color: var(--red);
@supports (color: color-mix(in lab, red, red)) {
@@ -1232,6 +1292,12 @@
.text-yellow {
color: var(--yellow);
}
.text-yellow\/70 {
color: var(--yellow);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--yellow) 70%, transparent);
}
}
.text-yellow\/80 {
color: var(--yellow);
@supports (color: color-mix(in lab, red, red)) {
@@ -1287,6 +1353,11 @@
.filter {
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
.backdrop-blur-sm {
--tw-backdrop-blur: blur(var(--blur-sm));
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
}
.transition {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
@@ -1335,6 +1406,75 @@
-webkit-user-select: none;
user-select: none;
}
.group-hover\:visible {
&:is(:where(.group):hover *) {
@media (hover: hover) {
visibility: visible;
}
}
}
.group-hover\:opacity-100 {
&:is(:where(.group):hover *) {
@media (hover: hover) {
opacity: 100%;
}
}
}
.file\:mr-4 {
&::file-selector-button {
margin-right: calc(var(--spacing) * 4);
}
}
.file\:rounded {
&::file-selector-button {
border-radius: 0.25rem;
}
}
.file\:border-0 {
&::file-selector-button {
border-style: var(--tw-border-style);
border-width: 0px;
}
}
.file\:bg-blue {
&::file-selector-button {
background-color: var(--blue);
}
}
.file\:px-3 {
&::file-selector-button {
padding-inline: calc(var(--spacing) * 3);
}
}
.file\:py-1 {
&::file-selector-button {
padding-block: calc(var(--spacing) * 1);
}
}
.file\:text-sm {
&::file-selector-button {
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
}
.file\:font-medium {
&::file-selector-button {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
}
.file\:text-mantle {
&::file-selector-button {
color: var(--mantle);
}
}
.file\:transition {
&::file-selector-button {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
}
.hover\:-translate-y-0\.5 {
&:hover {
@media (hover: hover) {
@@ -1616,6 +1756,27 @@
}
}
}
.file\:hover\:cursor-pointer {
&::file-selector-button {
&:hover {
@media (hover: hover) {
cursor: pointer;
}
}
}
}
.file\:hover\:bg-blue\/80 {
&::file-selector-button {
&:hover {
@media (hover: hover) {
background-color: var(--blue);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--blue) 80%, transparent);
}
}
}
}
}
.focus\:border-blue {
&:focus {
border-color: var(--blue);
@@ -2301,6 +2462,42 @@
syntax: "*";
inherits: false;
}
@property --tw-backdrop-blur {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-brightness {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-contrast {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-grayscale {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-hue-rotate {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-invert {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-opacity {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-saturate {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-sepia {
syntax: "*";
inherits: false;
}
@property --tw-duration {
syntax: "*";
inherits: false;
@@ -2362,6 +2559,15 @@
--tw-drop-shadow-color: initial;
--tw-drop-shadow-alpha: 100%;
--tw-drop-shadow-size: initial;
--tw-backdrop-blur: initial;
--tw-backdrop-brightness: initial;
--tw-backdrop-contrast: initial;
--tw-backdrop-grayscale: initial;
--tw-backdrop-hue-rotate: initial;
--tw-backdrop-invert: initial;
--tw-backdrop-opacity: initial;
--tw-backdrop-saturate: initial;
--tw-backdrop-sepia: initial;
--tw-duration: initial;
--tw-ease: initial;
}

View File

@@ -0,0 +1,84 @@
// localtime.js - Converts UTC <time> elements to the user's local timezone.
//
// Usage: <time datetime="2026-01-14T10:30:00Z" data-localtime="datetime">fallback</time>
//
// Supported data-localtime values:
// "date" → "Mon 2 Jan 2026"
// "time" → "3:04 PM"
// "datetime" → "Mon 2 Jan 2026 at 3:04 PM"
// "short" → "Mon 2 Jan 3:04 PM"
// "histdate" → "2 Jan 2006 15:04"
(function () {
const SHORT_DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const SHORT_MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
function pad(n) { return n < 10 ? '0' + n : '' + n; }
function formatTime12(d) {
let h = d.getHours();
const m = pad(d.getMinutes());
const ampm = h >= 12 ? 'PM' : 'AM';
h = h % 12 || 12;
return h + ':' + m + ' ' + ampm;
}
function formatDate(d) {
return SHORT_DAYS[d.getDay()] + ' ' + d.getDate() + ' ' +
SHORT_MONTHS[d.getMonth()] + ' ' + d.getFullYear();
}
function formatLocalTime(el) {
const iso = el.getAttribute('datetime');
if (!iso) return;
const d = new Date(iso);
if (isNaN(d.getTime())) return;
const fmt = el.getAttribute('data-localtime');
let text;
switch (fmt) {
case 'date':
text = formatDate(d);
break;
case 'time':
text = formatTime12(d);
break;
case 'datetime':
text = formatDate(d) + ' at ' + formatTime12(d);
break;
case 'short':
text = SHORT_DAYS[d.getDay()] + ' ' + d.getDate() + ' ' +
SHORT_MONTHS[d.getMonth()] + ' ' + formatTime12(d);
break;
case 'histdate':
text = d.getDate() + ' ' + SHORT_MONTHS[d.getMonth()] + ' ' +
d.getFullYear() + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
break;
default:
text = formatDate(d) + ' at ' + formatTime12(d);
}
el.textContent = text;
// Add timezone tooltip so users know the displayed time is in their local timezone
var tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
el.title = 'Displayed in your local timezone (' + tz + ')';
}
function processAll(root) {
const els = (root || document).querySelectorAll('time[data-localtime]');
els.forEach(formatLocalTime);
}
// Process on page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { processAll(); });
} else {
processAll();
}
// Re-process after HTMX swaps
document.addEventListener('htmx:afterSettle', function (evt) {
processAll(evt.detail.elt);
});
})();