diff --git a/onchain_scout_gate/static/app.js b/onchain_scout_gate/static/app.js
index d0315ad..aad6d41 100644
--- a/onchain_scout_gate/static/app.js
+++ b/onchain_scout_gate/static/app.js
@@ -262,59 +262,62 @@ function renderDailyReport(payload) {
`;
}
-const PANEL_FOLD_STORAGE_KEY = "matrix_panel_fold_v1";
+const SECTION_NAV_STORAGE = "matrix_section_nav_v1";
-function initPanelFolds() {
- let saved = {};
- try {
- const raw = localStorage.getItem(PANEL_FOLD_STORAGE_KEY);
- if (raw) saved = JSON.parse(raw);
- } catch (_) {
- saved = {};
- }
+function isNavSectionActive(navId) {
+ const sec = document.querySelector(`.matrix-nav-section[data-nav-id="${navId}"]`);
+ return !!(sec && sec.classList.contains("is-nav-active"));
+}
- function applyFold(panel, folded) {
- const toggle = panel.querySelector(".matrix-panel-fold-toggle");
- if (folded) {
- panel.classList.add("is-folded");
- if (toggle) toggle.setAttribute("aria-expanded", "false");
- } else {
- panel.classList.remove("is-folded");
- if (toggle) toggle.setAttribute("aria-expanded", "true");
+function initSectionNav() {
+ const nav = document.getElementById("sectionNav");
+ if (!nav) return;
+ const buttons = nav.querySelectorAll(".matrix-nav-item[data-nav-id]");
+ const sections = document.querySelectorAll(".matrix-nav-section[data-nav-id]");
+
+ function activate(navId, opts = {}) {
+ const scrollTop = opts.scrollTop !== false;
+ buttons.forEach((b) => b.classList.toggle("is-active", b.dataset.navId === navId));
+ sections.forEach((s) => s.classList.toggle("is-nav-active", s.dataset.navId === navId));
+ try {
+ sessionStorage.setItem(SECTION_NAV_STORAGE, navId);
+ } catch (_) {
+ /* ignore */
+ }
+ if (scrollTop) {
+ nav.scrollIntoView({ behavior: "smooth", block: "nearest" });
+ }
+ try {
+ const url = `#${navId}`;
+ if (location.hash !== url) history.replaceState(null, "", url);
+ } catch (_) {
+ /* ignore */
}
}
- document.querySelectorAll(".matrix-panel-fold[data-fold-id]").forEach((panel) => {
- const id = panel.dataset.foldId;
- const defaultOpen = panel.dataset.foldDefault === "open";
- let folded;
- if (Object.prototype.hasOwnProperty.call(saved, id)) {
- folded = saved[id] === true;
- } else {
- folded = !defaultOpen;
- }
- applyFold(panel, folded);
-
- const toggle = panel.querySelector(".matrix-panel-fold-toggle");
- if (!toggle) return;
-
- const onToggle = (e) => {
- if (e.target.closest("[data-fold-ignore]")) return;
- const willFold = !panel.classList.contains("is-folded");
- applyFold(panel, willFold);
- saved[id] = willFold;
- try {
- localStorage.setItem(PANEL_FOLD_STORAGE_KEY, JSON.stringify(saved));
- } catch (_) {
- /* ignore quota */
+ let initial = "gemma-funnel";
+ const hash = (location.hash || "").replace(/^#/, "");
+ if (hash && document.querySelector(`.matrix-nav-section[data-nav-id="${hash}"]`)) {
+ initial = hash;
+ } else {
+ try {
+ const saved = sessionStorage.getItem(SECTION_NAV_STORAGE);
+ if (saved && document.querySelector(`.matrix-nav-section[data-nav-id="${saved}"]`)) {
+ initial = saved;
}
- };
+ } catch (_) {
+ /* ignore */
+ }
+ }
+ activate(initial, { scrollTop: false });
- toggle.addEventListener("click", onToggle);
- toggle.addEventListener("keydown", (e) => {
- if (e.key === "Enter" || e.key === " ") {
- e.preventDefault();
- onToggle(e);
+ buttons.forEach((btn) => {
+ btn.addEventListener("click", () => {
+ const id = btn.dataset.navId;
+ if (!id) return;
+ activate(id);
+ if (id === "scan-layers" || id === "runtime-logs" || id === "telemetry") {
+ refresh();
}
});
});
@@ -430,11 +433,6 @@ async function saveDailyReportSettings() {
}
}
-function isPanelFolded(foldId) {
- const panel = document.querySelector(`.matrix-panel-fold[data-fold-id="${foldId}"]`);
- return !!(panel && panel.classList.contains("is-folded"));
-}
-
async function refresh() {
if (document.visibilityState !== "visible") return;
const mobileLite = isMobileLite();
@@ -452,7 +450,7 @@ async function refresh() {
const statusPre = document.getElementById("status");
const cf = document.getElementById("config");
- if (!mobileLite) {
+ if (!mobileLite || isNavSectionActive("telemetry")) {
if (statusPre) {
const st = pretty(status);
if (statusPre.textContent !== st) statusPre.textContent = st;
@@ -512,7 +510,7 @@ async function refresh() {
const watchRows = allAlerts.filter((a) => (a.details && a.details.signal_level) === "WATCH");
const triggerRows = allAlerts.filter((a) => (a.details && a.details.signal_level) === "TRIGGER");
- if (!mobileLite || !isPanelFolded("scan-layers")) {
+ if (isNavSectionActive("scan-layers")) {
renderItems("watchAlerts", watchRows, (a) => `
${a.symbol} ${escapeHtml(a.chain || "")}
级别: ${(a.details && a.details.signal_level) || "N/A"}
@@ -541,7 +539,7 @@ async function refresh() {
}
}
- if (!mobileLite || !isPanelFolded("runtime-logs")) {
+ if (isNavSectionActive("runtime-logs")) {
renderItems("logs", logs.items || [], (l) => `
[${l.level}] ${escapeHtml(l.message)}
${formatIsoToBeijing(l.created_at)}
@@ -877,7 +875,7 @@ tickClock();
setInterval(tickClock, 1000);
initMatrixRain();
initFunnelWindowControls();
-initPanelFolds();
+initSectionNav();
refresh();
const REFRESH_MS = isMobileLite() ? 10000 : 4000;
setInterval(refresh, REFRESH_MS);
diff --git a/onchain_scout_gate/static/style.css b/onchain_scout_gate/static/style.css
index d6ae676..bc6123b 100644
--- a/onchain_scout_gate/static/style.css
+++ b/onchain_scout_gate/static/style.css
@@ -885,6 +885,70 @@ pre {
background: linear-gradient(180deg, rgba(0, 30, 28, 0.65), transparent);
}
+.matrix-section-nav {
+ position: sticky;
+ top: 0;
+ z-index: 85;
+ margin: 0 auto;
+ width: 100%;
+ max-width: min(1760px, 100%);
+ padding: 0 0 10px;
+ background: linear-gradient(180deg, rgba(0, 12, 10, 0.97) 70%, rgba(0, 12, 10, 0.85));
+ border-bottom: 1px solid rgba(0, 255, 213, 0.2);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
+}
+
+.matrix-section-nav-inner {
+ display: flex;
+ flex-wrap: nowrap;
+ gap: 8px;
+ overflow-x: auto;
+ overflow-y: hidden;
+ -webkit-overflow-scrolling: touch;
+ scrollbar-width: thin;
+ padding: 4px 2px 6px;
+}
+
+.matrix-nav-item {
+ flex: 0 0 auto;
+ font-family: inherit;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.04em;
+ padding: 10px 14px;
+ border-radius: 2px;
+ border: 1px solid rgba(0, 255, 213, 0.28);
+ background: rgba(0, 20, 18, 0.75);
+ color: #7dffc8;
+ cursor: pointer;
+ white-space: nowrap;
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
+}
+
+.matrix-nav-item:hover {
+ border-color: rgba(0, 255, 213, 0.55);
+ color: #b8fff0;
+}
+
+.matrix-nav-item.is-active {
+ border-color: rgba(255, 0, 170, 0.55);
+ background: linear-gradient(135deg, rgba(0, 255, 213, 0.18), rgba(255, 0, 170, 0.12));
+ color: #e8fff9;
+ box-shadow: 0 0 16px rgba(0, 255, 213, 0.15);
+}
+
+.matrix-nav-section {
+ display: none;
+}
+
+.matrix-nav-section.is-nav-active {
+ display: block;
+}
+
+.matrix-panel-body {
+ margin-top: 4px;
+}
+
.matrix-title-wrap {
position: relative;
}
@@ -1527,6 +1591,17 @@ body.matrix-theme {
padding: 8px 10px;
}
+ .matrix-section-nav {
+ top: 0;
+ padding-bottom: 8px;
+ }
+
+ .matrix-nav-item {
+ font-size: 10px;
+ padding: 10px 12px;
+ min-height: 40px;
+ }
+
.matrix-main {
padding: 12px 0 24px;
gap: 14px;
diff --git a/onchain_scout_gate/templates/dashboard.html b/onchain_scout_gate/templates/dashboard.html
index 6c74bfc..a03efd1 100644
--- a/onchain_scout_gate/templates/dashboard.html
+++ b/onchain_scout_gate/templates/dashboard.html
@@ -87,16 +87,27 @@
+
+
-
-
+
+
// GEMMA 漏斗 · 优先矩阵
-
- LIVE FEED
- ▼
-
+ LIVE FEED
-
+
-
-
+
+
// 关键位突破监控
-
- 人工录入 · 5m 门控
- ▼
-
+ 人工录入 · 5m 门控
-
+
GEMMA 漏斗仅供参考;在此录入上/下沿。箱体突破与收敛突破均支持「标准突破」或「趋势突破」(无 1.5H 方案)。
// 规则加载中…
@@ -166,15 +174,12 @@
-
-
+
+
// 每日晨报 · 昨日复盘
-
-
- ▼
-
+
-
+
@@ -192,15 +197,12 @@
-
-
+
+
// 监控黑名单 · base
-
- 0 条规则
- ▼
-
+ 0 条规则
-
+
Gate USDT 永续左侧 base(每行一个或逗号分隔),如 XAU XAUT XAG;all_swaps 与
watchlist 均会在入池后剔除。保存后下一轮扫描生效。
@@ -220,15 +222,12 @@
-
-
+
+
// 下单执行器 · 转发链
-
- 仅扫描端维护 · 同一信号广播
- ▼
-
+ 仅扫描端维护 · 同一信号广播
-
+
关键位门控通过且计划 RR 达标后,向列表中已启用的执行器 POST /v1/signal(单一 SL/TP,与录入方案一致)。
各执行器自行配置 Gate API、盈亏比、移动保本等;不支持执行器反向注册。
@@ -258,12 +257,11 @@
-
-
+
+
// 策略寄存器 · 5m
- ▼
-
+
全市场雷达:横盘 + 5m 突破 + 放量 · 仅参考推送 · 正式下单请用「关键位突破监控」
@@ -286,13 +284,12 @@
-
-
+
+
// 全市场扫描 · 观察层 / 触发层
- ▼
-
-
自动箱体雷达 · 默认折叠 · 与关键位下单无关
+
+
自动箱体雷达 · 与关键位下单无关
// 观察层 · WATCH
@@ -306,12 +303,11 @@
-