Files
gacha-event-tracker/src/client/styles.css
T
Lucas WintherandClaude Opus 5 b1df7b2581 refactor(ui): make an event row a single open target
The row already opened the event; the tick sat on top of it, so the two
smallest targets on the page did different things a few pixels apart.
"Done" was never the only thing a reader wants to say about an event
either — status, effort and notes all live in the detail sheet.

The tick becomes a chevron: decorative, aria-hidden, and leaning towards
where the row is about to take you on hover. The full-bleed button is
the only control, so there is no second stop for anyone tabbing or using
a screen reader. Undo on a revealed ignored row stays a real button —
it is a real action with nowhere better to sit.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 21:16:25 +02:00

241 lines
5.9 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@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 (110160ms) 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;
}