console: add always-visible back-to-top button + collapsible dashboard panels (per-section, persisted)

This commit is contained in:
2026-10-07 15:06:22 +11:00
parent 6f32301540
commit e02e5712d2
4 changed files with 533 additions and 1 deletions

View File

@@ -254,7 +254,6 @@ button.button, input[type="submit"].button { font-family: inherit; }
/* loading pulse for polling */
.tool-grid.htmx-request .tool-card { opacity: .6; transition: opacity .2s; }
/* -------------------------------------------------- Tool embed */
.tool-bar {
position: sticky; top: 52px; z-index: 15;
@@ -345,3 +344,44 @@ button.button, input[type="submit"].button { font-family: inherit; }
[data-theme="dark"] .form-error { background: #3a2424; color: #ffb4ab; border-color: #7a4e4c; }
[data-theme="dark"] .tool-icon { color: #1c1e22; }
[data-theme="dark"] .category-pill { background: var(--surface-2); }
/* ---------- Back to top ---------- */
.back-to-top {
position: fixed;
right: 24px;
bottom: 24px;
z-index: 100;
width: 44px;
height: 44px;
border-radius: var(--rounded-full);
border: 1px solid var(--hairline);
background: var(--surface-2);
color: var(--ink);
font-size: 20px;
line-height: 1;
cursor: pointer;
box-shadow: 0 4px 14px rgba(0,0,0,.15);
display: flex; align-items: center; justify-content: center;
opacity: .85;
transition: opacity .2s, transform .15s;
}
.back-to-top:hover { opacity: 1; transform: translateY(-2px); }
[data-theme="dark"] .back-to-top { background: var(--surface-2); box-shadow: 0 4px 14px rgba(0,0,0,.5); }
/* ---------- Collapsible dashboard panels ---------- */
.section-toggle {
background: none;
border: 1px solid var(--hairline);
color: var(--ink);
border-radius: var(--rounded-full);
width: 26px; height: 26px;
line-height: 1;
cursor: pointer;
font-size: 14px;
display: inline-flex; align-items: center; justify-content: center;
opacity: .7;
transition: transform .2s, opacity .2s;
}
.section-toggle:hover { opacity: 1; }
.section.collapsed .section-toggle { transform: rotate(-90deg); }
.section.collapsed .tool-grid { display: none; }

View File

@@ -33,6 +33,7 @@
<main>
{% block content %}{% endblock %}
</main>
<button type="button" id="backToTop" class="back-to-top" aria-label="Back to top" title="Back to top">↑</button>
<script>
(function () {
var root = document.documentElement;
@@ -53,5 +54,15 @@
sync();
})();
</script>
<script>
(function () {
var btn = document.getElementById('backToTop');
if (!btn) return;
btn.addEventListener('click', function () {
(window.scrollTo || function(){ document.documentElement.scrollTop = 0; document.body.scrollTop = 0; })
.call(window, { top: 0, behavior: 'smooth' });
});
})();
</script>
</body>
</html>

View File

@@ -11,6 +11,8 @@
{% for section in sections %}
<section class="section" data-dash-section>
<div class="section-head">
<button type="button" class="section-toggle" data-section-toggle
title="Collapse / expand this panel" aria-label="Collapse expand panel" aria-expanded="true">▾</button>
<span class="category-pill">
<span class="pill-dot" style="background:{{ section.color }}"></span>
{{ section.label }}
@@ -67,5 +69,26 @@
var inp = document.querySelector("[data-filter-input]");
if (inp) inp.value = window.__dashFilter;
apply();
// --- Collapsible panels (persist per-section in localStorage) ---
function paneKey(label) { return "fhlPane_" + (label || "").trim().toLowerCase().replace(/\s+/g, "_"); }
function syncToggle(sec) {
var b = sec.querySelector("[data-section-toggle]");
if (b) b.setAttribute("aria-expanded", sec.classList.contains("collapsed") ? "false" : "true");
}
document.querySelectorAll("[data-dash-section]").forEach(function (sec) {
var head = sec.querySelector(".section-head");
var label = head ? head.textContent.trim() : "";
var wasCollapsed = false;
try { wasCollapsed = localStorage.getItem(paneKey(label)) === "1"; } catch (_) {}
if (wasCollapsed) sec.classList.add("collapsed");
syncToggle(sec);
var b = sec.querySelector("[data-section-toggle]");
if (b) b.addEventListener("click", function () {
sec.classList.toggle("collapsed");
try { localStorage.setItem(paneKey(label), sec.classList.contains("collapsed") ? "1" : "0"); } catch (_) {}
syncToggle(sec);
});
});
})();
</script>