From 1b3d68d9f730051d66ccb93700fb8b1daf92b230 Mon Sep 17 00:00:00 2001 From: librelad Date: Wed, 19 Aug 2026 21:28:30 +0100 Subject: [PATCH] Highlight the hovered app row on the Updates tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The row head is the click target that expands a row and already set cursor:pointer, but nothing changed under the pointer — so a clickable row looked identical to a static one right up until it opened, and the only visual state was post-click. The tint is painted by the row rather than the head. .ov-row's `padding: 0` loses to .updater-row's `padding: 12px 15px` on equal specificity, so the head is only 34px tall inside a 60px row; filling the head left a floating band with 13px of untinted row above and below it. Painting the row covers the full height and picks up its 11px radius for free. Keyed on `:has(> .ov-row-head:hover)` rather than `.ov-row:hover` so the highlight tracks what is actually clickable: on an expanded row, drifting into the detail body stops indicating a click target, because there isn't one there. The head-level rule stays as the fallback for engines without :has() — it still marks the row, just inset — and the pair is mutually exclusive so the two tints never stack. :has() is already relied on elsewhere in the WebUI CSS. Verified against the running WebUI by driving Chrome over CDP and dispatching a real mouseMoved, since lp-shot cannot hover: the hovered row goes from rgba(255,255,255,.035) to .086, its neighbour stays at .035, and the head fill reads transparent so nothing double-tints. Confirmed visually in the capture too. ov-row-head is emitted only by renderUpdates(), so no other tab changes. Co-Authored-By: Claude Opus 5 --- .../components/apps/overview/css/overview.css | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/containers/libreportal/frontend/components/apps/overview/css/overview.css b/containers/libreportal/frontend/components/apps/overview/css/overview.css index 21bdf97..0dca797 100644 --- a/containers/libreportal/frontend/components/apps/overview/css/overview.css +++ b/containers/libreportal/frontend/components/apps/overview/css/overview.css @@ -213,6 +213,28 @@ .ov-row-head .updater-row-ver { margin-left: auto; } .ov-row-head .ov-row-actions { margin-left: 12px; display: flex; align-items: center; gap: 8px; } .ov-row-head:focus-visible { outline: 2px solid rgba(var(--page-updater-rgb), .7); outline-offset: -2px; } +/* Hover feedback. The head is the click target for expanding a row and already + sets cursor:pointer, but nothing changed under the pointer, so a clickable row + looked identical to a static one right up until it opened. + The tint is painted by the ROW, not the head. .ov-row's `padding: 0` loses to + .updater-row's `padding: 12px 15px` on equal specificity, so the head is only + 34px tall inside a 60px row — filling the head left a floating band with 13px + of untinted row above and below it. Painting the row covers the full height and + picks up its 11px radius for free. + Keyed on the head rather than `.ov-row:hover` so the highlight tracks the thing + that is actually clickable: on an expanded row, drifting into the detail body + stops indicating a click target, because there isn't one there. + The head rule is the fallback for engines without :has() — it still marks the + row, just inset. The pair is mutually exclusive so the two tints never stack. */ +.ov-row, .ov-row-head { transition: background-color .15s ease; } +.ov-row-head { border-radius: 10px; } +.ov-row-head[aria-expanded="true"] { border-radius: 10px 10px 0 0; } +.ov-row-head:hover { background: rgba(var(--text-rgb), .06); } +.ov-row:has(> .ov-row-head:hover) { background: rgba(var(--text-rgb), .085); } +.ov-row:has(> .ov-row-head:hover) > .ov-row-head { background: transparent; } +@media (prefers-reduced-motion: reduce) { + .ov-row, .ov-row-head { transition: none; } +} /* Tasks-style app row: icon tile + status pill + Details toggle -------------- */ .ov-row-icon {