④段整套同步(六节 + P/S/F/J + 模板)+ open-design 移到技能根

一、④段整套同步(用户选候选 A)
- 新增 `references/stage-proto-doc/assets/产品文档模板.html`(16 KB/247 行,从备份拷、转 LF)。
- ④段 SKILL.md 换成备份 15:28 那版,并按模板口径改齐:
  · 「产品文档框架」从「四块+一附录」改成**六节** —— 页面结构/功能清单/使用场景/操作流程/
    跨页与界面状态/待确认与偏离;与模板同构,⛔ 不从空白页重写结构。
  · 编号从 R/S/F 改成 **P 页面/S 场景/F 功能/J 二级功能**,各自唯一、按展示顺序通排。
  · 保留旧版有价值的要求:每项功能补齐 做什么/入口在哪/输入边界/异常与恢复;重点功能三处标红;
    横向流程图每步写清页面/功能/操作/完成判据。
  · 完成标准「七节」→「六节」,自查清单同步换掉「需求」那批条目,补模板与配图两条。
  · 路径 `designs/<项目>/` → `docs/pm/<项目>/ui/`(2 处)。
  · 补回本机那条「每份产出文档开头都要有一张图」的引用(指向主 skill,段内不复述)。
- 主 skill 同步:四段结构表④行、④段沿革、画图小节(④段画页面结构图 + 用户路径)。

二、open-design 移到技能根(用户选候选 A)
- `git mv product-planning/references/stage-delivery/assets/open-design → open-design/`(4178 文件)。
- `.gitignore` 白名单加 `!/open-design/` —— 否则移出去就不再纳入版本控制。
- 引用改 11 处:路径形态 `assets/open-design/...` → `<技能根>/open-design/...`;
  「随段携带的自带件」改称「同根技能」;「路径怎么找」那条改成自带件(段内相对路径)与
  同根技能(`<技能根>/<技能名>/`)两类分开写。
- open-design 自己的 ATTRIBUTION.md 两处同步(它现在与 oil-ui-pro 同层)。
- 副作用(有益):命名闸门读数由 2063 降到 225 —— 此前它把 open-design 里 919 个第三方 md
  当成规则一起扫了;现在只扫本包 23 个 md。已确认④段的 H1 与 4a/4b/4c 子步仍在检查范围内。

三、验收
- check_naming.py --ws E:/ProgramData/AIProject/contentm_agent = 通过 225 / 失败 0 / 提示 27。
- 活文件里 `assets/open-design` 残留 0;工作区 CRLF 文件 0。
This commit is contained in:
admin committed 2026-10-15 21:17:28 +08:00
1 parent fa0a9026c8
commit 5afeb6b78a
4184 files changed
+326 -70

No files matched your search

