v3: the header stops cropping itself and the search stops losing its place — a review of the header, search bar and presence row turned up tabs cut off at both ends, avatars that could only be all there or gone, and a search that forgot what it was showing. Between 860px and about 1000px, and again just past 1150px where the username and Playlists links arrive, presence plus tabs ran wider than the header's centre column; the slot couldn't shrink, so the column clipped it on both sides, and logged in FOLLOWED went missing with one person online. The container queries meant to shed presence under 460px and tabs under 280px never fired, overridden by later rules of the same weight, and no fixed width would have been right anyway: the tabs run from 314px (nyt, English) to 485px (melange, French). The slot can now shrink, and the presence row sizes itself through a new useRoomBeside, which measures the room the tabs leave it in the header and in the row under it: as many avatars as fit, at most six seats, the rest folded into a "+N" counter (square in brutalist), and never fewer than an avatar and the counter — below that the tabs scroll instead. So presence shows at every width, phones included, where a 512px rule used to hide it. When the header's tabs do scroll, they get the app's right-edge fade in the header's own colour, only while they overflow, and the active tab is scrolled clear of it. The avatars fan out on hover only where there's a pointer, so a tap doesn't leave them spread, and the label-less "+" button squares up with the icons beside it on small screens. Search: the ✕ on results opened directly ran navigate(-1) out of the app and now goes home; after several searches it stepped back one query at a time and now returns to where the search began in one step, carried in the entry's state, which useTabParam now keeps across result tabs; those tabs (/search/~/users) weren't recognised as the results page at all. On results the bar stays open holding the current query, including after a new search or Back, where it used to collapse and empty. The input no longer grabs focus on a results page, so a phone's keyboard doesn't cover the results, and submitting puts it away. Escape closes an opened search and returns focus to its button, or on results reverts an edited query, overriding the search input's own clear-on-Escape. The two search bars share one draft, so crossing the breakpoint keeps it.
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 44s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 44s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
81
src/App.css
81
src/App.css
@@ -2655,7 +2655,6 @@ body.has-player .chat-fab {
|
||||
min-width: 0;
|
||||
overflow-x: clip;
|
||||
overflow-y: visible;
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
@@ -2725,24 +2724,13 @@ body.has-player .chat-fab {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* As the center column shrinks (viewport narrow, search collapsed),
|
||||
shed content in order: presence first, then tabs (still in .index-below-header) */
|
||||
@container (max-width: 460px) {
|
||||
.index-presence {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 280px) {
|
||||
.feed-sort {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-center-slot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
/* Lets the tabs scroll as a last resort, rather than the centred slot
|
||||
overflowing the column and being clipped at both ends. */
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-header-brand:hover {
|
||||
@@ -2884,7 +2872,8 @@ body.has-player .chat-fab {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Ghost search button — always visible except when the center search bar is shown */
|
||||
/* Ghost search button — opens the search (in the center column, or the row
|
||||
under the header on small viewports) and turns into its ✕ */
|
||||
.nav-search-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -3404,17 +3393,39 @@ body.has-player .chat-fab {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.index-presence:hover .index-presence-avatar {
|
||||
margin-left: 4px;
|
||||
/* Hover only where there's a pointer to hover with — a tap would otherwise
|
||||
leave the row fanned out and the tapped avatar raised. */
|
||||
@media (hover: hover) {
|
||||
.index-presence:hover .index-presence-avatar {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.index-presence:hover .index-presence-avatar:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.index-presence-avatar:hover {
|
||||
transform: translateY(-3px) scale(1.12);
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.index-presence:hover .index-presence-avatar:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.index-presence-avatar:hover {
|
||||
transform: translateY(-3px) scale(1.12);
|
||||
z-index: 2;
|
||||
/* The "+N" that ends a crowded row, sized and ringed like the avatars. */
|
||||
.index-presence-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-2xs);
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
cursor: default;
|
||||
box-sizing: border-box;
|
||||
border: var(--border-width) solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.index-status {
|
||||
@@ -3465,8 +3476,14 @@ body.has-player .chat-fab {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* In the app header the tabs never overflow — suppress the gradient there */
|
||||
/* In the app header the tabs only scroll as a last resort, and a permanent
|
||||
fade would dim the last tab whenever they fit — so it shows only while they
|
||||
overflow, in the header's own colour. */
|
||||
.app-header .feed-sort-scroller::after {
|
||||
background: linear-gradient(to right, transparent, var(--header-bg));
|
||||
}
|
||||
|
||||
.app-header .feed-sort-scroller:not(.feed-sort-scroller--overflowing)::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -3992,12 +4009,6 @@ body.has-player .chat-fab {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@media (max-width: 512px) {
|
||||
.index-below-header .index-presence {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 512px) {
|
||||
.app-header-brand-name {
|
||||
display: none;
|
||||
@@ -4006,6 +4017,12 @@ body.has-player .chat-fab {
|
||||
.btn-new-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Down to its "+", the button squares up with the icons beside it. */
|
||||
.app-header-nav .btn-primary {
|
||||
width: 2.25rem;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Playlist card image thumbnail ── */
|
||||
|
||||
Reference in New Issue
Block a user