@my-life-buddies/buddy-creator-skill 1.6.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.
Files changed (159) hide show
  1. package/INSTALL.md +25 -0
  2. package/PATCHES.md +57 -0
  3. package/README.md +76 -0
  4. package/SKILL.md +78 -0
  5. package/THIRD_PARTY_NOTICES.md +154 -0
  6. package/agents/openai.yaml +4 -0
  7. package/assets/licenses/bail.txt +26 -0
  8. package/assets/licenses/ccount.txt +26 -0
  9. package/assets/licenses/character-entities-html4.txt +26 -0
  10. package/assets/licenses/character-entities-legacy.txt +26 -0
  11. package/assets/licenses/character-entities.txt +26 -0
  12. package/assets/licenses/character-reference-invalid.txt +26 -0
  13. package/assets/licenses/comma-separated-tokens.txt +26 -0
  14. package/assets/licenses/debug.txt +24 -0
  15. package/assets/licenses/decode-named-character-reference.txt +26 -0
  16. package/assets/licenses/dequal.txt +25 -0
  17. package/assets/licenses/devlop.txt +26 -0
  18. package/assets/licenses/escape-string-regexp.txt +13 -0
  19. package/assets/licenses/estree-util-is-identifier-name.txt +26 -0
  20. package/assets/licenses/extend.txt +27 -0
  21. package/assets/licenses/hast-util-to-jsx-runtime.txt +26 -0
  22. package/assets/licenses/hast-util-whitespace.txt +26 -0
  23. package/assets/licenses/html-url-attributes.txt +25 -0
  24. package/assets/licenses/inline-style-parser.txt +13 -0
  25. package/assets/licenses/is-alphabetical.txt +26 -0
  26. package/assets/licenses/is-alphanumerical.txt +26 -0
  27. package/assets/licenses/is-decimal.txt +26 -0
  28. package/assets/licenses/is-hexadecimal.txt +26 -0
  29. package/assets/licenses/is-plain-obj.txt +13 -0
  30. package/assets/licenses/longest-streak.txt +26 -0
  31. package/assets/licenses/markdown-table.txt +26 -0
  32. package/assets/licenses/mdast-util-find-and-replace.txt +26 -0
  33. package/assets/licenses/mdast-util-from-markdown.txt +26 -0
  34. package/assets/licenses/mdast-util-gfm-autolink-literal.txt +26 -0
  35. package/assets/licenses/mdast-util-gfm-footnote.txt +26 -0
  36. package/assets/licenses/mdast-util-gfm-strikethrough.txt +26 -0
  37. package/assets/licenses/mdast-util-gfm-table.txt +26 -0
  38. package/assets/licenses/mdast-util-gfm-task-list-item.txt +26 -0
  39. package/assets/licenses/mdast-util-gfm.txt +26 -0
  40. package/assets/licenses/mdast-util-mdx-expression.txt +26 -0
  41. package/assets/licenses/mdast-util-mdx-jsx.txt +26 -0
  42. package/assets/licenses/mdast-util-mdxjs-esm.txt +26 -0
  43. package/assets/licenses/mdast-util-phrasing.txt +27 -0
  44. package/assets/licenses/mdast-util-to-hast.txt +26 -0
  45. package/assets/licenses/mdast-util-to-markdown.txt +26 -0
  46. package/assets/licenses/mdast-util-to-string.txt +26 -0
  47. package/assets/licenses/micromark-core-commonmark.txt +26 -0
  48. package/assets/licenses/micromark-extension-gfm-autolink-literal.txt +26 -0
  49. package/assets/licenses/micromark-extension-gfm-footnote.txt +26 -0
  50. package/assets/licenses/micromark-extension-gfm-strikethrough.txt +26 -0
  51. package/assets/licenses/micromark-extension-gfm-table.txt +26 -0
  52. package/assets/licenses/micromark-extension-gfm-tagfilter.txt +26 -0
  53. package/assets/licenses/micromark-extension-gfm-task-list-item.txt +26 -0
  54. package/assets/licenses/micromark-extension-gfm.txt +26 -0
  55. package/assets/licenses/micromark-factory-destination.txt +26 -0
  56. package/assets/licenses/micromark-factory-label.txt +26 -0
  57. package/assets/licenses/micromark-factory-space.txt +26 -0
  58. package/assets/licenses/micromark-factory-title.txt +26 -0
  59. package/assets/licenses/micromark-factory-whitespace.txt +26 -0
  60. package/assets/licenses/micromark-util-character.txt +26 -0
  61. package/assets/licenses/micromark-util-chunked.txt +26 -0
  62. package/assets/licenses/micromark-util-classify-character.txt +26 -0
  63. package/assets/licenses/micromark-util-combine-extensions.txt +26 -0
  64. package/assets/licenses/micromark-util-decode-numeric-character-reference.txt +26 -0
  65. package/assets/licenses/micromark-util-decode-string.txt +26 -0
  66. package/assets/licenses/micromark-util-encode.txt +26 -0
  67. package/assets/licenses/micromark-util-html-tag-name.txt +26 -0
  68. package/assets/licenses/micromark-util-normalize-identifier.txt +26 -0
  69. package/assets/licenses/micromark-util-resolve-all.txt +26 -0
  70. package/assets/licenses/micromark-util-sanitize-uri.txt +26 -0
  71. package/assets/licenses/micromark-util-subtokenize.txt +26 -0
  72. package/assets/licenses/micromark-util-symbol.txt +26 -0
  73. package/assets/licenses/micromark-util-types.txt +26 -0
  74. package/assets/licenses/micromark.txt +26 -0
  75. package/assets/licenses/ms.txt +25 -0
  76. package/assets/licenses/parse-entities.txt +26 -0
  77. package/assets/licenses/phosphor-icons--react.txt +25 -0
  78. package/assets/licenses/property-information.txt +26 -0
  79. package/assets/licenses/react-dom.txt +25 -0
  80. package/assets/licenses/react-markdown.txt +25 -0
  81. package/assets/licenses/react.txt +25 -0
  82. package/assets/licenses/remark-gfm.txt +26 -0
  83. package/assets/licenses/remark-parse.txt +25 -0
  84. package/assets/licenses/remark-rehype.txt +26 -0
  85. package/assets/licenses/remark-stringify.txt +25 -0
  86. package/assets/licenses/scheduler.txt +25 -0
  87. package/assets/licenses/space-separated-tokens.txt +26 -0
  88. package/assets/licenses/stringify-entities.txt +26 -0
  89. package/assets/licenses/style-to-js.txt +26 -0
  90. package/assets/licenses/style-to-object.txt +26 -0
  91. package/assets/licenses/trim-lines.txt +26 -0
  92. package/assets/licenses/trough.txt +25 -0
  93. package/assets/licenses/types--debug.txt +25 -0
  94. package/assets/licenses/types--estree-jsx.txt +25 -0
  95. package/assets/licenses/types--estree.txt +25 -0
  96. package/assets/licenses/types--hast.txt +25 -0
  97. package/assets/licenses/types--mdast.txt +25 -0
  98. package/assets/licenses/types--ms.txt +25 -0
  99. package/assets/licenses/types--unist.txt +25 -0
  100. package/assets/licenses/ungap--structured-clone.txt +19 -0
  101. package/assets/licenses/unified.txt +25 -0
  102. package/assets/licenses/unist-util-is.txt +26 -0
  103. package/assets/licenses/unist-util-position.txt +26 -0
  104. package/assets/licenses/unist-util-stringify-position.txt +26 -0
  105. package/assets/licenses/unist-util-visit-parents.txt +26 -0
  106. package/assets/licenses/unist-util-visit.txt +26 -0
  107. package/assets/licenses/vfile-message.txt +26 -0
  108. package/assets/licenses/vfile.txt +25 -0
  109. package/assets/licenses/zwitch.txt +26 -0
  110. package/assets/preview/assets/index-B5VmkoSx.js +39 -0
  111. package/assets/preview/assets/index-DWKP6b-3.css +4 -0
  112. package/assets/preview/brand-logo.png +0 -0
  113. package/assets/preview/brand.css +100 -0
  114. package/assets/preview/index.html +7 -0
  115. package/assets/preview/reader.css +165 -0
  116. package/assets/preview/reader.js +135 -0
  117. package/assets/preview/widgets.css +1 -0
  118. package/assets/preview/widgets.js +168 -0
  119. package/assets/widget-reference/conversation.png +0 -0
  120. package/assets/widget-reference/expanded.png +0 -0
  121. package/assets/widget-v2/app.js +62 -0
  122. package/assets/widget-v2/style.css +1 -0
  123. package/assets/widget-v3/app.js +65 -0
  124. package/assets/widget-v3/style.css +103 -0
  125. package/assets/widget-v4/app.js +66 -0
  126. package/assets/widget-v4/style.css +187 -0
  127. package/package.json +37 -0
  128. package/references/artifact-schema.md +70 -0
  129. package/references/catalog.json +401 -0
  130. package/references/cli-workflow.md +15 -0
  131. package/references/definition.md +24 -0
  132. package/references/dialogue-examples.md +133 -0
  133. package/references/host-guide.md +219 -0
  134. package/references/interview.md +235 -0
  135. package/references/knowledge.md +40 -0
  136. package/references/methods-protocol.md +60 -0
  137. package/references/methods.md +98 -0
  138. package/references/opening.md +14 -0
  139. package/references/preview-panel.md +66 -0
  140. package/references/recovery.md +46 -0
  141. package/references/service.md +76 -0
  142. package/references/widgets-protocol.md +83 -0
  143. package/references/widgets.md +99 -0
  144. package/scripts/buddy.py +108 -0
  145. package/scripts/buddy_client.py +67 -0
  146. package/scripts/buddy_core.py +908 -0
  147. package/scripts/completion.py +296 -0
  148. package/scripts/interview.py +218 -0
  149. package/scripts/methods.py +181 -0
  150. package/scripts/preview.py +631 -0
  151. package/scripts/preview_panel.py +80 -0
  152. package/scripts/sources.py +239 -0
  153. package/scripts/widget_render.py +66 -0
  154. package/scripts/widget_render_v1.py +176 -0
  155. package/scripts/widget_render_v2.py +98 -0
  156. package/scripts/widget_render_v3.py +86 -0
  157. package/scripts/widget_render_v4.py +121 -0
  158. package/scripts/widgets.py +227 -0
  159. package/version.json +8 -0
@@ -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(); });
@@ -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});