Files
hunjing/frontend/design/rune-engine-preview.html
T
2026-08-21 16:00:13 +08:00

231 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>魂镜 · 符文引擎设计预览</title>
<link rel="stylesheet" href="tokens.css" />
<link rel="stylesheet" href="_base.css" />
<link rel="stylesheet" href="_chrome.css" />
<style>
/* ============================================================
* 铜镜鉴心 v2.1 — 符文引擎(Macros · 占位符显化)
* 功能融合 §8.4:{{char}}/{{user}}/{{original}} 等占位符
* 在组装管线末端显化为实际内容。核心隐喻——
* 符文形态(mono · 未显化的符箓)→ 显化(宋体 · 激活的文字)
* ============================================================ */
/* ── 主区 ── */
.page-header .spacer { flex: 1; }
.section-label .glyph { color: var(--gilt); opacity: 0.85; font-family: "Songti SC", serif; font-size: 14px; }
.section-label .sub { color: var(--fg-dim); opacity: 0.7; letter-spacing: 0.04em; }
/* ═══ 符文卡(签名:mono 符文形态 → 宋体显化)═══ */
.rune-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(248px, 1fr)); gap: 12px; }
.rune-card { border: 1px solid var(--rule); border-radius: 10px; background: var(--surface); padding: 14px 16px; transition: border-color 0.15s, transform 0.15s; position: relative; overflow: hidden; }
.rune-card:hover { border-color: var(--patina-dim); transform: translateY(-1px); }
.rune-card::after { content: ""; position: absolute; inset: 0; background-image: var(--sweep-gradient); background-size: 220% 100%; background-position: -40% 0; opacity: 0; transition: opacity 0.4s; pointer-events: none; }
.rune-card:hover::after { opacity: 0.5; }
.rune-form { font-family: "JetBrains Mono", monospace; font-size: 15px; color: var(--gilt); letter-spacing: 0.02em; }
.rune-arrow { color: var(--fg-faint); font-family: "JetBrains Mono", monospace; font-size: 11px; margin: 7px 0 5px; display: flex; align-items: center; gap: 5px; }
.rune-arrow .line { flex: 1; height: 1px; background: var(--rule); }
.rune-val { font-family: "Songti SC", serif; font-size: 17px; color: var(--patina); font-weight: 600; }
.rune-val.empty { color: var(--fg-faint); font-style: italic; font-weight: 400; }
.rune-desc { font-size: 11.5px; color: var(--muted); margin-top: 6px; line-height: 1.45; }
.rune-card.custom { border-style: dashed; }
.rune-card.custom .rune-form { color: var(--cinnabar); }
/* ═══ 显化沙盒 ═══ */
.sandbox { border: 1px solid var(--gilt-dim); border-radius: 10px; background: var(--face-sheen), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow); overflow: hidden; }
.sandbox-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--rule); }
.sandbox-head .t { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); display: flex; align-items: center; gap: 7px; }
.ctx-select { font-family: inherit; font-size: 12px; color: var(--fg-dim); background: var(--glass); border: 1px solid var(--rule); border-radius: 7px; padding: 5px 10px; cursor: pointer; }
.ctx-select:focus { outline: none; border-color: var(--patina-dim); }
.sandbox-body { padding: 16px; display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 760px) { .sandbox-body { grid-template-columns: 1fr; } }
.sb-col .lb { font-family: "JetBrains Mono", monospace; font-size: 10px; letter-spacing: 0.12em; color: var(--fg-faint); text-transform: uppercase; margin-bottom: 6px; }
.sb-input { width: 100%; min-height: 96px; resize: vertical; font-family: "JetBrains Mono", monospace; font-size: 13px; line-height: 1.7; color: var(--fg); background: var(--ink); border: 1px solid var(--rule); border-radius: 7px; padding: 10px 12px; }
.sb-input:focus { outline: none; border-color: var(--patina-dim); }
.sb-output { width: 100%; min-height: 96px; font-family: "Songti SC", serif; font-size: 15px; line-height: 1.8; color: var(--fg); background: var(--patina-wash); border: 1px solid var(--patina-dim); border-radius: 7px; padding: 10px 12px; white-space: pre-wrap; }
.sb-output .hl { color: var(--patina); font-weight: 600; }
.sb-output .unk { color: var(--cinnabar); font-family: "JetBrains Mono", monospace; font-size: 12px; }
/* ═══ 管线位置(符文在组装末端)═══ */
.pipeline { border: 1px solid var(--rule); border-radius: 10px; background: var(--surface); padding: 18px 20px; }
.pipeline-track { display: flex; align-items: center; gap: 0; flex-wrap: wrap; margin-top: 6px; }
.pl-node { font-family: "Songti SC", serif; font-size: 13px; color: var(--fg-dim); padding: 8px 14px; border: 1px solid var(--rule); border-radius: 7px; background: var(--ink); }
.pl-node.dim { color: var(--fg-faint); }
.pl-arrow { color: var(--fg-faint); font-family: "JetBrains Mono", monospace; font-size: 13px; padding: 0 6px; }
.pl-node.terminal { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); font-weight: 600; position: relative; }
.pl-node.terminal::after { content: "符文显化"; position: absolute; top: -17px; left: 50%; transform: translateX(-50%); font-family: "JetBrains Mono", monospace; font-size: 9px; letter-spacing: 0.1em; color: var(--patina); white-space: nowrap; }
.pipeline-note { font-size: 12.5px; color: var(--fg-dim); margin-top: 16px; line-height: 1.6; }
.pipeline-note .em { color: var(--fg); }
.pipeline-note .warn { color: var(--cinnabar); }
.foot-note { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-faint); letter-spacing: 0.04em; margin-top: 4px; }
@media (max-width: 900px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } }
</style>
</head>
<body>
<div class="app-shell">
<!-- ════════ 侧栏 ════════ -->
<div class="sidebar-mount" data-active="符文引擎"></div>
<!-- ════════ 主区 ════════ -->
<div class="main-area">
<div class="page-header">
<div>
<div class="page-title">符文引擎</div>
<div class="page-sub">Macros · 占位符于组装末端显化</div>
</div>
<div class="spacer"></div>
<span class="stat-pill">12 内置 · 3 自定义 · 全部就绪</span>
</div>
<div class="page-body">
<!-- 内置符文 -->
<section>
<div class="section-label"><span class="glyph">符</span>内置符文 <span class="sub">— 系统上下文自动取值</span></div>
<div class="rune-grid" id="builtinGrid"></div>
</section>
<!-- 自定义符文 -->
<section>
<div class="section-label"><span class="glyph">刻</span>自定义符文 <span class="sub">— 创作者定义,全局可用</span></div>
<div class="rune-grid" id="customGrid"></div>
</section>
<!-- 显化沙盒 -->
<section>
<div class="section-label"><span class="glyph">显</span>显化沙盒 <span class="sub">— 左侧符文形态,右侧显化结果</span></div>
<div class="sandbox">
<div class="sandbox-head">
<span class="t">实时显化</span>
<label style="font-size:12px;color:var(--fg-dim);display:flex;align-items:center;gap:8px">
上下文
<select class="ctx-select" id="ctxSelect">
<option value="qingwu">青梧 · 暮色书房</option>
<option value="zhaoyin">照影 · 竹林雨夜</option>
</select>
</label>
</div>
<div class="sandbox-body">
<div class="sb-col">
<div class="lb">符文形态 · Input</div>
<textarea class="sb-input" id="sbInput">{{char}}在{{scenario}}对{{user}}低语,{{tone}}。{{idle}}</textarea>
</div>
<div class="sb-col">
<div class="lb">显化 · Resolved</div>
<div class="sb-output" id="sbOutput"></div>
</div>
</div>
</div>
</section>
<!-- 管线位置 -->
<section>
<div class="section-label"><span class="glyph">位</span>组装管线位置 <span class="sub">— 符文引擎是末端,所有占位符在此显化</span></div>
<div class="pipeline">
<div class="pipeline-track">
<span class="pl-node">道体</span><span class="pl-arrow">→</span>
<span class="pl-node">世谱</span><span class="pl-arrow">→</span>
<span class="pl-node">眉批</span><span class="pl-arrow">→</span>
<span class="pl-node">刻符</span><span class="pl-arrow">→</span>
<span class="pl-node terminal">符文引擎</span><span class="pl-arrow">→</span>
<span class="pl-node dim">最终 prompt</span>
</div>
<p class="pipeline-note">
<span class="em">末端替换</span>:符文引擎不生成内容,只把上游所有占位符(<code style="font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--gilt)">{{char}}</code> / <code style="font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--gilt)">{{user}}</code> / 自定义等)替换为实际值。
<span class="warn">循环引用</span>(如 <code style="font-family:'JetBrains Mono',monospace;font-size:12px">{{a}}→{{b}}→{{a}}</code>)由引擎检测并截断,标记为未显化。
</p>
<p class="foot-note">参考 docs/contracts/contract-glossary.md §8.4 · 导入角色卡前必须先有符文引擎,否则卡内占位符是死的</p>
</div>
</section>
</div>
</div>
</div>
<script>
/* 上下文:符文显化的取值源(真应用接运行时上下文,此处为示意) */
const CTX = {
qingwu: {
char: "青梧", user: "辰", scenario: "暮色书房", tone: "沉静温厚",
time: "申时三刻", date: "甲辰年 七月初一", idle: "已 2 时辰未语",
personality: "沉静·好奇", original: "(变换前原文)",
},
zhaoyin: {
char: "照影", user: "辰", scenario: "竹林雨夜", tone: "清冷疏淡",
time: "亥时", date: "甲辰年 七月廿三", idle: "方才分别",
personality: "清冷·机敏", original: "(变换前原文)",
},
};
/* 内置符文清单 */
const BUILTIN = [
["{{char}}", "char", "当前角色名"],
["{{user}}", "user", "用户名"],
["{{original}}", "original", "变换前原文(刻符/ujj 用)"],
["{{scenario}}", "scenario", "当前场景"],
["{{personality}}", "personality", "人格摘要"],
["{{tone}}", "tone", "语气基调(自定义回退)"],
["{{time}}", "time", "当前时辰"],
["{{date}}", "date", "当前日期"],
["{{idle}}", "idle", "距上次对话"],
];
/* 自定义符文 */
const CUSTOM = [
["{{weather}}", "细雨", "场景天气(创作者定义)"],
["{{mood}}", "微醺", "角色即时心境"],
["{{hook}}", "未完的旧诺", "本幕钩子"],
];
let ctxKey = "qingwu";
function renderCards() {
const ctx = CTX[ctxKey];
const card = (form, valOrKey, desc, custom) => {
const isCustom = custom;
const val = isCustom ? valOrKey : ctx[valOrKey];
const empty = !val;
return `<div class="rune-card ${isCustom ? "custom" : ""}">
<div class="rune-form">${form}</div>
<div class="rune-arrow"><span>显化</span><span class="line"></span><span>↓</span></div>
<div class="rune-val ${empty ? "empty" : ""}">${empty ? "(待上下文)" : val}</div>
<div class="rune-desc">${desc}</div>
</div>`;
};
document.getElementById("builtinGrid").innerHTML =
BUILTIN.map(([f, k, d]) => card(f, k, d, false)).join("");
document.getElementById("customGrid").innerHTML =
CUSTOM.map(([f, v, d]) => card(f, v, d, true)).join("") +
`<div class="rune-card custom" style="display:flex;align-items:center;justify-content:center;color:var(--fg-faint);font-size:13px;cursor:pointer;border-style:dashed">+ 刻新符文</div>`;
}
/* 沙盒:把 {{xxx}} 替换为显化值,未知标红 */
function renderSandbox() {
const ctx = Object.assign({}, CTX[ctxKey]);
CUSTOM.forEach(([f, v]) => { ctx[f.slice(2, -2)] = v; });
const input = document.getElementById("sbInput").value;
const out = input.replace(/\{\{([a-z_]+)\}\}/g, (m, key) =>
ctx[key] !== undefined
? `<span class="hl">${ctx[key]}</span>`
: `<span class="unk">${m}?</span>`
);
document.getElementById("sbOutput").innerHTML = out;
}
document.getElementById("sbInput").addEventListener("input", renderSandbox);
document.getElementById("ctxSelect").addEventListener("change", (e) => {
ctxKey = e.target.value; renderCards(); renderSandbox();
});
renderCards();
renderSandbox();
</script>
<script src="_theme.js" defer></script>
</body>
</html>