@tienne/gestalt 0.101.1 → 0.103.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 (192) hide show
  1. package/README.ko.md +2 -1
  2. package/README.md +2 -1
  3. package/dist/package.json +1 -1
  4. package/dist/plugin/role-agents/video-summarizer/AGENT.md +177 -113
  5. package/dist/plugin/skills/_shared/agent-delegation.md +1 -1
  6. package/dist/plugin/skills/_shared/proactive-routing.md +3 -2
  7. package/dist/plugin/skills/_shared/research-humanize.md +160 -0
  8. package/dist/plugin/skills/research/SKILL.md +20 -2
  9. package/dist/plugin/skills/research/references/design.md +14 -2
  10. package/dist/plugin/skills/reverse-plan/SKILL.md +3 -1
  11. package/dist/plugin/skills/ux-research/SKILL.md +3 -1
  12. package/dist/plugin/skills/video-digest/SKILL.md +408 -0
  13. package/dist/plugin/skills/video-digest/templates/background.md +15 -0
  14. package/dist/plugin/skills/video-digest/templates/blog.md +16 -0
  15. package/dist/plugin/skills/video-digest/templates/comments.md +19 -0
  16. package/dist/plugin/skills/video-digest/templates/counterpoints.md +15 -0
  17. package/dist/plugin/skills/video-digest/templates/flashcards.md +15 -0
  18. package/dist/plugin/skills/video-digest/templates/interview.md +15 -0
  19. package/dist/plugin/skills/video-digest/templates/meeting.md +15 -0
  20. package/dist/plugin/skills/video-digest/templates/mindmap.md +16 -0
  21. package/dist/plugin/skills/video-digest/templates/quiz.md +16 -0
  22. package/dist/plugin/skills/video-digest/templates/quotes.md +16 -0
  23. package/dist/plugin/skills/video-digest/templates/resources.md +14 -0
  24. package/dist/plugin/skills/video-digest/templates/timestamps.md +14 -0
  25. package/dist/src/cli/commands/digest.d.ts +57 -0
  26. package/dist/src/cli/commands/digest.d.ts.map +1 -0
  27. package/dist/src/cli/commands/digest.js +368 -0
  28. package/dist/src/cli/commands/digest.js.map +1 -0
  29. package/dist/src/cli/index.d.ts.map +1 -1
  30. package/dist/src/cli/index.js +77 -0
  31. package/dist/src/cli/index.js.map +1 -1
  32. package/dist/src/digest/blocks.d.ts +1853 -0
  33. package/dist/src/digest/blocks.d.ts.map +1 -0
  34. package/dist/src/digest/blocks.js +148 -0
  35. package/dist/src/digest/blocks.js.map +1 -0
  36. package/dist/src/digest/captions.d.ts +37 -0
  37. package/dist/src/digest/captions.d.ts.map +1 -0
  38. package/dist/src/digest/captions.js +138 -0
  39. package/dist/src/digest/captions.js.map +1 -0
  40. package/dist/src/digest/comments.d.ts +113 -0
  41. package/dist/src/digest/comments.d.ts.map +1 -0
  42. package/dist/src/digest/comments.js +153 -0
  43. package/dist/src/digest/comments.js.map +1 -0
  44. package/dist/src/digest/format.d.ts +9 -0
  45. package/dist/src/digest/format.d.ts.map +1 -0
  46. package/dist/src/digest/format.js +26 -0
  47. package/dist/src/digest/format.js.map +1 -0
  48. package/dist/src/digest/frames.d.ts +26 -0
  49. package/dist/src/digest/frames.d.ts.map +1 -0
  50. package/dist/src/digest/frames.js +109 -0
  51. package/dist/src/digest/frames.js.map +1 -0
  52. package/dist/src/digest/index.d.ts +25 -0
  53. package/dist/src/digest/index.d.ts.map +1 -0
  54. package/dist/src/digest/index.js +25 -0
  55. package/dist/src/digest/index.js.map +1 -0
  56. package/dist/src/digest/ingest.d.ts +65 -0
  57. package/dist/src/digest/ingest.d.ts.map +1 -0
  58. package/dist/src/digest/ingest.js +303 -0
  59. package/dist/src/digest/ingest.js.map +1 -0
  60. package/dist/src/digest/lint-extension.d.ts +57 -0
  61. package/dist/src/digest/lint-extension.d.ts.map +1 -0
  62. package/dist/src/digest/lint-extension.js +395 -0
  63. package/dist/src/digest/lint-extension.js.map +1 -0
  64. package/dist/src/digest/lint.d.ts +40 -0
  65. package/dist/src/digest/lint.d.ts.map +1 -0
  66. package/dist/src/digest/lint.js +517 -0
  67. package/dist/src/digest/lint.js.map +1 -0
  68. package/dist/src/digest/merge.d.ts +40 -0
  69. package/dist/src/digest/merge.d.ts.map +1 -0
  70. package/dist/src/digest/merge.js +104 -0
  71. package/dist/src/digest/merge.js.map +1 -0
  72. package/dist/src/digest/ocr.d.ts +96 -0
  73. package/dist/src/digest/ocr.d.ts.map +1 -0
  74. package/dist/src/digest/ocr.js +168 -0
  75. package/dist/src/digest/ocr.js.map +1 -0
  76. package/dist/src/digest/prose.d.ts +43 -0
  77. package/dist/src/digest/prose.d.ts.map +1 -0
  78. package/dist/src/digest/prose.js +112 -0
  79. package/dist/src/digest/prose.js.map +1 -0
  80. package/dist/src/digest/render-blocks.d.ts +32 -0
  81. package/dist/src/digest/render-blocks.d.ts.map +1 -0
  82. package/dist/src/digest/render-blocks.js +292 -0
  83. package/dist/src/digest/render-blocks.js.map +1 -0
  84. package/dist/src/digest/render-html.d.ts +22 -0
  85. package/dist/src/digest/render-html.d.ts.map +1 -0
  86. package/dist/src/digest/render-html.js +466 -0
  87. package/dist/src/digest/render-html.js.map +1 -0
  88. package/dist/src/digest/render-md.d.ts +18 -0
  89. package/dist/src/digest/render-md.d.ts.map +1 -0
  90. package/dist/src/digest/render-md.js +113 -0
  91. package/dist/src/digest/render-md.js.map +1 -0
  92. package/dist/src/digest/render.d.ts +47 -0
  93. package/dist/src/digest/render.d.ts.map +1 -0
  94. package/dist/src/digest/render.js +123 -0
  95. package/dist/src/digest/render.js.map +1 -0
  96. package/dist/src/digest/reports.d.ts +26 -0
  97. package/dist/src/digest/reports.d.ts.map +1 -0
  98. package/dist/src/digest/reports.js +70 -0
  99. package/dist/src/digest/reports.js.map +1 -0
  100. package/dist/src/digest/schema-base.d.ts +31 -0
  101. package/dist/src/digest/schema-base.d.ts.map +1 -0
  102. package/dist/src/digest/schema-base.js +19 -0
  103. package/dist/src/digest/schema-base.js.map +1 -0
  104. package/dist/src/digest/schema.d.ts +5713 -0
  105. package/dist/src/digest/schema.d.ts.map +1 -0
  106. package/dist/src/digest/schema.js +229 -0
  107. package/dist/src/digest/schema.js.map +1 -0
  108. package/dist/src/digest/server.d.ts +36 -0
  109. package/dist/src/digest/server.d.ts.map +1 -0
  110. package/dist/src/digest/server.js +278 -0
  111. package/dist/src/digest/server.js.map +1 -0
  112. package/dist/src/digest/slice.d.ts +20 -0
  113. package/dist/src/digest/slice.d.ts.map +1 -0
  114. package/dist/src/digest/slice.js +48 -0
  115. package/dist/src/digest/slice.js.map +1 -0
  116. package/dist/src/digest/store.d.ts +67 -0
  117. package/dist/src/digest/store.d.ts.map +1 -0
  118. package/dist/src/digest/store.js +152 -0
  119. package/dist/src/digest/store.js.map +1 -0
  120. package/dist/src/digest/svg.d.ts +44 -0
  121. package/dist/src/digest/svg.d.ts.map +1 -0
  122. package/dist/src/digest/svg.js +264 -0
  123. package/dist/src/digest/svg.js.map +1 -0
  124. package/dist/src/digest/templates.d.ts +38 -0
  125. package/dist/src/digest/templates.d.ts.map +1 -0
  126. package/dist/src/digest/templates.js +125 -0
  127. package/dist/src/digest/templates.js.map +1 -0
  128. package/dist/src/digest/translate.d.ts +46 -0
  129. package/dist/src/digest/translate.d.ts.map +1 -0
  130. package/dist/src/digest/translate.js +50 -0
  131. package/dist/src/digest/translate.js.map +1 -0
  132. package/dist/src/digest/viewer-js.d.ts +4 -0
  133. package/dist/src/digest/viewer-js.d.ts.map +1 -0
  134. package/dist/src/digest/viewer-js.js +710 -0
  135. package/dist/src/digest/viewer-js.js.map +1 -0
  136. package/dist/src/digest/viewer-logic.d.ts +46 -0
  137. package/dist/src/digest/viewer-logic.d.ts.map +1 -0
  138. package/dist/src/digest/viewer-logic.js +89 -0
  139. package/dist/src/digest/viewer-logic.js.map +1 -0
  140. package/dist/src/digest/ytdlp.d.ts +34 -0
  141. package/dist/src/digest/ytdlp.d.ts.map +1 -0
  142. package/dist/src/digest/ytdlp.js +64 -0
  143. package/dist/src/digest/ytdlp.js.map +1 -0
  144. package/dist/src/humanize/detectors.js +2 -2
  145. package/dist/src/humanize/detectors.js.map +1 -1
  146. package/dist/src/mcp/schemas.d.ts +78 -4
  147. package/dist/src/mcp/schemas.d.ts.map +1 -1
  148. package/dist/src/mcp/schemas.js +40 -2
  149. package/dist/src/mcp/schemas.js.map +1 -1
  150. package/dist/src/mcp/server.d.ts.map +1 -1
  151. package/dist/src/mcp/server.js +8 -2
  152. package/dist/src/mcp/server.js.map +1 -1
  153. package/dist/src/mcp/tools/digest-passthrough.d.ts +3 -0
  154. package/dist/src/mcp/tools/digest-passthrough.d.ts.map +1 -0
  155. package/dist/src/mcp/tools/digest-passthrough.js +148 -0
  156. package/dist/src/mcp/tools/digest-passthrough.js.map +1 -0
  157. package/dist/src/mcp/tools/research-passthrough.d.ts.map +1 -1
  158. package/dist/src/mcp/tools/research-passthrough.js +56 -0
  159. package/dist/src/mcp/tools/research-passthrough.js.map +1 -1
  160. package/dist/src/research/index.d.ts +1 -0
  161. package/dist/src/research/index.d.ts.map +1 -1
  162. package/dist/src/research/index.js +1 -0
  163. package/dist/src/research/index.js.map +1 -1
  164. package/dist/src/research/prose.d.ts +60 -0
  165. package/dist/src/research/prose.d.ts.map +1 -0
  166. package/dist/src/research/prose.js +200 -0
  167. package/dist/src/research/prose.js.map +1 -0
  168. package/package.json +1 -1
  169. package/plugin/.codex-plugin/plugin.json +1 -1
  170. package/plugin/.mcp.json +1 -1
  171. package/plugin/mcp.json +1 -1
  172. package/plugin/role-agents/video-summarizer/AGENT.md +177 -113
  173. package/plugin/skills/_shared/agent-delegation.md +1 -1
  174. package/plugin/skills/_shared/proactive-routing.md +3 -2
  175. package/plugin/skills/_shared/research-humanize.md +160 -0
  176. package/plugin/skills/research/SKILL.md +20 -2
  177. package/plugin/skills/research/references/design.md +14 -2
  178. package/plugin/skills/reverse-plan/SKILL.md +3 -1
  179. package/plugin/skills/ux-research/SKILL.md +3 -1
  180. package/plugin/skills/video-digest/SKILL.md +408 -0
  181. package/plugin/skills/video-digest/templates/background.md +15 -0
  182. package/plugin/skills/video-digest/templates/blog.md +16 -0
  183. package/plugin/skills/video-digest/templates/comments.md +19 -0
  184. package/plugin/skills/video-digest/templates/counterpoints.md +15 -0
  185. package/plugin/skills/video-digest/templates/flashcards.md +15 -0
  186. package/plugin/skills/video-digest/templates/interview.md +15 -0
  187. package/plugin/skills/video-digest/templates/meeting.md +15 -0
  188. package/plugin/skills/video-digest/templates/mindmap.md +16 -0
  189. package/plugin/skills/video-digest/templates/quiz.md +16 -0
  190. package/plugin/skills/video-digest/templates/quotes.md +16 -0
  191. package/plugin/skills/video-digest/templates/resources.md +14 -0
  192. package/plugin/skills/video-digest/templates/timestamps.md +14 -0
