@my-life-buddies/cli 0.2.0 → 0.4.0
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/README.md +70 -53
- package/dist/bin/core.js +936 -447
- package/dist/bin/core.js.map +4 -4
- package/dist/bin/preview.js +147 -12
- package/dist/bin/preview.js.map +3 -3
- package/dist/web/app.css +8 -0
- package/dist/web/app.js +7 -2
- package/dist/web/index.html +11 -3
- package/dist/web/widget-delivery.js +28 -0
- package/dist/web/widgets.css +31 -0
- package/dist/web/widgets.js +127 -0
- package/package.json +3 -3
- package/resources/README.md +3 -1
- package/resources/agent-template/package-lock.json +4 -4
- package/resources/agent-template/package.json +1 -1
- package/resources/buddy-creator/INSTALL.md +2 -2
- package/resources/buddy-creator/PATCHES.md +21 -1
- package/resources/buddy-creator/SKILL.md +16 -6
- package/resources/buddy-creator/agents/openai.yaml +1 -1
- package/resources/buddy-creator/assets/preview/index.html +2 -0
- package/resources/buddy-creator/assets/preview/widgets.css +1 -0
- package/resources/buddy-creator/assets/preview/widgets.js +168 -0
- package/resources/buddy-creator/assets/widget-reference/conversation.png +0 -0
- package/resources/buddy-creator/assets/widget-reference/expanded.png +0 -0
- package/resources/buddy-creator/assets/widget-v2/app.js +62 -0
- package/resources/buddy-creator/assets/widget-v2/style.css +1 -0
- package/resources/buddy-creator/assets/widget-v3/app.js +65 -0
- package/resources/buddy-creator/assets/widget-v3/style.css +103 -0
- package/resources/buddy-creator/assets/widget-v4/app.js +66 -0
- package/resources/buddy-creator/assets/widget-v4/style.css +187 -0
- package/resources/buddy-creator/references/artifact-schema.md +2 -0
- package/resources/buddy-creator/references/host-guide.md +12 -6
- package/resources/buddy-creator/references/recovery.md +6 -3
- package/resources/buddy-creator/references/service.md +2 -0
- package/resources/buddy-creator/references/widgets-protocol.md +83 -0
- package/resources/buddy-creator/references/widgets.md +99 -0
- package/resources/buddy-creator/scripts/buddy_core.py +75 -13
- package/resources/buddy-creator/scripts/completion.py +64 -8
- package/resources/buddy-creator/scripts/preview.py +46 -6
- package/resources/buddy-creator/scripts/widget_render.py +66 -0
- package/resources/buddy-creator/scripts/widget_render_v1.py +176 -0
- package/resources/buddy-creator/scripts/widget_render_v2.py +98 -0
- package/resources/buddy-creator/scripts/widget_render_v3.py +86 -0
- package/resources/buddy-creator/scripts/widget_render_v4.py +121 -0
- package/resources/buddy-creator/scripts/widgets.py +227 -0
- package/resources/buddy-creator/version.json +1 -1
- package/resources/buddy-creator.manifest.json +121 -26
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Read-only mirror of the host's saved design workflow. No confirmation writes.
|
|
2
|
+
const el = (tag, text, cls) => {
|
|
3
|
+
const node = document.createElement(tag);
|
|
4
|
+
if (text !== undefined) node.textContent = text;
|
|
5
|
+
if (cls) node.className = cls;
|
|
6
|
+
return node;
|
|
7
|
+
};
|
|
8
|
+
const root = document.getElementById('root');
|
|
9
|
+
const nav = el('nav', undefined, 'design-navigation');
|
|
10
|
+
nav.setAttribute('aria-label', '创作内容');
|
|
11
|
+
const bookButton = el('button', '四册 Booklet');
|
|
12
|
+
const designButton = el('button', '小挂件设计');
|
|
13
|
+
const progress = el('span', '正在读取创作进度', 'design-progress');
|
|
14
|
+
nav.append(bookButton, designButton, progress);
|
|
15
|
+
document.body.prepend(nav);
|
|
16
|
+
const panel = el('main', undefined, 'design-workspace');
|
|
17
|
+
panel.hidden = true;
|
|
18
|
+
document.body.append(panel);
|
|
19
|
+
let chosen = false, previousPhase, version;
|
|
20
|
+
const selectedStates = new Map();
|
|
21
|
+
function choose(design, manual = false) {
|
|
22
|
+
if (manual) chosen = true;
|
|
23
|
+
root.hidden = design; panel.hidden = !design;
|
|
24
|
+
bookButton.setAttribute('aria-pressed', String(!design));
|
|
25
|
+
designButton.setAttribute('aria-pressed', String(design));
|
|
26
|
+
// Do not stop the upstream reader before its first snapshot selects a stage.
|
|
27
|
+
if (manual && root.querySelector('.preview-header')) document.dispatchEvent(new Event('buddy:reader-browse'));
|
|
28
|
+
}
|
|
29
|
+
bookButton.onclick = () => choose(false, true);
|
|
30
|
+
designButton.onclick = () => choose(true, true);
|
|
31
|
+
choose(false);
|
|
32
|
+
const card = title => { const node = el('section', undefined, 'design-card'); node.append(el('h2', title)); return node; };
|
|
33
|
+
const badge = (text, pass = false) => el('span', text, 'design-badge' + (pass ? ' passed' : ''));
|
|
34
|
+
function field(parent, label, value) { const row = el('div', undefined, 'design-field'); row.append(el('dt', label), el('dd', value)); parent.append(row); }
|
|
35
|
+
const reviewLabels = { 'booklet-fit': '符合搭子需求', 'reading-order': '阅读顺序与长内容', 'chat-consistency': '对话卡片与展开页一致', 'conversation-update': '数据随对话逐步更新', 'read-only': '只读交互边界' };
|
|
36
|
+
function render(snapshot) {
|
|
37
|
+
const design = snapshot.widgetDesign;
|
|
38
|
+
if (!design) { nav.hidden = true; return; }
|
|
39
|
+
nav.hidden = false;
|
|
40
|
+
const index = design.steps.findIndex(step => step.id === design.phase);
|
|
41
|
+
progress.textContent = design.enabled ? design.steps.map((step, i) => (i < index ? '✓ ' : '') + step.label).join(' → ') : '已有作品';
|
|
42
|
+
if (!chosen && design.phase !== 'booklets' && design.phase !== 'legacy' && previousPhase !== design.phase) choose(true);
|
|
43
|
+
previousPhase = design.phase;
|
|
44
|
+
panel.replaceChildren();
|
|
45
|
+
const heading = el('div', undefined, 'design-heading');
|
|
46
|
+
heading.append(el('p', 'BOOKLET / 交互设计', 'design-eyebrow'), el('h1', '小挂件,让对话里的内容更好读'), el('p', design.nextStep));
|
|
47
|
+
if (snapshot.paused) heading.append(badge('创作已暂停;确认进度已保留'));
|
|
48
|
+
panel.append(heading);
|
|
49
|
+
const steps = el('ol', undefined, 'design-steps');
|
|
50
|
+
design.steps.forEach((step, i) => {
|
|
51
|
+
const li = el('li', undefined, i === index ? 'current' : i < index ? 'done' : '');
|
|
52
|
+
li.append(el('span', i < index ? '✓' : String(i + 1)), el('strong', step.label));
|
|
53
|
+
if (i === index) li.setAttribute('aria-current', 'step');
|
|
54
|
+
steps.append(li);
|
|
55
|
+
});
|
|
56
|
+
panel.append(steps, el('p', '此页同步清单、原型和验收进度。确认、补充和修改请在主对话中提出。', 'design-callout'));
|
|
57
|
+
const list = card('01 / 小挂件清单');
|
|
58
|
+
list.append(badge(design.catalogConfirmed ? '开发者已确认' : design.phase === 'booklets' ? '四册确认后开始' : '待开发者确认', design.catalogConfirmed));
|
|
59
|
+
if (!design.catalog) list.append(el('p', '完成四册后,将根据搭子的任务、持续服务和数据需求提出清单,供你确认、补充或调整。'));
|
|
60
|
+
else if (!design.items.length) list.append(el('p', design.catalog.data.noWidgetsReason), el('p', design.catalogConfirmed ? '已明确确认本作品不使用小挂件。' : '“不使用小挂件”也需要在主对话明确确认。'));
|
|
61
|
+
else {
|
|
62
|
+
for (const item of design.items) {
|
|
63
|
+
const entry = el('details', undefined, 'design-list-item'); entry.open = true;
|
|
64
|
+
entry.append(el('summary', item.name));
|
|
65
|
+
const fields = el('dl', undefined, 'design-specs');
|
|
66
|
+
for (const [label, key] of [['用途', 'purpose'], ['何时创建 / 展示', 'creationTrigger'], ['首次展示', 'initialContent'], ['对话更新规则', 'updateRules'], ['实例沿用 / 新建', 'instancePolicy'], ['对话摘要', 'conversationSummary']]) field(fields, label, item[key]);
|
|
67
|
+
field(fields, 'Booklet 依据', item.bookletRefs.map(ref => {
|
|
68
|
+
const source = snapshot.artifacts.find(a => a.id === ref.id);
|
|
69
|
+
const [stage, chapter] = ref.id.split('.');
|
|
70
|
+
const label = { definition: '定义', knowledge: '知识', methods: '方法', service: '服务' }[stage];
|
|
71
|
+
return label + '手册 · ' + (source?.title && source.title !== ref.id ? source.title : '第 ' + chapter + ' 章');
|
|
72
|
+
}).join(';'));
|
|
73
|
+
field(fields, '阅读能力', item.readInteractions.map(key => ({ tabs: '分组切换', filter: '预设筛选', collapse: '折叠', detail: '只读详情' })[key]).join('、') || '滚动阅读');
|
|
74
|
+
entry.append(fields);
|
|
75
|
+
const table = el('table'); const head = el('tr'); ['展示字段', '数据来源', '尚未补充时'].forEach(label => head.append(el('th', label))); table.append(head);
|
|
76
|
+
item.fields.forEach(f => { const row = el('tr'); [f.label, f.source, f.emptyText].forEach(value => row.append(el('td', value))); table.append(row); });
|
|
77
|
+
entry.append(table); list.append(entry);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
panel.append(list);
|
|
81
|
+
const prototypes = card('02 / 原型确认');
|
|
82
|
+
prototypes.append(el('p', '同一份数据,两种展示方式。切换下方示例状态,再试用小挂件内的阅读操作。', 'design-muted'));
|
|
83
|
+
if (!design.items.length) prototypes.append(el('p', design.catalogConfirmed ? '已确认不需要小挂件,本阶段无需原型。' : '清单确认后生成原型。'));
|
|
84
|
+
for (const item of design.items) {
|
|
85
|
+
const section = el('section', undefined, 'design-prototype');
|
|
86
|
+
section.append(el('h3', item.name), badge(item.prototypeConfirmed ? '当前原型已确认' : '待确认原型', item.prototypeConfirmed));
|
|
87
|
+
if (!item.prototype || !item.automaticCheck.pass || Object.keys(item.blockers).length) {
|
|
88
|
+
section.append(el('p', !item.prototype ? '确认清单后生成该小挂件的原型。' : Object.keys(item.blockers).length ? '依据或清单已调整,请在主对话继续校准这一项。' : item.automaticCheck.message));
|
|
89
|
+
} else {
|
|
90
|
+
const prototype = item.prototype, data = prototype.data;
|
|
91
|
+
let selected = selectedStates.get(item.id) || 'initial';
|
|
92
|
+
const control = el('div', undefined, 'design-state-tabs'); control.setAttribute('aria-label', item.name + '示例状态');
|
|
93
|
+
const grid = el('div', undefined, 'design-views');
|
|
94
|
+
const refresh = () => {
|
|
95
|
+
grid.replaceChildren();
|
|
96
|
+
for (const view of ['chat', 'expanded']) {
|
|
97
|
+
const figure = el('figure');
|
|
98
|
+
figure.append(el('figcaption', view === 'chat' ? '出现在对话中' : '展开后阅读'));
|
|
99
|
+
const frame = el('iframe'); frame.title = item.name + ' / ' + (view === 'chat' ? '对话卡片' : '展开页');
|
|
100
|
+
frame.width = data.design.screenWidth; frame.height = data.design.screenHeight;
|
|
101
|
+
frame.sandbox = 'allow-scripts';
|
|
102
|
+
frame.src = './widget-assets/' + prototype.hash + '/index.html?view=' + view + '&state=' + selected;
|
|
103
|
+
frame.loading = 'lazy'; figure.append(frame);
|
|
104
|
+
const link = el('a', '查看示意图'); link.target = '_blank'; link.rel = 'noopener';
|
|
105
|
+
link.href = './widget-assets/' + prototype.hash + '/' + view + '-' + selected + '.svg'; figure.append(link);
|
|
106
|
+
grid.append(figure);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
for (const sample of data.states) {
|
|
110
|
+
const button = el('button', sample.label); button.setAttribute('aria-pressed', String(sample.id === selected));
|
|
111
|
+
button.onclick = () => { selected = sample.id; selectedStates.set(item.id, selected); for (const b of control.children) b.setAttribute('aria-pressed', String(b === button)); refresh(); };
|
|
112
|
+
control.append(button);
|
|
113
|
+
}
|
|
114
|
+
const size = data.design;
|
|
115
|
+
section.append(el('p', '示例画布 ' + size.screenWidth + ' × ' + size.screenHeight + ';展开页 ' + (size.screenWidth - 32) + ' × ' + Math.round(size.screenHeight * .65) + '。', 'design-muted'), control, grid);
|
|
116
|
+
refresh();
|
|
117
|
+
section.append(el('p', '原型版本 ' + prototype.hash.slice(0, 10) + ' · 以上为示例数据,用于确认样式和交互。', 'design-muted'));
|
|
118
|
+
}
|
|
119
|
+
prototypes.append(section);
|
|
120
|
+
}
|
|
121
|
+
panel.append(prototypes);
|
|
122
|
+
const acceptance = card('03 / 设计验收');
|
|
123
|
+
acceptance.append(el('p', '自动检查验证结构与资源。开发者验收确认需求、阅读体验和对话更新是否符合预期。生产环境接入另行验证。', 'design-muted'));
|
|
124
|
+
for (const item of design.items) {
|
|
125
|
+
const group = el('section', undefined, 'design-acceptance');
|
|
126
|
+
group.append(el('h3', item.name), badge(item.accepted ? '开发者已验收当前版本' : '待开发者验收', item.accepted));
|
|
127
|
+
group.append(el('p', (item.automaticCheck.pass ? '✓ 自动检查:' : '○ 自动检查:') + item.automaticCheck.message));
|
|
128
|
+
const table = el('table'); const header = el('tr'); ['开发者检查项', '检查记录', '确认状态'].forEach(t => header.append(el('th', t))); table.append(header);
|
|
129
|
+
for (const key of design.reviewChecks) { const row = el('tr'); row.append(el('td', reviewLabels[key]), el('td', item.acceptance?.data.reviews[key] || '尚未记录'), el('td', item.accepted ? '已确认' : '待确认')); table.append(row); }
|
|
130
|
+
group.append(table);
|
|
131
|
+
if (item.acceptance?.unresolved?.length) group.append(el('p', '待处理:' + item.acceptance.unresolved.join(';')));
|
|
132
|
+
acceptance.append(group);
|
|
133
|
+
}
|
|
134
|
+
if (!design.items.length) acceptance.append(el('p', design.catalogConfirmed ? '已确认不需要小挂件,无需逐项设计验收。' : '原型确认后逐项验收。'));
|
|
135
|
+
panel.append(acceptance);
|
|
136
|
+
const deliverables = card('04 / 交付与开发衔接');
|
|
137
|
+
if (snapshot.completion?.status === 'ready' && snapshot.completion.files) {
|
|
138
|
+
deliverables.append(badge('成果已生成', true), el('p', '包含四册、服务模式图、小挂件清单、两视图三态原型和设计验收记录。'));
|
|
139
|
+
deliverables.append(el('p', '完整创建任务将在此处自动衔接开发:由当前 Agent 读取已确认手册与小挂件设计,继续实现,无需再次确认开始。仅设计、流程测试或已暂停的任务按原范围结束。实际开发进度请在主对话查看。', 'design-muted'));
|
|
140
|
+
const downloads = el('details'); downloads.append(el('summary', '查看全部成果文件'));
|
|
141
|
+
for (const file of snapshot.completion.files) {
|
|
142
|
+
const a = el('a', file); a.href = './deliverables/' + snapshot.revision + '/' + file; a.download = file.split('/').pop();
|
|
143
|
+
const row = el('p'); row.append(a); downloads.append(row);
|
|
144
|
+
if (file === 'BUDDY_MANUAL.md' || file === 'WIDGET_DESIGN.md') { const quick = a.cloneNode(true); quick.className = 'design-download'; quick.textContent = file === 'BUDDY_MANUAL.md' ? '下载创作手册' : '下载小挂件设计说明'; deliverables.append(quick); }
|
|
145
|
+
}
|
|
146
|
+
deliverables.append(downloads);
|
|
147
|
+
} else deliverables.append(el('p', snapshot.completion?.message || '清单、当前原型及设计验收确认完成后生成交付。四册内容可先在 Booklet 中阅读。'));
|
|
148
|
+
panel.append(deliverables);
|
|
149
|
+
}
|
|
150
|
+
let loading = false;
|
|
151
|
+
async function update() {
|
|
152
|
+
if (loading) return;
|
|
153
|
+
loading = true;
|
|
154
|
+
try {
|
|
155
|
+
const response = await fetch('./api/snapshot');
|
|
156
|
+
if (!response.ok) throw new Error('snapshot unavailable');
|
|
157
|
+
const snapshot = await response.json();
|
|
158
|
+
const next = JSON.stringify([snapshot.widgetDesign, snapshot.completion, snapshot.paused]);
|
|
159
|
+
if (next !== version) { version = next; render(snapshot); }
|
|
160
|
+
} catch { progress.textContent = '页面暂时无法同步;已展示的内容保留,请回主对话查看进度。'; }
|
|
161
|
+
finally { loading = false; }
|
|
162
|
+
}
|
|
163
|
+
update();
|
|
164
|
+
const events = new EventSource('./api/events');
|
|
165
|
+
events.addEventListener('changed', update);
|
|
166
|
+
events.addEventListener('ready', update);
|
|
167
|
+
events.addEventListener('unavailable', () => { progress.textContent = '内容暂时无法读取,请回主对话检查。'; });
|
|
168
|
+
document.addEventListener('visibilitychange', () => { if (!document.hidden) update(); });
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const params = new URLSearchParams(location.search);
|
|
3
|
+
const requested = params.get('state') || 'initial';
|
|
4
|
+
const screens = [...document.querySelectorAll('.screen')];
|
|
5
|
+
const screen = screens.find(node => node.dataset.state === requested) || screens[0];
|
|
6
|
+
for (const node of screens) node.hidden = node !== screen;
|
|
7
|
+
const overlay = screen.querySelector('.overlay');
|
|
8
|
+
const details = screen.querySelector('.detail-layer');
|
|
9
|
+
const open = screen.querySelector('.open-sheet');
|
|
10
|
+
const close = screen.querySelector('.sheet-close');
|
|
11
|
+
const detailClose = screen.querySelector('.detail-close');
|
|
12
|
+
let returnFocus = null;
|
|
13
|
+
function closeDetail() {
|
|
14
|
+
details.hidden = true;
|
|
15
|
+
if (returnFocus) returnFocus.focus();
|
|
16
|
+
}
|
|
17
|
+
function closeSheet() {
|
|
18
|
+
closeDetail();
|
|
19
|
+
overlay.hidden = true;
|
|
20
|
+
screen.querySelector('.chat-body').inert = false;
|
|
21
|
+
open.focus();
|
|
22
|
+
}
|
|
23
|
+
open.addEventListener('click', () => {
|
|
24
|
+
overlay.hidden = false;
|
|
25
|
+
screen.querySelector('.chat-body').inert = true;
|
|
26
|
+
close.focus();
|
|
27
|
+
});
|
|
28
|
+
close.addEventListener('click', closeSheet);
|
|
29
|
+
detailClose.addEventListener('click', closeDetail);
|
|
30
|
+
for (const button of screen.querySelectorAll('[data-group]')) {
|
|
31
|
+
button.addEventListener('click', () => {
|
|
32
|
+
const selected = button.dataset.group;
|
|
33
|
+
for (const option of screen.querySelectorAll('[data-group]')) option.setAttribute('aria-pressed', String(option.dataset.group === selected));
|
|
34
|
+
for (const section of screen.querySelectorAll('[data-section]')) section.hidden = selected !== 'all' && section.dataset.section !== selected;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
for (const button of screen.querySelectorAll('.detail-open')) {
|
|
38
|
+
button.addEventListener('click', () => {
|
|
39
|
+
returnFocus = button;
|
|
40
|
+
const row = button.closest('.row');
|
|
41
|
+
screen.querySelector('.detail-title').textContent = row.querySelector('.row-label').textContent;
|
|
42
|
+
screen.querySelector('.detail-text').textContent = row.querySelector('.detail-copy').textContent;
|
|
43
|
+
details.hidden = false;
|
|
44
|
+
detailClose.focus();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
document.addEventListener('keydown', event => {
|
|
48
|
+
if (event.key === 'Escape') {
|
|
49
|
+
if (!details.hidden) closeDetail();
|
|
50
|
+
else if (!overlay.hidden) closeSheet();
|
|
51
|
+
}
|
|
52
|
+
if (event.key === 'Tab' && !overlay.hidden) {
|
|
53
|
+
const scope = details.hidden ? screen.querySelector('.sheet') : screen.querySelector('.detail-box');
|
|
54
|
+
const buttons = [...scope.querySelectorAll('button,summary')].filter(el => el.getClientRects().length);
|
|
55
|
+
const first = buttons[0], last = buttons[buttons.length - 1];
|
|
56
|
+
if (event.shiftKey && document.activeElement === first) {event.preventDefault();last.focus();}
|
|
57
|
+
else if (!event.shiftKey && document.activeElement === last) {event.preventDefault();first.focus();}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
overlay.hidden = params.get('view') !== 'expanded';
|
|
61
|
+
screen.querySelector('.chat-body').inert = !overlay.hidden;
|
|
62
|
+
if (!overlay.hidden) close.focus({preventScroll:true});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;font:400 16px/1.5 -apple-system,BlinkMacSystemFont,"PingFang SC","Segoe UI",sans-serif;color:#243C37;background:#FCFCF9}button{font:inherit;color:inherit;cursor:pointer}button,summary{-webkit-tap-highlight-color:transparent}button:focus-visible,summary:focus-visible{outline:3px solid var(--accent);outline-offset:3px}[hidden]{display:none!important}h1,h2,h3,p{margin:0}h1,h2,h3{font-size:var(--title-size,16px);font-weight:500}svg{display:block;flex:none}button{border:0}button:active{transform:scale(.97)}.screen{font:400 16px/1.5 -apple-system,BlinkMacSystemFont,"PingFang SC","Segoe UI",sans-serif;color:#243C37;position:relative;width:100%;height:100%;overflow:hidden;--accent:#28645B;--soft:#EAF1E9;--muted:#75817B;--line:#E5E9E1;background:#FCFCF9}.icon{width:22px;height:22px;stroke:currentColor;stroke-width:1.65;fill:none;stroke-linecap:round;stroke-linejoin:round}.statusbar{height:38px;padding:12px 25px 0;display:flex;justify-content:space-between;align-items:center;font-size:13px;font-weight:500}.statusbar .device{display:flex;align-items:center;gap:5px}.device .icon{width:17px;height:17px}.chat-header{height:61px;padding:5px 19px 14px;display:flex;align-items:center;gap:12px;border-bottom:1px solid #E9ECE6;background:#FCFCF9}.chat-header .back{margin-right:2px}.buddy-identity{flex:1}.buddy-identity strong{display:block;font-size:16px;font-weight:500;letter-spacing:-.3px}.buddy-identity small{display:block;font-size:11px;color:var(--muted);margin-top:1px}.header-mark{width:33px;height:33px;border-radius:12px;background:var(--accent);color:white;display:grid;place-items:center;font-size:16px;font-weight:500}.chat-body{height:calc(100% - 178px);overflow-y:auto;padding:20px 16px 30px;scrollbar-width:none}.chat-date{text-align:center;font-size:11px;color:#929A93;margin:0 0 17px;letter-spacing:.3px}.message{display:flex;gap:9px;margin:13px 0;align-items:flex-start}.avatar{width:26px;height:26px;border-radius:10px;background:var(--soft);color:var(--accent);display:grid;place-items:center;font-size:13px;font-weight:500;flex:none;margin-top:3px}.bubble{padding:12px 14px;max-width:calc(100% - 41px);font-size:16px;line-height:1.65;border-radius:16px;white-space:pre-wrap;overflow-wrap:anywhere}.message.user{justify-content:flex-end;margin-bottom:20px}.user .bubble{background:#E6EEEB;border-top-right-radius:5px}.assistant .bubble{background:#F1F2EC;border-top-left-radius:5px}.chat-card{margin:16px 0 0 35px;border:1px solid #E2E7DC;border-radius:21px;background:#fff;overflow:hidden;box-shadow:0 5px 22px #334F3710}.card-top{padding:16px 16px 13px;display:flex;align-items:center;gap:11px}.identity-icon{display:grid;place-items:center;width:37px;height:37px;flex:none;border-radius:13px;color:var(--accent);background:var(--soft)}.card-kind{font-size:11px;color:var(--muted);letter-spacing:.4px;margin-bottom:2px}.chat-card h1{font-size:16px;letter-spacing:-.3px}.card-summary{font-size:16px;line-height:1.65;white-space:pre-wrap;overflow-wrap:anywhere;padding:0 16px 14px}.card-meta{margin:0 16px 15px;padding:10px 0;border-top:1px solid var(--line);display:flex;gap:16px}.card-meta .metric{flex:1;min-width:0}.metric-label{font-size:11px;color:var(--muted);display:block}.metric-value{font-size:16px;font-weight:500;display:block;margin-top:3px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.open-sheet{width:calc(100% - 32px);margin:0 16px 15px;padding:10px 14px;border-radius:12px;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:space-between;font-size:14px;font-weight:500}.open-sheet .icon{width:17px;height:17px}.card-foot{font-size:11px;color:var(--muted);padding:0 16px 14px;display:flex;align-items:center;gap:5px}.card-foot .icon{width:12px;height:12px}.composer{position:absolute;bottom:19px;left:16px;right:16px;display:flex;align-items:center;gap:10px;border:1px solid #E5E8DE;border-radius:16px;background:#fff;padding:12px;color:#859087;font-size:12px;min-height:49px;box-shadow:0 3px 17px #334F3708}.composer span{flex:1}.home-indicator{position:absolute;bottom:7px;left:36%;right:36%;height:4px;background:#35433E;border-radius:9px}.overlay{position:absolute;inset:0;display:grid;place-items:center;background:#263F3552;backdrop-filter:blur(2px);z-index:2}.sheet{width:calc(100% - 32px);height:65%;border-radius:25px;background:#FFFEFB;box-shadow:0 22px 60px #17362B30;display:flex;flex-direction:column;overflow:hidden;position:relative}.sheet-header{padding:14px 16px;display:flex;gap:10px;align-items:center;flex:none;min-height:66px;border-bottom:1px solid #E8ECE2}.sheet-header .identity-icon{width:32px;height:32px;border-radius:11px}.sheet-header .icon{width:19px;height:19px}.sheet-header h2{flex:1;font-size:16px;letter-spacing:-.2px}.close{width:32px;height:32px;flex:none;border:1px solid #E5E8DF;border-radius:50%;background:#FFFEFB;display:grid;place-items:center;color:#526B5E}.close .icon{width:16px;height:16px}.sheet-body{overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#CBD3C5 transparent;min-height:0;flex:1;padding:0 16px 16px}.overview{padding:18px 0 15px;border-bottom:1px solid var(--line)}.eyebrow{font-size:10px;color:var(--accent);letter-spacing:1.2px;font-weight:500;margin-bottom:6px}.overview-title{font-size:20px;line-height:1.4;letter-spacing:-.5px;font-weight:500;color:#203D31}.overview-subtitle{font-size:12px;color:var(--muted);margin-top:5px;line-height:1.5;white-space:pre-wrap}.overview-metrics{display:grid;grid-template-columns:repeat(var(--metric-count),1fr);gap:10px;margin-top:14px}.overview-metrics .metric{padding-left:10px;border-left:1px solid #DDE4D7;min-width:0}.overview-metrics .metric:first-child{padding-left:0;border-left:0}.overview-metrics .metric-value{font-size:20px;line-height:1.35;letter-spacing:-.4px}.progress-track{height:5px;background:#E4EADB;border-radius:9px;overflow:hidden;margin-top:14px}.progress-fill{height:100%;background:var(--accent);border-radius:9px}.progress-caption{font-size:11px;color:var(--muted);margin-top:6px}.reading-nav{position:sticky;top:0;z-index:1;background:#FFFEFB;padding:13px 0 10px;display:flex;gap:7px;overflow-x:auto;scrollbar-width:none;box-shadow:0 3px 0 #FFFEFB}.reading-nav button{border:1px solid #E3E8DC;background:#F5F6EF;border-radius:10px;padding:8px 12px;flex:none;font-size:13px;white-space:nowrap;color:#6B7C6D}.reading-nav button[aria-pressed=true]{border-color:var(--accent);background:var(--accent);color:#fff;box-shadow:0 3px 7px #205C4120}.section{margin:8px 0 18px}.section-heading{font-size:13px;font-weight:500;color:#53694F;margin:0 0 9px;padding:5px 0;display:flex;align-items:center;gap:8px;cursor:default;list-style:none}.section-heading::-webkit-details-marker{display:none}.section-heading .icon{width:14px;height:14px;margin-left:auto;transition:transform .16s ease}summary.section-heading{cursor:pointer}details[open]>summary .icon{transform:rotate(90deg)}.section-count{font-size:10px;color:#809077;background:#EEF1E7;padding:1px 6px;border-radius:5px}.row{display:flex;gap:10px;position:relative;align-items:flex-start;padding:13px 0;border-bottom:1px solid #E8ECE2}.row:last-child{border-bottom:0}.row-symbol{width:29px;height:29px;flex:none;color:var(--accent);border:1px solid #E0E7D8;background:#F4F6EE;border-radius:10px;display:grid;place-items:center;margin-top:1px}.row-symbol .icon{width:17px;height:17px}.row-content{flex:1;min-width:0}.row-label{font-size:16px;font-weight:500;line-height:1.45;overflow-wrap:anywhere}.row-value{font-size:16px;font-weight:400;line-height:1.6;margin-top:3px;white-space:pre-wrap;overflow-wrap:anywhere;color:#405348}.row-caption{font-size:12px;line-height:1.55;color:var(--muted);margin-top:5px;white-space:pre-wrap;overflow-wrap:anywhere}.status{display:inline-flex;align-items:center;gap:4px;font-size:10px;line-height:1.5;padding:3px 7px;border-radius:6px;margin-top:7px;color:#687565;background:#EEF1E8}.status:before{content:"";width:4px;height:4px;border-radius:50%;background:currentColor}.status.positive{color:#2D6A50;background:#EAF2E7}.status.pending{color:#92662E;background:#F7EFDC}.status.changed{color:#416B7A;background:#E9F0F1}.status.neutral{color:#73806E;background:#EDF0E8}.detail-open{flex:none;background:transparent;padding:8px 0 8px 4px;border-radius:9px;color:#849477;margin-top:2px;min-width:28px;min-height:36px}.detail-open .icon{width:17px;height:17px}.timeline .row{border:0;padding-left:63px;min-height:88px;padding-top:7px;padding-bottom:17px}.timeline .row:before{content:"";position:absolute;left:46px;top:15px;bottom:-8px;width:1px;background:#DAE3D1}.timeline .row:last-child:before{bottom:15px}.timeline .row:after{content:"";position:absolute;left:42px;top:13px;width:9px;height:9px;background:var(--accent);border:2px solid #FFFEFB;border-radius:50%;box-shadow:0 0 0 1px #BDD0B6}.timeline .row-label{position:absolute;left:0;top:10px;width:35px;font-size:11px;line-height:1.5;font-weight:500;font-variant-numeric:tabular-nums;color:#6F826A}.timeline .row-value{font-size:16px;color:#243C37;margin:0}.timeline .row-symbol{display:none}.cards .row{background:#F5F6F0;border:1px solid #E6EADF;border-radius:16px;padding:14px 12px;margin-bottom:10px}.cards .row:last-child{border-bottom:1px solid #E6EADF}.cards .row-symbol{background:#FFFEFB}.ledger .row{align-items:center}.ledger .row-content{display:grid;grid-template-columns:1fr auto;gap:2px 10px;align-items:baseline}.ledger .row-label{font-size:16px;font-weight:400}.ledger .row-value{font-size:16px;font-weight:500;margin:0;text-align:right;font-variant-numeric:tabular-nums}.ledger .row-caption,.ledger .status{grid-column:1/-1}.ledger .status{justify-self:start}.checklist .row{padding:12px 0}.checklist .row-symbol{border-radius:50%;width:24px;height:24px;margin-top:2px;background:transparent}.checklist .row.positive .row-symbol{background:#EAF2E7;color:#2D6A50;border-color:#C4D4BD}.checklist .row-value{font-size:16px;color:#687B60}.note{display:flex;align-items:flex-start;gap:6px;border-top:1px solid var(--line);padding-top:13px;font-size:11px;color:#8A9580;line-height:1.5}.note .icon{width:13px;height:13px;margin-top:1px}.detail-layer{position:absolute;inset:0;background:#263F3566;display:grid;place-items:center;z-index:3}.detail-box{width:calc(100% - 32px);max-height:65%;overflow-y:auto;background:#FFFEFB;border-radius:23px;padding:18px;box-shadow:0 15px 40px #18382B26}.detail-head{display:flex;align-items:center;gap:10px;margin-bottom:14px}.detail-head h3{flex:1;font-size:16px}.detail-text{font-size:16px;line-height:1.75;white-space:pre-wrap;overflow-wrap:anywhere}.detail-foot{font-size:11px;color:var(--muted);border-top:1px solid var(--line);margin-top:18px;padding-top:12px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(prefers-reduced-motion:no-preference){button{transition:background .16s ease,transform .16s ease,box-shadow .16s ease}.overlay{animation:veil .18s ease both}.sheet{animation:arrive .24s cubic-bezier(.2,.8,.2,1) both}.detail-box{animation:arrive .2s ease both}@keyframes arrive{from{transform:translateY(8px) scale(.99);opacity:.3}to{transform:none;opacity:1}}@keyframes veil{from{opacity:0}to{opacity:1}}}.svg-export *{animation:none!important;transition:none!important}.svg-export .overlay{backdrop-filter:none}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const params = new URLSearchParams(location.search);
|
|
3
|
+
const requested = params.get('state') || 'initial';
|
|
4
|
+
const screens = [...document.querySelectorAll('.screen')];
|
|
5
|
+
const screen = screens.find(node => node.dataset.state === requested) || screens[0];
|
|
6
|
+
for (const node of screens) node.hidden = node !== screen;
|
|
7
|
+
const overlay = screen.querySelector('.overlay');
|
|
8
|
+
const details = screen.querySelector('.detail-layer');
|
|
9
|
+
const sheet = screen.querySelector('.sheet');
|
|
10
|
+
const open = screen.querySelector('.open-sheet');
|
|
11
|
+
const close = screen.querySelector('.sheet-close');
|
|
12
|
+
const detailClose = screen.querySelector('.detail-close');
|
|
13
|
+
let returnFocus = null;
|
|
14
|
+
function closeDetail() {
|
|
15
|
+
details.hidden = true;
|
|
16
|
+
sheet.inert = false;
|
|
17
|
+
if (returnFocus) returnFocus.focus();
|
|
18
|
+
}
|
|
19
|
+
function closeSheet() {
|
|
20
|
+
closeDetail();
|
|
21
|
+
overlay.hidden = true;
|
|
22
|
+
screen.querySelector('.chat-body').inert = false;
|
|
23
|
+
open.focus();
|
|
24
|
+
}
|
|
25
|
+
open.addEventListener('click', event => {
|
|
26
|
+
overlay.hidden = false;
|
|
27
|
+
screen.querySelector('.chat-body').inert = true;
|
|
28
|
+
(event.detail === 0 ? close : sheet).focus({preventScroll:true});
|
|
29
|
+
});
|
|
30
|
+
close.addEventListener('click', closeSheet);
|
|
31
|
+
detailClose.addEventListener('click', closeDetail);
|
|
32
|
+
for (const button of screen.querySelectorAll('[data-group]')) {
|
|
33
|
+
button.addEventListener('click', () => {
|
|
34
|
+
const selected = button.dataset.group;
|
|
35
|
+
for (const option of screen.querySelectorAll('[data-group]')) option.setAttribute('aria-pressed', String(option.dataset.group === selected));
|
|
36
|
+
for (const section of screen.querySelectorAll('[data-section]')) section.hidden = selected !== 'all' && section.dataset.section !== selected;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
for (const button of screen.querySelectorAll('.detail-open')) {
|
|
40
|
+
button.addEventListener('click', () => {
|
|
41
|
+
returnFocus = button;
|
|
42
|
+
const row = button.closest('.row');
|
|
43
|
+
screen.querySelector('.detail-title').textContent = row.querySelector('.row-label, .row-value').textContent;
|
|
44
|
+
screen.querySelector('.detail-text').textContent = row.querySelector('.detail-copy').textContent;
|
|
45
|
+
details.hidden = false;
|
|
46
|
+
sheet.inert = true;
|
|
47
|
+
detailClose.focus();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
document.addEventListener('keydown', event => {
|
|
51
|
+
if (event.key === 'Escape') {
|
|
52
|
+
if (!details.hidden) closeDetail();
|
|
53
|
+
else if (!overlay.hidden) closeSheet();
|
|
54
|
+
}
|
|
55
|
+
if (event.key === 'Tab' && !overlay.hidden) {
|
|
56
|
+
const scope = details.hidden ? screen.querySelector('.sheet') : screen.querySelector('.detail-box');
|
|
57
|
+
const buttons = [...scope.querySelectorAll('button,summary')].filter(el => el.getClientRects().length);
|
|
58
|
+
const first = buttons[0], last = buttons[buttons.length - 1];
|
|
59
|
+
if (event.shiftKey && (document.activeElement === first || document.activeElement === sheet)) {event.preventDefault();last.focus();}
|
|
60
|
+
else if (!event.shiftKey && document.activeElement === last) {event.preventDefault();first.focus();}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
overlay.hidden = params.get('view') !== 'expanded';
|
|
64
|
+
screen.querySelector('.chat-body').inert = !overlay.hidden;
|
|
65
|
+
if (!overlay.hidden) sheet.focus({preventScroll:true});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
* { box-sizing: border-box; }
|
|
2
|
+
html, body { margin: 0; width: 100%; height: 100%; background: #fff; }
|
|
3
|
+
button { font: inherit; color: inherit; border: 0; cursor: pointer; }
|
|
4
|
+
button, summary { -webkit-tap-highlight-color: transparent; }
|
|
5
|
+
button:focus-visible, summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
6
|
+
[hidden] { display: none !important; }
|
|
7
|
+
h1, h2, h3, p { margin: 0; }
|
|
8
|
+
h1, h2, h3 { font-size: var(--title-size, 16px); font-weight: 500; }
|
|
9
|
+
.screen { font: 400 16px/1.5 -apple-system, BlinkMacSystemFont, "PingFang SC", "Segoe UI", sans-serif; color: #202733; position: relative; width: 100%; height: 100%; overflow: hidden; background: #fff; --accent: #326BC4; --muted: #7C8490; --line: #E9EDF2; }
|
|
10
|
+
.icon { display: block; flex: none; width: 22px; height: 22px; stroke: currentColor; stroke-width: 1.6; fill: none; stroke-linecap: round; stroke-linejoin: round; }
|
|
11
|
+
.statusbar { height: 40px; padding: 10px 25px 0; display: flex; align-items: center; justify-content: space-between; font-size: 13px; font-weight: 500; }
|
|
12
|
+
.device { display: flex; gap: 5px; }
|
|
13
|
+
.device .icon { width: 16px; height: 16px; }
|
|
14
|
+
.chat-header { height: 56px; padding: 0 20px 10px; display: flex; align-items: center; gap: 16px; border-bottom: 1px solid var(--line); }
|
|
15
|
+
.chat-header strong { flex: 1; text-align: center; font-size: 16px; font-weight: 500; }
|
|
16
|
+
.back { font-size: 28px; line-height: 1; }
|
|
17
|
+
.chat-body { height: calc(100% - 176px); overflow-y: auto; padding: 20px 16px 28px; scrollbar-width: none; }
|
|
18
|
+
.chat-date { color: #9299A3; text-align: center; font-size: 11px; margin-bottom: 18px; }
|
|
19
|
+
.message { display: flex; gap: 9px; align-items: flex-start; margin: 14px 0; }
|
|
20
|
+
.avatar { flex: none; width: 26px; height: 26px; border-radius: 8px; background: #E9F0FB; color: var(--accent); display: grid; place-items: center; font-size: 12px; font-weight: 500; margin-top: 3px; }
|
|
21
|
+
.bubble { max-width: calc(100% - 38px); font-size: 16px; line-height: 1.65; padding: 12px 14px; border-radius: 12px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
22
|
+
.user { justify-content: flex-end; }
|
|
23
|
+
.user .bubble { background: #EAF1FB; border-top-right-radius: 3px; }
|
|
24
|
+
.assistant .bubble { background: #F4F5F7; border-top-left-radius: 3px; }
|
|
25
|
+
.chat-card { margin: 16px 0 0 35px; border: 1px solid #DFE5ED; border-radius: 14px; background: #fff; overflow: hidden; }
|
|
26
|
+
.card-content { padding: 17px 16px 18px; }
|
|
27
|
+
.card-kind { font-size: 12px; color: var(--accent); margin-bottom: 8px; }
|
|
28
|
+
.chat-card h1 { font-size: 16px; font-weight: 500; line-height: 1.45; }
|
|
29
|
+
.card-summary { font-size: 16px; line-height: 1.65; color: #66707E; margin-top: 9px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
30
|
+
.open-sheet { width: 100%; padding: 12px 16px; display: flex; align-items: center; justify-content: space-between; border-top: 1px solid var(--line); background: #fff; color: var(--accent); font-size: 14px; font-weight: 500; }
|
|
31
|
+
.open-sheet .icon { width: 17px; height: 17px; }
|
|
32
|
+
.open-sheet:hover { background: #F6F8FC; }
|
|
33
|
+
.composer { position: absolute; bottom: 19px; left: 16px; right: 16px; display: flex; align-items: center; gap: 10px; min-height: 49px; padding: 12px; border: 1px solid #E6E9EF; border-radius: 14px; background: #fff; color: #9299A3; font-size: 12px; }
|
|
34
|
+
.composer span { flex: 1; }
|
|
35
|
+
.home-indicator { position: absolute; bottom: 7px; left: 36%; right: 36%; height: 4px; border-radius: 8px; background: #252C37; }
|
|
36
|
+
.overlay { position: absolute; inset: 0; z-index: 2; background: #17243B45; display: grid; place-items: center; }
|
|
37
|
+
.sheet { width: calc(100% - 32px); height: 65%; border-radius: 22px; background: #fff; display: flex; flex-direction: column; overflow: hidden; position: relative; box-shadow: 0 12px 36px #192B4720; outline: none; }
|
|
38
|
+
.sheet-header { flex: none; height: 60px; display: flex; align-items: center; padding: 0 16px 0 20px; border-bottom: 1px solid var(--line); }
|
|
39
|
+
.sheet-header h2 { flex: 1; font-size: 16px; font-weight: 500; }
|
|
40
|
+
.close { width: 32px; height: 32px; background: transparent; color: #8490A0; border-radius: 8px; display: grid; place-items: center; flex: none; }
|
|
41
|
+
.close .icon { width: 20px; height: 20px; }
|
|
42
|
+
.close:hover { background: #F3F5F8; color: #26344C; }
|
|
43
|
+
.sheet-body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 0 20px 20px; scrollbar-width: thin; scrollbar-color: #D5DBE5 transparent; }
|
|
44
|
+
.overview { padding: 22px 0 15px; }
|
|
45
|
+
.overview h3 { font-size: 20px; line-height: 1.4; font-weight: 500; letter-spacing: -.4px; }
|
|
46
|
+
.overview-subtitle { color: var(--muted); font-size: 13px; margin-top: 6px; line-height: 1.6; white-space: pre-wrap; }
|
|
47
|
+
.reading-meta { color: #606E81; font-size: 13px; margin-top: 12px; }
|
|
48
|
+
.overview-metrics { display: grid; grid-template-columns: repeat(var(--metric-count), 1fr); gap: 16px; margin-top: 22px; }
|
|
49
|
+
.metric-label { display: block; font-size: 12px; color: var(--muted); }
|
|
50
|
+
.metric-value { display: block; font-size: 20px; font-weight: 500; line-height: 1.4; margin-top: 4px; letter-spacing: -.3px; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
|
|
51
|
+
.overview-metrics .metric:first-child .metric-value { color: var(--accent); }
|
|
52
|
+
.progress-track { height: 3px; overflow: hidden; background: #ECF0F5; margin-top: 16px; }
|
|
53
|
+
.progress-track > div { height: 100%; background: var(--accent); }
|
|
54
|
+
.progress-caption { font-size: 11px; color: var(--muted); margin-top: 7px; }
|
|
55
|
+
.reading-nav { position: sticky; top: 0; z-index: 1; display: flex; gap: 22px; background: #fff; border-bottom: 1px solid var(--line); overflow-x: auto; scrollbar-width: none; }
|
|
56
|
+
.reading-nav button { flex: none; position: relative; padding: 12px 0; font-size: 14px; background: transparent; color: #8A93A1; white-space: nowrap; }
|
|
57
|
+
.reading-nav button[aria-pressed=true] { color: var(--accent); font-weight: 500; }
|
|
58
|
+
.reading-nav button[aria-pressed=true]:after { position: absolute; content: ""; height: 2px; background: var(--accent); left: 0; right: 0; bottom: 0; }
|
|
59
|
+
.reading-nav button:hover { color: var(--accent); }
|
|
60
|
+
.section { margin: 20px 0 24px; }
|
|
61
|
+
.section-heading { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; color: #8A93A1; font-size: 12px; font-weight: 400; line-height: 1.5; list-style: none; }
|
|
62
|
+
.section-heading::-webkit-details-marker { display: none; }
|
|
63
|
+
summary.section-heading { cursor: pointer; padding: 4px 0; }
|
|
64
|
+
.section-heading .icon { margin-left: auto; width: 13px; height: 13px; }
|
|
65
|
+
details[open] > summary .icon { transform: rotate(90deg); }
|
|
66
|
+
.row { display: flex; align-items: flex-start; gap: 10px; padding: 15px 0; border-bottom: 1px solid var(--line); }
|
|
67
|
+
.row:last-child { border-bottom: none; }
|
|
68
|
+
.row-content { flex: 1; min-width: 0; }
|
|
69
|
+
.row-label { font-size: 16px; font-weight: 500; line-height: 1.5; overflow-wrap: anywhere; }
|
|
70
|
+
.row-value { font-size: 16px; font-weight: 400; line-height: 1.6; margin-top: 4px; color: #263243; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
71
|
+
.row-caption { font-size: 13px; color: #87909D; line-height: 1.6; margin-top: 6px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
72
|
+
.status { display: inline-block; font-size: 12px; line-height: 1.5; color: #8893A2; margin-top: 8px; }
|
|
73
|
+
.status.pending { color: #956B40; }
|
|
74
|
+
.status.changed { color: var(--accent); }
|
|
75
|
+
.detail-open { min-height: 38px; width: 26px; background: transparent; color: #9BA5B3; display: grid; place-items: center; padding: 0; flex: none; border-radius: 5px; }
|
|
76
|
+
.detail-open .icon { width: 15px; height: 15px; }
|
|
77
|
+
.timeline .row { position: relative; border: 0; gap: 21px; padding: 9px 0 23px; min-height: 82px; }
|
|
78
|
+
.timeline .time { flex: none; width: 36px; color: #738196; font-size: 12px; font-variant-numeric: tabular-nums; line-height: 1.6; padding-top: 3px; overflow-wrap: anywhere; }
|
|
79
|
+
.timeline .row:before { position: absolute; content: ""; left: 45px; top: 19px; bottom: -11px; width: 1px; background: #E2EAF6; }
|
|
80
|
+
.timeline .row:last-child:before { display: none; }
|
|
81
|
+
.timeline .row:after { position: absolute; content: ""; left: 43px; top: 17px; width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }
|
|
82
|
+
.timeline .row-value { margin-top: 0; }
|
|
83
|
+
.cards .row { padding: 16px 0 18px; }
|
|
84
|
+
.cards .row-value { color: #68758A; }
|
|
85
|
+
.ledger .row-content { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 12px; align-items: baseline; }
|
|
86
|
+
.ledger .row-label { font-size: 16px; font-weight: 400; }
|
|
87
|
+
.ledger .row-value { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
|
|
88
|
+
.ledger .row-caption, .ledger .status { grid-column: 1 / -1; }
|
|
89
|
+
.checklist .row-value { color: #718098; }
|
|
90
|
+
.note { border-top: 1px solid var(--line); padding-top: 14px; color: #A1A8B2; font-size: 11px; }
|
|
91
|
+
.detail-layer { position: absolute; inset: 0; z-index: 3; background: #17243B45; display: grid; place-items: center; }
|
|
92
|
+
.detail-box { width: calc(100% - 32px); max-height: 65%; overflow-y: auto; padding: 20px; border-radius: 22px; background: #fff; box-shadow: 0 12px 36px #192B4720; }
|
|
93
|
+
.detail-head { display: flex; align-items: center; gap: 10px; margin-bottom: 18px; }
|
|
94
|
+
.detail-head h3 { flex: 1; font-size: 16px; }
|
|
95
|
+
.detail-text { font-size: 16px; line-height: 1.75; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
96
|
+
.detail-foot { color: var(--muted); font-size: 11px; border-top: 1px solid var(--line); margin-top: 20px; padding-top: 14px; }
|
|
97
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
98
|
+
button { transition: color .15s, background .15s; }
|
|
99
|
+
button:active { opacity: .7; }
|
|
100
|
+
.sheet, .detail-box { animation: enter .18s ease-out both; }
|
|
101
|
+
@keyframes enter { from { transform: translateY(6px); opacity: .5; } to { transform: none; opacity: 1; } }
|
|
102
|
+
}
|
|
103
|
+
.svg-export * { animation: none !important; transition: none !important; }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const params = new URLSearchParams(location.search);
|
|
3
|
+
const requested = params.get('state') || 'initial';
|
|
4
|
+
const screens = [...document.querySelectorAll('.screen')];
|
|
5
|
+
const screen = screens.find(node => node.dataset.state === requested) || screens[0];
|
|
6
|
+
for (const node of screens) node.hidden = node !== screen;
|
|
7
|
+
const overlay = screen.querySelector('.overlay');
|
|
8
|
+
const details = screen.querySelector('.detail-layer');
|
|
9
|
+
const sheet = screen.querySelector('.sheet');
|
|
10
|
+
const open = screen.querySelector('.open-sheet');
|
|
11
|
+
const close = screen.querySelector('.sheet-close');
|
|
12
|
+
const detailClose = screen.querySelector('.detail-close');
|
|
13
|
+
let returnFocus = null;
|
|
14
|
+
function closeDetail() {
|
|
15
|
+
details.hidden = true;
|
|
16
|
+
sheet.inert = false;
|
|
17
|
+
if (returnFocus) returnFocus.focus();
|
|
18
|
+
}
|
|
19
|
+
function closeSheet() {
|
|
20
|
+
closeDetail();
|
|
21
|
+
overlay.hidden = true;
|
|
22
|
+
screen.querySelector('.chat-body').inert = false;
|
|
23
|
+
open.focus();
|
|
24
|
+
}
|
|
25
|
+
open.addEventListener('click', event => {
|
|
26
|
+
overlay.hidden = false;
|
|
27
|
+
screen.querySelector('.chat-body').inert = true;
|
|
28
|
+
(event.detail === 0 ? close : sheet).focus({preventScroll:true});
|
|
29
|
+
});
|
|
30
|
+
close.addEventListener('click', closeSheet);
|
|
31
|
+
detailClose.addEventListener('click', closeDetail);
|
|
32
|
+
for (const button of screen.querySelectorAll('[data-group]')) {
|
|
33
|
+
button.addEventListener('click', () => {
|
|
34
|
+
const selected = button.dataset.group;
|
|
35
|
+
for (const option of screen.querySelectorAll('[data-group]')) option.setAttribute('aria-pressed', String(option.dataset.group === selected));
|
|
36
|
+
for (const section of screen.querySelectorAll('[data-section]')) section.hidden = selected !== 'all' && section.dataset.section !== selected;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
for (const button of screen.querySelectorAll('.detail-open')) {
|
|
40
|
+
button.addEventListener('click', () => {
|
|
41
|
+
returnFocus = button;
|
|
42
|
+
const row = button.closest('.row');
|
|
43
|
+
screen.querySelector('.detail-title').textContent = row.querySelector('.row-label, .row-value').textContent;
|
|
44
|
+
screen.querySelector('.detail-text').textContent = row.querySelector('.detail-copy').textContent;
|
|
45
|
+
details.hidden = false;
|
|
46
|
+
sheet.inert = true;
|
|
47
|
+
detailClose.focus();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
document.addEventListener('keydown', event => {
|
|
51
|
+
if (event.key === 'Escape') {
|
|
52
|
+
if (!details.hidden) closeDetail();
|
|
53
|
+
else if (!overlay.hidden) closeSheet();
|
|
54
|
+
}
|
|
55
|
+
if (event.key === 'Tab' && !overlay.hidden) {
|
|
56
|
+
const scope = details.hidden ? screen.querySelector('.sheet') : screen.querySelector('.detail-box');
|
|
57
|
+
const buttons = [...scope.querySelectorAll('button,summary')].filter(el => el.getClientRects().length);
|
|
58
|
+
const first = buttons[0], last = buttons[buttons.length - 1];
|
|
59
|
+
if (event.shiftKey && (document.activeElement === first || document.activeElement === sheet)) {event.preventDefault();last.focus();}
|
|
60
|
+
else if (!event.shiftKey && document.activeElement === last) {event.preventDefault();first.focus();}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
overlay.hidden = params.get('view') !== 'expanded';
|
|
64
|
+
screen.querySelector('.chat-body').inert = !overlay.hidden;
|
|
65
|
+
// Embedded examples must not steal the Booklet's focus or reading position.
|
|
66
|
+
if (!overlay.hidden && window === window.top) sheet.focus({preventScroll:true});
|