壳完善
This commit is contained in:
@@ -370,9 +370,15 @@ function formatPlayPresentHtml(body, view) {
|
||||
tweaks?.shell_id && PRESENT_SHELL_IDS?.includes?.(tweaks.shell_id)
|
||||
? tweaks.shell_id
|
||||
: tweaks?.shell_id &&
|
||||
["prose", "chat_monitor", "turn_panel", "chapter_reader"].includes(
|
||||
tweaks.shell_id,
|
||||
)
|
||||
[
|
||||
"prose",
|
||||
"chat_monitor",
|
||||
"spotlight",
|
||||
"turn_panel",
|
||||
"split_board",
|
||||
"choice_dock",
|
||||
"chapter_reader",
|
||||
].includes(tweaks.shell_id)
|
||||
? tweaks.shell_id
|
||||
: "prose";
|
||||
const parsed = tryParseJsonDoc(trimmed);
|
||||
@@ -1404,6 +1410,14 @@ function renderSpecialtyBodyHtml(body, skill) {
|
||||
const html = renderMechanismBodyHtml(body);
|
||||
if (html) return html;
|
||||
}
|
||||
if (
|
||||
body.风格与写法 != null ||
|
||||
body.推进与决策 != null ||
|
||||
skillName.includes("叙事指南")
|
||||
) {
|
||||
const html = renderNarrativeBodyHtml(body);
|
||||
if (html) return html;
|
||||
}
|
||||
|
||||
const looksAesthetics =
|
||||
body.设定逻辑 != null ||
|
||||
@@ -1432,6 +1446,60 @@ function renderSpecialtyBodyHtml(body, skill) {
|
||||
return parts.length ? `<div class="af-mosaic">${parts.join("")}</div>` : "";
|
||||
}
|
||||
|
||||
/** 叙事指南 · 一份全文结构化卡(不强调双投影裁剪) */
|
||||
function renderNarrativeBodyHtml(body) {
|
||||
const order = [
|
||||
"依据的体验",
|
||||
"叙事纲领",
|
||||
"风格与遣词",
|
||||
"笔墨焦点",
|
||||
"禁忌与不偏好",
|
||||
"情境备用",
|
||||
"推进与决策",
|
||||
"内容与表达",
|
||||
"风格与写法",
|
||||
];
|
||||
const used = new Set();
|
||||
const parts = [];
|
||||
// 若仍是旧的「风格与写法 / 推进与决策」两大包,整块展示
|
||||
if (body.风格与写法 != null || body.推进与决策 != null) {
|
||||
if (Array.isArray(body.依据的体验) && body.依据的体验.length) {
|
||||
parts.push(
|
||||
`<section class="af-panel"><h4>依据的体验</h4>${skillProseList(body.依据的体验)}</section>`,
|
||||
);
|
||||
used.add("依据的体验");
|
||||
}
|
||||
if (body.风格与写法 != null) {
|
||||
parts.push(
|
||||
`<section class="af-panel af-panel-wide"><h4>风格与写法</h4>${renderStructuredValueHtml(body.风格与写法, 0)}</section>`,
|
||||
);
|
||||
used.add("风格与写法");
|
||||
}
|
||||
if (body.推进与决策 != null) {
|
||||
parts.push(
|
||||
`<section class="af-panel af-panel-wide"><h4>推进与决策</h4>${renderStructuredValueHtml(body.推进与决策, 0)}</section>`,
|
||||
);
|
||||
used.add("推进与决策");
|
||||
}
|
||||
} else {
|
||||
for (const key of order) {
|
||||
if (body[key] == null) continue;
|
||||
used.add(key);
|
||||
const wide = key === "风格与遣词" || key === "推进与决策";
|
||||
parts.push(
|
||||
`<section class="af-panel${wide ? " af-panel-wide" : ""}"><h4>${esc(key)}</h4>${renderStructuredValueHtml(body[key], 0)}</section>`,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const [k, v] of Object.entries(body)) {
|
||||
if (used.has(k) || v == null || v === "") continue;
|
||||
parts.push(
|
||||
`<section class="af-panel"><h4>${esc(k)}</h4>${renderStructuredValueHtml(v, 0)}</section>`,
|
||||
);
|
||||
}
|
||||
return parts.length ? `<div class="af-mosaic af-narrative">${parts.join("")}</div>` : "";
|
||||
}
|
||||
|
||||
function skillSection(title, inner, cls = "") {
|
||||
if (!inner) return "";
|
||||
return `<section class="skill-card-section${cls ? ` ${cls}` : ""}"><h4>${esc(title)}</h4>${inner}</section>`;
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
</div>
|
||||
<div class="main-head-right">
|
||||
<a class="link" href="/modules.html">技能</a>
|
||||
<a class="link" href="/shells.html">呈现壳</a>
|
||||
<a class="link" href="/stats.html">Token</a>
|
||||
<a class="link" href="/settings.html">设置</a>
|
||||
<button type="button" class="btn-sm" id="btn-save-instance" hidden>保存定稿</button>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<nav class="st-nav" aria-label="主导航">
|
||||
<a href="/">对话</a>
|
||||
<a href="/modules.html" aria-current="page">技能</a>
|
||||
<a href="/shells.html">呈现壳</a>
|
||||
<a href="/stats.html">Token</a>
|
||||
<a href="/settings.html">设置</a>
|
||||
</nav>
|
||||
|
||||
@@ -1,19 +1,31 @@
|
||||
/**
|
||||
* 游玩呈现壳渲染(P2 最小适配)
|
||||
* 游玩呈现壳渲染(P2)
|
||||
* 契约:docs/play-presentation-shells.md · present.v1
|
||||
*
|
||||
* 信息层级约定(所有壳共用):
|
||||
* P0 body — 最大字号与面积,阅读主轴
|
||||
* P1 actions — 需要决策时醒目,但不压过正文
|
||||
* P2 monitor — 芯片密排,一眼扫完
|
||||
* P3 header / footer / aside — 辅助,紧凑
|
||||
*/
|
||||
|
||||
export const PRESENT_SHELL_IDS = [
|
||||
"prose",
|
||||
"chat_monitor",
|
||||
"spotlight",
|
||||
"turn_panel",
|
||||
"split_board",
|
||||
"choice_dock",
|
||||
"chapter_reader",
|
||||
];
|
||||
|
||||
const SHELL_LABELS = {
|
||||
prose: "纯散文",
|
||||
chat_monitor: "对话+监控",
|
||||
spotlight: "场面主视",
|
||||
turn_panel: "回合面板",
|
||||
split_board: "双栏看板",
|
||||
choice_dock: "选择坞",
|
||||
chapter_reader: "章节阅读",
|
||||
};
|
||||
|
||||
@@ -26,20 +38,30 @@ const REGION_LABELS = {
|
||||
hidden: "隐藏",
|
||||
};
|
||||
|
||||
/** 默认开建议行动的壳 */
|
||||
const ACTIONS_DEFAULT_ON = new Set(["turn_panel", "choice_dock"]);
|
||||
|
||||
function isPresentShellId(v) {
|
||||
return typeof v === "string" && PRESENT_SHELL_IDS.includes(v);
|
||||
}
|
||||
|
||||
function shellDefaultRegions(shell) {
|
||||
export function shellDefaultRegions(shell) {
|
||||
switch (shell) {
|
||||
case "chat_monitor":
|
||||
return ["monitor", "body", "footer"];
|
||||
case "spotlight":
|
||||
return ["monitor", "body", "footer"];
|
||||
case "turn_panel":
|
||||
return ["monitor", "header", "body", "footer", "aside"];
|
||||
return ["monitor", "header", "body", "aside", "footer"];
|
||||
case "split_board":
|
||||
return ["header", "body", "aside", "footer"];
|
||||
case "choice_dock":
|
||||
return ["monitor", "body", "footer"];
|
||||
case "chapter_reader":
|
||||
return ["header", "body", "aside", "footer"];
|
||||
case "prose":
|
||||
default:
|
||||
return ["body"];
|
||||
return ["body", "footer"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,9 +93,35 @@ function normalizeBlocks(raw) {
|
||||
return out;
|
||||
}
|
||||
|
||||
function renderChipsHtml(value, esc) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
||||
const entries = Object.entries(value).filter(([k]) => k != null && String(k).trim());
|
||||
if (!entries.length) return null;
|
||||
return `<div class="present-chips" role="list">${entries
|
||||
.map(
|
||||
([k, v]) =>
|
||||
`<span class="present-chip" role="listitem"><span class="present-chip-k">${esc(k)}</span><span class="present-chip-v">${esc(
|
||||
typeof v === "string" ? v : JSON.stringify(v),
|
||||
)}</span></span>`,
|
||||
)
|
||||
.join("")}</div>`;
|
||||
}
|
||||
|
||||
function regionInnerHtml(region, rawValue, esc) {
|
||||
if (region === "monitor") {
|
||||
const chips = renderChipsHtml(rawValue, esc);
|
||||
if (chips) return chips;
|
||||
}
|
||||
if (region === "aside" && rawValue && typeof rawValue === "object" && !Array.isArray(rawValue)) {
|
||||
const chips = renderChipsHtml(rawValue, esc);
|
||||
if (chips) return chips;
|
||||
}
|
||||
const text = formatBlockContent(rawValue).trim();
|
||||
return `<div class="present-region-body">${esc(text).replace(/\n/g, "<br>")}</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{ ok: boolean, packet: object, fallbackPlain: boolean } | null}
|
||||
* null = 不是 present 包(交给其它渲染器)
|
||||
*/
|
||||
export function parsePresentDoc(doc, fallbackShell = "prose") {
|
||||
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
|
||||
@@ -110,7 +158,6 @@ export function parsePresentDoc(doc, fallbackShell = "prose") {
|
||||
};
|
||||
}
|
||||
|
||||
/** 纯文本 → prose 包(供调用方在非 JSON 时使用) */
|
||||
export function presentFromPlain(text, shell = "prose") {
|
||||
return {
|
||||
schema: "present.v1",
|
||||
@@ -120,6 +167,65 @@ export function presentFromPlain(text, shell = "prose") {
|
||||
};
|
||||
}
|
||||
|
||||
function regionTitle(region, labels, _shell) {
|
||||
// 正文默认不挂标题(面积留给文字);创作填了 block_labels.body 才显示
|
||||
if (region === "body") {
|
||||
return labels.body || labels["正文"] || null;
|
||||
}
|
||||
return (
|
||||
labels[region] ||
|
||||
labels[REGION_LABELS[region]] ||
|
||||
REGION_LABELS[region] ||
|
||||
region
|
||||
);
|
||||
}
|
||||
|
||||
function renderRegion(region, rawValue, esc, { labels, emptyStates, shell }) {
|
||||
let hasContent =
|
||||
rawValue !== undefined &&
|
||||
rawValue !== null &&
|
||||
(typeof rawValue === "string"
|
||||
? rawValue.trim()
|
||||
: Array.isArray(rawValue)
|
||||
? rawValue.length
|
||||
: typeof rawValue === "object"
|
||||
? Object.keys(rawValue).length
|
||||
: true);
|
||||
|
||||
let displayRaw = rawValue;
|
||||
if (!hasContent && emptyStates[region]) {
|
||||
displayRaw = String(emptyStates[region]);
|
||||
hasContent = true;
|
||||
}
|
||||
if (!hasContent && region !== "body") return "";
|
||||
if (!hasContent && region === "body") displayRaw = "(暂无正文)";
|
||||
|
||||
const title = regionTitle(region, labels, shell);
|
||||
const titleHtml = title
|
||||
? `<h4 class="present-region-title">${esc(title)}</h4>`
|
||||
: "";
|
||||
|
||||
return `
|
||||
<section class="present-region present-region--${esc(region)}" data-region="${esc(region)}">
|
||||
${titleHtml}
|
||||
${regionInnerHtml(region, displayRaw, esc)}
|
||||
</section>`;
|
||||
}
|
||||
|
||||
function renderActionsHtml(actions, esc) {
|
||||
if (!actions.length) return "";
|
||||
return `
|
||||
<section class="present-region present-region--actions" data-region="actions">
|
||||
<h4 class="present-region-title">建议行动</h4>
|
||||
<div class="present-action-row" role="list">${actions
|
||||
.map(
|
||||
(a) =>
|
||||
`<span class="present-action" role="listitem">${esc(a)}</span>`,
|
||||
)
|
||||
.join("")}</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} packet present.v1
|
||||
* @param {(s: string) => string} esc
|
||||
@@ -141,7 +247,7 @@ export function renderPresentShellHtml(packet, esc, opts = {}) {
|
||||
const showActions =
|
||||
typeof tweaks.show_suggested_actions === "boolean"
|
||||
? tweaks.show_suggested_actions
|
||||
: shell === "turn_panel";
|
||||
: ACTIONS_DEFAULT_ON.has(shell);
|
||||
const tone =
|
||||
typeof tweaks.tone_chrome === "string" && tweaks.tone_chrome.trim()
|
||||
? tweaks.tone_chrome.trim()
|
||||
@@ -149,41 +255,96 @@ export function renderPresentShellHtml(packet, esc, opts = {}) {
|
||||
|
||||
const blocks = packet.blocks ?? {};
|
||||
const regions = shellDefaultRegions(shell);
|
||||
const parts = [];
|
||||
const ctx = { labels, emptyStates, shell };
|
||||
|
||||
for (const region of regions) {
|
||||
if (region === "hidden") continue;
|
||||
let text = formatBlockContent(blocks[region]).trim();
|
||||
if (!text && emptyStates[region]) text = String(emptyStates[region]);
|
||||
if (!text && region !== "body") continue;
|
||||
if (!text && region === "body") text = "(暂无正文)";
|
||||
|
||||
const title =
|
||||
labels[region] ||
|
||||
labels[REGION_LABELS[region]] ||
|
||||
REGION_LABELS[region] ||
|
||||
region;
|
||||
const isBody = region === "body";
|
||||
parts.push(`
|
||||
<section class="present-region present-region--${esc(region)}" data-region="${esc(region)}">
|
||||
${isBody && shell === "prose" ? "" : `<h4 class="present-region-title">${esc(title)}</h4>`}
|
||||
<div class="present-region-body">${esc(text).replace(/\n/g, "<br>")}</div>
|
||||
</section>`);
|
||||
const by = {};
|
||||
for (const r of regions) {
|
||||
by[r] = renderRegion(r, blocks[r], esc, ctx);
|
||||
}
|
||||
|
||||
const actions = showActions ? packet.meta?.suggested_actions ?? [] : [];
|
||||
if (actions.length) {
|
||||
parts.push(`
|
||||
<section class="present-region present-region--actions" data-region="actions">
|
||||
<h4 class="present-region-title">建议行动</h4>
|
||||
<ul class="present-actions">${actions.map((a) => `<li>${esc(a)}</li>`).join("")}</ul>
|
||||
</section>`);
|
||||
const actionsHtml = renderActionsHtml(actions, esc);
|
||||
|
||||
let layoutHtml = "";
|
||||
switch (shell) {
|
||||
case "chat_monitor":
|
||||
layoutHtml = `
|
||||
<div class="present-stack">
|
||||
${by.monitor || ""}
|
||||
<div class="present-stage">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
${actionsHtml}
|
||||
</div>`;
|
||||
break;
|
||||
case "spotlight":
|
||||
layoutHtml = `
|
||||
<div class="present-stack present-stack--spotlight">
|
||||
${by.monitor || ""}
|
||||
<div class="present-stage present-stage--hero">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
${actionsHtml}
|
||||
</div>`;
|
||||
break;
|
||||
case "turn_panel":
|
||||
layoutHtml = `
|
||||
<div class="present-board">
|
||||
<div class="present-board-top">${by.monitor || ""}${by.header || ""}</div>
|
||||
<div class="present-board-mid">
|
||||
<div class="present-stage">${by.body || ""}</div>
|
||||
<div class="present-board-side">${by.aside || ""}${by.footer || ""}</div>
|
||||
</div>
|
||||
${actionsHtml}
|
||||
</div>`;
|
||||
break;
|
||||
case "split_board":
|
||||
layoutHtml = `
|
||||
<div class="present-split">
|
||||
<div class="present-split-main">
|
||||
${by.header || ""}
|
||||
<div class="present-stage">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
</div>
|
||||
<div class="present-split-side">${by.aside || ""}</div>
|
||||
${actionsHtml}
|
||||
</div>`;
|
||||
break;
|
||||
case "choice_dock":
|
||||
layoutHtml = `
|
||||
<div class="present-stack present-stack--dock">
|
||||
${by.monitor || ""}
|
||||
<div class="present-stage">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
<div class="present-dock">${actionsHtml}</div>
|
||||
</div>`;
|
||||
break;
|
||||
case "chapter_reader":
|
||||
layoutHtml = `
|
||||
<div class="present-reader">
|
||||
<div class="present-reader-main">
|
||||
${by.header || ""}
|
||||
<div class="present-stage present-stage--read">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
</div>
|
||||
<div class="present-reader-aside">${by.aside || ""}</div>
|
||||
</div>`;
|
||||
break;
|
||||
case "prose":
|
||||
default:
|
||||
layoutHtml = `
|
||||
<div class="present-stack present-stack--prose">
|
||||
<div class="present-stage present-stage--read">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
${actionsHtml}
|
||||
</div>`;
|
||||
break;
|
||||
}
|
||||
|
||||
const shellLabel = SHELL_LABELS[shell] || shell;
|
||||
return `
|
||||
<div class="present-shell present-shell--${esc(shell)} present-tone--${esc(tone)}" data-shell="${esc(shell)}">
|
||||
<div class="present-shell-badge" title="呈现壳">${esc(shellLabel)}</div>
|
||||
${parts.join("")}
|
||||
${layoutHtml}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
export { SHELL_LABELS, REGION_LABELS, isPresentShellId };
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<nav class="st-nav" aria-label="主导航">
|
||||
<a href="/">对话</a>
|
||||
<a href="/modules.html">技能</a>
|
||||
<a href="/shells.html">呈现壳</a>
|
||||
<a href="/stats.html">Token</a>
|
||||
<a href="/settings.html" aria-current="page">设置</a>
|
||||
</nav>
|
||||
|
||||
173
web/shells.css
Normal file
173
web/shells.css
Normal file
@@ -0,0 +1,173 @@
|
||||
/* 呈现壳预览页
|
||||
* 注意:本页也引入 styles.css(present 样式),会给 body 设 overflow:hidden;
|
||||
* 必须在此覆盖,否则侧栏后几项点不到。
|
||||
*/
|
||||
html,
|
||||
body.shells-page {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.shells-page .st-shell.shells-shell {
|
||||
max-width: 1180px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.shells-page .st-rail {
|
||||
--st-rail-w: 220px;
|
||||
width: var(--st-rail-w);
|
||||
position: sticky;
|
||||
top: var(--st-top-h, 48px);
|
||||
max-height: calc(100dvh - var(--st-top-h, 48px));
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.shells-tone-label {
|
||||
margin-top: 1rem !important;
|
||||
}
|
||||
|
||||
.shells-density {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 10px;
|
||||
align-items: baseline;
|
||||
margin: 0.5rem 0 0.35rem;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--accent-soft, #e8f0fe) 70%, transparent);
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.shells-density span {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--muted, #666);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.shells-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.shells-tones {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.shells-tone-chip {
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border, #ddd);
|
||||
background: var(--surface, #fff);
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.shells-tone-chip.active {
|
||||
border-color: transparent;
|
||||
background: var(--accent-soft, #e8f0fe);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.shells-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.shells-meta p {
|
||||
margin: 0 0 0.35rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.shells-preview-label,
|
||||
.shells-tweaks h2,
|
||||
.shells-help h2 {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--muted, #666);
|
||||
}
|
||||
|
||||
.shells-preview {
|
||||
min-height: 280px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px dashed var(--border, #ccc);
|
||||
background:
|
||||
radial-gradient(ellipse at 20% 0%, rgba(15, 118, 110, 0.06), transparent 50%),
|
||||
var(--bg, #f6f6f4);
|
||||
}
|
||||
|
||||
.shells-preview .present-shell {
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 10px 28px rgba(28, 25, 23, 0.07);
|
||||
}
|
||||
|
||||
.shells-preview[data-shell="turn_panel"] .present-shell,
|
||||
.shells-preview[data-shell="split_board"] .present-shell,
|
||||
.shells-preview[data-shell="chapter_reader"] .present-shell {
|
||||
max-width: 780px;
|
||||
}
|
||||
|
||||
/* 预览区不裁切长正文 */
|
||||
.shells-preview {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.shells-tweak-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.shells-tweak-grid .field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.shells-tweak-grid input[type="text"] {
|
||||
font: inherit;
|
||||
padding: 6px 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border, #ddd);
|
||||
}
|
||||
|
||||
.shells-help ul {
|
||||
margin: 0;
|
||||
padding-left: 1.2rem;
|
||||
line-height: 1.55;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.shells-help .muted {
|
||||
font-size: 12px;
|
||||
color: var(--muted, #888);
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.shells-page .st-rail {
|
||||
position: static;
|
||||
max-height: none;
|
||||
width: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.shells-preview .present-shell {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
74
web/shells.html
Normal file
74
web/shells.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>呈现壳预览 · Writing Agent</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Noto+Serif+SC:wght@500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="/settings.css" />
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<link rel="stylesheet" href="/shells.css" />
|
||||
</head>
|
||||
<body class="settings-page shells-page">
|
||||
<header class="st-top">
|
||||
<a class="st-brand" href="/">Writing Agent</a>
|
||||
<nav class="st-nav" aria-label="主导航">
|
||||
<a href="/">对话</a>
|
||||
<a href="/modules.html">技能</a>
|
||||
<a href="/shells.html" aria-current="page">呈现壳</a>
|
||||
<a href="/stats.html">Token</a>
|
||||
<a href="/settings.html">设置</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="st-shell shells-shell">
|
||||
<aside class="st-rail" aria-label="壳列表">
|
||||
<p class="st-rail-label">呈现壳</p>
|
||||
<nav class="shells-list" id="shell-list" role="tablist"></nav>
|
||||
<p class="st-rail-label shells-tone-label">装饰语气</p>
|
||||
<div class="shells-tones" id="tone-list" role="group" aria-label="tone_chrome"></div>
|
||||
<p class="st-rail-label shells-tone-label">正文长度</p>
|
||||
<div class="shells-tones" id="length-list" role="group" aria-label="body length"></div>
|
||||
<p class="st-rail-note">
|
||||
正文按 600~3000 字设计:随字数撑高、页面滚动;侧栏/选择坞钉住。创作只改内容绑定。
|
||||
</p>
|
||||
</aside>
|
||||
|
||||
<main class="st-main">
|
||||
<header class="st-main-head">
|
||||
<div>
|
||||
<h1 id="panel-title">呈现壳预览</h1>
|
||||
<p class="st-sub" id="panel-subtitle">用示例数据看布局与可调项</p>
|
||||
</div>
|
||||
</header>
|
||||
<div class="st-body shells-body">
|
||||
<section class="shells-meta" id="shell-meta"></section>
|
||||
<section class="shells-preview-wrap">
|
||||
<h2 class="shells-preview-label">预览</h2>
|
||||
<div id="shell-preview" class="shells-preview"></div>
|
||||
</section>
|
||||
<section class="shells-tweaks">
|
||||
<h2>本页可调(对应创作「微调轴」)</h2>
|
||||
<div class="shells-tweak-grid" id="tweak-controls"></div>
|
||||
</section>
|
||||
<section class="shells-help">
|
||||
<h2>设计意图</h2>
|
||||
<ul>
|
||||
<li>正文默认装得下单轮 <strong>600~3000 字</strong>:区域随内容长高,由页面/消息区滚动,不塞进固定小框。</li>
|
||||
<li>双栏壳:侧栏 sticky;选择坞:选项区 sticky 贴底——长文读完控件还在。</li>
|
||||
<li>壳差在「谁占主柱」;可改的是字段/显示名/行动开关/tone,不是重画布局。</li>
|
||||
</ul>
|
||||
<p class="muted">说明文档:<code>docs/play-presentation-shells.md</code></p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/shells.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
358
web/shells.js
Normal file
358
web/shells.js
Normal file
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
* 呈现壳预览页:切换壳 / 装饰语气 / 正文长度,用示例数据渲染 present-shells。
|
||||
* 正文设计目标:单轮常见 600~3000 字 —— 随字数撑高,外层滚动;侧栏/选项坞钉住。
|
||||
*/
|
||||
import {
|
||||
PRESENT_SHELL_IDS,
|
||||
renderPresentShellHtml,
|
||||
} from "./present-shells.js";
|
||||
|
||||
const SHELL_INFO = {
|
||||
prose: {
|
||||
name: "纯散文",
|
||||
blurb: "整块都是正文,无 HUD。长文直接往下滚。",
|
||||
density: "正文随字数伸展;无侧栏争面积",
|
||||
scenes: "轻对话、纯叙事、不要状态栏",
|
||||
},
|
||||
chat_monitor: {
|
||||
name: "对话 + 监控",
|
||||
blurb: "顶栏芯片固定矮;主聊/场面再长也只往下长。",
|
||||
density: "监控一行;正文撑高滚动",
|
||||
scenes: "网恋 / 日常扮演 / 轻 AIRP",
|
||||
},
|
||||
spotlight: {
|
||||
name: "场面主视",
|
||||
blurb: "芯片一行 + 大正文柱。适合一幕写满。",
|
||||
density: "正文主导并伸展;监控不占高",
|
||||
scenes: "电影感 RP、一幕一景、少机制",
|
||||
},
|
||||
turn_panel: {
|
||||
name: "回合面板",
|
||||
blurb: "场面叙述再长,右侧交互物钉在视口内对照。",
|
||||
density: "正文撑高;侧栏 sticky;行动在下",
|
||||
scenes: "资源、检定、场景交互、跑团感",
|
||||
},
|
||||
split_board: {
|
||||
name: "双栏看板",
|
||||
blurb: "左栏长文滚动,右栏线索钉住——对照读。",
|
||||
density: "左正文伸展;右侧栏 sticky",
|
||||
scenes: "调查、推理、多线索并行",
|
||||
},
|
||||
choice_dock: {
|
||||
name: "选择坞",
|
||||
blurb: "先读完长局面;选项坞 sticky 贴视口底,不用翻回顶。",
|
||||
density: "正文撑高;底部坞钉住",
|
||||
scenes: "选项驱动、AVG、分支关口",
|
||||
},
|
||||
chapter_reader: {
|
||||
name: "章节阅读",
|
||||
blurb: "章题 + 长阅读柱;窄进度侧栏 sticky。",
|
||||
density: "正文伸展为主;侧栏窄且钉住",
|
||||
scenes: "长文 / 爽文 / 先纲后章",
|
||||
},
|
||||
};
|
||||
|
||||
const TONES = [
|
||||
{ id: "default", label: "默认" },
|
||||
{ id: "messenger", label: "讯息感" },
|
||||
{ id: "book", label: "书页感" },
|
||||
{ id: "terminal", label: "终端感" },
|
||||
];
|
||||
|
||||
const LENGTHS = [
|
||||
{ id: "short", label: "短(~120)" },
|
||||
{ id: "mid", label: "中(~800)" },
|
||||
{ id: "long", label: "长(~2500)" },
|
||||
];
|
||||
|
||||
/** 中等长度 ≈800 字 */
|
||||
const BODY_MID = `雨还在下。你把伞往她那边偏了偏,她没说话,只是把购物袋往怀里收紧了一点。水从伞骨边缘连成线,砸在柏油路上,溅起细碎的白点。
|
||||
|
||||
路口的灯跳成绿色。你们一起迈步——这一次,谁也没有先松开。对面便利店的玻璃上映着两个人并肩的轮廓,忽明忽暗,像谁随手画的草稿。
|
||||
|
||||
「伞……」她终于开口,声音被雨声压得很低,「明天不用还。」
|
||||
|
||||
你点头,又觉得这回答太轻,补了一句:「那我请你喝热的?」她侧过脸,雨丝粘在睫毛上,笑意只闪了一下,像怕被看穿。
|
||||
|
||||
电梯门开的时候,楼道里干燥得过分。她把袋子换到另一只手,钥匙在指间转了一圈,忽然停住:「其实我今天本来想绕路的。」
|
||||
|
||||
「为什么?」
|
||||
|
||||
「因为预报说会停。结果没有。」她把钥匙推进锁孔,「所以——只好借你的伞了。」
|
||||
|
||||
门开了一条缝。暖光漏出来,照在你们脚边的水渍上。她没有立刻进去,只是抬眼看你,像在等一句还没说出口的话。你把伞收拢,水仍顺着伞尖滴到地毯边缘。谁也没有提「进来坐坐」,但空气里已经有了那句话的形状。`;
|
||||
|
||||
/** 长文 ≈2500 字(重复段落拼成,预览用) */
|
||||
const BODY_LONG = `${BODY_MID}
|
||||
|
||||
天桥下的风从另一头灌过来,把塑料袋吹得啪啪响。你想起上一次走过这里,还是夏天,蝉声吵得人没法好好说话;如今只剩雨,以及偶尔驶过的车灯,把积水切成一条条亮痕。
|
||||
|
||||
便利店里的收音机断断续续播着交通路况。店员打着哈欠换班,玻璃门开合时带进一阵湿冷。你站在门口抖伞,余光看见货架尽头有人停了很久——拿起一瓶水,又放下,再拿起,像在跟自己商量一件比购物更大的事。
|
||||
|
||||
她的短信迟了三分钟才到:「到家了吗?」你盯着屏幕上的光标,打了又删:刚进小区 / 伞还在我这 / 明天……最后只留下一句:「刚到。伞明天给你。」她回了个省略号,又补:「不用那么快。」
|
||||
|
||||
你把手机塞回口袋,忽然觉得「不用那么快」四个字比天气预报更准。有些东西一旦说破就会散,只好让它停在半湿半干之间。
|
||||
|
||||
楼道灯感应得慢,你摸黑上了两级台阶才亮。隔壁门缝里漏出电视剧的笑声,被雨声削成模糊的边。你靠着墙站了一会儿,听自己的呼吸从急变匀,才继续往上走。
|
||||
|
||||
到家后你把伞撑在阳台,水沿着伞骨汇成细流,滴进盆里,一声一声,像在数今晚还剩多少没说清的话。窗玻璃上凝着雾,你用指尖随便划了一道,很快又糊住——外面的城市灯火依旧,只是被雨帘隔成另一层世界。
|
||||
|
||||
你泡了杯热水,坐到桌前,本想写点什么,笔尖却停在空白处。纸页吸走一点水汽,皱起细纹。你写下一句又涂掉:关于伞,关于绿灯,关于她说「绕路」时那种几乎听不见的停顿。最后只留下日期,和一行没写完的「明天」。
|
||||
|
||||
夜里雨势小了些。你关灯前又看了一眼阳台——伞还在,水还在滴,只是间隔更长了,像什么人放慢了脚步,却没有真的离开。
|
||||
|
||||
(——以下为加长段,模拟单轮 2000+ 字场面——)
|
||||
|
||||
次日清晨,天边只剩薄云。你带着伞出门,金属骨架在布套里轻轻碰撞。电梯里遇见邻居遛狗,它的爪子在地垫上留下两串湿印,很快被后来的人踩散。你想:痕迹就是这样,被人看见时已不是原样。
|
||||
|
||||
到了约定的路口,她比你早到半分钟,手里多了一杯热饮,杯套上印着歪歪扭扭的笑脸。「还给你。」你把伞递过去。她却摇头:「今天晴。」伞停在两人之间,像一句还没决定归属的句子。
|
||||
|
||||
你们沿着河堤走。水面上漂着昨夜的落叶,偶有涟漪被风抹平。她讲起最近在忙的展览,讲到一半又停住,说其实不太想聊工作。于是话题滑向更碎的东西:哪家面馆换了老板、哪条巷子的灯坏了三天、哪一次下雨你们刚好都没带伞。
|
||||
|
||||
「所以才需要借。」她说。
|
||||
|
||||
「借完还要还。」你说。
|
||||
|
||||
「有的不用还。」她看着河对岸的塔吊,「有的还了,反而像没发生过。」
|
||||
|
||||
塔吊缓缓旋转,吊臂在灰蓝天空里划出钝角。你忽然明白,呈现壳要解决的不是「字大一点」,而是这一长段话落在界面里时,读者还愿不愿意往下看——状态栏不能抢戏,选项不能在中途消失,侧栏得钉住好让人对照线索。
|
||||
|
||||
你们在桥上站了一会儿。风把伞套吹得鼓起,又瘪下去。她把热饮塞进你手里:「暖的。你昨晚肯定没睡好。」你没有否认。杯壁的温度从掌心爬上手腕,像把雨夜重新烘了一遍,只留下能说出口的那一层。
|
||||
|
||||
分开前,她抬手点了点伞:「这个,先放你那儿。等下一场雨。」
|
||||
|
||||
你点头。伞重新沉进包里,重量不大,却让背包的一侧微微倾斜——刚好提醒你:故事还会继续,而界面必须装得下继续。`;
|
||||
|
||||
const BODY_SHORT = {
|
||||
prose:
|
||||
"雨还在下。你把伞往她那边偏了偏,她没说话,只是把购物袋往怀里收紧了一点。\n\n路口的灯跳成绿色。你们一起迈步——这一次,谁也没有先松开。",
|
||||
chat_monitor:
|
||||
"【短信】\n她:到家了吗?\n你:刚进小区。伞借你的,明天还给你?\n她:……不用那么快。",
|
||||
spotlight:
|
||||
"水从铁栏杆上砸下来,把对面那人的轮廓冲得忽明忽暗。\n\n他抬起头——认出是你的瞬间,肩膀松了一下,又立刻绷紧。",
|
||||
turn_panel:
|
||||
"货架后传来塑料袋摩擦声。你屏住呼吸,左手按住刀柄——也许只是风。\n\n收银台抽屉半开,里面有未拆封的电池。",
|
||||
split_board:
|
||||
"抽屉夹层里有一张折痕发白的收据。日期是失踪前夜,商户栏写着「北港五金」。\n\n窗台积灰被抹过一道——有人近来开过窗。",
|
||||
choice_dock:
|
||||
"她把钥匙放在桌上,没有推过来,也没有收回。\n\n「你要是现在走,我就当今晚什么都没发生。」",
|
||||
chapter_reader:
|
||||
" 天亮得比往日晚。林晚站在窗边,把画册合上,指尖在封面上停了一息。\n\n 「今天,」她说,「我们换条路回家。」",
|
||||
};
|
||||
|
||||
const SAMPLE_META = {
|
||||
prose: {
|
||||
blocks: { footer: "本段为示例备注(可关)" },
|
||||
meta: { suggested_actions: [] },
|
||||
},
|
||||
chat_monitor: {
|
||||
blocks: {
|
||||
monitor: { 好感: "暖络", 今日话题: "下雨天", 体力: "还好" },
|
||||
footer: "",
|
||||
},
|
||||
meta: { suggested_actions: ["回一句关心", "约周末还伞"] },
|
||||
},
|
||||
spotlight: {
|
||||
blocks: {
|
||||
monitor: { 地点: "天桥下", 天气: "暴雨", 张力: "紧" },
|
||||
footer: "",
|
||||
},
|
||||
meta: { suggested_actions: ["先开口", "递伞过去"] },
|
||||
},
|
||||
turn_panel: {
|
||||
blocks: {
|
||||
monitor: { 体力: "6/10", 物资: "压缩饼干×2", 威胁: "低" },
|
||||
header: "第 12 回合 · 废弃便利店",
|
||||
footer: "检定:潜行(通过)",
|
||||
aside: { 可交互: "电池 / 后门 / 死角", 噪音: "低" },
|
||||
},
|
||||
meta: { suggested_actions: ["摸电池", "从后门离开", "大声喝问"] },
|
||||
},
|
||||
split_board: {
|
||||
blocks: {
|
||||
header: "案发现场 · 二楼书房",
|
||||
aside: {
|
||||
线索: "北港收据",
|
||||
证物: "窗台指痕",
|
||||
待查: "五金店监控",
|
||||
},
|
||||
footer: "",
|
||||
},
|
||||
meta: { suggested_actions: ["去北港", "采指纹", "先问管家"] },
|
||||
},
|
||||
choice_dock: {
|
||||
blocks: {
|
||||
monitor: { 信任: "中", 时间: "子夜前" },
|
||||
footer: "",
|
||||
},
|
||||
meta: {
|
||||
suggested_actions: ["拿走钥匙留下", "把钥匙推回去", "什么也不说,坐着"],
|
||||
},
|
||||
},
|
||||
chapter_reader: {
|
||||
blocks: {
|
||||
header: "卷三 · 第 7 章 雨停之前",
|
||||
aside: "进度:卷三 7/18\n下一钩:母亲来电",
|
||||
footer: "",
|
||||
},
|
||||
meta: { suggested_actions: [] },
|
||||
},
|
||||
};
|
||||
|
||||
function pickBody(shell, length) {
|
||||
if (length === "short") return BODY_SHORT[shell] || BODY_SHORT.prose;
|
||||
if (length === "mid") return BODY_MID;
|
||||
return BODY_LONG;
|
||||
}
|
||||
|
||||
function countChars(s) {
|
||||
return Array.from(String(s ?? "").replace(/\s/g, "")).length;
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
return String(s ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
const state = {
|
||||
shell: "spotlight",
|
||||
tone: "default",
|
||||
length: "mid",
|
||||
showActions: true,
|
||||
blockLabels: {},
|
||||
};
|
||||
|
||||
function buildTweaks() {
|
||||
return {
|
||||
tone_chrome: state.tone,
|
||||
show_suggested_actions: state.showActions,
|
||||
block_labels: { ...state.blockLabels },
|
||||
empty_states: {
|
||||
aside: "(侧栏空:可在创作里改空态文案)",
|
||||
footer: "(文末空)",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildPacket() {
|
||||
const base = SAMPLE_META[state.shell] || SAMPLE_META.prose;
|
||||
const body = pickBody(state.shell, state.length);
|
||||
return {
|
||||
schema: "present.v1",
|
||||
shell: state.shell,
|
||||
blocks: { ...base.blocks, body },
|
||||
meta: base.meta,
|
||||
_chars: countChars(body),
|
||||
};
|
||||
}
|
||||
|
||||
function render() {
|
||||
const info = SHELL_INFO[state.shell] || {
|
||||
name: state.shell,
|
||||
blurb: "",
|
||||
scenes: "",
|
||||
density: "",
|
||||
};
|
||||
const packet = buildPacket();
|
||||
document.getElementById("panel-title").textContent = info.name;
|
||||
document.getElementById("panel-subtitle").textContent = `${info.scenes} · 正文约 ${packet._chars} 字(不计空白)`;
|
||||
|
||||
document.getElementById("shell-meta").innerHTML = `
|
||||
<p><strong>${esc(info.name)}</strong>(<code>${esc(state.shell)}</code>)</p>
|
||||
<p>${esc(info.blurb)}</p>
|
||||
<p class="shells-density"><span>长文策略</span>${esc(info.density)} · 当前试读 <strong>${packet._chars}</strong> 字</p>
|
||||
<p class="muted">适用:${esc(info.scenes)}。请用上方「正文长度」切到长文,确认侧栏/选项是否仍可用。</p>
|
||||
`;
|
||||
|
||||
const preview = document.getElementById("shell-preview");
|
||||
preview.dataset.shell = state.shell;
|
||||
preview.innerHTML = renderPresentShellHtml(packet, esc, { tweaks: buildTweaks() });
|
||||
|
||||
document.querySelectorAll("[data-shell]").forEach((btn) => {
|
||||
btn.classList.toggle("active", btn.getAttribute("data-shell") === state.shell);
|
||||
});
|
||||
document.querySelectorAll("[data-tone]").forEach((btn) => {
|
||||
btn.classList.toggle("active", btn.getAttribute("data-tone") === state.tone);
|
||||
});
|
||||
document.querySelectorAll("[data-length]").forEach((btn) => {
|
||||
btn.classList.toggle("active", btn.getAttribute("data-length") === state.length);
|
||||
});
|
||||
|
||||
const showActionsEl = document.getElementById("tweak-show-actions");
|
||||
if (showActionsEl) showActionsEl.checked = state.showActions;
|
||||
}
|
||||
|
||||
function mount() {
|
||||
const list = document.getElementById("shell-list");
|
||||
list.innerHTML = PRESENT_SHELL_IDS.map((id) => {
|
||||
const info = SHELL_INFO[id];
|
||||
return `<button type="button" class="st-rail-item" data-shell="${esc(id)}" role="tab">
|
||||
${esc(info?.name || id)}
|
||||
</button>`;
|
||||
}).join("");
|
||||
|
||||
document.getElementById("tone-list").innerHTML = TONES.map(
|
||||
(t) =>
|
||||
`<button type="button" class="shells-tone-chip" data-tone="${esc(t.id)}">${esc(t.label)}</button>`,
|
||||
).join("");
|
||||
|
||||
document.getElementById("length-list").innerHTML = LENGTHS.map(
|
||||
(t) =>
|
||||
`<button type="button" class="shells-tone-chip" data-length="${esc(t.id)}">${esc(t.label)}</button>`,
|
||||
).join("");
|
||||
|
||||
document.getElementById("tweak-controls").innerHTML = `
|
||||
<label class="field">
|
||||
<input type="checkbox" id="tweak-show-actions" />
|
||||
显示「建议行动」(对应微调轴 show_suggested_actions)
|
||||
</label>
|
||||
<label class="field">
|
||||
正文区显示名(block_labels.body)
|
||||
<input type="text" id="tweak-label-body" placeholder="例如:短信记录 / 场面 / 本章" />
|
||||
</label>
|
||||
<label class="field">
|
||||
监控区显示名(block_labels.monitor)
|
||||
<input type="text" id="tweak-label-monitor" placeholder="例如:状态 / 今日" />
|
||||
</label>
|
||||
`;
|
||||
|
||||
list.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest("[data-shell]");
|
||||
if (!btn) return;
|
||||
state.shell = btn.getAttribute("data-shell");
|
||||
render();
|
||||
});
|
||||
|
||||
document.getElementById("tone-list").addEventListener("click", (e) => {
|
||||
const btn = e.target.closest("[data-tone]");
|
||||
if (!btn) return;
|
||||
state.tone = btn.getAttribute("data-tone");
|
||||
render();
|
||||
});
|
||||
|
||||
document.getElementById("length-list").addEventListener("click", (e) => {
|
||||
const btn = e.target.closest("[data-length]");
|
||||
if (!btn) return;
|
||||
state.length = btn.getAttribute("data-length");
|
||||
render();
|
||||
});
|
||||
|
||||
const syncLabels = () => {
|
||||
state.showActions = document.getElementById("tweak-show-actions").checked;
|
||||
const body = document.getElementById("tweak-label-body").value.trim();
|
||||
const monitor = document.getElementById("tweak-label-monitor").value.trim();
|
||||
state.blockLabels = {};
|
||||
if (body) state.blockLabels.body = body;
|
||||
if (monitor) state.blockLabels.monitor = monitor;
|
||||
render();
|
||||
};
|
||||
document.getElementById("tweak-controls").addEventListener("change", syncLabels);
|
||||
document.getElementById("tweak-controls").addEventListener("input", syncLabels);
|
||||
|
||||
render();
|
||||
}
|
||||
|
||||
mount();
|
||||
@@ -67,7 +67,7 @@
|
||||
<body>
|
||||
<div class="stats-page">
|
||||
<header>
|
||||
<p><a href="/">← 对话</a> · <a href="/modules.html">技能</a> · <a href="/settings.html">设置</a></p>
|
||||
<p><a href="/">← 对话</a> · <a href="/modules.html">技能</a> · <a href="/shells.html">呈现壳</a> · <a href="/settings.html">设置</a></p>
|
||||
<h1>Token 统计</h1>
|
||||
<p class="stats-muted">按时间戳筛选调用记录(存于本地 stats/*.jsonl)</p>
|
||||
</header>
|
||||
|
||||
401
web/styles.css
401
web/styles.css
@@ -1,4 +1,4 @@
|
||||
/* Functional layout 鈥<EFBFBD> agent-first, no production-line chrome */
|
||||
/* Functional layout â agent-first, no production-line chrome */
|
||||
|
||||
:root {
|
||||
--bg: #f4f4f5;
|
||||
@@ -22,7 +22,7 @@
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* 寮€鍒涗綔鍚庣缉杩涘乏渚ф爮 */
|
||||
/* å¼åä½å缩è¿å·¦ä¾§æ */
|
||||
body.rail-collapsed {
|
||||
--books-w: var(--books-w-collapsed);
|
||||
}
|
||||
@@ -31,7 +31,7 @@ body.rail-collapsed {
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden; /* 娴忚<EFBFBD>鍣ㄥ眰姘镐笉鍑虹幇椤甸潰婊氬姩鏉<EFBFBD> */
|
||||
overflow: hidden; /* æµè§å¨å±æ°¸ä¸åºç°é¡µé¢æ»å¨æ¡ */
|
||||
}
|
||||
button, input, textarea { font: inherit; }
|
||||
a.link { color: var(--accent); text-decoration: none; }
|
||||
@@ -138,7 +138,7 @@ body.rail-collapsed .rail-tab {
|
||||
}
|
||||
body.rail-collapsed .rail-panels { display: none; }
|
||||
body.rail-collapsed .panel-rail { align-items: stretch; }
|
||||
/* composer 鎸夊唴瀹硅嚜閫傚簲锛涗腑闂村尯鍚冩帀鍓╀綑楂樺害锛屾暣椤甸攣鍦ㄨ<EFBFBD>鍙e唴 */
|
||||
/* composer æå
容èªéåºï¼ä¸é´åºåæå©ä½é«åº¦ï¼æ´é¡µéå¨è§å£å
*/
|
||||
.panel-main {
|
||||
display: grid;
|
||||
grid-template-rows: var(--head-h) minmax(0, 1fr) auto;
|
||||
@@ -195,7 +195,7 @@ body.rail-collapsed .panel-rail { align-items: stretch; }
|
||||
.panel-head-actions { display: flex; gap: 4px; align-items: center; }
|
||||
.btn-sm.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
|
||||
|
||||
/* Books 鈥<EFBFBD> explorer breadcrumb + list */
|
||||
/* Books â explorer breadcrumb + list */
|
||||
.path-bar {
|
||||
display: flex; align-items: center; flex-wrap: nowrap; gap: 2px;
|
||||
margin: 6px 8px 4px; padding: 6px 10px;
|
||||
@@ -267,7 +267,7 @@ body.rail-collapsed .panel-rail { align-items: stretch; }
|
||||
body[data-lifecycle="play"] { --accent: #16a34a; --accent-soft: #dcfce7; --bg: #f0fdf4; }
|
||||
body[data-lifecycle="play"] .panel-rail { background: #f7fef9; }
|
||||
|
||||
/* Skill picker 鈥<EFBFBD> primary action when starting */
|
||||
/* Skill picker â primary action when starting */
|
||||
.skill-picker {
|
||||
padding: 16px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
@@ -285,7 +285,7 @@ body[data-lifecycle="play"] .panel-rail { background: #f7fef9; }
|
||||
.skill-card-desc { font-size: 12px; color: var(--muted); line-height: 1.4; }
|
||||
.skill-card-tag { font-size: 11px; color: var(--accent); margin-top: 6px; }
|
||||
|
||||
/* Feed 鈥<EFBFBD> SillyTavern 寮忓<EFBFBD>璇濇皵娉<EFBFBD> */
|
||||
/* Feed â SillyTavern å¼å¯¹è¯æ°æ³¡ */
|
||||
.message-feed {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
@@ -557,7 +557,7 @@ body.rail-collapsed .message-feed {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
/* 涓诲<EFBFBD>璇濋獙鏀跺崱锛氬悆婊′腑闂存爮 */
|
||||
/* 主对è¯éªæ¶å¡ï¼å满ä¸é´æ */
|
||||
.msg-review {
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
@@ -594,7 +594,7 @@ body.rail-collapsed .message-feed {
|
||||
.msg-review .ws-focus-hint { margin-bottom: 0; font-size: 11px; }
|
||||
.msg-review .review-worker-set { margin: 0 0 6px; }
|
||||
|
||||
/* 浜х墿绱у噾鍗★細妯<EFBFBD>悜涓昏酱锛岄<EFBFBD>瑙堜竴琛<EFBFBD> */
|
||||
/* 产ç©ç´§åå¡ï¼æ¨ªå主轴ï¼é¢è§ä¸è¡ */
|
||||
.artifact-compact-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -650,8 +650,8 @@ body.rail-collapsed .message-feed {
|
||||
border-radius: 999px;
|
||||
background: var(--accent-soft);
|
||||
}
|
||||
.artifact-compact-toggle::before { content: "灞曞紑"; }
|
||||
.artifact-compact[open] .artifact-compact-toggle::before { content: "鏀惰捣"; }
|
||||
.artifact-compact-toggle::before { content: "å±å¼"; }
|
||||
.artifact-compact[open] .artifact-compact-toggle::before { content: "æ¶èµ·"; }
|
||||
.artifact-compact-preview {
|
||||
margin: 0;
|
||||
font-family: var(--font-read);
|
||||
@@ -707,7 +707,7 @@ body.rail-collapsed .message-feed {
|
||||
}
|
||||
.ws-focus-body .artifact-compact-list { margin: 0; }
|
||||
|
||||
/* 灞曞紑鍖猴細mosaic / 鐡风爾缃戞牸锛屽悆婊℃í鍚戝<E98D9A>搴<EFBFBD> */
|
||||
/* å±å¼åºï¼mosaic / ç·ç ç½æ ¼ï¼å满横å宽度 */
|
||||
.af-mosaic {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -818,7 +818,7 @@ body.rail-collapsed .af-mosaic {
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* 鎶€鑳戒笓鐢ㄦ<EFBFBD>鏂囧崱 */
|
||||
/* æè½ä¸ç¨æ£æå¡ */
|
||||
.skill-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1244,7 +1244,7 @@ body.rail-collapsed .af-mosaic {
|
||||
.msg-q-list li { margin: 0.35rem 0; }
|
||||
.msg-q-lead { margin: 0; line-height: 1.55; font-size: 14px; }
|
||||
|
||||
/* Cursor 寮忚<EFBFBD>闂<EFBFBD>崱锛氳创鍦ㄦ秷鎭<EFBFBD>祦涓嬫柟锛屽崰婊″崗璋冨垪瀹藉害锛屼笉閬<EFBFBD>洊鍘嗗彶 */
|
||||
/* Cursor å¼è¯¢é®å¡ï¼è´´å¨æ¶æ¯æµä¸æ¹ï¼å 满åè°å宽度ï¼ä¸é®çåå² */
|
||||
.questions-card-host {
|
||||
flex: 0 1 auto;
|
||||
width: 100%;
|
||||
@@ -1255,7 +1255,7 @@ body.rail-collapsed .af-mosaic {
|
||||
min-height: 0;
|
||||
}
|
||||
.questions-card-host[hidden] { display: none !important; }
|
||||
/* 寮€鍚<EFBFBD>椂鍗<EFBFBD> feed 鍓╀綑楂樺害鐨勪笂闄愶紝涓嶆妸鏁撮〉鎾戝嚭瑙嗗彛锛涘嬁涓<E5AC81> [hidden] 骞跺瓨鎶<EFBFBD> display */
|
||||
/* å¼å¯æ¶å feed å©ä½é«åº¦çä¸éï¼ä¸ææ´é¡µæåºè§å£ï¼å¿ä¸ [hidden] 并忢 display */
|
||||
.questions-card-host.is-open:not([hidden]) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1276,7 +1276,7 @@ body.rail-collapsed .af-mosaic {
|
||||
background: var(--surface, #fff);
|
||||
overflow: hidden;
|
||||
}
|
||||
/* 鎮<EFBFBD>诞鎺т欢灞傦細涓嶅崰鏂囨。娴侀珮搴<EFBFBD> */
|
||||
/* æ¬æµ®æ§ä»¶å±ï¼ä¸å ææ¡£æµé«åº¦ */
|
||||
.qcard-float {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -1349,7 +1349,7 @@ body.rail-collapsed .af-mosaic {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding: 10px 12px;
|
||||
overflow: auto; /* 浠呭崱鍐呮粴鍔<EFBFBD>紱椤甸潰鏈<EFBFBD>韩涓嶆粴 */
|
||||
overflow: auto; /* ä»
å¡å
æ»å¨ï¼é¡µé¢æ¬èº«ä¸æ» */
|
||||
}
|
||||
.qcard.has-pager .qcard-body { padding-top: 36px; }
|
||||
.qcard.has-skip .qcard-body { padding-bottom: 40px; }
|
||||
@@ -1461,7 +1461,7 @@ body.rail-collapsed .af-mosaic {
|
||||
}
|
||||
.ws-slot-chip.ws-slot-on { background: var(--accent-soft); border-color: transparent; }
|
||||
.ws-slot-chip.ws-slot-off { opacity: 0.55; text-decoration: line-through; }
|
||||
/* 浜х墿缁撴瀯鍖栧睍绀<EFBFBD> 鈥<> 瀵归綈 qcard / flow-step / ws-badge */
|
||||
/* 产ç©ç»æåå±ç¤º â å¯¹é½ qcard / flow-step / ws-badge */
|
||||
.artifact-friendly { display: flex; flex-direction: column; gap: 10px; }
|
||||
.artifact-list { margin: 0; padding-left: 18px; line-height: 1.5; }
|
||||
.artifact-hero {
|
||||
@@ -1561,7 +1561,7 @@ body.rail-collapsed .af-mosaic {
|
||||
.artifact-kv-row.complex .artifact-kv-key { margin-bottom: 2px; }
|
||||
.artifact-kv-val { min-width: 0; font-size: 13px; line-height: 1.45; }
|
||||
|
||||
/* 鐧惧垎姣旇繘搴︽潯 */
|
||||
/* ç¾åæ¯è¿åº¦æ¡ */
|
||||
.pct-meter {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1913,7 +1913,7 @@ body.rail-collapsed .af-mosaic {
|
||||
.timeline-item { padding: 6px 0; border-bottom: 1px solid var(--border); }
|
||||
.timeline-empty { color: var(--muted); }
|
||||
|
||||
/* Composer 鈥<EFBFBD> 楂樺害闅忓唴瀹癸紝浣嗘湁涓婇檺锛岄伩鍏嶆尋鐖嗚<E99096>鍙<EFBFBD> */
|
||||
/* Composer â é«åº¦éå
容ï¼ä½æä¸éï¼é¿å
æ¤çè§å£ */
|
||||
.composer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 10px 12px;
|
||||
@@ -2001,81 +2001,362 @@ body.rail-collapsed .af-mosaic {
|
||||
body:not(.rail-collapsed) .rail-panels { display: none; }
|
||||
}
|
||||
|
||||
/* —— 游玩呈现壳 present.v1(P2 最小) —— */
|
||||
/* —— 游玩呈现壳 present.v1 —— */
|
||||
.present-shell {
|
||||
--present-ink: #1c1917;
|
||||
--present-muted: #78716c;
|
||||
--present-line: #e7e5e4;
|
||||
--present-soft: #f5f5f4;
|
||||
--present-accent: #0f766e;
|
||||
--present-body: 17.5px;
|
||||
--present-body-lh: 1.78;
|
||||
--present-body-hero: 19px;
|
||||
--present-body-read: 18.5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 0;
|
||||
position: relative;
|
||||
padding: 10px 12px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
background: var(--surface);
|
||||
padding: 14px 16px 16px;
|
||||
border: 1px solid var(--present-line);
|
||||
border-radius: 12px;
|
||||
background: var(--surface, #fff);
|
||||
color: var(--present-ink);
|
||||
}
|
||||
.present-shell-badge {
|
||||
align-self: flex-start;
|
||||
font-size: 11px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--muted);
|
||||
padding: 1px 7px;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--present-muted);
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface-2, rgba(0, 0, 0, 0.03));
|
||||
border: 1px solid var(--present-line);
|
||||
background: var(--present-soft);
|
||||
}
|
||||
.present-stack,
|
||||
.present-board,
|
||||
.present-split,
|
||||
.present-reader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
.present-region {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
.present-region-title {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--muted);
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--present-muted);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.present-region-body {
|
||||
margin: 0;
|
||||
line-height: 1.55;
|
||||
line-height: 1.65;
|
||||
white-space: pre-wrap;
|
||||
font-size: 14px;
|
||||
}
|
||||
.present-region--monitor .present-region-body,
|
||||
.present-region--aside .present-region-body {
|
||||
font-size: 13px;
|
||||
color: var(--text, inherit);
|
||||
padding: 6px 8px;
|
||||
/* P0:正文舞台 —— 随字数伸展(600~3000 靠外层滚动,不塞进小框) */
|
||||
.present-stage {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.present-region--body {
|
||||
min-width: 0;
|
||||
}
|
||||
.present-region--body .present-region-body,
|
||||
.present-stage .present-region--body .present-region-body,
|
||||
.present-stage.present-region--body .present-region-body {
|
||||
font-size: var(--present-body);
|
||||
line-height: var(--present-body-lh);
|
||||
letter-spacing: 0.01em;
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
}
|
||||
.present-stage--hero .present-region-body {
|
||||
font-size: var(--present-body-hero);
|
||||
line-height: 1.82;
|
||||
padding: 4px 2px 8px;
|
||||
}
|
||||
.present-stage--read .present-region-body {
|
||||
font-family: var(--font-read);
|
||||
font-size: var(--present-body-read);
|
||||
line-height: 1.9;
|
||||
max-width: 42rem;
|
||||
}
|
||||
/* 双栏:侧栏钉住,正文再长也能对照 */
|
||||
.present-board-side,
|
||||
.present-split-side,
|
||||
.present-reader-aside {
|
||||
position: sticky;
|
||||
top: 0.75rem;
|
||||
align-self: start;
|
||||
max-height: calc(100dvh - 3rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
/* P2:监控芯片 */
|
||||
.present-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 8px;
|
||||
}
|
||||
.present-chip {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface-2, rgba(0, 0, 0, 0.03));
|
||||
border: 1px solid var(--present-line);
|
||||
background: var(--present-soft);
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.present-shell--chat_monitor .present-region--monitor {
|
||||
order: -1;
|
||||
}
|
||||
.present-shell--turn_panel {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
.present-shell--chapter_reader .present-region--header .present-region-body {
|
||||
font-size: 15px;
|
||||
.present-chip-k {
|
||||
color: var(--present-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
.present-chip-v {
|
||||
color: var(--present-ink);
|
||||
font-weight: 500;
|
||||
}
|
||||
.present-region--monitor .present-region-body,
|
||||
.present-region--aside .present-region-body,
|
||||
.present-region--footer .present-region-body {
|
||||
font-size: 12.5px;
|
||||
line-height: 1.45;
|
||||
color: var(--present-ink);
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--present-line);
|
||||
background: var(--present-soft);
|
||||
}
|
||||
.present-region--header .present-region-body {
|
||||
font-size: 14px;
|
||||
font-weight: 650;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
/* P1:行动 */
|
||||
.present-action-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.present-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 7px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid color-mix(in srgb, var(--present-accent) 35%, var(--present-line));
|
||||
background: color-mix(in srgb, var(--present-accent) 8%, #fff);
|
||||
color: var(--present-ink);
|
||||
font-size: 13px;
|
||||
font-weight: 550;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.present-actions {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
line-height: 1.45;
|
||||
font-size: 13px;
|
||||
}
|
||||
.present-tone--messenger .present-region--body {
|
||||
border-left: 3px solid var(--border);
|
||||
padding-left: 10px;
|
||||
/* —— 各壳布局 —— */
|
||||
.present-shell--chat_monitor {
|
||||
--present-accent: #0369a1;
|
||||
background:
|
||||
linear-gradient(180deg, #f0f9ff 0%, #fff 48px);
|
||||
}
|
||||
.present-tone--book .present-region--body {
|
||||
max-width: 42rem;
|
||||
.present-shell--chat_monitor .present-stage {
|
||||
padding: 14px 16px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--present-line);
|
||||
background: #fff;
|
||||
}
|
||||
.present-tone--terminal .present-region-body {
|
||||
.present-shell--spotlight {
|
||||
--present-accent: #b45309;
|
||||
padding-top: 14px;
|
||||
}
|
||||
.present-shell--spotlight .present-stack--spotlight {
|
||||
gap: 14px;
|
||||
}
|
||||
.present-shell--spotlight .present-stage--hero {
|
||||
padding: 6px 4px 4px;
|
||||
}
|
||||
.present-shell--spotlight .present-region--monitor .present-region-title {
|
||||
display: none;
|
||||
}
|
||||
.present-shell--turn_panel {
|
||||
--present-accent: #047857;
|
||||
background: linear-gradient(165deg, #f0fdf4 0%, #fff 28%);
|
||||
}
|
||||
.present-board-top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.present-board-mid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.7fr) minmax(140px, 0.85fr);
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
}
|
||||
.present-board-side {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.present-shell--turn_panel .present-stage {
|
||||
padding: 16px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--present-line);
|
||||
background: #fff;
|
||||
}
|
||||
.present-shell--split_board {
|
||||
--present-accent: #4338ca;
|
||||
}
|
||||
.present-split {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.55fr) minmax(150px, 0.9fr);
|
||||
gap: 14px 16px;
|
||||
align-items: start;
|
||||
}
|
||||
.present-split-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
.present-split-side {
|
||||
min-width: 0;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--present-line);
|
||||
background: var(--present-soft);
|
||||
}
|
||||
.present-split .present-region--actions {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.present-shell--choice_dock {
|
||||
--present-accent: #be123c;
|
||||
}
|
||||
.present-stack--dock {
|
||||
gap: 10px;
|
||||
}
|
||||
.present-dock {
|
||||
margin-top: 4px;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--present-accent) 28%, var(--present-line));
|
||||
background: color-mix(in srgb, var(--present-accent) 6%, #fff);
|
||||
/* 长正文时选项坞钉在视口底,读完仍看得到 */
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
box-shadow: 0 -8px 20px color-mix(in srgb, #fff 70%, transparent);
|
||||
}
|
||||
.present-dock .present-region-title {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.present-dock .present-action {
|
||||
padding: 9px 14px;
|
||||
font-size: 13.5px;
|
||||
border-width: 1.5px;
|
||||
}
|
||||
.present-shell--chapter_reader {
|
||||
--present-accent: #44403c;
|
||||
background:
|
||||
linear-gradient(90deg, transparent 0, transparent),
|
||||
linear-gradient(180deg, #fafaf9 0%, #fff 40%);
|
||||
}
|
||||
.present-reader {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(120px, 0.42fr);
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
}
|
||||
.present-reader-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
.present-reader-aside {
|
||||
font-size: 12px;
|
||||
padding-top: 4px;
|
||||
border-left: 1px solid var(--present-line);
|
||||
padding-left: 14px;
|
||||
}
|
||||
.present-shell--chapter_reader .present-region--header .present-region-body {
|
||||
font-family: var(--font-read);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.present-shell--prose .present-stage--read {
|
||||
padding: 6px 2px;
|
||||
}
|
||||
.present-shell--prose .present-region--footer .present-region-body {
|
||||
opacity: 0.85;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.present-board-mid,
|
||||
.present-split,
|
||||
.present-reader {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.present-board-side,
|
||||
.present-split-side,
|
||||
.present-reader-aside {
|
||||
position: static;
|
||||
max-height: none;
|
||||
}
|
||||
.present-reader-aside {
|
||||
border-left: none;
|
||||
border-top: 1px solid var(--present-line);
|
||||
padding-left: 0;
|
||||
padding-top: 12px;
|
||||
}
|
||||
}
|
||||
.present-tone--messenger .present-stage {
|
||||
border-left: 3px solid #7dd3fc;
|
||||
padding-left: 12px;
|
||||
}
|
||||
.present-tone--book .present-stage--read .present-region-body,
|
||||
.present-tone--book .present-stage .present-region-body {
|
||||
font-family: var(--font-read);
|
||||
max-width: 38rem;
|
||||
}
|
||||
.present-tone--terminal {
|
||||
--present-ink: #d6d3d1;
|
||||
--present-muted: #a8a29e;
|
||||
--present-line: #3f3f46;
|
||||
--present-soft: #27272a;
|
||||
background: #18181b;
|
||||
color: #e7e5e4;
|
||||
}
|
||||
.present-tone--terminal .present-chip,
|
||||
.present-tone--terminal .present-stage,
|
||||
.present-tone--terminal .present-dock,
|
||||
.present-tone--terminal .present-split-side {
|
||||
background: #27272a;
|
||||
border-color: #3f3f46;
|
||||
}
|
||||
.present-tone--terminal .present-region-body,
|
||||
.present-tone--terminal .present-action {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
.present-tone--terminal .present-region--body .present-region-body {
|
||||
font-size: calc(var(--present-body) - 0.5px);
|
||||
}
|
||||
.present-tone--terminal .present-action {
|
||||
font-size: 13px;
|
||||
color: #e7e5e4;
|
||||
background: #3f3f46;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user