From d3066aefdc6044acd08debae5fc29af83147df73 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Wed, 19 Aug 2026 06:13:48 +0200 Subject: [PATCH] feat(focus): the game chips answer the pointer, as the dailies do MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They are the same object twice — a pill carrying a game's colour, pressed or not, that you tap — so they get the same hover rather than a second one: a ring in the game's hue, a one-pixel lift, and the label coming up to full ink while the chip is not the pressed one. Sharing it meant `.tick-chip` becoming `.hue-chip`, and dropping the `data-done` attribute it keyed off. Both chips were already saying the same thing in `aria-pressed`, and a second attribute repeating it is one more thing to keep in step — a rule that reads the announced state cannot disagree with it. The focus strip needed a pixel of headroom for that lift. `scroll-x` sets `overflow-x: auto`, which computes the vertical axis to `auto` as well, so a chip that rises loses that pixel and its ring to the scroller's edge; `pt-1` is the room, and `mt` gives the same total gap back. Co-Authored-By: Claude Opus 5 (1M context) --- src/client/components/Dailies.tsx | 12 ++++---- src/client/components/GameFocus.tsx | 15 ++++++++-- src/client/styles.css | 45 +++++++++++++++-------------- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/client/components/Dailies.tsx b/src/client/components/Dailies.tsx index 97cf91d..828b93f 100644 --- a/src/client/components/Dailies.tsx +++ b/src/client/components/Dailies.tsx @@ -194,11 +194,11 @@ function TickChip({ aria-pressed={isDone} aria-label={`${ariaLabel}${isDone ? ", done today" : ", not done today"}`} title={title} - // Done-ness for the stylesheet: the chip's own colours are the game's - // hue and arrive inline, which no rule can override, so the hover has to - // be drawn from what inline does not own — see `.tick-chip`. - data-done={isDone} - className="tick-chip flex max-w-[15rem] items-center gap-2 rounded-full border px-3 py-1.5 text-xs font-medium" + // `.hue-chip` is the shared hover: the chip's own colours are the game's + // hue and arrive inline, which no rule can override, so it is drawn from + // what inline does not own. `--hue` below is the only thing it needs; + // pressed-ness it reads off `aria-pressed`, which is already up there. + className="hue-chip flex max-w-[15rem] items-center gap-2 rounded-full border px-3 py-1.5 text-xs font-medium" style={{ ["--hue" as string]: hue, borderColor: isDone ? hue : `color-mix(in srgb, ${hue} 20%, transparent)`, @@ -222,7 +222,7 @@ function TickChip({ opacity={isDone ? 1 : 0.25} /> - {label} + {label} {streak > 1 && ( {streak}d )} diff --git a/src/client/components/GameFocus.tsx b/src/client/components/GameFocus.tsx index 45484e8..b7cb064 100644 --- a/src/client/components/GameFocus.tsx +++ b/src/client/components/GameFocus.tsx @@ -61,7 +61,12 @@ export function GameFocus({
- {label} + {label} {count} ); diff --git a/src/client/styles.css b/src/client/styles.css index 0187d3f..07220eb 100644 --- a/src/client/styles.css +++ b/src/client/styles.css @@ -295,24 +295,25 @@ body { filter 130ms ease-out; } -/* ---- Today's dailies ---------------------------------------------------- */ +/* ---- Chips coloured by a game hue --------------------------------------- */ /* - * A daily chip is the fastest thing on the page to act on, and it was saying - * none of that: the pointer stayed an arrow and nothing happened under it, so a - * strip of tappable jobs read as a row of status badges. + * Today's dailies and the focus bar are the same object twice: a pill carrying + * a game's colour, pressed or not, that you tap. Both were saying none of that + * — the pointer stayed an arrow and nothing happened under it, so two strips of + * controls read as rows of status badges. * - * The hover is drawn from what the component does *not* own. A chip's border, - * ink and wash are mixed from the game's hue, which is data rather than a token - * and therefore arrives as an inline style — and no rule here can override one - * of those. So the cursor, a ring in the same hue, a one-pixel lift and the - * label coming up to full ink carry it instead. `--hue` and `data-done` are the - * two things the chip hands over for that. + * The hover has to be drawn from what the components do *not* own. A chip's + * border, ink and wash are mixed from the game's hue, which is data rather than + * a token and therefore arrives as an inline style — and no rule here can + * override one of those. So a ring in the same hue, a one-pixel lift and the + * label coming up to full ink carry it instead. `--hue` is the one thing a chip + * has to hand over; `aria-pressed` it was already saying out loud. * - * The label only brightens while the job is outstanding: on a finished chip the - * ink *is* the game's hue, and overriding it with `ink` would trade the one - * thing that says which game for a hover state. + * The label only brightens on a chip that is *not* pressed. On a pressed one + * the ink already *is* the game's hue, and overriding it with `ink` would trade + * the one thing saying which game for a hover state. */ -.tick-chip { +.hue-chip { transition: border-color 130ms ease-out, background-color 130ms ease-out, @@ -321,22 +322,22 @@ body { transform 120ms cubic-bezier(0.2, 0.7, 0.3, 1); } -.tick-label { +.hue-chip-label { transition: color 130ms ease-out; } @media (hover: hover) { - .tick-chip:hover { + .hue-chip:hover { transform: translateY(-1px); box-shadow: 0 0 0 1px color-mix(in srgb, var(--hue) 45%, transparent); } - .tick-chip[data-done="false"]:hover .tick-label { + .hue-chip[aria-pressed="false"]:hover .hue-chip-label { color: var(--color-ink); } } /* Press feedback on both mouse and touch, as the event row has. */ -.tick-chip:active { +.hue-chip:active { transform: scale(0.96); transition-duration: 60ms; } @@ -349,16 +350,16 @@ body { .row-summary, .row-check, .meter-tick, - .tick-chip, - .tick-label { + .hue-chip, + .hue-chip-label { transition: none; } .row-check:hover, .row-check:active, .event-row:hover .row-open, .event-row:hover .row-rail, - .tick-chip:hover, - .tick-chip:active { + .hue-chip:hover, + .hue-chip:active { transform: none; } }