Merge branch 'finals' into development

This commit is contained in:
2026-03-15 17:14:29 +11:00
36 changed files with 7274 additions and 117 deletions

View File

@@ -330,6 +330,9 @@
max-width: 96rem;
}
}
.mx-1 {
margin-inline: calc(var(--spacing) * 1);
}
.mx-auto {
margin-inline: auto;
}
@@ -454,6 +457,9 @@
width: calc(var(--spacing) * 5);
height: calc(var(--spacing) * 5);
}
.h-0 {
height: calc(var(--spacing) * 0);
}
.h-1 {
height: calc(var(--spacing) * 1);
}
@@ -475,6 +481,9 @@
.h-9 {
height: calc(var(--spacing) * 9);
}
.h-10 {
height: calc(var(--spacing) * 10);
}
.h-12 {
height: calc(var(--spacing) * 12);
}
@@ -493,6 +502,9 @@
.h-screen {
height: 100vh;
}
.max-h-40 {
max-height: calc(var(--spacing) * 40);
}
.max-h-60 {
max-height: calc(var(--spacing) * 60);
}
@@ -556,9 +568,15 @@
.w-14 {
width: calc(var(--spacing) * 14);
}
.w-16 {
width: calc(var(--spacing) * 16);
}
.w-20 {
width: calc(var(--spacing) * 20);
}
.w-24 {
width: calc(var(--spacing) * 24);
}
.w-26 {
width: calc(var(--spacing) * 26);
}
@@ -631,6 +649,12 @@
.min-w-0 {
min-width: calc(var(--spacing) * 0);
}
.min-w-\[500px\] {
min-width: 500px;
}
.min-w-\[700px\] {
min-width: 700px;
}
.flex-1 {
flex: 1;
}
@@ -674,6 +698,9 @@
--tw-scale-z: 100%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
.rotate-180 {
rotate: 180deg;
}
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
@@ -959,6 +986,10 @@
border-top-style: var(--tw-border-style);
border-top-width: 1px;
}
.border-t-2 {
border-top-style: var(--tw-border-style);
border-top-width: 2px;
}
.border-b {
border-bottom-style: var(--tw-border-style);
border-bottom-width: 1px;
@@ -992,9 +1023,6 @@
.border-overlay0 {
border-color: var(--overlay0);
}
.border-peach {
border-color: var(--peach);
}
.border-peach\/50 {
border-color: var(--peach);
@supports (color: color-mix(in lab, red, red)) {
@@ -1088,6 +1116,12 @@
.bg-green {
background-color: var(--green);
}
.bg-green\/5 {
background-color: var(--green);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--green) 5%, transparent);
}
}
.bg-green\/10 {
background-color: var(--green);
@supports (color: color-mix(in lab, red, red)) {
@@ -1112,9 +1146,6 @@
.bg-mauve {
background-color: var(--mauve);
}
.bg-overlay0 {
background-color: var(--overlay0);
}
.bg-overlay0\/10 {
background-color: var(--overlay0);
@supports (color: color-mix(in lab, red, red)) {
@@ -1588,6 +1619,11 @@
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.transition-transform {
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.duration-150 {
--tw-duration: 150ms;
transition-duration: 150ms;
@@ -1865,6 +1901,16 @@
}
}
}
.hover\:bg-surface0\/50 {
&:hover {
@media (hover: hover) {
background-color: var(--surface0);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--surface0) 50%, transparent);
}
}
}
}
.hover\:bg-surface1 {
&:hover {
@media (hover: hover) {
@@ -2416,6 +2462,16 @@
gap: calc(var(--spacing) * 12);
}
}
.lg\:divide-x {
@media (width >= 64rem) {
:where(& > :not(:last-child)) {
--tw-divide-x-reverse: 0;
border-inline-style: var(--tw-border-style);
border-inline-start-width: calc(1px * var(--tw-divide-x-reverse));
border-inline-end-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
}
}
}
.lg\:px-8 {
@media (width >= 64rem) {
padding-inline: calc(var(--spacing) * 8);

View File

@@ -0,0 +1,152 @@
// bracket-lines.js
// Draws smooth SVG bezier connector lines between series cards in a playoff bracket.
// Lines connect from the bottom-center of a source card to the top-center of a
// destination card. Winner paths are solid green, loser paths are dashed red.
//
// Usage: Add data-bracket-lines to a container element. Inside, series cards
// should have data-series="N" attributes. The container needs a data-connections
// attribute with a JSON array of connection objects:
// [{"from": 1, "to": 3, "type": "winner"}, ...]
//
// Optional toSide field ("left" or "right") makes the line arrive at the
// left or right edge (vertically centered) of the destination card instead
// of the top-center.
//
// An SVG element with data-bracket-svg inside the container is used for drawing.
(function () {
var WINNER_COLOR = "var(--green)";
var LOSER_COLOR = "var(--red)";
var STROKE_WIDTH = 2;
var DASH_ARRAY = "6 3";
// Curvature control: how far the control points extend
// as a fraction of the total distance between cards
var CURVE_FACTOR = 0.4;
function drawBracketLines(container) {
var svg = container.querySelector("[data-bracket-svg]");
if (!svg) return;
var connectionsAttr = container.getAttribute("data-connections");
if (!connectionsAttr) return;
var connections;
try {
connections = JSON.parse(connectionsAttr);
} catch (e) {
return;
}
// Clear existing paths
while (svg.firstChild) {
svg.removeChild(svg.firstChild);
}
// Get container position for relative coordinates
var containerRect = container.getBoundingClientRect();
// Size SVG to match container
svg.setAttribute("width", containerRect.width);
svg.setAttribute("height", containerRect.height);
connections.forEach(function (conn) {
var fromCard = container.querySelector(
'[data-series="' + conn.from + '"]',
);
var toCard = container.querySelector('[data-series="' + conn.to + '"]');
if (!fromCard || !toCard) return;
var fromRect = fromCard.getBoundingClientRect();
var toRect = toCard.getBoundingClientRect();
// Start: bottom-center of source card
var x1 = fromRect.left + fromRect.width / 2 - containerRect.left;
var y1 = fromRect.bottom - containerRect.top;
var x2, y2, d;
if (conn.toSide === "left") {
// End: left edge, vertically centered
x2 = toRect.left - containerRect.left;
y2 = toRect.top + toRect.height / 2 - containerRect.top;
// Bezier: go down first, then curve into the left side
var dy = y2 - y1;
var dx = x2 - x1;
d =
"M " + x1 + " " + y1 +
" C " + x1 + " " + (y1 + dy * 0.5) +
", " + (x2 + dx * 0.2) + " " + y2 +
", " + x2 + " " + y2;
} else if (conn.toSide === "right") {
// End: right edge, vertically centered
x2 = toRect.right - containerRect.left;
y2 = toRect.top + toRect.height / 2 - containerRect.top;
// Bezier: go down first, then curve into the right side
var dy = y2 - y1;
var dx = x2 - x1;
d =
"M " + x1 + " " + y1 +
" C " + x1 + " " + (y1 + dy * 0.5) +
", " + (x2 + dx * 0.2) + " " + y2 +
", " + x2 + " " + y2;
} else {
// Default: end at top-center of destination card
x2 = toRect.left + toRect.width / 2 - containerRect.left;
y2 = toRect.top - containerRect.top;
var dy = y2 - y1;
d =
"M " + x1 + " " + y1 +
" C " + x1 + " " + (y1 + dy * CURVE_FACTOR) +
", " + x2 + " " + (y2 - dy * CURVE_FACTOR) +
", " + x2 + " " + y2;
}
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", d);
path.setAttribute("fill", "none");
path.setAttribute("stroke-width", STROKE_WIDTH);
if (conn.type === "winner") {
path.setAttribute("stroke", WINNER_COLOR);
} else {
path.setAttribute("stroke", LOSER_COLOR);
path.setAttribute("stroke-dasharray", DASH_ARRAY);
}
svg.appendChild(path);
});
}
function drawAllBrackets() {
var containers = document.querySelectorAll("[data-bracket-lines]");
containers.forEach(drawBracketLines);
}
// Draw on initial load
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", drawAllBrackets);
} else {
// DOM already loaded (e.g. script loaded via HTMX swap)
drawAllBrackets();
}
// Redraw on window resize (debounced)
var resizeTimer;
window.addEventListener("resize", function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(drawAllBrackets, 100);
});
// Redraw after HTMX swaps
document.addEventListener("htmx:afterSwap", function () {
// Small delay to let the DOM settle
setTimeout(drawAllBrackets, 50);
});
// Expose for manual triggering if needed
window.drawBracketLines = drawAllBrackets;
})();