dsh-plugin-deck-studio 0.2.13 → 0.2.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client.js +494 -8
- package/index.js +81 -7
- package/lib/deck-templates.js +6 -1
- package/package.json +2 -2
- package/ui/workbench.css +26 -1
- package/ui/workbench.html +290 -26
package/index.js
CHANGED
|
@@ -395,9 +395,38 @@ export function createService(config, logger, env = process.env) {
|
|
|
395
395
|
// F-71:标出「来自注册表」——preset 工具实例靠它区分静态源,运行时删除的 deck 才能被移除。
|
|
396
396
|
config.decks.push({ id: entry.id, label: entry.label || entry.id, sourceRoot: entry.sourceRoot, kind: entry.kind ?? "deck", designId: entry.designId ?? null, fromRegistry: true });
|
|
397
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* 0.2.15:补挂「工作副本里有 manifest、但注册表里没有条目」的 deck(ghost deck)。
|
|
400
|
+
*
|
|
401
|
+
* 以前这类 deck 只在 status() 的兜底分支里露一个标题:没有 adapter → 源里已出好的整页图
|
|
402
|
+
* baseline、资源库、封面、9 份文档全部拿不到,工作台打开就是一屏「未出图」的空白页,
|
|
403
|
+
* 看起来像「资源都在就是没显示」。这里在 adapters 之前补挂:manifest 里的源还可读
|
|
404
|
+
* (script.md 在)就造一个只读 adapter;读不到的留给 status() 标「未挂载」。
|
|
405
|
+
*/
|
|
406
|
+
function adoptManifestDecks() {
|
|
407
|
+
const adopted = [];
|
|
408
|
+
for (const deckId of workspace.listDeckIds()) {
|
|
409
|
+
if (config.decks.some((deck) => deck.id === deckId)) continue;
|
|
410
|
+
const manifest = workspace.manifest(deckId);
|
|
411
|
+
const root = String(manifest?.source?.root ?? "").trim();
|
|
412
|
+
if (!root || !existsSync(join(root, "script.md"))) continue;
|
|
413
|
+
const kind = manifest?.kind === "design-canvas" ? "design-canvas" : "deck";
|
|
414
|
+
config.decks.push({
|
|
415
|
+
id: deckId,
|
|
416
|
+
label: String(manifest?.title ?? "").trim() || deckId,
|
|
417
|
+
sourceRoot: root,
|
|
418
|
+
kind,
|
|
419
|
+
designId: manifest?.designId ?? null,
|
|
420
|
+
fromManifest: true,
|
|
421
|
+
});
|
|
422
|
+
adopted.push(deckId);
|
|
423
|
+
}
|
|
424
|
+
return adopted;
|
|
425
|
+
}
|
|
398
426
|
// F-157 首次启动自举:新装 profile 打开 bootstrap 时,自动登记示例 deck / 拷资源库 / 导入预设风格,
|
|
399
427
|
// 避免「装完四个页面全是空的」。可用 profile patch 里 bootstrap: false 关掉。
|
|
400
428
|
// 示例 deck 必须在 adapters 之前登记;资源和 fashioned 风格放后面(依赖 adapters/workspace)。
|
|
429
|
+
const adoptedDeckIds = adoptManifestDecks();
|
|
401
430
|
if (config.bootstrap === true) bootstrapSampleDeck();
|
|
402
431
|
const adapters = config.decks.map((deck) => createLocalSourceAdapter(deck));
|
|
403
432
|
|
|
@@ -517,9 +546,12 @@ export function createService(config, logger, env = process.env) {
|
|
|
517
546
|
const success = String(payload.success ?? "").trim();
|
|
518
547
|
const scopeOut = String(payload.scopeOut ?? "").trim();
|
|
519
548
|
if (!title) throw new Error("deck 标题不能为空");
|
|
549
|
+
// 0.2.18:开工方式。表单式(默认)要求先把受众 / 意图 / 页数定清楚;
|
|
550
|
+
// 探索式只要一个标题——主意还模糊时先建本 deck,简报随对话补充(对齐 html deck-studio 的 explore)。
|
|
551
|
+
const creationMode = payload.mode === "explore" ? "explore" : "form";
|
|
520
552
|
// 空白画布(design 会话用):0 页,等 Agent 把风格样张沉淀到 designs/<ABBR>/。
|
|
521
553
|
const blank = Boolean(payload.blank);
|
|
522
|
-
const pageTitles = newDeckPageTitles(payload, blank);
|
|
554
|
+
const pageTitles = creationMode === "explore" ? ["待定 · 边聊边定"] : newDeckPageTitles(payload, blank);
|
|
523
555
|
const pad = (n) => String(n).padStart(2, "0");
|
|
524
556
|
const now = new Date();
|
|
525
557
|
const id = `deck-${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}-${Math.random().toString(36).slice(2, 6)}`;
|
|
@@ -553,13 +585,16 @@ export function createService(config, logger, env = process.env) {
|
|
|
553
585
|
"confidential: client",
|
|
554
586
|
"render: image",
|
|
555
587
|
"deck-type: talk",
|
|
588
|
+
...(creationMode === "explore" ? ["creation-mode: explore"] : []),
|
|
556
589
|
`pages: ${pageTitles.length}`,
|
|
557
590
|
"status: draft",
|
|
558
591
|
"---",
|
|
559
592
|
"",
|
|
560
593
|
`# ${title}`,
|
|
561
594
|
"",
|
|
562
|
-
|
|
595
|
+
creationMode === "explore"
|
|
596
|
+
? "> 由「新建 Deck · 探索模式」创建:还没定简报,先和 Agent 把听众 / 想让人带走什么 / 场合聊清楚,再补 script;未确认前不出图。"
|
|
597
|
+
: "> 由「新建 Deck」创建:先补意图与资料,再改 script;未确认前不出图。",
|
|
563
598
|
"",
|
|
564
599
|
"## 故事线",
|
|
565
600
|
"",
|
|
@@ -594,6 +629,7 @@ export function createService(config, logger, env = process.env) {
|
|
|
594
629
|
scopeOut,
|
|
595
630
|
created: now.toISOString(),
|
|
596
631
|
owner: config.userName,
|
|
632
|
+
creationMode,
|
|
597
633
|
}),
|
|
598
634
|
"utf8",
|
|
599
635
|
);
|
|
@@ -672,6 +708,8 @@ export function createService(config, logger, env = process.env) {
|
|
|
672
708
|
style: { abbr: styleAbbr, description: styleDescription },
|
|
673
709
|
kind: payload.kind ?? "deck",
|
|
674
710
|
designId: payload.designId ?? null,
|
|
711
|
+
// 0.2.18:探索式建档在注册表里也留痕(重启后 UI 能显示「探索式」)。
|
|
712
|
+
creationMode,
|
|
675
713
|
});
|
|
676
714
|
writeFileSync(decksRegistryFile, `${JSON.stringify({ decks: entries }, null, 2)}\n`, "utf8");
|
|
677
715
|
|
|
@@ -700,7 +738,8 @@ export function createService(config, logger, env = process.env) {
|
|
|
700
738
|
designId: payload.designId ?? null,
|
|
701
739
|
style: { abbr: styleAbbr, description: styleDescription },
|
|
702
740
|
pages: pageTitles.length,
|
|
703
|
-
|
|
741
|
+
creationMode,
|
|
742
|
+
note: `初始化:script ${pageTitles.length} 页 + 意图 / 风格 / 参考资料清单 / 文档骨架${creationMode === "explore" ? "(探索式:简报随对话补充)" : ""}`,
|
|
704
743
|
},
|
|
705
744
|
});
|
|
706
745
|
return {
|
|
@@ -837,9 +876,16 @@ export function createService(config, logger, env = process.env) {
|
|
|
837
876
|
return { designId: design.designId, added, design: designProjectView(design) };
|
|
838
877
|
}
|
|
839
878
|
|
|
840
|
-
/**
|
|
879
|
+
/**
|
|
880
|
+
* F-157:把仓库自带的示例 deck 登记成只读源。
|
|
881
|
+
*
|
|
882
|
+
* 0.2.15:判定从「`config.decks` 非空就跳过」改成「这份示例没被挂上才登记」。
|
|
883
|
+
* 旧口径会被「导入预设风格时自动建的 8 本风格画布」顶掉——装过风格的机器上示例 deck
|
|
884
|
+
* 永远不会再登记,而它的工作副本还在 `decks/<id>/deck.json`,于是变成 ghost deck:
|
|
885
|
+
* 列表里有 10 页标题,打开却一页图都没有。
|
|
886
|
+
*/
|
|
841
887
|
function bootstrapSampleDeck() {
|
|
842
|
-
if (config.decks.
|
|
888
|
+
if (config.decks.some((deck) => deck.id === "sample-deckline-intro")) return;
|
|
843
889
|
const sampleRoot = join(REPO_ROOT, "samples", "deckline-intro");
|
|
844
890
|
if (!existsSync(join(sampleRoot, "script.md"))) return;
|
|
845
891
|
config.decks.push({ id: "sample-deckline-intro", label: "Deckline 示例手册", sourceRoot: sampleRoot, kind: "deck", designId: null, fromBootstrap: true });
|
|
@@ -1874,6 +1920,29 @@ export function createService(config, logger, env = process.env) {
|
|
|
1874
1920
|
};
|
|
1875
1921
|
}
|
|
1876
1922
|
|
|
1923
|
+
/**
|
|
1924
|
+
* 0.2.16:这本 deck 该用哪个目录当「会话工作区」。
|
|
1925
|
+
*
|
|
1926
|
+
* 背景:桌面宿主的 shell cwd 是 profile 目录(被工作区守卫挡掉),宿主客户端的
|
|
1927
|
+
* workspaces 服务又没有 initializeDefault,源目录也常常还没登记成工作区——三者叠加,
|
|
1928
|
+
* 「+ 新建会话」永远解析不到工作区,于是**从没有会话的 deck 永远建不出会话**(点了没反应)。
|
|
1929
|
+
* 这里给宿主桥一个明确的落点:deck 自己的项目目录优先;源落在插件安装位(node_modules)
|
|
1930
|
+
* 或 App 包里时不拿来当工作区,退回这本 deck 的工作副本目录 <dataRoot>/decks/<id>。
|
|
1931
|
+
*/
|
|
1932
|
+
function sessionWorkspacePathOf(deckId) {
|
|
1933
|
+
const sourceRoot = String(adapterOf(deckId)?.sourceRoot ?? "").trim();
|
|
1934
|
+
const installed = /[/\\]node_modules[/\\]/.test(sourceRoot) || sourceRoot.startsWith("/Applications/");
|
|
1935
|
+
if (sourceRoot && !installed) return sourceRoot;
|
|
1936
|
+
try {
|
|
1937
|
+
const dir = workspace.deckDir(deckId);
|
|
1938
|
+
// 工作区必须指向真实存在的目录;工作副本还没建出来时先建空的(没有 deck.json,不会被当成 deck 列出)。
|
|
1939
|
+
mkdirSync(dir, { recursive: true });
|
|
1940
|
+
return dir;
|
|
1941
|
+
} catch {
|
|
1942
|
+
return "";
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1877
1946
|
/** media 路由解析:只放行白名单目录的图片。 */
|
|
1878
1947
|
function mediaFile(deckId, scope, path) {
|
|
1879
1948
|
const adapter = adapterOf(deckId);
|
|
@@ -1951,8 +2020,11 @@ export function createService(config, logger, env = process.env) {
|
|
|
1951
2020
|
}
|
|
1952
2021
|
for (const deckId of workspace.listDeckIds()) {
|
|
1953
2022
|
if (decks.some((deck) => deck.deckId === deckId)) continue;
|
|
2023
|
+
// 0.2.15:走到这里说明这本 deck 的源没挂上(目录不在了 / script.md 丢了)。
|
|
2024
|
+
// 明确标 unmounted + sourceAvailable:false,前端据此给「重新登记 / 换源」的入口,
|
|
2025
|
+
// 而不是列一屏看起来正常、点开全是空白页的 deck。
|
|
1954
2026
|
const manifest = workspace.manifest(deckId);
|
|
1955
|
-
decks.push({ deckId, label: manifest?.title ?? deckId, sourceRoot: manifest?.source?.root ?? "", sourceAvailable:
|
|
2027
|
+
decks.push({ deckId, label: manifest?.title ?? deckId, sourceRoot: manifest?.source?.root ?? "", sourceAvailable: false, unmounted: true, title: manifest?.title ?? deckId, pageCount: manifest?.slides?.length ?? 0, status: manifest?.status ?? "", warnings: [] });
|
|
1956
2028
|
}
|
|
1957
2029
|
const tasks = store.taskList(200);
|
|
1958
2030
|
const counts = {};
|
|
@@ -2276,10 +2348,12 @@ export function createService(config, logger, env = process.env) {
|
|
|
2276
2348
|
// 当前 deck 生效的出图节点/模型(下拉可换)
|
|
2277
2349
|
renderNodes: renderState(activeId),
|
|
2278
2350
|
// 首页/标签用:deck 列表、风格(designs/<ABBR>/ 样张)、资源库
|
|
2279
|
-
decks: decks.map((item) => ({ deckId: item.deckId, label: item.label, pageCount: item.pageCount, status: item.status ?? "", sourceRoot: item.sourceRoot ?? "", kind: item.kind ?? "deck" })),
|
|
2351
|
+
decks: decks.map((item) => ({ deckId: item.deckId, label: item.label, pageCount: item.pageCount, status: item.status ?? "", sourceRoot: item.sourceRoot ?? "", kind: item.kind ?? "deck", sourceAvailable: item.sourceAvailable !== false, unmounted: item.unmounted === true })),
|
|
2280
2352
|
designs: designSamples(activeId),
|
|
2281
2353
|
...homeCatalog(activeId),
|
|
2282
2354
|
sourceRoot: adapter?.sourceRoot ?? deck?.source?.root ?? "",
|
|
2355
|
+
// 0.2.16:给宿主桥注册 DSH 工作区用的项目目录(见 sessionWorkspacePathOf)。
|
|
2356
|
+
sessionWorkspacePath: sessionWorkspacePathOf(activeId),
|
|
2283
2357
|
// P0:黄条与 Inspector 用的数据目录状态(active 级别)
|
|
2284
2358
|
dataDir: activeDataDir,
|
|
2285
2359
|
dataDirMismatch: needsBanner,
|
package/lib/deck-templates.js
CHANGED
|
@@ -20,7 +20,7 @@ const orPlaceholder = (value, hint) => (pad(value) ? pad(value) : `(待补:$
|
|
|
20
20
|
* intent.md 初始内容。
|
|
21
21
|
* @param {{title:string, audience?:string, topic?:string, intent?:string, references?:string, created?:string, owner?:string}} input
|
|
22
22
|
*/
|
|
23
|
-
export function buildIntentMarkdown({ title, audience = "", topic = "", intent = "", references = "", occasion = "", success = "", scopeOut = "", argument = "", skeletonType = "", pageOrder = "", keepThree = "", created = "", owner = "" } = {}) {
|
|
23
|
+
export function buildIntentMarkdown({ title, audience = "", topic = "", intent = "", references = "", occasion = "", success = "", scopeOut = "", argument = "", skeletonType = "", pageOrder = "", keepThree = "", created = "", owner = "", creationMode = "" } = {}) {
|
|
24
24
|
const heading = pad(title) || "(deck 标题)";
|
|
25
25
|
return [
|
|
26
26
|
"---",
|
|
@@ -28,10 +28,15 @@ export function buildIntentMarkdown({ title, audience = "", topic = "", intent =
|
|
|
28
28
|
`owner: ${owner}`,
|
|
29
29
|
...(created ? [`created: ${created}`] : []),
|
|
30
30
|
"status: draft",
|
|
31
|
+
// 0.2.18:探索式建档留痕——重启 / 重读 intent 时仍然知道这本是「边聊边定」的。
|
|
32
|
+
...(creationMode === "explore" ? ["creation-mode: explore"] : []),
|
|
31
33
|
"---",
|
|
32
34
|
"",
|
|
33
35
|
"# 意图",
|
|
34
36
|
"",
|
|
37
|
+
...(creationMode === "explore"
|
|
38
|
+
? ["- **创建模式**:探索式 —— 简报随对话补充;下面「待补」的字段由 Agent 逐条问清楚,不是漏填表单。"]
|
|
39
|
+
: []),
|
|
35
40
|
`- **听众**:${orPlaceholder(audience, "讲给谁:角色 / 部门 / 层级 / 对 AI 的熟悉度")}`,
|
|
36
41
|
`- **主题**:${orPlaceholder(topic || heading, "一句话说清这场讲什么")}`,
|
|
37
42
|
`- **带走**:${orPlaceholder(intent, "听完他们应该能带走什么(1–3 条,可验证)")}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-deck-studio",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
4
4
|
"description": "Deckline:DSH 的 image deck / PPT 工作台插件——原生 Chat + 整页图片 deck 工作区 + 设计风格库;用对话做 PPT、按页出图、版本可追踪、导出 PDF/PPTX。",
|
|
5
5
|
"icon": "assets/icon.svg",
|
|
6
6
|
"homepage": "https://github.com/yuna78/dsh-deck-studio#readme",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"verify": "node verify/smoke.mjs",
|
|
48
|
-
"prepublishOnly": "node verify/smoke.mjs",
|
|
48
|
+
"prepublishOnly": "node -e \"const fs=require('fs');const{execSync}=require('child_process');if(fs.existsSync('verify/smoke.mjs')){execSync('node verify/smoke.mjs',{stdio:'inherit'})}else{console.log('[prepublishOnly] verify/ 不在发布目录(公开包白名单不含),跳过;发布前请在开发仓跑 npm run smoke')}\"",
|
|
49
49
|
"prepack": "node scripts/prepack-assets.mjs"
|
|
50
50
|
},
|
|
51
51
|
"dsh": {
|
package/ui/workbench.css
CHANGED
|
@@ -371,6 +371,19 @@ body.resizing{user-select:none;cursor:col-resize}
|
|
|
371
371
|
.newdeck-body .nd-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
|
372
372
|
.newdeck-body .field > span{font-size:10.5px;color:#5c6464}
|
|
373
373
|
.newdeck-body input,.newdeck-body textarea,.newdeck-body select{width:100%;font:inherit;font-size:12px;padding:7px 9px;border:1px solid var(--line);border-radius:5px;background:#fffdf8;color:var(--ink);resize:vertical}
|
|
374
|
+
/* 0.2.18 / 0.2.19:表单 / 探索两种开工方式。
|
|
375
|
+
必须写成 .newdeck-body .nd-mode-opt 这种权重,并且放在上面那条基础 input 规则之后——
|
|
376
|
+
否则 radio 会被 `.newdeck-body input{width:100%;padding:7px 9px}` 接管:撑满整行、把文字挤成一字一行
|
|
377
|
+
(0.2.18 的排版事故就是同权重 + 顺序靠前导致的)。 */
|
|
378
|
+
.newdeck-body .nd-mode{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:0 0 10px}
|
|
379
|
+
.newdeck-body .nd-mode-opt{display:flex;align-items:flex-start;gap:8px;padding:8px 10px;border:1px solid var(--line);border-radius:6px;background:#fffdf8;cursor:pointer;font-size:12px;color:var(--ink);min-width:0}
|
|
380
|
+
.newdeck-body .nd-mode-opt input[type="radio"]{width:auto;height:auto;flex:0 0 auto;margin:2px 0 0;padding:0;border:0;background:none;accent-color:#0c4651}
|
|
381
|
+
.newdeck-body .nd-mode-opt > span{flex:1 1 auto;display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
382
|
+
.newdeck-body .nd-mode-opt b{font-weight:600;font-size:12px;line-height:1.35}
|
|
383
|
+
.newdeck-body .nd-mode-opt em{font-style:normal;font-size:11px;line-height:1.45;color:#728086;overflow-wrap:anywhere}
|
|
384
|
+
.newdeck-body .nd-mode-opt:has(input:checked){border-color:#0c4651;box-shadow:inset 0 0 0 1px #0c4651}
|
|
385
|
+
.newdeck-body .nd-mode-opt:has(input:checked) em{color:#48605f}
|
|
386
|
+
.newdeck-body .nd-mode-note{margin:0 0 10px}
|
|
374
387
|
/* 新建 Deck · 风格选择:样张预览卡片(F-45) */
|
|
375
388
|
.nd-styles{display:grid;grid-template-columns:repeat(auto-fill,minmax(158px,1fr));gap:8px;margin-bottom:8px}
|
|
376
389
|
.nd-style{display:flex;flex-direction:column;padding:0;border:1px solid var(--line);border-radius:8px;background:#fffdf8;cursor:pointer;font:inherit;text-align:left;overflow:hidden;transition:border-color .12s,box-shadow .12s}
|
|
@@ -479,6 +492,15 @@ body.has-chat .layout{grid-template-columns:var(--nav-w,430px) 5px minmax(0,1fr)
|
|
|
479
492
|
.chat-session-item span{font-size:9.5px;color:#8a9599}
|
|
480
493
|
.chat-session-empty{margin:6px 8px 8px;font-size:10.5px;color:#8a9599;line-height:1.6}
|
|
481
494
|
#nativeChatFrame{flex:1;min-height:0;width:100%;border:0;background:var(--paper)}
|
|
495
|
+
/* F-202B:桌面宿主(dsh-app://app)不嵌 DSH 页面——工作台对话列留一个 Dock 占位,
|
|
496
|
+
* 宿主用 shell.overlay 把原生会话精确盖在这里;原生 Dock 不可达时回退 #chatHostCard。 */
|
|
497
|
+
#chatDockArea{flex:1;min-height:0;background:var(--paper)}
|
|
498
|
+
#chatDockArea[hidden]{display:none}
|
|
499
|
+
#chatDockArea:empty::before{content:"还没有绑定会话——点上方 + 新建";margin:44px auto;display:block;width:80%;text-align:center;color:#8a9599;font-size:11px}
|
|
500
|
+
#chatHostCard{flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:26px 20px;overflow:auto}
|
|
501
|
+
#chatHostCard[hidden]{display:none}
|
|
502
|
+
#chatHostCard .chat-host-hint{margin:0;font-size:11px;line-height:1.7;color:var(--ink);opacity:.78;text-align:center;max-width:30em}
|
|
503
|
+
#chatHostCard .btn{min-width:180px;text-align:center}
|
|
482
504
|
|
|
483
505
|
/* 叙事结构概览(脚本页顶部,从 script 派生) */
|
|
484
506
|
.story-overview{margin:10px 0 18px;border:1px solid var(--line);border-radius:7px;background:#f8f6f0;padding:10px 14px}
|
|
@@ -668,7 +690,10 @@ body.has-chat .layout{grid-template-columns:var(--nav-w,430px) 5px minmax(0,1fr)
|
|
|
668
690
|
body.presenting{overflow:hidden}
|
|
669
691
|
.present-stage{width:100%;height:100%;display:flex;align-items:center;justify-content:center;padding:22px 22px 82px;cursor:pointer}
|
|
670
692
|
.present-slide{margin:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center}
|
|
671
|
-
.
|
|
693
|
+
/* 0.2.24:放映时整页图必须随屏幕放大——以前只写 max-width/max-height,图小于画布时永远保持
|
|
694
|
+
自然尺寸(1280×720 的页图在 2K/4K 屏上就缩在中间一小块)。改成 width/height:100% + object-fit:contain,
|
|
695
|
+
大图缩小、小图放大都按比例铺满;阴影改用 drop-shadow,跟着图片轮廓而不是整块元素。 */
|
|
696
|
+
.present-slide img{width:100%;height:100%;max-width:none;max-height:none;object-fit:contain;border-radius:0;background:transparent;filter:drop-shadow(0 22px 62px #000b);user-select:none;-webkit-user-drag:none}
|
|
672
697
|
.present-empty{width:min(900px,82vw);aspect-ratio:16/9;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;border:1px dashed #ffffff2e;border-radius:8px;color:#c9c2b4;font-size:13px;text-align:center;padding:24px}
|
|
673
698
|
.present-empty b{font-size:20px;color:#f3ede1}
|
|
674
699
|
.present-bar{position:absolute;left:50%;bottom:18px;transform:translateX(-50%);display:flex;align-items:center;gap:10px;padding:6px 10px 6px 6px;border-radius:999px;background:#0c4651d9;border:1px solid #ffffff24;box-shadow:0 12px 34px #0009;font-size:12px;color:#e9e4d8;max-width:calc(100% - 28px);backdrop-filter:blur(8px)}
|