fix(ui): visual consistency sweep — shapes, buttons, hero, header alignment (#745)

* fix(ui): square shape across badges, pills and non-player buttons

zunoz on Discord flagged inconsistent shapes for play buttons, badges
and pills across the app. Unify to var(--radius-sm) for non-player
surfaces so the same visual indicator looks identical wherever it
appears. Player Bar, Fullscreen Player and Mini Player keep their
circular shape as part of the player family; toggle switches, sliders,
search input, pagination dots and theme overrides are left alone.

Covers: hero play + nav arrows, album-card details button, album
header icon buttons, playlist suggestion play, album-row nav arrows;
.badge (incl. New / album-detail), genre pill, np chip/tag/badge,
np-dash toolbar badge, radio filter chip, radio card chip, mp album
plays pill, settings search-result badge, alphabet filter buttons,
download hint, mobile search chip, artist release-group count, artist
external link, all Orbit session pills, device-sync count badge;
ServersTab "Aktiv" badge, PlaylistCard loading badge, AlbumRow /
ArtistRow "more" buttons.

* fix(composers): collapse empty space between virtual rows

The composer grid uses text-only tiles (~78 px intrinsic) but
estimateRowHeightPx scaled with cell width like the image variants,
clamped to a 200 px maximum. On normal viewports every virtual row
reserved ~200 px while the actual card was ~78 px, leaving ~120 px of
empty space below each row.

Pin the composer variant to min === max so the rowHeight is a fixed
88 px regardless of cell width.

* fix(ui): unify secondary action buttons on btn-surface

Action rows on the Artist, Album, Tracks, Favorites and Most Played
pages mixed btn-ghost (borderless), btn-surface (bordered) and bare
.btn (no variant, picked up bordered look in light themes only).
Result was per-page and per-theme inconsistency — secondary buttons
sometimes had borders, sometimes not.

Unify on btn-surface for all secondary actions so the same affordance
looks identical across pages and the difference between themes is just
border tone, not border presence.

- Tracks hero: Enqueue and Reroll buttons
- AlbumHeader: Shuffle, Enqueue, Star, Share, Bio, Download and the
  Offline-cache states
- MostPlayed: sort toggle and compilations filter (now matches the
  Albums page header)

* fix(hero): make pagination dots visible on light backdrops

The pagination dots used `rgba(255, 255, 255, 0.35)` which disappears
against white-dominant cover art and on light themes, even under the
hero gradient overlay. zunoz on Discord reported the inactive dots as
effectively invisible.

Bump inactive dots to 85 % white with a dark outline + drop shadow so
they read against any backdrop, and switch the active dot to the
accent color so the highlight reads as colour, not just width.

Also opaque-fill `.badge` (`var(--accent)` + `--ctp-crust` text) so the
hero pills do not disappear against light cover art either — base
class previously used `--accent-dim` which is too transparent for
badges per the existing badge rule.

* fix(tracks): align Browse-all-tracks header with rows

The Tracks page header sat outside the scroll container while rows
sat inside it, so the scrollbar gutter shrank only the rows and the
last header column drifted right of its row data. With OpenDyslexic
selected the wider glyphs pushed "Duration" past the viewport edge
entirely; zunoz on Discord reported it.

Move SongListHeader inside the scroll container with position:
sticky so header and rows share the same width budget. Sticky
keeps the header visible while scrolling as a side benefit.

* test(cardGridLayout): pin composer variant to fixed row height

Guards the fix that collapsed the empty space between Composers grid
rows. Re-introducing the `cellWidthPx + extra` scaling for composer
would silently bring back ~120 px of dead space per virtual row, so
the test asserts the fixed 88 px output across a wide cellWidth range.

Image variants (artist / album / playlist) get baseline assertions so
the composer case is documented as the deliberate exception, not an
oversight.

* docs(changelog): UI consistency sweep (PR #745)
This commit is contained in:
Frank Stellmacher
2026-05-17 12:32:43 +02:00
committed by GitHub
parent 4214a20ded
commit 3b94368ffa
31 changed files with 114 additions and 57 deletions
@@ -28,7 +28,7 @@
gap: 5px;
width: 44px;
height: 44px;
border-radius: 50%;
border-radius: var(--radius-sm);
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.1);
color: var(--text-secondary);
@@ -24,7 +24,7 @@
justify-content: center;
width: 32px;
height: 32px;
border-radius: 50%;
border-radius: var(--radius-sm);
background: var(--bg-card);
border: 1px solid var(--border-subtle);
color: var(--text-secondary);
@@ -189,7 +189,7 @@
color: var(--ctp-crust);
border: none;
padding: 8px 20px;
border-radius: var(--radius-full);
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
@@ -8,7 +8,7 @@
.alphabet-filter-btn {
min-width: 26px;
padding: 4px 7px;
border-radius: 999px;
border-radius: var(--radius-sm);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.03em;
+1 -1
View File
@@ -48,7 +48,7 @@
font-size: 12px;
color: var(--text-muted);
background: var(--bg-card);
border-radius: 10px;
border-radius: var(--radius-sm);
padding: 1px 8px;
}
+1 -1
View File
@@ -12,7 +12,7 @@
gap: 6px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 20px;
border-radius: var(--radius-sm);
padding: 7px 14px;
font-size: 14px;
font-weight: 500;
+3 -1
View File
@@ -202,6 +202,8 @@
margin-bottom: 0.5rem;
background: var(--accent);
color: var(--ctp-crust);
/* Konsistenz mit `.album-card-new-badge`: eckig, nicht pill. */
border-radius: var(--radius-sm);
}
.album-detail-back {
@@ -256,7 +258,7 @@
color: var(--text-secondary);
background: rgba(203, 166, 247, 0.08);
border: 1px solid rgba(203, 166, 247, 0.2);
border-radius: var(--radius-full);
border-radius: var(--radius-sm);
padding: 3px 10px 3px 8px;
cursor: default;
white-space: nowrap;
+1 -1
View File
@@ -743,7 +743,7 @@
flex-shrink: 0;
margin-left: 8px;
padding: 2px 7px;
border-radius: 999px;
border-radius: var(--radius-sm);
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.03em;
+1 -1
View File
@@ -12,7 +12,7 @@
border: 1px solid color-mix(in srgb, var(--genre-color, var(--accent)) 22%, transparent);
background: color-mix(in srgb, var(--genre-color, var(--accent)) 6%, transparent);
color: var(--text-primary);
border-radius: 999px;
border-radius: var(--radius-sm);
padding: 0.28em 0.78em;
cursor: pointer;
font-weight: 600;
+9 -4
View File
@@ -37,13 +37,18 @@
width: 8px;
height: 8px;
border-radius: var(--radius-full);
background: rgba(255, 255, 255, 0.35);
/* Near-opaque white plus a dark outline so the dot reads against any hero
backdrop — including light cover art under the gradient overlay where
translucent dots disappear. */
background: rgba(255, 255, 255, 0.85);
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.45), 0 1px 2px rgba(0, 0, 0, 0.35);
transition: all 0.3s ease;
}
.hero-dot-active {
width: 24px;
background: rgba(255, 255, 255, 0.9);
background: var(--accent);
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.45), 0 1px 4px rgba(0, 0, 0, 0.4);
}
/* Wrapper that pins prev/next arrows to the hero's left/right edges. */
@@ -65,7 +70,7 @@
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-full);
border-radius: var(--radius-sm);
background: rgba(0, 0, 0, 0.45);
color: rgba(255, 255, 255, 0.92);
border: 1px solid rgba(255, 255, 255, 0.18);
@@ -161,7 +166,7 @@
padding: var(--space-3) var(--space-5);
background: var(--accent);
color: var(--ctp-crust);
border-radius: var(--radius-full);
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 600;
transition: all var(--transition-fast);
+1 -1
View File
@@ -170,7 +170,7 @@
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 999px;
border-radius: var(--radius-sm);
background: var(--bg-card);
color: var(--accent);
font-size: 12px;
+2 -2
View File
@@ -101,7 +101,7 @@
font-size: 13px;
font-weight: 600;
padding: 6px 14px;
border-radius: 999px;
border-radius: var(--radius-sm);
background: rgba(255, 255, 255, 0.10);
color: rgba(255, 255, 255, 0.75);
letter-spacing: 0.03em;
@@ -508,7 +508,7 @@
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 999px;
border-radius: var(--radius-sm);
background: rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.85);
letter-spacing: 0.04em;
+2 -2
View File
@@ -204,7 +204,7 @@
font-size: 10px;
font-weight: 700;
padding: 1px 6px;
border-radius: 999px;
border-radius: var(--radius-sm);
min-width: 14px;
text-align: center;
line-height: 1.3;
@@ -371,7 +371,7 @@
font-size: 12px;
font-weight: 500;
padding: 4px 10px;
border-radius: 999px;
border-radius: var(--radius-sm);
background: rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.75);
cursor: pointer;
@@ -83,7 +83,7 @@
color: var(--text-muted);
font-weight: 500;
padding: 4px 12px;
border-radius: 999px;
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--text-primary) 5%, transparent);
border: 1px solid color-mix(in srgb, var(--text-primary) 8%, transparent);
white-space: nowrap;
@@ -115,7 +115,7 @@
trigger fires intermittently. */
height: 26px;
padding: 0 12px;
border-radius: 999px;
border-radius: var(--radius-sm);
background: var(--accent);
border: 1px solid var(--accent);
color: #fff;
@@ -203,7 +203,7 @@
font: inherit;
letter-spacing: inherit;
padding: 2px 6px;
border-radius: 999px;
border-radius: var(--radius-sm);
cursor: pointer;
transition: color 150ms ease, background 150ms ease;
}
@@ -1116,7 +1116,7 @@
font-size: 10.5px;
font-weight: 600;
letter-spacing: 0.02em;
border-radius: 999px;
border-radius: var(--radius-sm);
border-width: 1px;
border-style: solid;
}
@@ -1175,7 +1175,7 @@
text-transform: uppercase;
color: var(--ctp-crust, #11111b);
background: var(--accent);
border-radius: 999px;
border-radius: var(--radius-sm);
}
.orbit-guest-queue__live-badge svg { animation: orbit-live-pulse 2s ease-in-out infinite; }
@keyframes orbit-live-pulse {
@@ -1246,7 +1246,7 @@
.orbit-guest-queue__count {
padding: 1px 6px;
background: color-mix(in srgb, var(--text-primary) 8%, transparent);
border-radius: 999px;
border-radius: var(--radius-sm);
font-size: 10px;
color: var(--text-primary);
}
@@ -1309,7 +1309,7 @@
padding: 1px 7px;
background: var(--accent);
color: var(--bg-primary, #0b0b13);
border-radius: 999px;
border-radius: var(--radius-sm);
font-size: 10px;
letter-spacing: 0;
text-transform: none;
@@ -1677,7 +1677,7 @@
.because-card-pill {
padding: 2px 10px;
border: 1px solid var(--border-subtle);
border-radius: 999px;
border-radius: var(--radius-sm);
font-size: 11px;
color: var(--text-secondary, inherit);
white-space: nowrap;
@@ -1700,7 +1700,7 @@
color: var(--text-primary, var(--text-secondary, inherit));
background: color-mix(in srgb, var(--text-primary) 14%, transparent);
padding: 3px 10px;
border-radius: 999px;
border-radius: var(--radius-sm);
text-shadow: none;
}
.because-card-meta > span:not(:last-child)::after {
@@ -214,7 +214,7 @@
align-items: center;
gap: 6px;
padding: 5px 12px;
border-radius: var(--radius-full);
border-radius: var(--radius-sm);
background: var(--bg-card);
border: 1px solid var(--border);
color: var(--text-secondary);
@@ -3,7 +3,7 @@
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 999px;
border-radius: var(--radius-sm);
background: var(--text-secondary);
color: var(--ctp-surface1);
font-size: 10px;
+1 -1
View File
@@ -18,7 +18,7 @@
display: inline-flex;
align-items: center;
padding: 4px 14px;
border-radius: 999px;
border-radius: var(--radius-sm);
font-size: 12px;
font-weight: 500;
background: var(--bg-card);
+1 -1
View File
@@ -328,7 +328,7 @@
font-size: 11px;
font-weight: 500;
padding: 2px 8px;
border-radius: 999px;
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--accent) 15%, transparent);
color: var(--accent);
}
+1 -1
View File
@@ -269,7 +269,7 @@
height: 24px;
padding: 0;
border: none;
border-radius: 50%;
border-radius: var(--radius-sm);
background: var(--accent);
color: var(--ctp-crust);
cursor: pointer;
+5 -3
View File
@@ -3,11 +3,13 @@
display: inline-flex;
align-items: center;
padding: 2px var(--space-2);
border-radius: var(--radius-full);
border-radius: var(--radius-sm);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.03em;
background: var(--accent-dim);
color: var(--accent);
/* Opaque fill `--accent-dim` is too transparent and disappears against
light-toned hero backdrops when the cover art is white-dominant. */
background: var(--accent);
color: var(--ctp-crust);
}
@@ -33,7 +33,13 @@
letter-spacing: 0.04em;
color: var(--text-muted);
cursor: default;
background: transparent;
/* Header lives inside the scroll container so its width matches the rows'
(scrollbar gutter applies to both). Sticky keeps it pinned while scrolling
and the opaque background hides rows passing underneath. */
position: sticky;
top: 0;
z-index: 2;
background: var(--bg-card);
border-bottom: 1px solid var(--border-subtle);
}