chore: add React, Tailwind and the client build

Tailwind v4 @theme carries the two colour axes as tokens: a deep blue-black
ground, per-game hues for identity, and a heat ramp for urgency that is
monotonic in salience (dim, blue, amber, red).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-15 00:28:17 +02:00
co-authored by Claude Opus 5
parent 5964f4b2fa
commit 49d52e3587
5 changed files with 328 additions and 1 deletions
+128
View File
@@ -0,0 +1,128 @@
@import "tailwindcss";
/*
* Two orthogonal colour systems.
*
* hue → WHICH GAME (identity; lives in games.ts, applied inline)
* heat → HOW URGENT (state; the ramp below)
*
* Keeping them separate is what lets one glance answer "whose event is this?"
* and "how long have I got?" at the same time. Never colour an urgency element
* with a game hue, or vice versa.
*/
@theme {
/* Ground is deep blue-black, not neutral near-black — the whole surface
should feel like a lit instrument panel rather than a void. */
--color-ground: #12141c;
--color-surface: #1b1e2a;
--color-raised: #232739;
--color-hairline: #2c3145;
--color-ink: #e9ebf3;
--color-muted: #888fa6;
--color-faint: #5a6178;
/* Heat ramp: monotonic in salience, dim → blue → amber → red. */
--color-calm: #55607f;
--color-near: #5b8def;
--color-soon: #e8b33c;
--color-critical: #ff4d6a;
--font-display: "Chakra Petch", ui-sans-serif, system-ui, sans-serif;
--font-body: "Public Sans", ui-sans-serif, system-ui, sans-serif;
}
:root {
color-scheme: dark;
}
html,
body,
#root {
height: 100%;
}
body {
margin: 0;
background: var(--color-ground);
color: var(--color-ink);
font-family: var(--font-body);
-webkit-font-smoothing: antialiased;
/* Faint vertical gradient so the page reads as a panel with a light source
rather than a flat fill. Subtle enough not to fight the data. */
background-image: radial-gradient(
120% 70% at 50% -10%,
#1a1e2c 0%,
var(--color-ground) 60%
);
background-attachment: fixed;
}
/* Countdowns tick. Tabular figures keep them from reflowing every second. */
.tnum {
font-variant-numeric: tabular-nums;
font-feature-settings: "tnum" 1;
}
/* Eyebrow labels: the utility voice of the interface. */
.eyebrow {
font-family: var(--font-display);
font-weight: 600;
font-size: 0.6875rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--color-faint);
}
/* ---- Signature: the segmented depletion meter -------------------------- */
/* Discrete ticks, not a smooth bar — lifted from in-game stamina meters.
Spent ticks recede; remaining ticks carry the urgency colour. */
.meter {
display: flex;
gap: 2px;
height: 10px;
align-items: stretch;
}
.meter-tick {
flex: 1 1 0;
border-radius: 1px;
background: var(--color-hairline);
transition: background-color 240ms ease;
}
.meter-tick[data-live="true"] {
background: var(--tick);
box-shadow: 0 0 6px -1px var(--tick);
}
.meter-tick[data-unknown="true"] {
background: repeating-linear-gradient(
90deg,
var(--color-hairline) 0 2px,
transparent 2px 4px
);
box-shadow: none;
}
@media (prefers-reduced-motion: no-preference) {
.meter-tick {
animation: tick-in 320ms cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}
@keyframes tick-in {
from {
opacity: 0;
transform: scaleY(0.3);
}
}
}
/* Focus ring: visible, and never removed. */
:where(a, button, [tabindex]):focus-visible {
outline: 2px solid var(--color-near);
outline-offset: 2px;
border-radius: 3px;
}
/* Horizontal scrollers keep their gutter but hide the bar chrome. */
.scroll-x {
overflow-x: auto;
scrollbar-width: thin;
scrollbar-color: var(--color-hairline) transparent;
}