aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-16 10:10:24 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-16 10:10:24 +0200
commit88f4ade0ee1e1a2b6011fd39320ede34d493929f (patch)
tree5c7f724871a93cf55e1141d1c37976305e25ce41 /packages/meshbay-hub/src/meshbay_hub/static
parentb52720497594f9c30e7d1c89d83ed997614676a2 (diff)
downloadmeshbay-88f4ade0ee1e1a2b6011fd39320ede34d493929f.tar.gz
fix(hub): scope every :hover rule to devices that can hover
A tap left buttons in :hover on touchscreens (pager arrows, nav, player...). All rules now sit in @media (hover: hover); test_hover_scoped.py holds it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css271
1 files changed, 165 insertions, 106 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index 627f2ca..de4f2ae 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -101,6 +101,14 @@
foreign highlight. Unrelated to real hover/focus — safe to kill outright. */
*, *::before, *::after { -webkit-tap-highlight-color: transparent; }
+/* Every :hover rule in this file sits inside @media (hover: hover). A tap on a
+ touchscreen leaves the element in :hover until something else is tapped,
+ since no pointer ever moves away, so a tapped button kept its hover colour
+ (the music player's next/prev, the pager arrows, the nav hamburger: found
+ live on Android Chrome and in the app, 2026-08-26 and again 2026-10-16).
+ Fixing it button by button only moved the bug to the next one; scoping
+ them all is what test_hover_scoped.py holds. Real hover is unaffected. */
+
body {
font-family: system-ui, -apple-system, sans-serif;
background: var(--bg-base);
@@ -110,7 +118,7 @@ body {
}
a { color: var(--accent); text-decoration: none; }
-a:hover { text-decoration: underline; }
+@media (hover: hover) { a:hover { text-decoration: underline; } }
/* ── Cross-group search page ──────────────────────────────────────────────── */
@@ -141,7 +149,7 @@ a:hover { text-decoration: underline; }
background: none; border: none; padding: 2px; cursor: pointer;
color: var(--text-dim); display: flex; align-items: center;
}
-.search-bar-clear:hover { color: var(--text); }
+@media (hover: hover) { .search-bar-clear:hover { color: var(--text); } }
.search-bar-clear .icon { width: 14px; height: 14px; }
.search-progress {
@@ -182,7 +190,7 @@ a:hover { text-decoration: underline; }
display: flex; align-items: center; gap: 10px;
padding: 8px 10px; border-radius: 6px; cursor: pointer;
}
-.search-media-row:hover { background: var(--bg-raised); }
+@media (hover: hover) { .search-media-row:hover { background: var(--bg-raised); } }
.search-media-icon {
flex-shrink: 0; width: 32px; height: 32px;
display: flex; align-items: center; justify-content: center;
@@ -244,7 +252,7 @@ a:hover { text-decoration: underline; }
font-size: 1.375em;
text-decoration: none;
}
-.nav-brand:hover { text-decoration: none; }
+@media (hover: hover) { .nav-brand:hover { text-decoration: none; } }
/* The M is a picture standing in for a letter, so it is sized in `em` and
follows the wordmark rather than being pinned to a pixel height.
@@ -304,7 +312,7 @@ a:hover { text-decoration: underline; }
font-size: 0.85em;
transition: background 0.12s;
}
-.user-menu-trigger:hover { background: rgba(255, 255, 255, 0.15); }
+@media (hover: hover) { .user-menu-trigger:hover { background: rgba(255, 255, 255, 0.15); } }
.user-menu-avatar {
width: 28px; height: 28px;
@@ -368,7 +376,7 @@ a:hover { text-decoration: underline; }
color: var(--text); font-size: 0.85em;
cursor: pointer; text-align: left;
}
-.user-menu-item:hover { background: var(--bg-raised); }
+@media (hover: hover) { .user-menu-item:hover { background: var(--bg-raised); } }
.user-menu-logout { color: var(--error); }
.umi-icon { width: 17px; height: 17px; flex-shrink: 0; color: var(--text-secondary); }
@@ -387,7 +395,9 @@ a:hover { text-decoration: underline; }
cursor: pointer;
text-decoration: none;
}
-.nav-btn:hover { background: rgba(255, 255, 255, 0.18); text-decoration: none; }
+@media (hover: hover) {
+ .nav-btn:hover { background: rgba(255, 255, 255, 0.18); text-decoration: none; }
+}
/* ── Layout ───────────────────────────────────────────────────────────────── */
@@ -426,7 +436,7 @@ a:hover { text-decoration: underline; }
color: var(--text-dim);
font-size: 0.75em;
}
-.sidebar-legal:hover { color: var(--accent); }
+@media (hover: hover) { .sidebar-legal:hover { color: var(--accent); } }
.register-legal {
margin: 12px 0 0;
@@ -477,7 +487,9 @@ a:hover { text-decoration: underline; }
color: var(--text-dim);
}
.sidebar-item.active .sidebar-owner { color: inherit; opacity: 0.75; }
-.sidebar-item:hover { background: var(--sidebar-hover); text-decoration: none; }
+@media (hover: hover) {
+ .sidebar-item:hover { background: var(--sidebar-hover); text-decoration: none; }
+}
.sidebar-item.active { background: var(--accent); color: var(--accent-text); }
.sidebar-empty {
@@ -936,9 +948,9 @@ a:hover { text-decoration: underline; }
font-size: 0.95em;
font-weight: 600;
}
-.welcome-cta:hover { background: #15803d; text-decoration: none; }
+@media (hover: hover) { .welcome-cta:hover { background: #15803d; text-decoration: none; } }
.welcome-legal { color: var(--text-secondary); font-size: 0.9em; }
-.welcome-legal:hover { color: var(--accent); }
+@media (hover: hover) { .welcome-legal:hover { color: var(--accent); } }
@media (max-width: 1000px) {
.welcome { flex-direction: column; align-items: center; gap: 36px; }
@@ -995,7 +1007,7 @@ input:focus { outline: none; border-color: var(--border-focus); }
font-size: 1.15em;
cursor: pointer;
}
-.pw-toggle:hover { background: none; color: var(--text); }
+@media (hover: hover) { .pw-toggle:hover { background: none; color: var(--text); } }
button {
padding: 10px 20px;
@@ -1007,7 +1019,7 @@ button {
cursor: pointer;
transition: background 0.15s;
}
-button:hover { background: var(--accent-hover); }
+@media (hover: hover) { button:hover { background: var(--accent-hover); } }
button:disabled { opacity: 0.5; cursor: not-allowed; }
/* ── Alerts ───────────────────────────────────────────────────────────────── */
@@ -1066,10 +1078,12 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
color: var(--text);
display: block;
}
-.group-card:hover {
- border-color: var(--accent);
- box-shadow: var(--shadow-lg);
- text-decoration: none;
+@media (hover: hover) {
+ .group-card:hover {
+ border-color: var(--accent);
+ box-shadow: var(--shadow-lg);
+ text-decoration: none;
+ }
}
.group-card h3 { font-size: 1em; margin-bottom: 4px; }
@@ -1123,7 +1137,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
align-items: center;
justify-content: center;
}
-.group-tab:hover { color: var(--text); background: none; }
+@media (hover: hover) { .group-tab:hover { color: var(--text); background: none; } }
.group-tab.active {
color: var(--accent);
border-bottom-color: var(--accent);
@@ -1141,7 +1155,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
align-items: center;
margin-left: auto;
}
-.group-mute-btn:hover { color: var(--text); }
+@media (hover: hover) { .group-mute-btn:hover { color: var(--text); } }
.group-mute-btn .icon { width: 20px; height: 20px; }
/* ── Chat panel ──────────────────────────────────────────────────────────── */
@@ -1236,7 +1250,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
text-decoration: none;
overflow: hidden;
}
-.chat-link-preview:hover { background: var(--bg-surface); }
+@media (hover: hover) { .chat-link-preview:hover { background: var(--bg-surface); } }
.clp-img {
width: 72px;
height: 72px;
@@ -1356,9 +1370,11 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
cursor: pointer;
transition: border-color 0.12s, color 0.12s, background 0.12s;
}
-.tb-btn:hover { border-color: var(--accent); color: var(--accent); }
+@media (hover: hover) { .tb-btn:hover { border-color: var(--accent); color: var(--accent); } }
.tb-btn:disabled { opacity: 0.45; cursor: not-allowed; }
-.tb-btn:disabled:hover { border-color: var(--border); color: var(--text); }
+@media (hover: hover) {
+ .tb-btn:disabled:hover { border-color: var(--border); color: var(--text); }
+}
.tb-btn .icon { width: 15px; height: 15px; }
/* Square, for a toolbar button whose name is in its tooltip rather than
beside it. The horizontal padding goes; the height does not, so it lines up
@@ -1381,7 +1397,9 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
border-color: var(--accent);
color: var(--accent-text);
}
-.tb-btn.primary:hover { background: var(--accent-hover); color: var(--accent-text); }
+@media (hover: hover) {
+ .tb-btn.primary:hover { background: var(--accent-hover); color: var(--accent-text); }
+}
.tb-btn.active {
background: color-mix(in srgb, var(--accent) 12%, var(--bg-surface));
border-color: var(--accent);
@@ -1434,7 +1452,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
padding: 2px 4px;
border-radius: 4px;
}
-.crumb:hover { background: var(--bg-raised); text-decoration: none; }
+@media (hover: hover) { .crumb:hover { background: var(--bg-raised); text-decoration: none; } }
.crumb-sep { color: var(--text-dim); }
.crumb-refresh {
background: none;
@@ -1442,7 +1460,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
font: inherit;
color: var(--text-secondary);
}
-.crumb-refresh:hover:not(:disabled) { color: var(--text); }
+@media (hover: hover) { .crumb-refresh:hover:not(:disabled) { color: var(--text); } }
.crumb-refresh:disabled { cursor: default; }
.crumb-refresh.spinning .icon { animation: spin 0.8s linear infinite; }
@@ -1477,7 +1495,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
}
.file-table th.sortable { cursor: pointer; }
-.file-table th.sortable:hover { color: var(--text); }
+@media (hover: hover) { .file-table th.sortable:hover { color: var(--text); } }
.file-row td {
padding: 8px 12px;
@@ -1485,7 +1503,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
vertical-align: middle;
}
-.file-row:hover { background: var(--bg-raised); }
+@media (hover: hover) { .file-row:hover { background: var(--bg-raised); } }
.dir-row { cursor: pointer; }
.dir-row td { font-weight: 500; }
@@ -1522,7 +1540,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
color: var(--text-dim);
cursor: pointer;
}
-.file-loc:hover { color: var(--accent); text-decoration: underline; }
+@media (hover: hover) { .file-loc:hover { color: var(--accent); text-decoration: underline; } }
.file-size { white-space: nowrap; color: var(--text-secondary); }
.file-type { color: var(--text-dim); }
.file-date { white-space: nowrap; color: var(--text-dim); }
@@ -1573,7 +1591,9 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
cursor: pointer;
line-height: 1;
}
-.file-menu-btn:hover { background: var(--bg-raised); border-color: var(--accent); color: var(--accent); }
+@media (hover: hover) {
+ .file-menu-btn:hover { background: var(--bg-raised); border-color: var(--accent); color: var(--accent); }
+}
.file-menu {
position: absolute;
@@ -1600,7 +1620,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
cursor: pointer;
border-radius: 0;
}
-.file-menu button:hover { background: var(--bg-raised); }
+@media (hover: hover) { .file-menu button:hover { background: var(--bg-raised); } }
/* ── File link (clickable name) ─────────────────────────────────────────── */
@@ -1609,7 +1629,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
cursor: pointer;
text-decoration: none;
}
-.file-link:hover { text-decoration: underline; }
+@media (hover: hover) { .file-link:hover { text-decoration: underline; } }
/* ── File preview overlay ───────────────────────────────────────────────── */
@@ -1665,7 +1685,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
flex-shrink: 0;
color: var(--text-secondary);
}
-.chat-attach:hover { background: var(--bg-raised); color: var(--accent); }
+@media (hover: hover) { .chat-attach:hover { background: var(--bg-raised); color: var(--accent); } }
.chat-attachment {
display: flex;
@@ -1692,7 +1712,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
.file-menu button { display: flex; align-items: center; gap: 8px; }
.file-menu button.danger { color: var(--error); }
-.file-menu button.danger:hover { background: var(--error-bg); }
+@media (hover: hover) { .file-menu button.danger:hover { background: var(--error-bg); } }
.fmi { font-size: 0.9em; width: 16px; text-align: center; flex-shrink: 0; }
/* ── Download button + progress ──────────────────────────────────────────── */
@@ -1711,7 +1731,9 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
justify-content: center;
cursor: pointer;
}
-.dl-btn:hover { background: var(--bg-raised); border-color: var(--accent); }
+@media (hover: hover) {
+ .dl-btn:hover { background: var(--bg-raised); border-color: var(--accent); }
+}
.dl-btn:disabled { opacity: 0.3; cursor: not-allowed; }
@@ -1858,7 +1880,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
background: var(--accent); color: var(--accent-text);
font-family: inherit; font-size: 0.83em; cursor: pointer;
}
-.app-save:hover:not(:disabled) { background: var(--accent-hover); }
+@media (hover: hover) { .app-save:hover:not(:disabled) { background: var(--accent-hover); } }
/* No transition on the enabled/disabled swap. It animates a colour change that
coincides with a text-colour change, which reads as a flicker — and it made
the state genuinely hard to observe: a measurement taken during it reports a
@@ -2044,10 +2066,10 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
justify-content: center;
flex-shrink: 0;
}
-.video-close:hover { background: rgba(255, 255, 255, 0.25); }
+@media (hover: hover) { .video-close:hover { background: rgba(255, 255, 255, 0.25); } }
.video-close.dl-active { background: rgba(34, 197, 94, 0.25); pointer-events: none; }
.video-close.cast-active { background: rgba(59, 130, 246, 0.35); }
-.video-close.cast-active:hover { background: rgba(59, 130, 246, 0.5); }
+@media (hover: hover) { .video-close.cast-active:hover { background: rgba(59, 130, 246, 0.5); } }
.cast-url-input {
background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 4px; color: #e2e8f0; font-size: 0.75em; font-family: inherit;
@@ -2058,9 +2080,11 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
.cast-wrapper { display: inline-block; position: relative; }
.video-close:disabled { opacity: 0.4; cursor: default; }
.video-close.cast-btn.casting { background: rgba(59, 130, 246, 0.35); }
-.video-close.cast-btn.casting:hover { background: rgba(59, 130, 246, 0.5); }
+@media (hover: hover) {
+ .video-close.cast-btn.casting:hover { background: rgba(59, 130, 246, 0.5); }
+}
.cast-picker-item.cast-menu-current { color: #94a3b8; cursor: default; }
-.cast-picker-item.cast-menu-current:hover { background: none; }
+@media (hover: hover) { .cast-picker-item.cast-menu-current:hover { background: none; } }
/* The cast button in a page's toolbar (cast-session.js): the same panel as
any other menu there, hung under the button rather than at a point. */
@@ -2069,7 +2093,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
/* From the music bar, at the bottom of the window: the menu opens upward. */
.cast-menu-up { top: auto !important; bottom: calc(100% + 4px); }
.ctx-menu-item.cast-menu-current { color: var(--text-dim); cursor: default; }
-.ctx-menu-item.cast-menu-current:hover { background: none; }
+@media (hover: hover) { .ctx-menu-item.cast-menu-current:hover { background: none; } }
.ctx-menu-item svg { width: 16px; height: 16px; flex-shrink: 0; }
.photo-album-actions { display: flex; align-items: center; gap: 4px; }
.cast-picker {
@@ -2084,12 +2108,12 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
color: #e2e8f0; font-size: 0.82em; cursor: pointer; text-align: left;
font-family: inherit;
}
-.cast-picker-item:hover { background: rgba(255, 255, 255, 0.09); }
+@media (hover: hover) { .cast-picker-item:hover { background: rgba(255, 255, 255, 0.09); } }
.cast-picker-item svg { width: 16px; height: 16px; flex-shrink: 0; }
.cast-picker-scanning { color: #94a3b8; cursor: default; }
-.cast-picker-scanning:hover { background: none; }
+@media (hover: hover) { .cast-picker-scanning:hover { background: none; } }
.cast-picker-empty { color: #64748b; cursor: default; font-style: italic; }
-.cast-picker-empty:hover { background: none; }
+@media (hover: hover) { .cast-picker-empty:hover { background: none; } }
.cast-picker-sep { height: 1px; margin: 4px 10px; background: rgba(255,255,255,0.1); }
.cast-status-label {
color: #94a3b8; font-size: 0.78em; display: flex; align-items: center;
@@ -2270,7 +2294,9 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
color: #e2e8f0;
font-size: 44px;
}
-.video-remote-skip:hover:not(:disabled) { background: rgba(148, 163, 184, 0.12); }
+@media (hover: hover) {
+ .video-remote-skip:hover:not(:disabled) { background: rgba(148, 163, 184, 0.12); }
+}
.video-remote-skip .icon { stroke-width: 1.4; }
.video-remote-skip-n {
position: absolute;
@@ -2292,7 +2318,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
font-size: 34px;
box-shadow: 0 8px 24px rgba(35, 177, 240, 0.35);
}
-.video-remote-main:hover:not(:disabled) { background: #4cc3f5; }
+@media (hover: hover) { .video-remote-main:hover:not(:disabled) { background: #4cc3f5; } }
.video-remote-main .icon { stroke-width: 2.6; }
/* The triangle is a closed path: filled, it reads as play at a glance. */
.video-remote-main.is-paused .icon path { fill: currentColor; }
@@ -2311,7 +2337,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
font-size: 0.9rem;
cursor: pointer;
}
-.video-remote-stop:hover { border-color: #fca5a5; color: #fecaca; }
+@media (hover: hover) { .video-remote-stop:hover { border-color: #fca5a5; color: #fecaca; } }
/* Where the film was picked up again. Sits over the picture rather than
pushing it down, and clears itself once the viewer is watching. */
@@ -2407,7 +2433,9 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
cursor: pointer;
margin-right: 4px;
}
-.play-btn:hover { background: var(--bg-raised); border-color: var(--success); }
+@media (hover: hover) {
+ .play-btn:hover { background: var(--bg-raised); border-color: var(--success); }
+}
.play-btn:disabled { opacity: 0.3; cursor: not-allowed; }
/* ── Admin panel ─────────────────────────────────────────────────────────── */
@@ -2431,7 +2459,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
margin-bottom: -2px;
transition: color 0.15s, border-color 0.15s;
}
-.admin-tab:hover { color: var(--text); }
+@media (hover: hover) { .admin-tab:hover { color: var(--text); } }
.admin-tab.active {
color: var(--accent);
border-bottom-color: var(--accent);
@@ -2532,7 +2560,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
word-break: break-word;
min-width: 0;
}
-.admin-table tr:hover { background: var(--bg-raised); }
+@media (hover: hover) { .admin-table tr:hover { background: var(--bg-raised); } }
.admin-table .admin-empty {
text-align: center;
padding: 24px;
@@ -2642,9 +2670,9 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
cursor: pointer;
white-space: nowrap;
}
-.admin-btn:hover { border-color: var(--accent); color: var(--accent); }
+@media (hover: hover) { .admin-btn:hover { border-color: var(--accent); color: var(--accent); } }
.admin-btn.danger { color: var(--error); }
-.admin-btn.danger:hover { border-color: var(--error); }
+@media (hover: hover) { .admin-btn.danger:hover { border-color: var(--error); } }
.admin-btn:disabled { opacity: 0.4; cursor: not-allowed; }
/* Used in four places and never defined, so it rendered as a bare browser
@@ -2659,7 +2687,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
cursor: pointer;
white-space: nowrap;
}
-.btn-secondary:hover { border-color: var(--border); color: var(--text); }
+@media (hover: hover) { .btn-secondary:hover { border-color: var(--border); color: var(--text); } }
.btn-secondary:disabled { opacity: 0.4; cursor: not-allowed; }
/* The recovery screen's "e-mail it to myself": a mailto: link, shaped like the
Copy button beside it. */
@@ -2683,7 +2711,7 @@ a.recovery-mail {
font-size: 0.8em;
cursor: pointer;
}
-.link-btn:hover { color: var(--accent); }
+@media (hover: hover) { .link-btn:hover { color: var(--accent); } }
/* The header is a flex row, so a textarea in it collapses to its content
width unless told otherwise. */
@@ -2799,7 +2827,7 @@ a.transfer-name {
padding: 0 2px;
display: flex;
}
-.transfer-cancel:hover { color: var(--error); }
+@media (hover: hover) { .transfer-cancel:hover { color: var(--error); } }
/* Same shape as cancel, and beside it: pausing and cancelling are the two
things a person does to a transfer, and one of them is not destructive. */
.transfer-pause {
@@ -2810,7 +2838,7 @@ a.transfer-name {
padding: 0 2px;
display: flex;
}
-.transfer-pause:hover { color: var(--accent); }
+@media (hover: hover) { .transfer-pause:hover { color: var(--accent); } }
/* Not a button: there is nothing to click. Dimmer than the cancel beside it,
and it carries its explanation in a tooltip rather than in the row, which
would be four lines of prose in a panel that has none. */
@@ -3136,11 +3164,16 @@ a.transfer-name {
border-radius: 6px;
opacity: 0.75;
}
-.nav-tray:hover,
.nav-tray:focus-visible {
opacity: 1;
background: rgba(255, 255, 255, 0.1);
}
+@media (hover: hover) {
+ .nav-tray:hover {
+ opacity: 1;
+ background: rgba(255, 255, 255, 0.1);
+ }
+}
/* ── Notification bell ───────────────────────────────────────────────────── */
@@ -3160,8 +3193,8 @@ a.transfer-name {
padding: 4px;
border-radius: 6px;
}
-.nav-search:hover,
.nav-search[aria-current="page"] { background: rgba(255, 255, 255, 0.1); }
+@media (hover: hover) { .nav-search:hover { background: rgba(255, 255, 255, 0.1); } }
.notif-badge {
position: absolute;
top: -6px;
@@ -3199,8 +3232,10 @@ a.transfer-name {
cursor: pointer;
border: 1px solid var(--border);
}
-.notif-item:hover {
- background: var(--bg-raised);
+@media (hover: hover) {
+ .notif-item:hover {
+ background: var(--bg-raised);
+ }
}
.notif-unread {
/* `--primary` is not defined here either; it is `--accent`. */
@@ -3296,7 +3331,7 @@ a.transfer-name {
cursor: pointer;
font-size: 0.9em;
}
-.btn-danger:hover { filter: brightness(1.1); }
+@media (hover: hover) { .btn-danger:hover { filter: brightness(1.1); } }
.btn-danger:disabled { opacity: 0.5; cursor: not-allowed; }
.notif-feed h3 { display: flex; align-items: center; justify-content: space-between; }
@@ -3379,7 +3414,9 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
font-size: 0.8em;
cursor: pointer;
}
-.chat-older-btn:hover:not(:disabled) { background: var(--bg-surface); color: var(--text); }
+@media (hover: hover) {
+ .chat-older-btn:hover:not(:disabled) { background: var(--bg-surface); color: var(--text); }
+}
.chat-older-btn:disabled { opacity: 0.6; cursor: default; }
.chat-older-icon { width: 13px; height: 13px; transform: rotate(180deg); }
@@ -3435,7 +3472,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
cursor: pointer;
box-shadow: var(--shadow-lg);
}
-.chat-jump:hover { color: var(--text); }
+@media (hover: hover) { .chat-jump:hover { color: var(--text); } }
.chat-jump.unread {
background: var(--accent);
color: var(--accent-text);
@@ -3464,10 +3501,12 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
cursor: pointer;
transition: background 0.12s, color 0.12s, border-color 0.12s;
}
-.tb-icon-btn:hover:not(:disabled) {
- background: var(--bg-raised);
- color: var(--text);
- border-color: var(--accent);
+@media (hover: hover) {
+ .tb-icon-btn:hover:not(:disabled) {
+ background: var(--bg-raised);
+ color: var(--text);
+ border-color: var(--accent);
+ }
}
/* Present but inert: the bar shows what exists here, and greys out what this
selection cannot do. `not-allowed` rather than `default` so the reason is
@@ -3478,10 +3517,12 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
}
.tb-icon-btn .icon { width: 16px; height: 16px; }
.tb-icon-btn.danger { color: var(--error); }
-.tb-icon-btn.danger:hover:not(:disabled) {
- background: var(--error-bg);
- border-color: var(--error);
- color: var(--error);
+@media (hover: hover) {
+ .tb-icon-btn.danger:hover:not(:disabled) {
+ background: var(--error-bg);
+ border-color: var(--error);
+ color: var(--error);
+ }
}
@@ -3505,7 +3546,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
cursor: pointer;
transition: border-color 0.12s, background 0.12s;
}
-.choice:hover { border-color: var(--accent); }
+@media (hover: hover) { .choice:hover { border-color: var(--accent); } }
.choice.selected { border-color: var(--accent); background: var(--bg-raised); }
.choice:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
@@ -3538,7 +3579,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
font-weight: 600;
cursor: pointer;
}
-.btn-primary:hover:not(:disabled) { background: var(--accent-hover); }
+@media (hover: hover) { .btn-primary:hover:not(:disabled) { background: var(--accent-hover); } }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
/* A root whose volume is absent. Its files stay listed — they are frozen, not
@@ -3627,9 +3668,13 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
line-height: 1; vertical-align: middle;
}
.sdt-action-btn + .sdt-action-btn { margin-left: 6px; }
-.sdt-action-btn:hover:not(:disabled) { background: var(--bg-raised); color: var(--text); }
+@media (hover: hover) {
+ .sdt-action-btn:hover:not(:disabled) { background: var(--bg-raised); color: var(--text); }
+}
.sdt-action-btn:disabled { opacity: 0.3; cursor: default; }
-.sdt-action-danger:hover:not(:disabled) { color: var(--error); border-color: var(--error); }
+@media (hover: hover) {
+ .sdt-action-danger:hover:not(:disabled) { color: var(--error); border-color: var(--error); }
+}
.sdt-action-btn .icon { width: 1em; height: 1em; display: block; }
.sdt-dir-name { display: inline-flex; align-items: center; gap: 6px; font-weight: 500; }
.sdt-dir-name .icon { width: 16px; height: 16px; flex-shrink: 0; }
@@ -3640,7 +3685,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
padding: 2px 6px; cursor: pointer; font-size: 0.85em; color: var(--text-dim);
line-height: 1;
}
-.root-eject-btn:hover { background: var(--bg-raised); }
+@media (hover: hover) { .root-eject-btn:hover { background: var(--bg-raised); } }
.root-eject-btn { margin-left: 8px; }
/* An ejected root in the Files table: still listed, deliberately — its files
are frozen, not gone — but not somewhere you can walk into. */
@@ -4187,7 +4232,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
transition: background 0.12s, color 0.12s;
}
.tb-typefilter button + button { border-left: 1px solid var(--border); }
-.tb-typefilter button:hover { color: var(--accent); }
+@media (hover: hover) { .tb-typefilter button:hover { color: var(--accent); } }
.tb-typefilter button.active {
background: var(--accent);
color: var(--accent-text);
@@ -4213,7 +4258,9 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
border: 1px solid var(--border);
transition: border-color 0.12s, transform 0.12s;
}
-.video-card:hover { border-color: var(--accent); transform: translateY(-2px); }
+@media (hover: hover) {
+ .video-card:hover { border-color: var(--accent); transform: translateY(-2px); }
+}
.video-poster {
width: 100%;
@@ -4321,7 +4368,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
}
.video-detail .video-title { color: var(--text); }
.video-detail .video-close { background: var(--bg-surface); color: var(--text); }
-.video-detail .video-close:hover { background: var(--border); }
+@media (hover: hover) { .video-detail .video-close:hover { background: var(--border); } }
.video-detail-body {
padding: 16px 20px;
@@ -4386,7 +4433,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
font: inherit;
cursor: pointer;
}
-.video-overview-toggle:hover { text-decoration: underline; }
+@media (hover: hover) { .video-overview-toggle:hover { text-decoration: underline; } }
.video-overview-toggle.standalone {
float: none;
display: inline-block;
@@ -4464,7 +4511,9 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
text-align: left;
margin-bottom: 4px;
}
-.video-episode-row:hover { border-color: var(--accent); color: var(--accent); }
+@media (hover: hover) {
+ .video-episode-row:hover { border-color: var(--accent); color: var(--accent); }
+}
/* The episode last started from this modal, so the one to watch next is the
row under it. A border and a tint, not a colour change of the text alone,
so it does not read as the hover state left behind. */
@@ -4508,7 +4557,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
border-radius: 6px;
cursor: pointer;
}
-.video-flat-row:hover { background: var(--bg-raised); }
+@media (hover: hover) { .video-flat-row:hover { background: var(--bg-raised); } }
.video-flat-thumb-slot { width: 64px; height: 40px; flex-shrink: 0; }
.video-flat-thumb {
@@ -4560,7 +4609,9 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
cursor: pointer;
text-align: left;
}
-.video-season-trigger:hover { border-color: var(--accent); color: var(--accent); }
+@media (hover: hover) {
+ .video-season-trigger:hover { border-color: var(--accent); color: var(--accent); }
+}
.video-season-current { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.video-season-caret { width: 14px; height: 14px; flex-shrink: 0; color: var(--text-dim); }
@@ -4597,7 +4648,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
text-align: left;
cursor: pointer;
}
-.video-season-option:hover { background: var(--bg-raised); }
+@media (hover: hover) { .video-season-option:hover { background: var(--bg-raised); } }
.video-season-option.active { color: var(--accent); }
.video-season-option-count { color: var(--text-dim); font-size: 0.85em; flex-shrink: 0; }
@@ -4634,7 +4685,9 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
text-align: left;
cursor: pointer;
}
-.video-search-result:hover:not(:disabled) { border-color: var(--accent); background: var(--bg-raised); }
+@media (hover: hover) {
+ .video-search-result:hover:not(:disabled) { border-color: var(--accent); background: var(--bg-raised); }
+}
.video-search-result:disabled { opacity: 0.5; cursor: not-allowed; }
.video-search-result-thumb { width: 46px; height: 68px; flex-shrink: 0; }
.video-search-result-poster {
@@ -4681,7 +4734,9 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
border: 1px solid var(--border);
transition: border-color 0.12s, transform 0.12s;
}
-.music-card:hover { border-color: var(--accent); transform: translateY(-2px); }
+@media (hover: hover) {
+ .music-card:hover { border-color: var(--accent); transform: translateY(-2px); }
+}
.music-cover {
width: 100%;
@@ -4740,7 +4795,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
}
.music-detail .video-title { color: var(--text); }
.music-detail .video-close { background: var(--bg-surface); color: var(--text); }
-.music-detail .video-close:hover { background: var(--border); }
+@media (hover: hover) { .music-detail .video-close:hover { background: var(--border); } }
.music-queue-now-playing {
padding: 10px 20px;
@@ -4814,7 +4869,9 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
text-align: left;
cursor: pointer;
}
-.music-track-row:hover { background: var(--bg-raised); border-color: var(--border); }
+@media (hover: hover) {
+ .music-track-row:hover { background: var(--bg-raised); border-color: var(--border); }
+}
.music-track-row.active { border-color: var(--accent); color: var(--accent); }
.music-track-no { width: 24px; flex-shrink: 0; color: var(--text-dim); text-align: right; }
.music-track-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -4955,7 +5012,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
color: var(--text-dim);
cursor: pointer;
}
-.index-dock-hide:hover { background: var(--bg-surface); }
+@media (hover: hover) { .index-dock-hide:hover { background: var(--bg-surface); } }
.index-dock-hide .icon { width: 16px; height: 16px; }
@media (max-width: 640px) {
.index-dock-head { flex-direction: column; gap: 2px; }
@@ -5009,12 +5066,6 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
cursor: pointer;
position: relative;
}
-/* (hover: hover) — a tap on a touchscreen fires a synthetic hover that
- never gets a "pointer left" to end it, so next/prev/shuffle stayed
- highlighted as if still being pressed until some other element was
- tapped (found live on mobile Chrome, 2026-08-26; not reproducible on
- desktop, which actually has a mouse to move away with). Scoped to
- devices that can genuinely hover — real hover behaviour is unaffected. */
@media (hover: hover) {
.music-player-btn:hover { background: var(--bg-surface); color: var(--text); }
}
@@ -5140,7 +5191,9 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
align-items: center;
justify-content: center;
}
-.photo-icon-btn:hover { color: var(--text); background: var(--bg-surface); }
+@media (hover: hover) {
+ .photo-icon-btn:hover { color: var(--text); background: var(--bg-surface); }
+}
.photo-icon-btn-icon { width: 22px; height: 22px; }
/* .tb-btn's own chevron is drawn pointing down; rotated here to read as
@@ -5165,7 +5218,9 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
border: 1px solid var(--border);
transition: border-color 0.12s, transform 0.12s;
}
-.photo-album-card:hover { border-color: var(--accent); transform: translateY(-2px); }
+@media (hover: hover) {
+ .photo-album-card:hover { border-color: var(--accent); transform: translateY(-2px); }
+}
.photo-album-cover {
width: 100%;
@@ -5205,7 +5260,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
background: var(--bg-raised);
border: 1px solid var(--border);
}
-.photo-tile:hover { border-color: var(--accent); }
+@media (hover: hover) { .photo-tile:hover { border-color: var(--accent); } }
.photo-tile { position: relative; }
/* A video among the photos: a play mark and its length over the thumbnail. */
.photo-tile-video {
@@ -5227,7 +5282,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
border: 0; background: rgba(0, 0, 0, 0.6); color: #fff; cursor: pointer;
display: flex; align-items: center; justify-content: center;
}
-.photo-lightbox-play:hover { background: var(--accent); }
+@media (hover: hover) { .photo-lightbox-play:hover { background: var(--accent); } }
.photo-lightbox-play .icon { width: 28px; height: 28px; }
.photo-tile-thumb {
width: 100%;
@@ -5282,7 +5337,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
align-items: center;
justify-content: center;
}
-.photo-nav:hover { background: rgba(255, 255, 255, 0.22); }
+@media (hover: hover) { .photo-nav:hover { background: rgba(255, 255, 255, 0.22); } }
.photo-nav:disabled { opacity: 0.3; cursor: default; }
.photo-nav-icon { width: 20px; height: 20px; }
.photo-nav-prev-icon { transform: rotate(90deg); }
@@ -5342,10 +5397,12 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
padding: 5px;
border-radius: 4px;
}
-.photo-zoom-controls .photo-icon-btn:hover { background: rgba(255, 255, 255, 0.15); }
+@media (hover: hover) {
+ .photo-zoom-controls .photo-icon-btn:hover { background: rgba(255, 255, 255, 0.15); }
+}
.photo-zoom-controls .photo-icon-btn.active { background: rgba(255, 255, 255, 0.18); }
.photo-zoom-controls .photo-icon-btn:disabled { opacity: 0.35; cursor: default; }
-.photo-zoom-controls .photo-icon-btn:disabled:hover { background: none; }
+@media (hover: hover) { .photo-zoom-controls .photo-icon-btn:disabled:hover { background: none; } }
.photo-zoom-controls .photo-icon-btn-icon { width: 18px; height: 18px; }
.photo-icon-btn-text { font-size: 0.72em; font-weight: 600; padding: 5px 8px; }
.photo-zoom-percent {
@@ -5625,7 +5682,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
text-align: left;
cursor: pointer;
}
-.ctx-menu-item:hover:not(:disabled) { background: var(--sidebar-hover); }
+@media (hover: hover) { .ctx-menu-item:hover:not(:disabled) { background: var(--sidebar-hover); } }
.ctx-menu-item:disabled { color: var(--text-dim); cursor: default; }
.ctx-menu-item.danger { color: var(--error); }
@@ -5673,10 +5730,12 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
transition: opacity 0.12s, background 0.12s;
}
.ctx-dots svg { width: 18px; height: 18px; }
-.ctx-dots:hover { background: var(--sidebar-hover); color: var(--text); }
+@media (hover: hover) { .ctx-dots:hover { background: var(--sidebar-hover); color: var(--text); } }
.ctx-dots:focus-visible { opacity: 1; outline: 2px solid var(--border-focus); }
-.music-card:hover .ctx-dots,
-.music-track-row:hover .ctx-dots { opacity: 1; }
+@media (hover: hover) {
+ .music-card:hover .ctx-dots,
+ .music-track-row:hover .ctx-dots { opacity: 1; }
+}
@media (hover: none), (pointer: coarse) {
.ctx-dots { opacity: 1; }
@@ -5691,7 +5750,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
color: #fff;
backdrop-filter: blur(2px);
}
-.music-card-dots:hover { background: rgba(0, 0, 0, 0.75); color: #fff; }
+@media (hover: hover) { .music-card-dots:hover { background: rgba(0, 0, 0, 0.75); color: #fff; } }
/* A flat folder row expands on click; its menu button sits in the same row and
must not take the whole width with it. */