From e0ba53f36906a6bb0ccfcf0347762bb080915785 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Sat, 15 Aug 2026 00:39:42 +0200 Subject: [PATCH] feat: show an excerpt and make rows respond to hover and press MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The whole row is now one click target, which gives a generous tap area and a single unambiguous hover region. Hover transitions are fast and eased out (110-160ms) so a 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 full framerate. The meter is the element that visibly wakes up, since it is the signature. Press feedback lands on touch as well as mouse, and every hover rule sits behind `hover: hover` so a tap never leaves a row stuck looking hovered. Co-Authored-By: Claude Opus 5 (1M context) --- src/client/components/EventRow.tsx | 147 ++++++++++++++++------------- src/client/styles.css | 104 ++++++++++++++++++++ 2 files changed, 185 insertions(+), 66 deletions(-) diff --git a/src/client/components/EventRow.tsx b/src/client/components/EventRow.tsx index 0d98186..d5115f8 100644 --- a/src/client/components/EventRow.tsx +++ b/src/client/components/EventRow.tsx @@ -28,78 +28,93 @@ export function EventRow({ row, completed, onToggle, onOpen }: EventRowProps) { return (
  • - {/* Game identity: a hue stripe, never an urgency colour. */} - - -
    -
    - - - - {countdown} - -
    - -
    - -
    -
    - + {/* The whole row opens the event. A single full-bleed target gives a + generous tap area on mobile and one unambiguous hover region — the + content above it is pointer-transparent so clicks fall through. */} + +
    + {/* Game identity rail. Grows on hover — the cheapest possible signal + that this row is live under the cursor. */} + + +
    +
    +
    + + {game.short} + + + {event.title} + +
    + + + {countdown} + +
    + + {event.summary !== null && ( +

    + {event.summary} +

    + )} + +
    + +
    +
    + + {/* Sits above the row target so ticking done never opens the sheet. */} + +
  • ); } diff --git a/src/client/styles.css b/src/client/styles.css index 5caa3f3..e489056 100644 --- a/src/client/styles.css +++ b/src/client/styles.css @@ -113,6 +113,110 @@ body { } } +/* ---- 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); + } + .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-rail { + transform: none; + } +} + /* Focus ring: visible, and never removed. */ :where(a, button, [tabindex]):focus-visible { outline: 2px solid var(--color-near);