@@ -0,0 +1,466 @@
1
+ import { extensionKey } from './blocks.js';
2
+ import { CAPTION_LABEL, formatTime, stripNumbering, watchUrlAt } from './format.js';
3
+ import { frameFileName } from './frames.js';
4
+ import { extensionHtml, extensionPanelId, sectionDiagramHtml, } from './render-blocks.js';
5
+ import { renderDigestMarkdown, renderExtensionMarkdown } from './render-md.js';
6
+ import { DIFFICULTY_LABEL, isPreset, LENGTH_LABEL, reportKey, reportLabel } from './reports.js';
7
+ import { VIEWER_JS } from './viewer-js.js';
8
+ import { boldPattern, keywordPattern, REPORT_DIFFICULTIES, REPORT_LENGTHS, youtubeWatchUrl, } from './schema.js';
9
+ const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
10
+ const MAX_TOTAL_IMAGE_BYTES = 15 * 1024 * 1024;
11
+ const e = (s) => s
12
+ .replace(/&/g, '&')
13
+ .replace(/</g, '&lt;')
14
+ .replace(/>/g, '&gt;')
15
+ .replace(/"/g, '&quot;')
16
+ .replace(/'/g, '&#39;');
17
+ /** research 렌더러의 예산과 같다. 한 장 5MB, 합계 15MB를 넘으면 그 프레임은 뺀다 */
18
+ class FrameBudget {
19
+ warnings;
20
+ total = 0;
21
+ constructor(warnings) {
22
+ this.warnings = warnings;
23
+ }
24
+ dataUri(data, fileName) {
25
+ if (data.length > MAX_IMAGE_BYTES) {
26
+ this.warnings.push(`프레임이 5MB를 넘어 건너뛰었다: ${fileName}`);
27
+ return null;
28
+ }
29
+ if (!this.charge(data.length, fileName))
30
+ return null;
31
+ return `data:image/jpeg;base64,${data.toString('base64')}`;
32
+ }
33
+ /** 이미 만든 data URI를 다시 실을 때도 HTML 크기는 그만큼 늘어서 같은 합계에 센다 */
34
+ charge(bytes, fileName) {
35
+ if (this.total + bytes > MAX_TOTAL_IMAGE_BYTES) {
36
+ this.warnings.push(`프레임 합계가 15MB를 넘어 건너뛰었다: ${fileName}`);
37
+ return false;
38
+ }
39
+ this.total += bytes;
40
+ return true;
41
+ }
42
+ }
43
+ // 본문에 `</script>`가 들어 있어도 태그를 닫지 못하게 꺾쇠를 유니코드 이스케이프로 바꾼다
44
+ export function embedJson(value) {
45
+ return JSON.stringify(value)
46
+ .replace(/</g, '\\u003c')
47
+ .replace(/\u2028/g, '\\u2028')
48
+ .replace(/\u2029/g, '\\u2029');
49
+ }
50
+ /**
51
+ * digest.json을 뷰어 HTML 한 장으로 바꾼다. LLM을 부르지 않는다.
52
+ * 본문 글자는 escape한 뒤 굵게와 키워드 마커 두 가지만 태그로 바꾼다.
53
+ */
54
+ export function renderDigestHtml(digest, transcript, opts = {}) {
55
+ const warnings = [];
56
+ const { meta, segments } = transcript;
57
+ const long = meta.durationSec >= 3600;
58
+ const isYoutube = meta.source === 'youtube' && Boolean(meta.videoId);
59
+ const at = (i) => segments[i]?.startSec ?? 0;
60
+ const loadFrame = opts.inlineFrames ? opts.loadFrame : undefined;
61
+ const budget = loadFrame ? new FrameBudget(warnings) : null;
62
+ const inline = (text, keywordIds) => e(text)
63
+ .replace(boldPattern(), (_m, inner) => `<strong>${inner}</strong>`)
64
+ .replace(keywordPattern(), (_m, id, label) => {
65
+ if (!keywordIds.has(id)) {
66
+ warnings.push(`키워드 목록에 없는 마커: ${id}`);
67
+ return label;
68
+ }
69
+ return `<button type="button" class="kw" data-kw="${id}">${label}</button>`;
70
+ });
71
+ // 스크립트 줄마다 Tab이 멈추면 긴 영상에서 정지점이 수천 개라 줄 링크는 Tab 순서에서 뺀다
72
+ const timeLink = (sec, tabbable = true) => {
73
+ const label = formatTime(sec, long);
74
+ const url = watchUrlAt(meta, sec);
75
+ if (!url)
76
+ return `<span class="ts" data-sec="${sec}">${label}</span>`;
77
+ const tab = tabbable ? '' : ' tabindex="-1"';
78
+ return `<a class="ts" href="${e(url)}" target="_blank" rel="noopener" data-sec="${sec}"${tab}>${label}</a>`;
79
+ };
80
+ const cite = (c) => `<sup><button type="button" class="cite" data-key="${c.key}" data-from="${c.from}" data-to="${c.to}" aria-label="출처 ${c.key + 1}">[${c.key + 1}]</button></sup>`;
81
+ const sec = (c) => segments[c.key]?.startSec ?? 0;
82
+ const blockCtx = { escape: e, cite, time: (c) => timeLink(sec(c)), sec };
83
+ const missingFrame = (subId, file) => warnings.push(`프레임이 없어 자리를 비웠다: ${subId} (${file})`);
84
+ // 확인 수단이 없는 렌더에서는 프레임이 없다고 경고하지 않는다. 예산 초과는 FrameBudget이 따로 경고한다
85
+ // 리포트 여럿이 같은 프레임을 쓰면 파일은 한 번만 읽는다. 같은 data URI가 HTML에 한 번 더 실리므로 예산은 다시 센다
86
+ const inlined = new Map();
87
+ const frame = (sec, subId) => {
88
+ const file = frameFileName(sec);
89
+ let src = null;
90
+ if (budget && inlined.has(file)) {
91
+ const hit = inlined.get(file);
92
+ if (hit && budget.charge(hit.bytes, file))
93
+ src = hit.src;
94
+ }
95
+ else if (budget && loadFrame) {
96
+ const data = loadFrame(file);
97
+ if (data)
98
+ src = budget.dataUri(data, file);
99
+ else
100
+ missingFrame(subId, file);
101
+ inlined.set(file, src && data ? { src, bytes: data.length } : null);
102
+ }
103
+ else if (opts.frameExists) {
104
+ if (opts.frameExists(file))
105
+ src = `frames/${file}`;
106
+ else
107
+ missingFrame(subId, file);
108
+ }
109
+ if (!src)
110
+ return '';
111
+ const url = watchUrlAt(meta, sec);
112
+ const img = `<img src="${e(src)}" alt="${formatTime(sec, long)} 장면" loading="lazy">`;
113
+ const body = url
114
+ ? `<a class="frame-link" href="${e(url)}" target="_blank" rel="noopener" data-sec="${sec}">${img}</a>`
115
+ : `<span class="frame-link" data-sec="${sec}">${img}</span>`;
116
+ return `<figure class="frame">${body}<figcaption>${timeLink(sec)}</figcaption></figure>`;
117
+ };
118
+ const reportsMeta = digest.reports.map((r) => ({ report: r, key: reportKey(r.options) }));
119
+ const base = digest.reports[0].options;
120
+ // 첫 리포트는 1차와 같은 id를 써서 이미 공유된 앵커가 그대로 열린다. 나머지는 id에 못 쓰는 ~로 접두를 붙여 겹치지 않게 한다
121
+ const renderReport = (report, ri, key) => {
122
+ const { lead, sections, skipped, keywords } = report;
123
+ const kwIds = new Set(keywords.map((k) => k.id));
124
+ const pfx = ri === 0 ? '' : `r${ri}~`;
125
+ const hidden = ri === 0 ? '' : ' hidden';
126
+ const bullets = (list) => `<ul>${list
127
+ .map((b) => `<li>${inline(b.text, kwIds)}${cite(b.cite)}${b.children?.length ? bullets(b.children) : ''}</li>`)
128
+ .join('')}</ul>`;
129
+ const sectionsHtml = sections
130
+ .map((s, si) => {
131
+ const subs = s.subsections
132
+ .map((sub, ui) => `<div class="sub" id="sub-${pfx}${e(sub.id)}" data-from="${sub.cite.from}" data-to="${sub.cite.to}"><h3><span class="num">${si + 1}.${ui + 1}.</span> ${e(stripNumbering(sub.title))} ${timeLink(at(sub.cite.key))}</h3>${frame(sub.frameSec, sub.id)}${bullets(sub.bullets)}</div>`)
133
+ .join('');
134
+ return `<section class="sec" id="sec-${pfx}${e(s.id)}"><h2><span class="num">${si + 1}.</span> ${e(stripNumbering(s.title))} ${timeLink(at(s.cite.key))}</h2><p class="conclusion">${inline(s.conclusion, kwIds)}${cite(s.cite)}</p>${s.diagram ? sectionDiagramHtml(s.diagram, stripNumbering(s.title), blockCtx) : ''}${subs}</section>`;
135
+ })
136
+ .join('');
137
+ const toc = `<nav class="toc" aria-label="목차"><h2>목차</h2><ol>${sections
138
+ .map((s, si) => `<li><a href="#sec-${pfx}${e(s.id)}" data-nav="sec-${pfx}${e(s.id)}">${si + 1}. ${e(stripNumbering(s.title))}</a><ol>${s.subsections
139
+ .map((sub, ui) => `<li><a href="#sub-${pfx}${e(sub.id)}">${si + 1}.${ui + 1}. ${e(stripNumbering(sub.title))}</a></li>`)
140
+ .join('')}</ol></li>`)
141
+ .join('')}</ol></nav>`;
142
+ const minimap = `<nav class="minimap" aria-label="섹션 이동" data-ri="${ri}"${hidden}>${sections
143
+ .map((s, si) => `<a href="#sec-${pfx}${e(s.id)}" data-nav="sec-${pfx}${e(s.id)}" title="${e(stripNumbering(s.title))}" aria-label="${si + 1}. ${e(stripNumbering(s.title))}"><span aria-hidden="true">${si + 1}</span></a>`)
144
+ .join('')}</nav>`;
145
+ const keywordsHtml = keywords.length
146
+ ? `<section class="extra" id="${pfx}keywords"><h2>키워드</h2><dl>${keywords
147
+ .map((k) => `<dt>${e(k.term)} ${timeLink(at(k.firstCite.key))}</dt><dd>${inline(k.explanation, kwIds)}</dd>`)
148
+ .join('')}</dl></section>`
149
+ : '';
150
+ const skippedHtml = skipped.length
151
+ ? `<section class="extra" id="${pfx}skipped"><h2>요약에서 뺀 구간</h2><ul>${skipped
152
+ .map((s) => `<li>${timeLink(at(s.from))} 문장 ${s.from + 1}-${s.to + 1}: ${inline(s.reason, kwIds)}</li>`)
153
+ .join('')}</ul></section>`
154
+ : '';
155
+ const scope = [
156
+ report.options.range
157
+ ? `<p class="report-scope">${formatTime(report.options.range[0], long)}~${formatTime(report.options.range[1], long)} 구간만 요약했어요.</p>`
158
+ : '',
159
+ report.options.prompt ? `<p class="report-scope">요청: ${e(report.options.prompt)}</p>` : '',
160
+ ].join('');
161
+ const body = `<div class="report" data-ri="${ri}" data-report="${e(key)}" lang="${e(report.options.lang)}"${hidden}>${scope}<section class="lead"><p class="q">${inline(lead.question, kwIds)}</p><p>${inline(lead.answer, kwIds)}</p><p class="takeaway">${inline(lead.takeaway, kwIds)}</p></section>
162
+ ${toc}
163
+ ${sectionsHtml}
164
+ ${keywordsHtml}
165
+ ${skippedHtml}</div>`;
166
+ return { body, minimap };
167
+ };
168
+ const rendered = reportsMeta.map(({ report, key }, ri) => renderReport(report, ri, key));
169
+ const pressed = (on) => `aria-pressed="${on ? 'true' : 'false'}"`;
170
+ const customs = reportsMeta.filter(({ report }) => !isPreset(report.options, base));
171
+ const reportBar = `<div class="report-bar" role="group" aria-label="리포트 고르기"><span class="bar-label">분량</span>${REPORT_LENGTHS.map((l) => `<button type="button" data-length="${l}" ${pressed(l === base.length)}>${LENGTH_LABEL[l]}</button>`).join('')}<span class="bar-label">난이도</span>${REPORT_DIFFICULTIES.map((d) => `<button type="button" data-difficulty="${d}" ${pressed(d === base.difficulty)}>${DIFFICULTY_LABEL[d]}</button>`).join('')}${customs.length
172
+ ? `<label class="bar-label">다른 리포트 <select id="report-select">${reportsMeta
173
+ .map(({ report, key }, ri) => `<option value="${ri}" data-report="${e(key)}">${e(reportLabel(report.options, long))}</option>`)
174
+ .join('')}</select></label>`
175
+ : ''}</div><div id="report-missing" class="report-missing" role="status" hidden><p>이 조합의 리포트는 아직 없어요. 아래 명령을 세션에 붙여 넣으면 만들어요.</p><code id="report-cmd"></code> <button type="button" id="copy-cmd">명령 복사</button></div>`;
176
+ const watchBase = isYoutube ? youtubeWatchUrl(meta.videoId) : null;
177
+ const player = isYoutube
178
+ ? `<div id="player-slot" class="player-slot"><div id="player" class="player"><div id="player-frame"></div><a class="player-fallback" href="${e(watchBase)}" target="_blank" rel="noopener"><img src="https://i.ytimg.com/vi/${meta.videoId}/hqdefault.jpg" alt=""><span>유튜브에서 열기</span></a><button type="button" id="player-dock" class="player-dock" aria-label="미니 플레이어 닫기">×</button></div></div>`
179
+ : `<div id="player-slot" class="player-slot"><div id="player" class="player player-local"><video id="player-video" controls playsinline preload="metadata" aria-label="영상 재생"></video><p class="player-note">로컬 영상은 <code>gestalt digest serve ${e(digest.slug)}</code>로 열어야 재생돼요. 원본 파일을 옮겼으면 ingest를 다시 돌려 주세요.</p><button type="button" id="player-dock" class="player-dock" aria-label="미니 플레이어 닫기">×</button></div></div>`;
180
+ const trLang = meta.translationLang;
181
+ const hasTr = trLang !== undefined && segments.some((s) => s.translation);
182
+ const trToggle = hasTr
183
+ ? ` <button type="button" id="tr-toggle" class="tr-toggle" aria-pressed="true" hidden>번역</button>`
184
+ : '';
185
+ const tr = (s) => hasTr && s.translation
186
+ ? `<span class="tr" lang="${e(trLang ?? '')}">${e(s.translation)}</span>`
187
+ : '';
188
+ const script = `<div class="script"><h2>스크립트 <small>${e(CAPTION_LABEL[meta.captionSource])}</small>${trToggle}</h2><div id="script-body" class="script-body" tabindex="0" role="region" aria-label="영상 스크립트"><ol id="script-list">${segments
189
+ .map((s) => `<li data-i="${s.i}">${timeLink(s.startSec, false)}<span class="txt"><span class="src">${e(s.text)}</span>${tr(s)}</span></li>`)
190
+ .join('')}</ol></div></div>`;
191
+ const facts = [meta.channel, meta.publishedAt, formatTime(meta.durationSec, long)]
192
+ .filter(Boolean)
193
+ .map((f) => `<span>${e(f)}</span>`)
194
+ .join('');
195
+ const data = {
196
+ videoId: isYoutube ? meta.videoId : null,
197
+ localVideo: meta.source === 'file',
198
+ watchBase,
199
+ long,
200
+ starts: segments.map((s) => s.startSec),
201
+ slug: digest.slug,
202
+ reports: reportsMeta.map(({ report, key }) => ({
203
+ key,
204
+ length: report.options.length,
205
+ difficulty: report.options.difficulty,
206
+ lang: report.options.lang,
207
+ preset: isPreset(report.options, base),
208
+ keywords: Object.fromEntries(report.keywords.map((k) => [
209
+ k.id,
210
+ {
211
+ term: k.term,
212
+ explanation: k.explanation.replace(keywordPattern(), '$2').replace(boldPattern(), '$1'),
213
+ sec: at(k.firstCite.key),
214
+ },
215
+ ])),
216
+ markdown: renderDigestMarkdown(digest, transcript, {
217
+ frameExists: opts.frameExists,
218
+ report,
219
+ }),
220
+ })),
221
+ extensions: digest.extensions.map((x) => ({
222
+ key: extensionKey(x),
223
+ panel: extensionPanelId(x),
224
+ markdown: renderExtensionMarkdown(x, transcript),
225
+ })),
226
+ };
227
+ const extensions = digest.extensions;
228
+ const made = new Set(extensions.map((x) => x.template));
229
+ const menuTemplates = (opts.templates ?? []).filter((t) => !made.has(t.id));
230
+ const hasTabs = extensions.length > 0 || menuTemplates.length > 0;
231
+ const tab = (id, label, on) => `<button type="button" role="tab" id="tab-${e(id)}" data-tab="${e(id)}" aria-controls="${e(id)}" aria-selected="${on ? 'true' : 'false'}"${on ? '' : ' tabindex="-1"'}>${e(label)}</button>`;
232
+ const moreMenu = menuTemplates.length
233
+ ? `<button type="button" id="more-btn" class="more-btn" aria-expanded="false" aria-controls="more-menu">+ 더 만들기</button></div><div id="more-menu" class="more-menu" hidden><p>템플릿을 고르면 세션에 붙여 넣을 명령을 복사해요.</p><ul>${menuTemplates
234
+ .map((t) => `<li><button type="button" data-template="${e(t.id)}"><b>${e(t.title)}</b><small>${e(t.description)}</small></button></li>`)
235
+ .join('')}</ul><div id="more-cmd-box" hidden><code id="more-cmd"></code> <button type="button" id="more-copy">명령 복사</button></div></div>`
236
+ : '</div>';
237
+ const tabRow = hasTabs
238
+ ? `<div class="tab-row"><div class="tabs" role="tablist" aria-label="리포트 종류">${tab('panel-summary', '요약', true)}${extensions
239
+ .map((x) => tab(extensionPanelId(x), x.title, false))
240
+ .join('')}</div>${moreMenu}`
241
+ : '';
242
+ const extPanels = extensions.map((x) => extensionHtml(x, blockCtx)).join('\n');
243
+ const summary = `${hasTabs ? reportBar : ''}${rendered.map((r) => r.body).join('\n')}`;
244
+ const summaryPanel = hasTabs
245
+ ? `<div id="panel-summary" role="tabpanel" aria-labelledby="tab-panel-summary" tabindex="0">${summary}</div>\n${extPanels}`
246
+ : summary;
247
+ const html = `<!doctype html>
248
+ <html lang="${e(base.lang)}">
249
+ <head>
250
+ <meta charset="utf-8">
251
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
252
+ <meta name="referrer" content="strict-origin-when-cross-origin">
253
+ <title>${e(meta.title)}</title>
254
+ <style>${CSS}</style>
255
+ ${hasTabs ? '<noscript><style>.ext{display:block!important}.tabs,.more-btn,.quiz-check,.zoom-btn{display:none}.quiz-exp{display:block!important}</style></noscript>' : ''}
256
+ </head>
257
+ <body>
258
+ <div class="layout">
259
+ <aside class="side">${player}${script}</aside>
260
+ <main class="main">
261
+ <header class="head"><h1>${e(meta.title)}</h1><p class="facts">${facts}</p><div class="tools"><button type="button" id="copy-md">마크다운 복사</button><label>글자 크기 <input id="font-range" type="range" min="85" max="145" step="5" value="100"> <output id="font-value">100%</output></label><p id="copy-status" class="sr-only" role="status" aria-live="polite"></p></div>
262
+ ${hasTabs ? '' : reportBar}
263
+ <noscript><p class="note">자바스크립트가 꺼져 있어 출처 툴팁과 키워드 팝업, 재생 따라가기, 리포트 전환이 동작하지 않아요.${isYoutube ? ' 시각 링크는 유튜브 새 탭으로 열려요.' : ''}</p></noscript></header>
264
+ ${tabRow}
265
+ ${summaryPanel}
266
+ </main>
267
+ </div>
268
+ ${rendered.map((r) => r.minimap).join('')}
269
+ <script type="application/json" id="digest-data">${embedJson(data)}</script>
270
+ <script>${VIEWER_JS}</script>
271
+ </body>
272
+ </html>
273
+ `;
274
+ return { html, warnings: [...new Set(warnings)] };
275
+ }
276
+ // 토큰은 research 렌더러와 같은 값을 쓴다
277
+ const CSS = `
278
+ :root{--bg:#fafaf9;--fg:#1c1917;--muted:#78716c;--line:#e7e5e4;--card:#fff;--accent:#2563eb;--warn:#b45309;--chip:#f5f5f4;--hl:#fef3c7;--on-accent:#fff;--scale:1;--float-w:min(360px,60vw,calc(40vh * 16 / 9))}
279
+ @media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#0c0a09;--fg:#f5f5f4;--muted:#a8a29e;--line:#292524;--card:#1c1917;--accent:#60a5fa;--warn:#fbbf24;--chip:#292524;--hl:#422006;--on-accent:#0c0a09}}
280
+ :root[data-theme="dark"]{--bg:#0c0a09;--fg:#f5f5f4;--muted:#a8a29e;--line:#292524;--card:#1c1917;--accent:#60a5fa;--warn:#fbbf24;--chip:#292524;--hl:#422006;--on-accent:#0c0a09}
281
+ *{box-sizing:border-box}
282
+ html{font-size:calc(100% * var(--scale))}
283
+ body{margin:0;background:var(--bg);color:var(--fg);font:1rem/1.7 -apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo","Pretendard",sans-serif}
284
+ a{color:var(--accent)}
285
+ button{font:inherit;color:inherit}
286
+ .layout{display:grid;grid-template-columns:minmax(300px,420px) minmax(0,1fr);gap:32px;max-width:1360px;margin:0 auto;padding:0 56px 0 16px}
287
+ .side{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;gap:12px;padding:16px 0}
288
+ .player{position:relative;background:#000;border-radius:8px;overflow:hidden;flex:none}
289
+ #player-frame{position:absolute;inset:0;width:100%;height:100%;border:0;opacity:0;pointer-events:none}
290
+ .player.is-live #player-frame{position:static;display:block;height:auto;aspect-ratio:16/9;opacity:1;pointer-events:auto}
291
+ .player.is-live .player-fallback{display:none}
292
+ .player-fallback{display:block;position:relative;color:#fff;text-decoration:none}
293
+ .player-fallback img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;opacity:.7}
294
+ .player-fallback span{position:absolute;inset:auto 0 0 0;padding:10px 14px;background:rgba(0,0,0,.6);font-weight:600}
295
+ .player-note{background:var(--chip);color:var(--muted);padding:12px 14px;font-size:.9em;margin:0}
296
+ .player-local video{display:none;width:100%;aspect-ratio:16/9;background:#000}
297
+ .player-local.is-live video{display:block}
298
+ .player-local.is-live .player-note{display:none}
299
+ .player-slot{flex:none}
300
+ .player-slot.is-floating{aspect-ratio:16/9}
301
+ .player.is-floating{position:fixed;right:calc(16px + env(safe-area-inset-right, 0px));bottom:calc(16px + env(safe-area-inset-bottom, 0px));width:var(--float-w);z-index:15;box-shadow:0 8px 24px rgba(0,0,0,.35)}
302
+ .player-dock{display:none;position:absolute;top:6px;right:6px;width:40px;height:40px;border:0;border-radius:50%;background:rgba(0,0,0,.6);color:#fff;cursor:pointer;align-items:center;justify-content:center;font-size:18px;line-height:1}
303
+ .player.is-floating .player-dock{display:flex}
304
+ .script{display:flex;flex-direction:column;min-height:0;flex:1;border:1px solid var(--line);border-radius:8px;background:var(--card)}
305
+ .script h2{margin:0;padding:8px 12px;font-size:.95em;border-bottom:1px solid var(--line)}
306
+ .script h2 small{color:var(--muted);font-weight:400}
307
+ .script-body{overflow:auto;flex:1;position:relative}
308
+ #script-list{list-style:none;margin:0;padding:4px 0}
309
+ #script-list li{display:flex;gap:10px;padding:4px 12px;font-size:.9em;line-height:1.55}
310
+ #script-list li.is-playing{background:var(--hl)}
311
+ .tr{display:block;color:var(--muted);font-size:.95em}
312
+ .script.hide-tr .tr{display:none}
313
+ .tr-toggle{float:right;border:1px solid var(--line);background:var(--card);border-radius:6px;padding:0 8px;font-size:.85em;font-weight:400;cursor:pointer}
314
+ .tr-toggle[aria-pressed="true"]{background:var(--accent);color:var(--on-accent);border-color:var(--accent)}
315
+ .ts{font-variant-numeric:tabular-nums;color:var(--accent);text-decoration:none;font-size:.85em;white-space:nowrap;cursor:pointer}
316
+ a.ts:hover{text-decoration:underline}
317
+ .main{padding:24px 0 96px;min-width:0}
318
+ .head h1{margin:0 0 4px;font-size:1.6em;line-height:1.35}
319
+ .facts{margin:0;color:var(--muted);display:flex;flex-wrap:wrap;gap:12px;font-size:.9em}
320
+ .tools{display:flex;flex-wrap:wrap;gap:16px;align-items:center;margin:12px 0;font-size:.85em;color:var(--muted)}
321
+ .tools button{border:1px solid var(--line);background:var(--card);border-radius:6px;padding:4px 10px;cursor:pointer}
322
+ .note{color:var(--warn);font-size:.9em}
323
+ .report-bar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:8px 0;font-size:.85em}
324
+ .report-bar .bar-label{color:var(--muted);margin-left:6px}
325
+ .report-bar .bar-label:first-child{margin-left:0}
326
+ .report-bar button{border:1px solid var(--line);background:var(--card);border-radius:999px;padding:3px 12px;cursor:pointer}
327
+ .report-bar button[aria-pressed="true"]{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}
328
+ .report-bar select{font:inherit;color:inherit;background:var(--card);border:1px solid var(--line);border-radius:6px;padding:2px 6px}
329
+ .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}
330
+ .report-missing{border:1px dashed var(--line);border-radius:8px;padding:8px 12px;margin:8px 0;font-size:.88em;background:var(--card)}
331
+ .report-missing p{margin:0 0 6px}
332
+ .report-missing code{display:block;white-space:nowrap;overflow-x:auto;background:var(--chip);padding:4px 8px;border-radius:4px;margin:0 0 6px}
333
+ #report-cmd{user-select:all}
334
+ .report-missing button{border:1px solid var(--line);background:var(--card);border-radius:6px;padding:2px 10px;cursor:pointer}
335
+ .report-scope{color:var(--muted);font-size:.9em;margin:12px 0 0}
336
+ .tab-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:8px;border-bottom:1px solid var(--line);margin:12px 0 8px}
337
+ .tabs{display:flex;flex-wrap:wrap;gap:2px}
338
+ .tabs [role="tab"]{border:0;border-bottom:2px solid transparent;background:none;color:var(--muted);padding:6px 12px;font:inherit;font-size:.92em;cursor:pointer}
339
+ .tabs [role="tab"][aria-selected="true"]{color:var(--fg);border-bottom-color:var(--accent);font-weight:600}
340
+ .more-btn{margin-left:auto;margin-bottom:4px;border:1px dashed var(--line);background:var(--card);border-radius:999px;padding:3px 12px;font-size:.85em;cursor:pointer}
341
+ .more-menu{border:1px solid var(--line);border-radius:8px;padding:8px 12px;margin:0 0 8px;background:var(--card);font-size:.88em}
342
+ .more-menu p{margin:0 0 6px;color:var(--muted)}
343
+ .more-menu ul{list-style:none;margin:0 0 6px;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:6px}
344
+ .more-menu li button{width:100%;text-align:left;border:1px solid var(--line);background:var(--bg);border-radius:6px;padding:6px 8px;cursor:pointer;color:inherit;font:inherit}
345
+ .more-menu li small{display:block;color:var(--muted);font-size:.85em}
346
+ .more-menu code{display:block;white-space:nowrap;overflow-x:auto;background:var(--chip);padding:4px 8px;border-radius:4px;margin:0 0 6px}
347
+ #more-copy{border:1px solid var(--line);background:var(--card);border-radius:6px;padding:2px 10px;cursor:pointer}
348
+ .ext h2{font-size:1.35em;border-bottom:1px solid var(--line);padding-bottom:6px}
349
+ .ext h3{font-size:1.1em;margin:20px 0 6px}
350
+ .ext h4{font-size:1em;margin:12px 0 4px}
351
+ .quote{margin:16px 0;padding:10px 16px;border-left:3px solid var(--accent);background:var(--chip);border-radius:0 8px 8px 0}
352
+ .quote blockquote{margin:0;font-size:1.05em}
353
+ .quote-tr{margin:6px 0 0;color:var(--muted)}
354
+ .quote figcaption{margin-top:6px;font-size:.9em;color:var(--muted);display:flex;flex-wrap:wrap;gap:8px}
355
+ .ext-toc{list-style:none;padding:0;margin:8px 0}
356
+ .ext-toc li{display:flex;gap:12px;padding:6px 0;border-bottom:1px solid var(--line)}
357
+ .ext-toc li .ts{flex:none;padding-top:2px}
358
+ .ext-toc p{margin:2px 0 0;color:var(--muted);font-size:.95em}
359
+ .actions{list-style:none;padding:0;margin:8px 0}
360
+ .actions li{display:flex;gap:10px;padding:4px 0}
361
+ .actions .box{flex:none;width:14px;height:14px;margin-top:6px;border:1.5px solid var(--muted);border-radius:3px}
362
+ .actions small{display:flex;gap:10px;color:var(--muted)}
363
+ .qa dt{font-weight:600;margin-top:12px}
364
+ .qa dt::before{content:"Q. ";color:var(--accent)}
365
+ .qa dd{margin:4px 0 0}
366
+ .cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;margin:12px 0}
367
+ .card{border:1px solid var(--line);border-radius:10px;background:var(--card);padding:12px 14px}
368
+ .card summary{cursor:pointer;font-weight:600;list-style:none;display:flex;justify-content:space-between;gap:10px}
369
+ .card summary::-webkit-details-marker{display:none}
370
+ .card summary::after{content:"";flex:none;width:7px;height:7px;margin-top:.4em;border-right:2px solid var(--muted);border-bottom:2px solid var(--muted);transform:rotate(45deg)}
371
+ .card[open] summary{color:var(--muted);font-weight:400;font-size:.9em}
372
+ .card[open] summary::after{transform:rotate(-135deg);margin-top:.6em}
373
+ .card-back{margin-top:8px}
374
+ .quiz{padding-left:20px}
375
+ .quiz-q{border:1px solid var(--line);border-radius:10px;background:var(--card);padding:10px 14px;margin:0 0 12px}
376
+ .quiz-q legend{font-weight:600;padding:0 4px}
377
+ .quiz-choice{display:block;padding:3px 0;cursor:pointer}
378
+ .quiz-check{margin-top:6px;border:1px solid var(--line);background:var(--bg);border-radius:6px;padding:2px 10px;cursor:pointer;color:inherit;font:inherit}
379
+ .quiz-exp{margin-top:8px;border-top:1px solid var(--line)}
380
+ .quiz-exp p{margin:6px 0}
381
+ .quiz-result{margin:6px 0}.quiz-result:empty{margin:0}
382
+ .quiz-q.is-right .quiz-result{color:var(--accent);font-weight:600}
383
+ .quiz-q.is-wrong .quiz-result{color:var(--warn);font-weight:600}
384
+ .quiz-choice.is-answer>span:not(.quiz-mark){background:var(--hl);font-weight:600}
385
+ .quiz-choice.is-picked>span:not(.quiz-mark){outline:2px dashed var(--warn);outline-offset:1px;border-radius:3px}
386
+ .diagram{margin:16px 0}
387
+ .dg-scroll{overflow-x:auto;border:1px solid var(--line);border-radius:10px;background:var(--card);padding:8px}
388
+ .dg-svg{display:block;max-width:none}
389
+ .dg-svg text{fill:var(--fg);font-size:13px;font-family:inherit}
390
+ .dg-svg rect{fill:var(--bg);stroke:var(--line)}
391
+ .dg-svg path,.dg-svg line{stroke:var(--muted);fill:none;stroke-width:1.2}
392
+ .dg-svg .mm-d0 rect,.dg-svg .dg-head rect{fill:var(--accent);stroke:var(--accent)}
393
+ .dg-svg .mm-d0 text,.dg-svg .dg-head text{fill:var(--on-accent);font-size:14px;font-weight:600}
394
+ .dg-svg .mm-d1 rect,.dg-flow rect,.dg-cycle rect{stroke:var(--accent)}
395
+ .dg-svg .dg-arrow{fill:var(--muted);stroke:none}
396
+ .dg-svg .mm-cite,.dg-svg .dg-cite{cursor:pointer}
397
+ .dg-svg .mm-cite:hover rect,.dg-svg .dg-cite:hover rect{fill:var(--hl)}
398
+ .dg-svg .mm-cite:focus{outline:none}.dg-svg .dg-cite:focus{outline:none}
399
+ .dg-svg .mm-cite:focus-visible rect,.dg-svg .dg-cite:focus-visible rect{stroke:var(--accent);stroke-width:2}
400
+ .dg-bar{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin-top:4px;font-size:.9em;color:var(--muted)}
401
+ .dg-bar span{margin-right:auto}
402
+ .zoom-btn{border:1px solid var(--line);background:var(--card);border-radius:6px;padding:2px 10px;cursor:pointer;color:inherit;font:inherit}
403
+ .dg-outline{margin-top:4px;font-size:.92em}
404
+ .dg-outline summary{cursor:pointer;color:var(--muted)}
405
+ #zoom-dialog{width:min(96vw,1400px);max-height:92vh;border:1px solid var(--line);border-radius:12px;background:var(--card);color:var(--fg);padding:12px}
406
+ #zoom-dialog::backdrop{background:rgba(0,0,0,.5)}
407
+ #zoom-dialog .zoom-body{overflow:auto;max-height:calc(92vh - 64px)}
408
+ #zoom-dialog .zoom-body svg{width:100%;height:auto;min-width:600px}
409
+ #zoom-dialog .zoom-close{float:right}
410
+ .cm-overview{font-size:1.05em}
411
+ .cm-meta{color:var(--muted);font-size:.9em}
412
+ .cm-groups{list-style:none;padding:0}
413
+ .cm-group{border:1px solid var(--line);border-radius:10px;background:var(--card);padding:10px 14px;margin:0 0 10px}
414
+ .cm-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px}
415
+ .cm-tone{font-size:.8em;border-radius:999px;padding:0 8px;background:var(--chip);color:var(--muted)}
416
+ .cm-positive .cm-tone{color:var(--accent)}
417
+ .cm-negative .cm-tone{color:var(--warn)}
418
+ .cm-count{margin-left:auto;color:var(--muted);font-size:.9em}
419
+ .cm-bar{height:4px;background:var(--chip);border-radius:2px;margin:6px 0}
420
+ .cm-bar i{display:block;height:100%;background:currentColor;border-radius:2px;color:var(--muted)}
421
+ .cm-positive .cm-bar i{color:var(--accent)}
422
+ .cm-negative .cm-bar i{color:var(--warn)}
423
+ .cm-ex{padding-left:18px;color:var(--muted);font-size:.95em}
424
+ .cm-ex q{white-space:pre-line}
425
+ body[data-tab="ext"] .minimap{display:none}
426
+ .lead{border-left:3px solid var(--accent);padding:4px 16px;margin:20px 0}
427
+ .lead .q{font-weight:700;font-size:1.1em}
428
+ .lead .takeaway{color:var(--muted)}
429
+ .toc{border:1px solid var(--line);border-radius:8px;padding:8px 20px;background:var(--card);margin:20px 0}
430
+ .toc h2{font-size:1em;margin:8px 0}
431
+ .toc ol{list-style:none;padding-left:0;margin:4px 0}
432
+ .toc ol ol{padding-left:16px;font-size:.92em}
433
+ .toc a{text-decoration:none;color:var(--fg)}
434
+ .toc a.is-current{color:var(--accent);font-weight:600}
435
+ .sec{scroll-margin-top:16px;padding-top:12px}
436
+ .sec h2{font-size:1.35em;border-bottom:1px solid var(--line);padding-bottom:6px}
437
+ .sub{scroll-margin-top:16px}
438
+ .sub h3{font-size:1.1em;margin:24px 0 8px}
439
+ .num{color:var(--muted)}
440
+ .conclusion{font-weight:500}
441
+ .frame{margin:8px 0 12px}
442
+ .frame img{display:block;width:100%;max-width:640px;height:auto;max-height:360px;aspect-ratio:auto 16/9;object-fit:contain;border-radius:6px;border:1px solid var(--line);cursor:pointer}
443
+ .frame figcaption{margin-top:2px}
444
+ .cite{border:0;background:none;padding:0 1px;color:var(--accent);cursor:pointer;font-size:.8em}
445
+ .kw{border:0;background:none;padding:0;border-bottom:1px dashed var(--accent);cursor:pointer}
446
+ .extra dt{font-weight:600;margin-top:8px}
447
+ .extra dd{margin:0 0 4px 0;color:var(--muted)}
448
+ .pop{position:absolute;z-index:20;max-width:min(420px,calc(100vw - 16px));background:var(--card);border:1px solid var(--line);border-radius:8px;box-shadow:0 6px 24px rgba(0,0,0,.18);padding:10px 12px;font-size:.88em}
449
+ .pop-head{display:flex;gap:6px;align-items:baseline;margin-bottom:4px}
450
+ .pop-range,.pop-more{color:var(--muted)}
451
+ .pop-lines{margin:0;padding-left:18px}
452
+ .pop-lines li.is-key{font-weight:600}
453
+ .pop p{margin:4px 0 0}
454
+ .minimap{position:fixed;right:12px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:6px;z-index:10}
455
+ .minimap a{display:flex;width:28px;height:28px;border-radius:50%;align-items:center;justify-content:center;background:var(--card);border:1px solid var(--line);color:var(--muted);text-decoration:none;font-size:12px}
456
+ .minimap a.is-current{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}
457
+ .minimap[hidden]{display:none}
458
+ @media (max-width:900px){
459
+ .layout{grid-template-columns:minmax(0,1fr);padding:0 16px;gap:0}
460
+ .side{position:static;height:auto}
461
+ .script-body{max-height:40vh}
462
+ .minimap{display:none}
463
+ .main{padding-bottom:calc(var(--float-w) * 9 / 16 + 32px + env(safe-area-inset-bottom, 0px))}
464
+ }
465
+ `;
466
+ //# sourceMappingURL=render-html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-html.js","sourceRoot":"","sources":["../../../src/digest/render-html.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { Extension } from './blocks.js';
2
+ import { type Digest, type Report, type Transcript } from './schema.js';
3
+ export interface RenderMdOptions {
4
+ /** 이 이름의 프레임이 있으면 소섹션에 `<framesHref>/...` 상대 경로로 싣는다 */
5
+ frameExists?: (fileName: string) => boolean;
6
+ /** 마크다운 파일에서 frames 폴더까지의 상대 경로. 슬러그 폴더 밖에 쓸 때만 바꾼다 */
7
+ framesHref?: string;
8
+ /** 내보낼 리포트. 없으면 첫 리포트다 */
9
+ report?: Report;
10
+ }
11
+ /**
12
+ * digest.json을 마크다운 한 파일로 바꾼다. LLM을 부르지 않는다.
13
+ * 기존 video-summarizer가 직접 쓰던 마크다운을 이 출력이 대신한다.
14
+ */
15
+ export declare function renderDigestMarkdown(digest: Digest, transcript: Transcript, opts?: RenderMdOptions): string;
16
+ /** 확장 리포트 한 벌을 마크다운으로. 머리말은 요약 마크다운과 같다 */
17
+ export declare function renderExtensionMarkdown(ext: Extension, transcript: Transcript): string;
18
+ //# sourceMappingURL=render-md.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-md.d.ts","sourceRoot":"","sources":["../../../src/digest/render-md.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAK7C,OAAO,EAIL,KAAK,MAAM,EACX,KAAK,MAAM,EACX,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,IAAI,GAAE,eAAoB,GACzB,MAAM,CAyDR;AAED,2CAA2C;AAC3C,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CAOtF"}
@@ -0,0 +1,113 @@
1
+ import { frameFileName } from './frames.js';
2
+ import { CAPTION_LABEL, formatTime, stripNumbering, watchUrlAt } from './format.js';
3
+ import { blockMarkdown, mdInline, sectionDiagramMd } from './render-blocks.js';
4
+ import { reportLabel } from './reports.js';
5
+ import { keywordPattern, } from './schema.js';
6
+ /** 키워드 마커는 표시 글자만 남긴다. 굵게(`**`)는 마크다운 문법 그대로라 둔다 */
7
+ const plain = (text) => text.replace(keywordPattern(), (_m, _id, label) => label);
8
+ /**
9
+ * digest.json을 마크다운 한 파일로 바꾼다. LLM을 부르지 않는다.
10
+ * 기존 video-summarizer가 직접 쓰던 마크다운을 이 출력이 대신한다.
11
+ */
12
+ export function renderDigestMarkdown(digest, transcript, opts = {}) {
13
+ const { meta, segments } = transcript;
14
+ const long = meta.durationSec >= 3600;
15
+ const at = (i) => segments[i]?.startSec ?? 0;
16
+ const framesHref = opts.framesHref ?? 'frames';
17
+ const cite = mdCiter(transcript);
18
+ const out = mdHeader(transcript);
19
+ const report = opts.report ?? digest.reports[0];
20
+ // 리포트가 하나뿐이고 옵션도 기본이면 1차 출력과 같게 둔다
21
+ if (digest.reports.length > 1 || report.options.range || report.options.prompt) {
22
+ out.push(`> 리포트: ${reportLabel(report.options, long)}`);
23
+ if (report.options.prompt) {
24
+ out.push(`> 요청: ${report.options.prompt.replace(/\s+/g, ' ')}`);
25
+ }
26
+ out.push('');
27
+ }
28
+ const { lead, sections, skipped, keywords } = report;
29
+ out.push(`**${plain(lead.question)}**`, '', plain(lead.answer), '', plain(lead.takeaway), '');
30
+ const bullet = (b, depth) => {
31
+ const pad = ' '.repeat(depth);
32
+ const lines = [`${pad}- ${plain(b.text)}${cite(b.cite)}`];
33
+ for (const c of b.children ?? [])
34
+ lines.push(...bullet(c, depth + 1));
35
+ return lines;
36
+ };
37
+ const time = mdTimer(transcript);
38
+ sections.forEach((s, si) => {
39
+ out.push(`## ${si + 1}. ${stripNumbering(s.title)}`, '', plain(s.conclusion), '');
40
+ if (s.diagram)
41
+ out.push(...sectionDiagramMd(s.diagram, { cite, time }));
42
+ s.subsections.forEach((sub, ui) => {
43
+ out.push(`### ${si + 1}.${ui + 1}. ${stripNumbering(sub.title)}`, '');
44
+ const frame = frameFileName(sub.frameSec);
45
+ if (opts.frameExists?.(frame))
46
+ out.push(`![${formatTime(sub.frameSec, long)}](${framesHref}/${frame})`, '');
47
+ for (const b of sub.bullets)
48
+ out.push(...bullet(b, 0));
49
+ out.push('');
50
+ });
51
+ });
52
+ if (keywords.length > 0) {
53
+ out.push('## 키워드', '');
54
+ for (const k of keywords)
55
+ out.push(`- **${k.term}**: ${plain(k.explanation)}${cite(k.firstCite)}`);
56
+ out.push('');
57
+ }
58
+ if (skipped.length > 0) {
59
+ out.push('## 요약에서 뺀 구간', '');
60
+ for (const s of skipped) {
61
+ out.push(`- ${formatTime(at(s.from), long)}~ 문장 ${s.from + 1}-${s.to + 1}: ${plain(s.reason)}`);
62
+ }
63
+ out.push('');
64
+ }
65
+ return finish(out);
66
+ }
67
+ /** 확장 리포트 한 벌을 마크다운으로. 머리말은 요약 마크다운과 같다 */
68
+ export function renderExtensionMarkdown(ext, transcript) {
69
+ const cite = mdCiter(transcript);
70
+ const out = mdHeader(transcript);
71
+ out.push(`> 리포트: ${mdInline(ext.title)}`, '');
72
+ const time = mdTimer(transcript);
73
+ for (const b of ext.blocks)
74
+ out.push(...blockMarkdown(b, { cite, time }));
75
+ return finish(out);
76
+ }
77
+ function mdCiter(transcript) {
78
+ const { meta, segments } = transcript;
79
+ const long = meta.durationSec >= 3600;
80
+ const at = (i) => segments[i]?.startSec ?? 0;
81
+ return (c) => {
82
+ const url = watchUrlAt(meta, at(c.key));
83
+ return url ? ` [[${c.key + 1}]](${url})` : ` [${c.key + 1} ${formatTime(at(c.key), long)}]`;
84
+ };
85
+ }
86
+ function mdTimer({ meta, segments }) {
87
+ const long = meta.durationSec >= 3600;
88
+ return (c) => {
89
+ const sec = segments[c.key]?.startSec ?? 0;
90
+ const url = watchUrlAt(meta, sec);
91
+ return url ? `[${formatTime(sec, long)}](${url})` : formatTime(sec, long);
92
+ };
93
+ }
94
+ function mdHeader({ meta }) {
95
+ const long = meta.durationSec >= 3600;
96
+ const out = [`# ${meta.title}`, ''];
97
+ const facts = [
98
+ meta.channel,
99
+ meta.publishedAt,
100
+ formatTime(meta.durationSec, long),
101
+ CAPTION_LABEL[meta.captionSource],
102
+ ].filter(Boolean);
103
+ out.push(`> ${facts.join(' | ')}`);
104
+ if (meta.source === 'youtube' && meta.url)
105
+ out.push(`> ${meta.url}`);
106
+ out.push('');
107
+ return out;
108
+ }
109
+ const finish = (out) => `${out
110
+ .join('\n')
111
+ .replace(/\n{3,}/g, '\n\n')
112
+ .trimEnd()}\n`;
113
+ //# sourceMappingURL=render-md.js.map