@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); } } } /* ---- Event rows -------------------------------------------------------- */ /* * Hover should feel instant. Everything here is fast (110–160ms) and eased out, * so the row responds on the way in and settles on the way out — a slow * symmetric fade is what makes an interface feel laggy even at 60fps. * * All hover states are behind `hover: hover` so a tap on a touch device does * not leave a row stuck in its hover appearance. */ .event-row { --row-bg: transparent; background: var(--row-bg); transition: background-color 130ms ease-out; } .event-row.is-complete { opacity: 0.4; } .row-rail { background: var(--hue); opacity: 0.55; transform: scaleY(0.82); transform-origin: center; transition: transform 160ms cubic-bezier(0.2, 0.7, 0.3, 1), opacity 130ms ease-out, box-shadow 160ms ease-out; } .row-title, .row-count, .row-summary { transition: color 130ms ease-out; } .row-check { transition: border-color 130ms ease-out, color 130ms ease-out, transform 120ms cubic-bezier(0.2, 0.7, 0.3, 1); } @media (hover: hover) { .event-row:hover { --row-bg: color-mix(in srgb, var(--color-raised) 62%, transparent); } .event-row:hover .row-rail { opacity: 1; transform: scaleY(1); box-shadow: 0 0 10px -2px var(--hue); } /* The meter is the signature element, so it is the thing that visibly wakes up: live ticks gain a little light under the cursor. */ .event-row:hover .meter-tick[data-live="true"] { filter: brightness(1.28) saturate(1.08); } .event-row:hover .row-summary { color: var(--color-ink); } .event-row:hover .row-check { border-color: var(--color-faint); color: var(--color-muted); } /* The chevron leans towards where the row is about to take you. It is the only thing left on the right of a row, so it has to carry "this opens" on its own. */ .event-row:hover .row-open { transform: translateX(2px); color: var(--color-ink); } .row-check:hover { transform: scale(1.08); border-color: var(--color-muted); color: var(--color-ink); } } /* Press feedback lands on both mouse and touch — the tactile half of "responsive". */ .event-row:active { --row-bg: color-mix(in srgb, var(--color-raised) 82%, transparent); transition-duration: 40ms; } .row-check:active { transform: scale(0.9); transition-duration: 60ms; } .meter-tick { transition: background-color 240ms ease, filter 130ms ease-out; } @media (prefers-reduced-motion: reduce) { .event-row, .row-rail, .row-title, .row-count, .row-summary, .row-check, .meter-tick { transition: none; } .row-check:hover, .row-check:active, .event-row:hover .row-open, .event-row:hover .row-rail { transform: none; } } /* 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; }