feat(dailies): a chip responds to the pointer
The strip is the fastest thing on the page to act on and nothing happened under the cursor, so a row of tappable jobs read as a row of status badges. The hover has to be 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 so arrives as an inline style that no rule can override. A ring in the same hue, a one-pixel lift and the label coming up to full ink do the work instead, off `--hue` and `data-done`. Only an outstanding chip brightens its label — on a finished one the ink *is* the hue, and overriding it would trade the one thing saying which game for a hover state. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f336e8d8a4
commit
440144fdc1
@@ -194,8 +194,13 @@ function TickChip({
|
|||||||
aria-pressed={isDone}
|
aria-pressed={isDone}
|
||||||
aria-label={`${ariaLabel}${isDone ? ", done today" : ", not done today"}`}
|
aria-label={`${ariaLabel}${isDone ? ", done today" : ", not done today"}`}
|
||||||
title={title}
|
title={title}
|
||||||
className="flex max-w-[15rem] items-center gap-2 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors"
|
// 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"
|
||||||
style={{
|
style={{
|
||||||
|
["--hue" as string]: hue,
|
||||||
borderColor: isDone ? hue : `color-mix(in srgb, ${hue} 20%, transparent)`,
|
borderColor: isDone ? hue : `color-mix(in srgb, ${hue} 20%, transparent)`,
|
||||||
// Colour identifies the game; done-ness is carried by the tick, the
|
// Colour identifies the game; done-ness is carried by the tick, the
|
||||||
// full-strength border and the wash. Keeping the label readable matters
|
// full-strength border and the wash. Keeping the label readable matters
|
||||||
@@ -217,7 +222,7 @@ function TickChip({
|
|||||||
opacity={isDone ? 1 : 0.25}
|
opacity={isDone ? 1 : 0.25}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span className="truncate">{label}</span>
|
<span className="tick-label truncate">{label}</span>
|
||||||
{streak > 1 && (
|
{streak > 1 && (
|
||||||
<span className="tnum shrink-0 text-[0.625rem] opacity-70">{streak}d</span>
|
<span className="tnum shrink-0 text-[0.625rem] opacity-70">{streak}d</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+52
-2
@@ -295,6 +295,52 @@ body {
|
|||||||
filter 130ms ease-out;
|
filter 130ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Today's dailies ---------------------------------------------------- */
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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 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.
|
||||||
|
*/
|
||||||
|
.tick-chip {
|
||||||
|
transition:
|
||||||
|
border-color 130ms ease-out,
|
||||||
|
background-color 130ms ease-out,
|
||||||
|
color 130ms ease-out,
|
||||||
|
box-shadow 130ms ease-out,
|
||||||
|
transform 120ms cubic-bezier(0.2, 0.7, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tick-label {
|
||||||
|
transition: color 130ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
.tick-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 {
|
||||||
|
color: var(--color-ink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Press feedback on both mouse and touch, as the event row has. */
|
||||||
|
.tick-chip:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
transition-duration: 60ms;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.event-row,
|
.event-row,
|
||||||
.row-rail,
|
.row-rail,
|
||||||
@@ -302,13 +348,17 @@ body {
|
|||||||
.row-count,
|
.row-count,
|
||||||
.row-summary,
|
.row-summary,
|
||||||
.row-check,
|
.row-check,
|
||||||
.meter-tick {
|
.meter-tick,
|
||||||
|
.tick-chip,
|
||||||
|
.tick-label {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
.row-check:hover,
|
.row-check:hover,
|
||||||
.row-check:active,
|
.row-check:active,
|
||||||
.event-row:hover .row-open,
|
.event-row:hover .row-open,
|
||||||
.event-row:hover .row-rail {
|
.event-row:hover .row-rail,
|
||||||
|
.tick-chip:hover,
|
||||||
|
.tick-chip:active {
|
||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user