fix(theme): nav-bar layout and sticky behavior in Swage (#8739)

Fixes a cluster of bugs in the Swage theme. Most are nav-bar related and reproducible in narrow viewports (≤840px); one affects the article list at any viewport.

1. **Horizontal page overflow.** `.nav_menu` had `width: 100%` plus `padding-left: 2rem` with default `box-sizing: content-box`, making it 32px wider than the viewport. The whole page side-scrolled. Fixed with `box-sizing: border-box`.

2. **Article title jumps down on hover.** `.flux:not(.current):hover .item .title { top: auto !important }` overrode the base-theme `top: 0`, dropping the absolutely-positioned title to its in-flow position on hover. The override was added in #4671 (a broad font-size/line-height refactor) and looks like collateral; no other Swage rule depends on it. Removed.

3. **Settings/sort buttons drift on scroll.** Both used `position: fixed` without an explicit `top`, so they resolved to their static document position and drifted with scroll. Compounded by being inside `position: sticky` parents (Firefox bug). Fixed by:
   - Making `.header` sticky so `.item.configure` (now `position: absolute; top: 0`) anchors to it.
   - Splitting `#nav_menu_sort .dropdown-menu` from its siblings and making it `position: absolute` (the only one that needs to float when open).

4. **Logo hidden selector typo.** Narrow-view rule used `.item .title` (descendant) but the actual element is `.item.title` (combined class), so the logo was never hidden in narrow view. Fixed.

5. **Logo overlaps nav buttons when sidebar is collapsed.** At any viewport, collapsing the sidebar leaves the absolutely-positioned logo overlapping the now-wider nav. Added `body:has(.aside:not(.visible)) .header .item.title { display: none }` to hide the logo whenever the sidebar isn't expanded. Trade-off: the logo is no longer visible when the sidebar is collapsed at any width. Keeping it visible would require repositioning or resizing the logo itself, which is a redesign rather than a bug fix.

6. **Forced two-row nav at narrow widths even when one row would fit.** Narrow layout used absolute positioning to pin a second row of buttons at `top: 36px`. Replaced with `display: flex; flex-wrap: wrap`, scoped to article-list bodies (`body:is(.normal, .reader, .global)`) so the settings-page custom layout is untouched.

## Screenshots
**Wide viewport, before:**
<img width="950" height="32" alt="Screenshot 2026-04-30 at 18 26 49" src="https://github.com/user-attachments/assets/58635491-3d36-46af-b4ed-45ff8893bf5a" />

**Narrow viewport, before:**
<img width="738" height="72" alt="Screenshot 2026-04-30 at 18 31 02" src="https://github.com/user-attachments/assets/9b66df9e-8677-47f3-8dfe-011c070b0010" />

**Settings and sort buttons drift on scroll (before, any viewport):**
<img width="116" height="83" alt="Screenshot 2026-04-30 at 17 48 52" src="https://github.com/user-attachments/assets/27a5a2b6-3fe7-4ee8-8cfb-4f399937bedc" />

**Article title shifts down on hover (narrow viewport, before):**
<img width="287" height="43" alt="Screenshot 2026-04-30 at 19 31 53" src="https://github.com/user-attachments/assets/642be719-9f4f-4d83-9ba9-3f584fa7684d" />

**Narrow viewport, after:**
<img width="663" height="32" alt="Screenshot 2026-04-30 at 18 26 17" src="https://github.com/user-attachments/assets/9378fc68-9942-4b5e-af21-f10eb87c474b" />

## Related

- Possibly fixes #6977 (buttons squeezed together at mobile widths in Swage).

## Notes

- `swage.rtl.css` regenerated via `npm run rtlcss`.
- `npm run stylelint` passes.
- All six fixes are small CSS bugs in the Swage theme (most in narrow-viewport nav layout, one in article-list hover); bundled as one PR for that reason. Happy to split into separate PRs if reviewers prefer.

---

- box-sizing: border-box on .nav_menu to fix 32px horizontal overflow
  caused by width: 100% + padding-left: 2rem.
- Remove .flux:not(.current):hover .item .title { top: auto !important }
  override that dropped article titles to their in-flow position on
  hover (introduced in #4671 as collateral from a wider refactor).
- Make .header position: sticky and switch .item.configure (and
  #nav_menu_sort .dropdown-menu) to position: absolute with explicit
  top: 0 so the cog and sort menu no longer drift when scrolling.
- Fix .item .title -> .item.title selector typo so the logo is
  actually hidden in narrow view as intended.
- Hide the logo when the sidebar is collapsed
  (body:has(.aside:not(.visible))) to avoid overlap with the now-wider
  nav.
- Replace the two-row absolute-positioned narrow-view nav layout with
  display: flex; flex-wrap: wrap, scoped to article-list bodies so
  settings/admin pages keep their custom layout.

Regenerated swage.rtl.css via npm run rtlcss. npm run stylelint passes.

Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
This commit is contained in:
polybjorn
2026-05-01 06:22:19 +00:00
committed by GitHub
parent bb9349f4e9
commit 28ab78da86
2 changed files with 106 additions and 16 deletions

View File

@@ -749,6 +749,9 @@ main.post .drop-section li.item.feed a:hover .icon {
.header {
height: auto;
position: sticky;
top: 0;
z-index: 96; /* above .nav_menu (90) and .item.configure (95) */
> .item {
padding: 0;
@@ -781,7 +784,8 @@ main.post .drop-section li.item.feed a:hover .icon {
}
.item.configure {
position: fixed;
position: absolute;
top: 0;
right: 0;
z-index: 95;
width: 35px;
@@ -957,6 +961,7 @@ main.post .drop-section li.item.feed a:hover .icon {
.nav_menu {
padding: 0 0 0 2rem;
width: 100%;
box-sizing: border-box;
font-size: 0;
background-color: var(--color-background-nav);
text-align: left;
@@ -1266,7 +1271,7 @@ main.global {
@media (max-width: 840px) {
body:not(.formLogin, .register) {
.header {
.item .title {
.item.title {
display: none;
}
}
@@ -1289,13 +1294,17 @@ main.global {
}
#nav_menu_sort,
#nav_menu_sort .dropdown-menu,
#nav_menu_sort .dropdown,
#toggle-order {
position: fixed;
right: 0;
}
#nav_menu_sort .dropdown-menu {
position: absolute;
right: 0;
}
#nav_menu_sort .dropdown-menu,
#nav_menu_read_all .dropdown-menu,
#dropdown-search-wrapper .dropdown-menu,
@@ -1381,7 +1390,6 @@ main.global {
.item.configure {
padding: 0;
position: fixed;
> .icon {
margin-top: 5px;
@@ -1399,10 +1407,6 @@ main.global {
text-align: center;
}
.flux:not(.current):hover .item .title {
top: auto !important;
}
.aside {
padding: 0;
width: 0;
@@ -1577,6 +1581,43 @@ main.global {
div#nav_menu_views {
right: 32px;
}
body:is(.normal, .reader, .global) {
.nav_menu {
display: flex;
height: auto;
flex-wrap: wrap;
align-items: center;
row-gap: 2px;
padding-right: 3rem; /* room for absolute-positioned cog */
}
.nav_menu .item.search {
position: static;
top: auto;
width: auto;
flex: 1 1 140px;
min-width: 100px;
margin-left: 0;
}
.nav_menu form#mark-read-menu,
.nav_menu a#actualize,
.nav_menu div#nav_menu_actions,
.nav_menu div#nav_menu_views,
.nav_menu a#toggle-order,
.nav_menu #nav_menu_sort,
.nav_menu #nav_menu_sort .dropdown {
position: static;
top: auto;
right: auto;
left: auto;
}
.item.configure {
right: 0;
}
}
}
@media (max-width: 410px) {
@@ -1680,3 +1721,7 @@ button.as-link {
min-height: initial;
}
}
body:has(.aside:not(.visible)) .header .item.title {
display: none;
}

View File

@@ -749,6 +749,9 @@ main.post .drop-section li.item.feed a:hover .icon {
.header {
height: auto;
position: sticky;
top: 0;
z-index: 96; /* above .nav_menu (90) and .item.configure (95) */
> .item {
padding: 0;
@@ -781,7 +784,8 @@ main.post .drop-section li.item.feed a:hover .icon {
}
.item.configure {
position: fixed;
position: absolute;
top: 0;
left: 0;
z-index: 95;
width: 35px;
@@ -957,6 +961,7 @@ main.post .drop-section li.item.feed a:hover .icon {
.nav_menu {
padding: 0 2rem 0 0;
width: 100%;
box-sizing: border-box;
font-size: 0;
background-color: var(--color-background-nav);
text-align: right;
@@ -1266,7 +1271,7 @@ main.global {
@media (max-width: 840px) {
body:not(.formLogin, .register) {
.header {
.item .title {
.item.title {
display: none;
}
}
@@ -1289,13 +1294,17 @@ main.global {
}
#nav_menu_sort,
#nav_menu_sort .dropdown-menu,
#nav_menu_sort .dropdown,
#toggle-order {
position: fixed;
left: 0;
}
#nav_menu_sort .dropdown-menu {
position: absolute;
left: 0;
}
#nav_menu_sort .dropdown-menu,
#nav_menu_read_all .dropdown-menu,
#dropdown-search-wrapper .dropdown-menu,
@@ -1381,7 +1390,6 @@ main.global {
.item.configure {
padding: 0;
position: fixed;
> .icon {
margin-top: 5px;
@@ -1399,10 +1407,6 @@ main.global {
text-align: center;
}
.flux:not(.current):hover .item .title {
top: auto !important;
}
.aside {
padding: 0;
width: 0;
@@ -1577,6 +1581,43 @@ main.global {
div#nav_menu_views {
left: 32px;
}
body:is(.normal, .reader, .global) {
.nav_menu {
display: flex;
height: auto;
flex-wrap: wrap;
align-items: center;
row-gap: 2px;
padding-left: 3rem; /* room for absolute-positioned cog */
}
.nav_menu .item.search {
position: static;
top: auto;
width: auto;
flex: 1 1 140px;
min-width: 100px;
margin-right: 0;
}
.nav_menu form#mark-read-menu,
.nav_menu a#actualize,
.nav_menu div#nav_menu_actions,
.nav_menu div#nav_menu_views,
.nav_menu a#toggle-order,
.nav_menu #nav_menu_sort,
.nav_menu #nav_menu_sort .dropdown {
position: static;
top: auto;
left: auto;
right: auto;
}
.item.configure {
left: 0;
}
}
}
@media (max-width: 410px) {
@@ -1680,3 +1721,7 @@ button.as-link {
min-height: initial;
}
}
body:has(.aside:not(.visible)) .header .item.title {
display: none;
}