@@ -0,0 +1,78 @@
---
name: trading-analysis-dashboard-template
description: |
Professional trading analysis dashboard template (single-file HTML) with
light/dark theme switch, dense market panels, chart interactions, demo/live
playback, and command palette behavior.
Use when users ask for a Wall-Street-style analytics terminal, trading cockpit,
or high-tech financial dashboard template with realistic data layout.
triggers:
- "trading analysis dashboard template"
- "wall street dashboard template"
- "financial terminal template"
- "trading cockpit template"
- "交易分析面板模板"
- "华尔街风格看板"
- "高科技金融 dashboard 模板"
od:
mode: template
platform: desktop
scenario: live-artifacts
preview:
type: html
entry: index.html
reload: debounce-100
design_system:
requires: true
sections: [color, typography, layout, components]
outputs:
primary: index.html
secondary:
- template.html
- example.html
example_prompt: "Create a Wall-Street-grade trading analysis dashboard template with a left rail, risk cockpit, market charts, live/demo mode, and realistic dense data. Keep it single-file HTML."
capabilities_required:
- file_write
---
# Trading Analysis Dashboard Template
Produce a premium, data-dense, Wall-Street style trading dashboard as a self-contained HTML artifact.
## Resource map
```text
trading-analysis-dashboard-template/
├── SKILL.md
├── assets/
│ └── template.html
├── references/
│ └── checklist.md
└── example.html
```
## Workflow
1. Read active `DESIGN.md`, then map typography/color/layout into CSS variables.
2. Copy `assets/template.html` to `index.html`.
3. Personalize headings, instrument names, and numeric labels to the user brief.
4. Preserve interaction fidelity:
- Light/Dark mode switch
- Live/Demo mode
- Chart hover crosshair and tooltip
- Click-to-focus chart (floating modal style)
- Keyboard command palette (`/`)
5. Keep output single-file HTML (inline CSS + inline JS, no framework dependency).
6. Keep placeholders honest (`—` or neutral labels) where real numbers are unknown.
7. Validate against `references/checklist.md` before emitting.
## Output contract
One sentence before artifact, then:
```xml
<artifact identifier="trading-analysis-dashboard" type="text/html" title="Trading Analysis Dashboard">
<!doctype html>
<html>...</html>
</artifact>
```
@@ -0,0 +1,284 @@
<!doctype html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Trading Analysis Dashboard</title>
<style>
:root{
--bg:#0a1220;--panel:#0f1b31;--panel2:#13233e;--line:#294161;--text:#edf3ff;--muted:#94a9c7;
--accent:#43b6ff;--accent2:#8f8dff;--good:#4be2ad;--bad:#ff7f9f;--warn:#ffd777;--chip:#162843;
}
html[data-theme="light"]{
--bg:#eaf0f8;--panel:#ffffff;--panel2:#f4f8ff;--line:#d6e0ee;--text:#0d1730;--muted:#5f7392;
--accent:#1d87cf;--accent2:#7365ff;--good:#1d9b72;--bad:#c1435f;--warn:#ac7a00;--chip:#edf3fb;
}
*{box-sizing:border-box} html,body{height:100%}
body{margin:0;font-family:Inter,system-ui,-apple-system,Segoe UI,sans-serif;background:radial-gradient(1000px 400px at 15% -10%,rgba(77,153,255,.18),transparent),var(--bg);color:var(--text);}
.app{display:grid;grid-template-columns:230px 1fr;height:100%}
.side{border-right:1px solid var(--line);padding:16px;background:linear-gradient(180deg,color-mix(in srgb,var(--panel) 90%,transparent),color-mix(in srgb,var(--panel2) 80%,transparent))}
.brand{font-weight:800;letter-spacing:.14em;font-size:13px;margin:8px 8px 14px}
.nav{display:grid;gap:6px}.nav button{border:0;text-align:left;padding:10px;border-radius:10px;background:transparent;color:var(--muted);font-weight:600;cursor:pointer}
.nav button.active,.nav button:hover{background:var(--chip);color:var(--text)}
.main{padding:14px;overflow:auto}
.top{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap}
.status{display:flex;gap:8px;align-items:center;padding:7px 10px;border:1px solid var(--line);border-radius:999px;background:var(--chip);font-size:11px;color:var(--muted)}
.dot{width:8px;height:8px;border-radius:999px;background:var(--good)}
.controls{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.seg{display:inline-flex;border:1px solid var(--line);background:var(--chip);border-radius:999px;padding:4px}
.seg button{border:0;background:transparent;color:var(--muted);font-size:11px;font-weight:700;padding:7px 10px;border-radius:999px;cursor:pointer}
.seg button.active{background:var(--panel);color:var(--text)}
.chip{border:1px solid var(--line);background:var(--chip);color:var(--text);padding:7px 10px;border-radius:8px;font-size:11px;font-weight:700;cursor:pointer}
.danger{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 35%,var(--line))}
.hero{margin-top:10px;display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr;gap:10px}
.card{border:1px solid var(--line);background:linear-gradient(140deg,color-mix(in srgb,var(--panel) 96%,transparent),color-mix(in srgb,var(--panel2) 94%,transparent));border-radius:14px;padding:12px;position:relative;overflow:hidden}
.k{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
.v{margin-top:4px;font-size:24px;font-weight:800;font-family:"JetBrains Mono",ui-monospace,monospace}
.layout{display:grid;grid-template-columns:1.4fr 1fr;gap:10px;margin-top:10px}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
table{width:100%;border-collapse:collapse;font-size:12px} th,td{padding:8px;border-bottom:1px solid var(--line);text-align:left}
th{font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.badge{font-size:10px;padding:2px 6px;border-radius:999px;border:1px solid var(--line)}
.good{color:var(--good)} .bad{color:var(--bad)}
.chart-shell{position:relative}
.chart{margin-top:8px;height:200px;border:1px solid var(--line);border-radius:10px;background:color-mix(in srgb,var(--chip) 50%,transparent)}
.chart svg{width:100%;height:100%}
.chart svg .axis-text{font:600 9px "JetBrains Mono",ui-monospace,monospace;fill:var(--muted);letter-spacing:.04em}
.chart svg .axis-title{font:700 9px Inter,system-ui,sans-serif;fill:var(--muted);letter-spacing:.08em;text-transform:uppercase}
.legend{display:flex;flex-wrap:wrap;gap:10px 14px;margin-top:6px;padding:0 4px;font-size:10px;color:var(--muted);letter-spacing:.04em}
.legend .lg{display:inline-flex;align-items:center;gap:6px;font-weight:700}
.legend .lg::before{content:"";width:10px;height:2px;background:var(--lg-c,var(--accent));border-radius:2px;display:inline-block}
.cursor{position:absolute;inset:0;pointer-events:none;opacity:0}
.chart-shell.active .cursor{opacity:1}
.cursor .vx,.cursor .hy{position:absolute;background:color-mix(in srgb,var(--accent) 80%,transparent)}
.cursor .vx{width:1px;top:0;bottom:0;left:50%}
.cursor .hy{height:1px;left:0;right:0;top:50%;background:color-mix(in srgb,var(--accent2) 70%,transparent)}
.cursor .lb{position:absolute;right:8px;top:8px;background:var(--panel);border:1px solid var(--line);border-radius:6px;padding:4px 6px;font:700 10px "JetBrains Mono",monospace}
.flow{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:10px}
.step{border:1px solid var(--line);border-radius:10px;background:var(--chip);padding:10px}
.step h4{margin:0 0 5px;font-size:11px;letter-spacing:.06em;text-transform:uppercase}
.step p{margin:0;font-size:11px;color:var(--muted)}
.risk{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-top:10px}
.bar{height:6px;border-radius:999px;background:color-mix(in srgb,var(--line) 80%,transparent);overflow:hidden;margin-top:6px}
.bar>i{display:block;height:100%;background:linear-gradient(90deg,var(--accent),var(--accent2));width:var(--w,50%)}
.palette{position:fixed;inset:0;background:rgba(5,10,20,.45);backdrop-filter:blur(6px);display:none;align-items:flex-start;justify-content:center;padding-top:15vh;z-index:40}
.palette.open{display:flex}
.palette-card{width:min(680px,92vw);border:1px solid var(--line);border-radius:12px;background:var(--panel);overflow:hidden}
.palette input{width:100%;border:0;border-bottom:1px solid var(--line);padding:12px;background:var(--chip);color:var(--text);font:12px "JetBrains Mono",monospace}
.palette button{width:100%;border:0;border-bottom:1px solid var(--line);padding:10px 12px;background:transparent;color:var(--text);text-align:left;cursor:pointer}
.focus{position:fixed;inset:0;background:rgba(4,8,16,.6);backdrop-filter:blur(4px);display:none;z-index:30}
.focus.show{display:block}
.chart-shell.floating{position:fixed!important;z-index:31!important;box-shadow:0 30px 90px rgba(0,0,0,.45)!important}
@media (max-width:1150px){.hero,.risk,.flow,.layout,.grid2{grid-template-columns:1fr}.app{grid-template-columns:84px 1fr}.brand span,.nav button span{display:none}}
</style>
</head>
<body>
<div class="focus" id="focus"></div>
<div class="app">
<aside class="side">
<div class="brand"><span>PROMETHEUS</span></div>
<nav class="nav">
<button><span>Home</span></button><button><span>Trade</span></button><button class="active"><span>Analyze</span></button><button><span>Performance</span></button><button><span>Develop</span></button>
</nav>
</aside>
<main class="main">
<div class="top">
<div class="status"><span class="dot"></span><span>Market Open · NYSE · Source: Cboe One · Latency: <b id="latency">12ms</b></span></div>
<div class="controls">
<div class="seg" id="mode"><button data-mode="live" class="active">LIVE</button><button data-mode="demo">DEMO</button></div>
<select class="chip" id="speed"><option value="0.5">0.5x</option><option value="1" selected>1x</option><option value="2">2x</option></select>
<select class="chip" id="scenario"><option value="risk-on" selected>Risk-on</option><option value="vol-crush">Vol Crush</option><option value="panic">Panic</option></select>
<button class="chip danger" id="kill">KILL SWITCH</button>
<div class="seg" id="theme"><button data-theme="dark" class="active">Dark</button><button data-theme="light">Light</button></div>
</div>
</div>
<div class="hero">
<div class="card"><div class="k">Alpha Pulse</div><div class="v" id="alpha">LONG BIAS · 0.78</div><div class="k" id="alpha-sub">Expected Move +1.9% · Horizon 2h</div></div>
<div class="card"><div class="k">Confidence</div><div class="v" id="conf">78.00%</div></div>
<div class="card"><div class="k">Risk Budget</div><div class="v" id="risk-budget">$42,500.00</div></div>
<div class="card"><div class="k">Execution Window</div><div class="v">09:30-11:30 ET</div></div>
</div>
<section class="layout">
<article class="card">
<div class="k">Option Chain</div>
<table>
<thead><tr><th>Time</th><th>Symbol</th><th>Contract</th><th>Premium</th><th>Sweep</th></tr></thead>
<tbody>
<tr class="orow" data-strike="160"><td>07:46:01</td><td>SPY</td><td>375 8/18</td><td>$44,751</td><td><span class="badge good">YES</span></td></tr>
<tr class="orow" data-strike="155"><td>07:46:02</td><td>SPY</td><td>370 8/18</td><td>$38,905</td><td><span class="badge">NO</span></td></tr>
<tr class="orow" data-strike="150"><td>07:46:03</td><td>QQQ</td><td>452 8/25</td><td>$52,204</td><td><span class="badge good">YES</span></td></tr>
</tbody>
</table>
<div class="flow">
<div class="step"><h4>Signal</h4><p id="s1">Flow consensus LONG</p></div>
<div class="step"><h4>Risk Check</h4><p id="s2">Exposure within limit</p></div>
<div class="step"><h4>Order</h4><p id="s3">AAPL BUY 200 @ 221.30</p></div>
<div class="step"><h4>Fill</h4><p id="s4">Waiting...</p></div>
</div>
</article>
<article class="grid2">
<div class="card chart-shell">
<div class="k">Option Greeks</div>
<div class="chart chart-interactive">
<svg viewBox="0 0 400 200" role="img" aria-label="Option Greeks: Delta and Gamma vs Strike">
<title>Option Greeks vs Strike</title>
<g stroke="var(--line)"><line x1="30" y1="170" x2="390" y2="170"/><line x1="30" y1="20" x2="30" y2="170"/></g>
<g class="axis-text" text-anchor="middle">
<text x="40" y="186">140</text>
<text x="120" y="186">145</text>
<text x="200" y="186">150</text>
<text x="300" y="186">155</text>
<text x="380" y="186">160</text>
</g>
<g class="axis-text" text-anchor="end">
<text x="26" y="44">1.0</text>
<text x="26" y="96">0.5</text>
<text x="26" y="148">0.0</text>
<text x="26" y="174">−0.5</text>
</g>
<text class="axis-title" x="210" y="198" text-anchor="middle">Strike ($)</text>
<text class="axis-title" x="-100" y="12" transform="rotate(-90)" text-anchor="middle">Sensitivity (Δ / Γ)</text>
<polyline fill="none" stroke="#9b79f4" stroke-width="3" points="40,40 120,62 200,92 300,122 380,146"/>
<polyline fill="none" stroke="#67d59b" stroke-width="3" points="40,128 120,124 200,118 300,129 380,126"/>
<circle class="link-target strike-150" cx="200" cy="92" r="4" fill="#9b79f4"/>
<circle class="link-target strike-155" cx="300" cy="122" r="4" fill="#9b79f4"/>
<circle class="link-target strike-160" cx="380" cy="146" r="4" fill="#9b79f4"/>
</svg>
</div>
<div class="legend"><span class="lg" style="--lg-c:#9b79f4">Delta (Δ)</span><span class="lg" style="--lg-c:#67d59b">Gamma (Γ)</span></div>
<div class="cursor"><span class="vx"></span><span class="hy"></span><span class="lb">x:0 y:0</span></div>
</div>
<div class="card chart-shell">
<div class="k">Cumulative PnL</div>
<div class="chart chart-interactive">
<svg viewBox="0 0 400 200" role="img" aria-label="Cumulative PnL by session time">
<title>Cumulative PnL by Session Time</title>
<g stroke="var(--line)"><line x1="30" y1="170" x2="390" y2="170"/><line x1="30" y1="20" x2="30" y2="170"/></g>
<g class="axis-text" text-anchor="middle">
<text x="30" y="186">09:30</text>
<text x="120" y="186">11:00</text>
<text x="210" y="186">12:30</text>
<text x="300" y="186">14:00</text>
<text x="390" y="186">16:00</text>
</g>
<g class="axis-text" text-anchor="end">
<text x="26" y="62">$24k</text>
<text x="26" y="100">$16k</text>
<text x="26" y="138">$8k</text>
<text x="26" y="174">$0</text>
</g>
<text class="axis-title" x="210" y="198" text-anchor="middle">Session Time (ET)</text>
<text class="axis-title" x="-100" y="12" transform="rotate(-90)" text-anchor="middle">Equity ($, USD)</text>
<polyline id="eq" fill="none" stroke="#47cf93" stroke-width="3" points="30,150 90,142 150,130 210,118 270,95 330,78 390,62"/>
</svg>
</div>
<div class="legend"><span class="lg" style="--lg-c:#47cf93">Equity Curve ($)</span></div>
<div class="cursor"><span class="vx"></span><span class="hy"></span><span class="lb">x:0 y:0</span></div>
</div>
<div class="card">
<div class="k">Risk Cockpit</div>
<div class="risk">
<div><div class="k">VaR</div><div class="v" id="var">$12,450.00</div><div class="bar"><i style="--w:64%"></i></div></div>
<div><div class="k">Beta</div><div class="v" id="beta">1.18</div><div class="bar"><i style="--w:58%"></i></div></div>
<div><div class="k">Exposure</div><div class="v" id="expo">68.20%</div><div class="bar"><i style="--w:68%"></i></div></div>
<div><div class="k">Max DD</div><div class="v" id="dd">2.40%</div><div class="bar"><i style="--w:24%"></i></div></div>
<div><div class="k">Concentration</div><div class="v" id="conc">41.80%</div><div class="bar"><i style="--w:42%"></i></div></div>
</div>
</div>
<div class="card"><div class="k">Demo Story</div><div class="v" id="story">Idle</div></div>
</article>
</section>
</main>
</div>
<div class="palette" id="palette">
<div class="palette-card">
<input id="cmd" value="/" />
<button data-cmd="mode demo">mode demo</button>
<button data-cmd="scenario panic">scenario panic</button>
<button data-cmd="theme light">theme light</button>
<button data-cmd="focus chart">focus chart</button>
</div>
</div>
<script>
const $ = (s, p = document) => p.querySelector(s);
const $$ = (s, p = document) => [...p.querySelectorAll(s)];
const focus = $("#focus");
let floating = null, placeholder = null, demoTimer = null, stepTimer = null;
const n = (v, t) => t === "usd" ? `$${Number(v).toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:2})}` : t === "pct" ? `${Number(v).toFixed(2)}%` : Number(v).toFixed(2);
const clearTimers = () => { if (demoTimer) clearInterval(demoTimer); if (stepTimer) clearTimeout(stepTimer); demoTimer = null; stepTimer = null; };
// Storage access can throw SecurityError inside sandboxed iframes that allow
// scripts but not same-origin (OpenDesign's preview), so persistence is
// strictly best-effort and must never abort initialization.
const safeGetTheme = () => { try { return localStorage.getItem("od-trading-theme"); } catch { return null; } };
const safeSetTheme = (t) => { try { localStorage.setItem("od-trading-theme", t); } catch { /* sandbox: persistence unavailable */ } };
const setTheme = (t) => { document.documentElement.setAttribute("data-theme", t); safeSetTheme(t); $$("#theme button").forEach(b => b.classList.toggle("active", b.dataset.theme === t)); };
const savedTheme = safeGetTheme(); if (savedTheme) setTheme(savedTheme);
$$("#theme button").forEach(b => b.onclick = () => setTheme(b.dataset.theme));
const updateTick = () => {
const speed = Number($("#speed").value || 1), scenario = $("#scenario").value;
const sf = scenario === "panic" ? -1.4 : scenario === "vol-crush" ? .4 : 1, j = () => (Math.random() - .5);
$("#latency").textContent = `${Math.max(8, Math.round(12 + j() * 8 * speed))}ms`;
$("#alpha").textContent = `${scenario === "panic" ? "DEFENSIVE" : "LONG BIAS"} · ${(0.78 + j() * 0.08 * speed).toFixed(2)}`;
$("#alpha-sub").textContent = `Expected Move ${scenario === "panic" ? "-" : "+"}${(1.1 + Math.random() * 1.6).toFixed(1)}% · Horizon 2h`;
$("#conf").textContent = n(78 + j() * 5 * speed, "pct");
$("#risk-budget").textContent = n(42500 + j() * 1800 * speed, "usd");
$("#var").textContent = n(12450 + j() * 500 * speed, "usd");
$("#expo").textContent = n(68.2 + j() * 2.8 * speed, "pct");
const xs = [30,90,150,210,270,330,390], ys = [150,142,130,118,95,78,62].map(v => Math.max(48, Math.min(170, v + j() * 14 * speed * (scenario === "panic" ? 1.6 : 1))));
$("#eq").setAttribute("points", xs.map((x,i)=>`${x},${ys[i].toFixed(1)}`).join(" "));
};
const demoStory = () => {
const steps = [
["1/5 Signal spike detected", () => $("#s1").textContent = "Flow consensus LONG · TRIGGERED"],
["2/5 Risk checks passed", () => $("#s2").textContent = "Exposure within limit · PASS"],
["3/5 Order routed", () => $("#s3").textContent = "Routing AAPL BUY 200..."],
["4/5 Fill confirmed", () => $("#s4").textContent = "Filled @ 221.31 · -0.2bp"],
["5/5 PnL + telemetry updated", updateTick]
];
let i = 0; clearTimers();
demoTimer = setInterval(updateTick, Math.max(700, 2200 / Number($("#speed").value || 1)));
const run = () => { $("#story").textContent = steps[i][0]; steps[i][1](); i = (i + 1) % steps.length; stepTimer = setTimeout(run, 6000 / Number($("#speed").value || 1)); };
run();
};
$$("#mode button").forEach(b => b.onclick = () => {
$$("#mode button").forEach(x => x.classList.toggle("active", x === b));
if (b.dataset.mode === "demo") demoStory(); else { clearTimers(); $("#story").textContent = "Idle"; }
});
$("#speed").onchange = () => $("#mode .active")?.dataset.mode === "demo" && demoStory();
$("#scenario").onchange = updateTick;
$("#kill").onclick = () => { if (!confirm("Engage kill switch?")) return; clearTimers(); $("#s1").textContent = "HALTED"; $("#s2").textContent = "BLOCK"; $("#s3").textContent = "Orders cancelled"; $("#s4").textContent = "Exposure set to 0%"; $("#story").textContent = "Risk halt engaged"; };
const clearLink = () => { $$(".orow").forEach(r => r.classList.remove("active")); $$(".link-target").forEach(n => n.classList.remove("active")); };
$$(".orow").forEach(r => { r.onmouseenter = () => { clearLink(); r.classList.add("active"); $$(".strike-" + r.dataset.strike).forEach(n => n.classList.add("active")); }; r.onmouseleave = clearLink; });
$$(".chart-shell").forEach(shell => {
const cv = $(".cursor", shell), vx = $(".vx", shell), hy = $(".hy", shell), lb = $(".lb", shell);
shell.onmousemove = (e) => { const rect = shell.getBoundingClientRect(), x = ((e.clientX-rect.left)/rect.width)*100, y = ((e.clientY-rect.top)/rect.height)*100; shell.classList.add("active"); vx.style.left = `${x}%`; hy.style.top = `${y}%`; lb.textContent = `x:${x.toFixed(1)} y:${(100-y).toFixed(1)}`; };
shell.onmouseleave = () => shell.classList.remove("active");
shell.onclick = () => {
if (floating === shell) { placeholder.parentNode.insertBefore(shell, placeholder); placeholder.remove(); placeholder = null; floating = null; shell.classList.remove("floating"); shell.style = ""; focus.classList.remove("show"); document.body.style.overflow = ""; return; }
if (floating) floating.click();
const rect = shell.getBoundingClientRect(); placeholder = document.createElement("div"); placeholder.style.width = rect.width + "px"; placeholder.style.height = rect.height + "px"; shell.parentNode.insertBefore(placeholder, shell);
floating = shell; shell.classList.add("floating"); shell.style.top = Math.max(24, (innerHeight - rect.height * 1.25) / 2) + "px"; shell.style.left = Math.max(24, (innerWidth - rect.width * 1.25) / 2) + "px"; shell.style.width = Math.min(innerWidth - 48, rect.width * 1.25) + "px"; shell.style.height = Math.min(innerHeight - 48, rect.height * 1.25) + "px"; document.body.appendChild(shell); focus.classList.add("show"); document.body.style.overflow = "hidden";
};
});
focus.onclick = () => floating && floating.click();
const palette = $("#palette"), cmd = $("#cmd");
const exec = (q) => { q = q.toLowerCase(); if (q.includes("theme dark")) setTheme("dark"); if (q.includes("theme light")) setTheme("light"); if (q.includes("mode demo")) $('#mode button[data-mode="demo"]').click(); if (q.includes("mode live")) $('#mode button[data-mode="live"]').click(); if (q.includes("scenario panic")) { $("#scenario").value = "panic"; updateTick(); } if (q.includes("focus chart")) $(".chart-shell")?.click(); palette.classList.remove("open"); };
document.addEventListener("keydown", (e) => { if (e.key === "/" && !palette.classList.contains("open")) { e.preventDefault(); palette.classList.add("open"); cmd.focus(); cmd.value = "/"; } else if (e.key === "Escape") { palette.classList.remove("open"); if (floating) floating.click(); } });
cmd.onkeydown = (e) => e.key === "Enter" && exec(cmd.value);
$$("#palette button").forEach(b => b.onclick = () => exec(b.dataset.cmd || ""));
palette.onclick = (e) => e.target === palette && palette.classList.remove("open");
$("#mode button[data-mode='demo']").click();
</script>
</body>
</html>
@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Trading Analysis Dashboard Template Example</title>
<style>html,body{margin:0;height:100%;background:#0a1220}iframe{width:100%;height:100%;border:0;display:block}</style>
</head>
<body>
<!-- Iframe-only shell (recvqholN2wcep): the daemon's sandboxed preview
route unwraps a body that is EXACTLY one iframe and serves the inner
template directly (see iframeOnlyHtmlShellTarget in
apps/daemon/src/routes/plugins/assets.ts). The previous wrapper added a
header strip above the iframe, which defeated the unwrap — the nested
iframe was then blocked by the preview CSP (default-src 'none', no
frame-src), so both the live preview and the baked gallery poster
rendered a gray broken frame. Keep this file a bare iframe shell, like
flowai-live-dashboard-template. -->
<iframe src="./assets/template.html" title="Trading Analysis Dashboard Template Example"></iframe>
</body>
</html>
@@ -0,0 +1,32 @@
# Trading Analysis Dashboard Template Checklist
## P0 (must pass before emitting `<artifact>`)
- [ ] Single-file HTML output (`<!doctype html>`, inline CSS/JS, no build step).
- [ ] Dense trading layout exists: left sidebar + top status + multi-panel body.
- [ ] Light/Dark switch works and persists in `localStorage`.
- [ ] Live/Demo mode switch works (demo changes at least one KPI and one chart state).
- [ ] At least 2 chart regions include:
- [ ] axis labels (x and y)
- [ ] units / scale hints (e.g., `$`, `%`, `Δ`)
- [ ] chart legends naming each plotted series
- [ ] At least one chart supports hover crosshair + tooltip-like readout.
- [ ] At least one chart supports click-to-focus (floating/overlay) and close.
- [ ] Risk cockpit panel shows exposure-style metrics (VaR/Beta/DD or equivalent).
- [ ] Trading workflow panel exists (`Signal -> Risk -> Order -> Fill` semantics).
- [ ] Placeholder values are honest (`—` if unknown), no fabricated marketing claims.
## P1 (quality bar)
- [ ] Typography hierarchy is explicit (display / title / body / mono number styles).
- [ ] Spacing rhythm is consistent (8px-ish system or similar).
- [ ] Professional financial tone (no toy-style icon overload).
- [ ] Theme contrast is readable under both light and dark.
- [ ] Motion effects are subtle and non-blocking.
## P2 (polish)
- [ ] Command palette (`/`) shortcuts included.
- [ ] Demo story sequence (step progression) is visible.
- [ ] Tables include state badges (good/neutral/risk).
- [ ] Dashboard feels presentation-ready without external assets.