diff --git a/assets/layout.html b/assets/layout.html
index 9e8088d..f8bbe1f 100644
--- a/assets/layout.html
+++ b/assets/layout.html
@@ -45,6 +45,21 @@
{{if not .EditMode}}{{end}}
diff --git a/assets/style.css b/assets/style.css
index 1465054..a0a639f 100644
--- a/assets/style.css
+++ b/assets/style.css
@@ -48,6 +48,28 @@
--reading-width: 60rem;
}
+/* Cross-document view transitions: page navigations stay full reloads, but the
+ browser holds the old frame until the new page is ready and swaps them
+ without a blank flash. The header and tree rail are named so they swap in
+ place (no crossfade) — only the rest of the page fades, and only briefly. */
+@view-transition { navigation: auto; }
+body > header { view-transition-name: app-header; }
+.tree-sidebar { view-transition-name: tree-sidebar; }
+::view-transition-group(*),
+::view-transition-old(*),
+::view-transition-new(*) { animation-duration: 0.12s; }
+::view-transition-group(app-header),
+::view-transition-group(tree-sidebar),
+::view-transition-old(app-header),
+::view-transition-new(app-header),
+::view-transition-old(tree-sidebar),
+::view-transition-new(tree-sidebar) { animation: none; }
+@media (prefers-reduced-motion: reduce) {
+ ::view-transition-group(*),
+ ::view-transition-old(*),
+ ::view-transition-new(*) { animation: none; }
+}
+
/* Anchor / TOC jumps are handled by the .center scroll container's own
scroll-padding-top (the header no longer overlaps content in the app-shell),
so no html-level scroll padding is needed. */
diff --git a/assets/tree-sidebar.js b/assets/tree-sidebar.js
index 079daf7..61fb3a2 100644
--- a/assets/tree-sidebar.js
+++ b/assets/tree-sidebar.js
@@ -40,6 +40,7 @@
}
})()
var activeRow = null
+ var live = false // set once the fetched tree is on screen
function disabledRow(text) {
var row = document.createElement("div")
@@ -169,15 +170,26 @@
}
function render() {
- container.textContent = ""
+ // If the layout pre-painted a snapshot of the previous page's tree, keep it
+ // on screen and build the live tree off-DOM, swapping it in once ready —
+ // no "…" placeholder, no scroll jump.
var listing = document.createElement("div")
- container.appendChild(listing)
- listing.appendChild(disabledRow("…"))
+ var hasSnapshot = container.firstChild !== null
+ if (!hasSnapshot) {
+ container.appendChild(listing)
+ listing.appendChild(disabledRow("…"))
+ }
fetchFolder("/", activePath === "/" ? "" : activePath)
.then(function (resp) {
listing.textContent = ""
renderInto(listing, "/", resp.entries)
+ if (hasSnapshot) {
+ var top = container.scrollTop
+ container.replaceChildren(listing)
+ container.scrollTop = top
+ }
+ live = true
if (activeRow) {
try {
activeRow.scrollIntoView({ block: "nearest" })
@@ -185,17 +197,34 @@
}
})
.catch(function () {
+ live = false
+ container.replaceChildren(listing)
listing.textContent = ""
var err = document.createElement("div")
err.className = "tree-row"
err.textContent = "(failed — tap to retry)"
err.addEventListener("click", function () {
+ container.textContent = ""
render()
})
listing.appendChild(err)
})
}
+ // Snapshot the tree (markup + scroll) on the way out so the next page's
+ // layout can paint it immediately. Skipped until the live tree has rendered,
+ // so a placeholder or error row never becomes the snapshot. The active
+ // highlight is left out; the next page marks its own row.
+ window.addEventListener("pagehide", function () {
+ if (!live) return
+ if (activeRow) activeRow.classList.remove("is-active")
+ var html = container.innerHTML
+ if (activeRow) activeRow.classList.add("is-active") // bfcache may restore this page
+ try {
+ sessionStorage.setItem("tree-snapshot", JSON.stringify({ html: html, top: container.scrollTop }))
+ } catch (e) {}
+ })
+
function setupMenuItem() {
var specials = document.querySelector(".actions-special")
if (!specials) return