feat(ui): F8-09 — turn report sticky icon-popup section menu #67
+25
-4
@@ -85,8 +85,8 @@ the body — both surfaces stay in sync by construction.
|
|||||||
|
|
||||||
## Table of contents and active highlight
|
## Table of contents and active highlight
|
||||||
|
|
||||||
`report/report-toc.svelte` renders a single sticky icon-popup
|
`report/report-toc.svelte` renders a single icon-popup trigger
|
||||||
trigger in the top-right corner of the report column. The trigger
|
pinned to the top-right corner of the report column. The trigger
|
||||||
shows `≡` followed by the title of the currently-active section
|
shows `≡` followed by the title of the currently-active section
|
||||||
(CSS-clamped with `text-overflow: ellipsis` so a long RU title
|
(CSS-clamped with `text-overflow: ellipsis` so a long RU title
|
||||||
cannot bloat the button). Clicking opens an anchored popover
|
cannot bloat the button). Clicking opens an anchored popover
|
||||||
@@ -97,8 +97,29 @@ the matching `<section id="report-<slug>">` into view via
|
|||||||
`scrollIntoView` (with `prefers-reduced-motion` falling back to
|
`scrollIntoView` (with `prefers-reduced-motion` falling back to
|
||||||
`behavior: "auto"`).
|
`behavior: "auto"`).
|
||||||
|
|
||||||
On viewports below `768.98 px` the same surface re-styles into a
|
The trigger uses `position: fixed` instead of `position: sticky`.
|
||||||
fixed bottom-sheet anchored above the layout-owned bottom-tabs
|
Per the CSS sticky spec a sticky element sticks within its nearest
|
||||||
|
ancestor with non-`visible` overflow — and `.active-view-host`
|
||||||
|
declares `overflow-y: auto` for the mobile scroll story. On
|
||||||
|
desktop the host grows with content and the document body becomes
|
||||||
|
the actual scroll container, so a sticky trigger inside the report
|
||||||
|
column never receives a scroll event and rides up with the page
|
||||||
|
content. A fixed trigger sidesteps the chain entirely; the
|
||||||
|
component is mounted only while the report active view is on
|
||||||
|
screen, so the fixed element is naturally tied to the view's
|
||||||
|
lifetime. The desktop offset is `right: calc(18 rem + 1.25 rem)`
|
||||||
|
to clear the always-on `lib/sidebar/sidebar.svelte`; below
|
||||||
|
1024 px the sidebar collapses to an overlay drawer, so the
|
||||||
|
default `right: 1.25 rem` matches the report's right padding. The
|
||||||
|
report-view itself adds a top padding equal to the trigger's
|
||||||
|
viewport offset plus its height so the first section's heading
|
||||||
|
does not render under the trigger at scroll position 0, and every
|
||||||
|
`<section id="report-…">` gets `scroll-margin-top: 7.5rem` so
|
||||||
|
`scrollIntoView({ block: "start" })` lands the heading below the
|
||||||
|
trigger after a popover-driven jump.
|
||||||
|
|
||||||
|
On viewports below `768.98 px` the popover surface re-styles into
|
||||||
|
a fixed bottom-sheet anchored above the layout-owned bottom-tabs
|
||||||
bar (mirrors `lib/active-view/map-toggles.svelte`), so the same
|
bar (mirrors `lib/active-view/map-toggles.svelte`), so the same
|
||||||
trigger and the same menuitem list serve desktop and mobile.
|
trigger and the same menuitem list serve desktop and mobile.
|
||||||
|
|
||||||
|
|||||||
@@ -134,10 +134,14 @@ TOC and the body iterate the same data.
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/*
|
||||||
|
`padding-top` clears the fixed TOC trigger that lives in the
|
||||||
|
viewport's top-right corner (`report/report-toc.svelte`,
|
||||||
|
`position: fixed; top: 4rem`). Without this padding the first
|
||||||
|
section (galaxy-summary) renders directly under the trigger.
|
||||||
|
*/
|
||||||
.report-view {
|
.report-view {
|
||||||
display: flex;
|
padding: 4.5rem 1.25rem 2rem;
|
||||||
flex-direction: column;
|
|
||||||
padding: 1rem 1.25rem 2rem;
|
|
||||||
font-family: system-ui, sans-serif;
|
font-family: system-ui, sans-serif;
|
||||||
}
|
}
|
||||||
.report-body {
|
.report-body {
|
||||||
@@ -146,9 +150,18 @@ TOC and the body iterate the same data.
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1.75rem;
|
gap: 1.75rem;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
`scroll-margin-top` lets `scrollIntoView({ block: "start" })`
|
||||||
|
land the section *heading* in view (not behind the fixed
|
||||||
|
trigger or the sticky in-game header). Budget: ~3 rem header +
|
||||||
|
~3 rem trigger area + ~1.5 rem breathing room.
|
||||||
|
*/
|
||||||
|
.report-body :global(section[id^="report-"]) {
|
||||||
|
scroll-margin-top: 7.5rem;
|
||||||
|
}
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
.report-view {
|
.report-view {
|
||||||
padding: 0.75rem;
|
padding: 4rem 0.75rem 0.75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -143,14 +143,43 @@ in F8-09: that switch is available in the app-shell view menu.
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/*
|
||||||
|
The trigger uses `position: fixed` rather than `position: sticky`
|
||||||
|
because on desktop the in-game shell's `.active-view-host`
|
||||||
|
declares `overflow-y: auto` while staying tall enough that no
|
||||||
|
overflow actually engages — the document body scrolls instead.
|
||||||
|
Per the CSS sticky spec the host is the "scrollport" for any
|
||||||
|
sticky descendant, so a sticky trigger inside the report column
|
||||||
|
never receives a scroll event and rides up with the page. Fixed
|
||||||
|
positioning anchors the trigger to the viewport directly; the
|
||||||
|
component is only mounted while the report active view is on
|
||||||
|
screen, so the fixed element is naturally tied to the view's
|
||||||
|
lifetime.
|
||||||
|
|
||||||
|
`top: 4rem` clears the sticky header (~3 rem). On ≥ 1024 px the
|
||||||
|
sidebar (`lib/sidebar/sidebar.svelte`) is always visible and
|
||||||
|
occupies the right 18 rem of the viewport, so the trigger has to
|
||||||
|
be pushed left by that much to stay inside the report column.
|
||||||
|
Below 1024 px the sidebar collapses into an overlay drawer,
|
||||||
|
so a viewport-right anchor of 1.25 rem matches the report's
|
||||||
|
right padding.
|
||||||
|
|
||||||
|
When the history banner is showing (historical turn view,
|
||||||
|
~2 rem extra) the trigger sits at the banner's lower edge —
|
||||||
|
acceptable for the rare historical-turn read path.
|
||||||
|
*/
|
||||||
.report-toc {
|
.report-toc {
|
||||||
position: sticky;
|
position: fixed;
|
||||||
top: 0.5rem;
|
top: 4rem;
|
||||||
align-self: flex-end;
|
right: 1.25rem;
|
||||||
margin-left: auto;
|
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
font-family: system-ui, sans-serif;
|
font-family: system-ui, sans-serif;
|
||||||
}
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.report-toc {
|
||||||
|
right: calc(18rem + 1.25rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
.trigger {
|
.trigger {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -221,6 +250,9 @@ in F8-09: that switch is available in the app-shell view menu.
|
|||||||
border-left-color: var(--color-accent);
|
border-left-color: var(--color-accent);
|
||||||
}
|
}
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
|
.report-toc {
|
||||||
|
right: 0.75rem;
|
||||||
|
}
|
||||||
.surface {
|
.surface {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: auto;
|
top: auto;
|
||||||
|
|||||||
Reference in New Issue
Block a user