Improve mobile navigation and responsive layout
All checks were successful
Build Authoring Service / build (push) Successful in 9s

This commit is contained in:
2026-07-23 23:08:27 +01:00
parent 1a49838b58
commit 19a63069b2

View File

@@ -216,7 +216,36 @@ APP_HTML = r"""<!doctype html>
.queue-item span { color: #b9a789; overflow-wrap: anywhere; }
pre { white-space: pre-wrap; overflow: auto; max-height: 280px; background: #0d0c0a; color: #ecdcbf; border: 1px solid rgba(217, 190, 124, 0.24); padding: 15px; border-radius: 8px; font-size: 12px; box-shadow: var(--shadow); }
@media (max-width: 980px) { .content-layout.previewing { grid-template-columns: 1fr; } }
@media (max-width: 860px) { .app { grid-template-columns: 1fr; } aside, main { max-height: none; } aside { border-right: 0; border-bottom: 1px solid var(--line); } .grid { grid-template-columns: 1fr; } .link-picker { grid-template-columns: 1fr; } }
@media (max-width: 860px) {
body { min-width: 0; }
.app { display: block; min-height: 100dvh; }
aside, main { max-height: none; }
aside {
border-right: 0;
border-bottom: 1px solid var(--line);
padding: 14px;
box-shadow: 0 14px 36px rgba(0, 0, 0, 0.22);
}
main { padding: 16px 12px 28px; }
aside .topbar { position: sticky; top: 0; z-index: 4; margin: -14px -14px 12px; padding: 12px 14px; background: rgba(24, 21, 17, 0.96); backdrop-filter: blur(14px); }
aside .page-list { max-height: min(42dvh, 430px); overflow: auto; overscroll-behavior: contain; padding-right: 3px; }
.grid, .link-picker { grid-template-columns: 1fr; }
form { padding: 14px; gap: 14px; }
form > .topbar { align-items: flex-start; flex-direction: column; }
main h1 { font-size: clamp(25px, 8vw, 31px); }
button { min-height: 44px; }
input, textarea, select { min-height: 44px; font-size: 16px; }
textarea { min-height: 42dvh; }
.toolbar, .actions { gap: 8px; }
.toolbar button, .actions button { flex: 1 1 132px; }
.preview-panel { min-height: 36dvh; padding: 15px; }
}
@media (max-width: 420px) {
.quick-link { margin-right: 10px; }
.tree-children { margin-left: 7px; padding-left: 7px; }
form { padding: 12px; }
.toolbar button, .actions button { flex-basis: 100%; }
}
</style>
</head>
<body>
@@ -1704,6 +1733,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
font-size: 13px;
line-height: 1.4;
}
.mobile-nav { display: none; }
.hidden { display: none !important; }
@keyframes breathe {
0%, 100% { opacity: 0.72; }
@@ -1821,17 +1851,158 @@ HIDDEN_APP_HTML = r"""<!doctype html>
.map-head .actions { justify-content: flex-start; max-width: none; }
}
@media (max-width: 720px) {
.map-head { position: static; padding: 16px; display: grid; }
#graph { top: 128px; height: calc(100% - 128px); }
body { overflow: hidden; min-width: 0; }
button { min-height: 44px; }
input, textarea, select { min-height: 44px; font-size: 16px; }
.observatory {
display: block;
height: 100dvh;
min-height: 0;
padding-bottom: calc(62px + env(safe-area-inset-bottom));
}
.left, .map, .drawer {
display: none;
width: 100%;
height: calc(100dvh - 62px - env(safe-area-inset-bottom));
max-height: none;
border: 0;
}
.observatory[data-mobile-view="library"] .left,
.observatory[data-mobile-view="sky"] .map,
.observatory[data-mobile-view="editor"] .drawer { display: block; }
.left, .drawer {
overflow-y: auto;
overscroll-behavior: contain;
padding: 14px 12px 28px;
}
.left > h1 { margin-top: 0; font-size: 28px; }
.drawer { border-left: 0; }
.mobile-nav {
position: fixed;
z-index: 60;
left: 0;
right: 0;
bottom: 0;
height: calc(62px + env(safe-area-inset-bottom));
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 6px;
padding: 7px 8px calc(7px + env(safe-area-inset-bottom));
border-top: 1px solid var(--line-strong);
background: rgba(15, 13, 11, 0.96);
backdrop-filter: blur(18px);
box-shadow: 0 -12px 34px rgba(0, 0, 0, 0.34);
}
.mobile-nav button {
min-width: 0;
min-height: 48px;
display: grid;
place-items: center;
padding: 4px 8px;
border-color: transparent;
color: var(--muted);
font-size: 12px;
font-weight: 850;
}
.mobile-nav button::before { display: block; font-size: 17px; line-height: 1; }
.mobile-nav button[data-mobile-view="library"]::before { content: ""; }
.mobile-nav button[data-mobile-view="sky"]::before { content: ""; }
.mobile-nav button[data-mobile-view="editor"]::before { content: ""; }
.mobile-nav button.active {
color: #ffe2a6;
border-color: rgba(211, 166, 77, 0.5);
background: rgba(211, 166, 77, 0.16);
}
.map { position: relative; min-height: 0; overflow: hidden; }
.map-head {
position: static;
height: 136px;
padding: 12px;
display: grid;
gap: 8px;
overflow: hidden;
background: rgba(15, 13, 11, 0.86);
border-bottom: 1px solid var(--line);
}
.map-head h1 { margin: 0; font-size: 22px; }
.map-head .subtle { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.map-head .actions {
flex-wrap: nowrap;
justify-content: flex-start;
max-width: none;
overflow-x: auto;
overscroll-behavior-x: contain;
padding-bottom: 4px;
scrollbar-width: thin;
}
.map-head .actions button { flex: 0 0 auto; }
#graph { top: 136px; height: calc(100% - 136px); }
.breadcrumb {
left: 10px;
top: 146px;
max-width: calc(100% - 20px);
flex-wrap: nowrap;
overflow-x: auto;
white-space: nowrap;
padding: 6px;
}
.breadcrumb button { min-height: 36px; }
.active-state {
left: 10px;
top: 198px;
max-width: calc(100% - 20px);
max-height: 92px;
flex-wrap: nowrap;
overflow: auto;
white-space: nowrap;
padding: 6px;
}
.minimap { right: 10px; bottom: 10px; width: 118px; }
.minimap svg { height: 66px; }
.map-note {
left: 10px;
right: 138px;
bottom: 10px;
max-width: none;
max-height: 72px;
overflow: auto;
padding: 8px 9px;
font-size: 12px;
}
.context-menu { left: 10px !important; right: 10px; bottom: calc(72px + env(safe-area-inset-bottom)); top: auto !important; min-width: 0; }
.workspace-switcher, .editor-modebar { position: sticky; top: -14px; z-index: 5; padding: 10px 0; background: rgba(19, 16, 13, 0.96); backdrop-filter: blur(14px); }
.modebar { display: grid; grid-template-columns: 1fr; }
.filters { grid-template-columns: 1fr 1fr; }
.filters #search { grid-column: 1 / -1; }
.left > .actions button { flex: 1 1 120px; }
.editor-grid { grid-template-columns: 1fr; }
.appearance-grid { grid-template-columns: 1fr; }
.story-flow-item { grid-template-columns: 30px minmax(0, 1fr); }
.story-flow-item > :last-child { grid-column: 1 / -1; }
.panel { padding: 11px; box-shadow: 0 14px 42px rgba(0, 0, 0, 0.24); }
.panel + .panel { margin-top: 10px; }
.actions { gap: 8px; }
.actions button { flex: 1 1 128px; }
.reader { padding: 18px; }
.reader-head, .reader-foot { align-items: stretch; flex-direction: column; }
.reader-stage { min-height: 0; overflow: auto; }
}
@media (max-width: 390px) {
.filters { grid-template-columns: 1fr; }
.filters #search { grid-column: auto; }
.workspace-switcher, .editor-modebar { grid-template-columns: 1fr; }
.map-note { right: 10px; }
.minimap { display: none; }
}
</style>
</head>
<body>
<div class="observatory">
<div class="observatory" data-mobile-view="sky">
<nav class="mobile-nav" aria-label="Observatory views">
<button type="button" data-mobile-view="library">Library</button>
<button type="button" data-mobile-view="sky" class="active" aria-current="page">Sky</button>
<button type="button" data-mobile-view="editor">Editor</button>
</nav>
<aside class="left">
<h1>Memory Observatory</h1>
<div class="subtle">A constellation map for the hidden soul of the website.</div>
@@ -2110,6 +2281,19 @@ HIDDEN_APP_HTML = r"""<!doctype html>
const arcFields = ["arcTitle","arcDomain","arcVisibility","arcTone","arcThemes","arcSummary","arcOrgWeb"];
const chapterFields = ["chapterTitle","chapterTimeframe","chapterTone","chapterPurpose","chapterStoryDraft"];
const $ = (id) => document.getElementById(id);
const mobileViewport = window.matchMedia("(max-width: 720px)");
function setMobileView(view) {
const observatory = document.querySelector(".observatory");
if (!observatory || !["library", "sky", "editor"].includes(view)) return;
observatory.dataset.mobileView = view;
document.querySelectorAll(".mobile-nav [data-mobile-view]").forEach((button) => {
const active = button.dataset.mobileView === view;
button.classList.toggle("active", active);
if (active) button.setAttribute("aria-current", "page");
else button.removeAttribute("aria-current");
});
if (view === "sky") window.requestAnimationFrame(() => applyCamera());
}
const toneColors = { warm: "#d3a64d", funny: "#7fb089", nostalgic: "#d06b78", wise: "#75a9bd", strange: "#a58ac9", soft: "#e6bd8c", hopeful: "#9fcf9f", protective: "#d98f71", melancholy: "#8fa7c6" };
const appearanceLabels = {
observatory: "Observatory stars",
@@ -3384,7 +3568,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
node.addEventListener("contextmenu", (event) => showContextMenu(event, node.dataset.id));
});
graph.querySelectorAll(".memory-node").forEach((node) => {
node.addEventListener("click", () => selectNode(node.dataset.id));
node.addEventListener("click", () => selectNode(node.dataset.id, { revealEditor: true }));
node.addEventListener("dblclick", () => zoomToNode(node.dataset.id));
node.addEventListener("pointerdown", (event) => beginNodeDrag(event, node.dataset.id));
node.addEventListener("contextmenu", (event) => showContextMenu(event, node.dataset.id));
@@ -3660,6 +3844,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
if (options.keepEditorMode === "story") setEditorMode("story", "Story route is active. The selected memory is highlighted inside the sequence.");
renderStoryBuilder();
render();
if (options.revealEditor && mobileViewport.matches) setMobileView("editor");
}
function relationForEvent(event) {
return { key: "stories", kind: "story-path", label: "Add to story route" };
@@ -4590,8 +4775,8 @@ HIDDEN_APP_HTML = r"""<!doctype html>
render();
});
});
$("newBtn").onclick = () => newEntry();
$("newStoryBtn").onclick = () => newStory();
$("newBtn").onclick = () => { newEntry(); if (mobileViewport.matches) setMobileView("editor"); };
$("newStoryBtn").onclick = () => { newStory(); if (mobileViewport.matches) setMobileView("editor"); };
$("newArcBtn").onclick = newArc;
$("newChapterBtn").onclick = newChapter;
$("deleteArcBtn").onclick = deleteArc;
@@ -4633,6 +4818,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
render();
}));
$("workspaceSwitcher").querySelectorAll("[data-workspace]").forEach((button) => button.addEventListener("click", () => setWorkspace(button.dataset.workspace)));
document.querySelectorAll(".mobile-nav [data-mobile-view]").forEach((button) => button.addEventListener("click", () => setMobileView(button.dataset.mobileView)));
$("editorModebar").querySelectorAll("[data-editor-mode]").forEach((button) => button.addEventListener("click", () => {
if (button.dataset.editorMode === "story") {
setEditorMode("story");