@dzhechkov/keysarium 1.3.0 → 1.5.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 (105) hide show
  1. package/README.md +70 -10
  2. package/bin/cli.js +0 -0
  3. package/package.json +5 -3
  4. package/src/utils.js +1 -1
  5. package/templates/.claude/commands/analyst-manual.md +38 -0
  6. package/templates/.claude/commands/brain-export.md +2 -0
  7. package/templates/.claude/commands/bto-benchmark.md +302 -0
  8. package/templates/.claude/commands/casarium.md +55 -4
  9. package/templates/.claude/commands/feature-adr.md +8 -0
  10. package/templates/.claude/commands/harvest.md +28 -46
  11. package/templates/.claude/rules/trust-tiers.md +6 -0
  12. package/templates/.claude/shards/feature-adr.shard.md +64 -10
  13. package/templates/.claude/shards/phase-ai-factory.shard.md +109 -0
  14. package/templates/.claude/skills/ai-factory-mapper/README.md +90 -0
  15. package/templates/.claude/skills/ai-factory-mapper/SKILL.md +309 -0
  16. package/templates/.claude/skills/ai-factory-mapper/assets/report-template.md +123 -0
  17. package/templates/.claude/skills/ai-factory-mapper/references/catalog.md +230 -0
  18. package/templates/.claude/skills/ai-factory-mapper/references/coverage-formula.md +219 -0
  19. package/templates/.claude/skills/ai-factory-mapper/references/decomposition-checklist.md +183 -0
  20. package/templates/.claude/skills/ai-factory-mapper/references/pattern-library.md +312 -0
  21. package/templates/.claude/skills/ai-factory-mapper/scripts/build_docx_report.js +423 -0
  22. package/templates/.claude/skills/analyst-manual-full/SKILL.md +286 -0
  23. package/templates/.claude/skills/edu-site-generator/SKILL.md +254 -0
  24. package/templates/.claude/skills/edu-site-generator/examples/sample-course-structure.md +116 -0
  25. package/templates/.claude/skills/edu-site-generator/modules/00-content-analysis.md +84 -0
  26. package/templates/.claude/skills/edu-site-generator/modules/01-course-structure.md +75 -0
  27. package/templates/.claude/skills/edu-site-generator/modules/02-data-generation.md +186 -0
  28. package/templates/.claude/skills/edu-site-generator/modules/03-scaffold.md +249 -0
  29. package/templates/.claude/skills/edu-site-generator/modules/04-components.md +130 -0
  30. package/templates/.claude/skills/edu-site-generator/modules/05-gamification.md +163 -0
  31. package/templates/.claude/skills/edu-site-generator/modules/06-deploy.md +88 -0
  32. package/templates/.claude/skills/edu-site-generator/modules/07-verification.md +97 -0
  33. package/templates/.claude/skills/edu-site-generator/references/component-templates.md +436 -0
  34. package/templates/.claude/skills/edu-site-generator/references/data-schemas.md +164 -0
  35. package/templates/.claude/skills/edu-site-generator/references/exercise-catalog.md +110 -0
  36. package/templates/.claude/skills/edu-site-generator/references/tech-stack.md +69 -0
  37. package/templates/.claude/skills/feature-adr/SKILL.md +245 -59
  38. package/templates/.claude/skills/feature-adr/modules/03-adr.md +43 -0
  39. package/templates/.claude/skills/feature-adr/modules/03.5-ideation-swarm.md +180 -0
  40. package/templates/.claude/skills/feature-adr/modules/06-implementation-plan.md +56 -7
  41. package/templates/.claude/skills/feature-adr/modules/08-qe.md +87 -23
  42. package/templates/.claude/skills/feature-adr/modules/09-fleet-qe.md +305 -0
  43. package/templates/.claude/skills/feature-adr/references/agentic-qe/README.md +143 -0
  44. package/templates/.claude/skills/feature-adr/references/agentic-qe/brutal-honesty-review.md +240 -0
  45. package/templates/.claude/skills/feature-adr/references/agentic-qe/chaos-engineering-resilience.md +163 -0
  46. package/templates/.claude/skills/feature-adr/references/agentic-qe/code-goal-planner.md +446 -0
  47. package/templates/.claude/skills/feature-adr/references/agentic-qe/enterprise-integration-testing.md +111 -0
  48. package/templates/.claude/skills/feature-adr/references/agentic-qe/mutation-testing.md +236 -0
  49. package/templates/.claude/skills/feature-adr/references/agentic-qe/performance-testing.md +315 -0
  50. package/templates/.claude/skills/feature-adr/references/agentic-qe/qcsd-ideation-swarm.md +126 -0
  51. package/templates/.claude/skills/feature-adr/references/agentic-qe/qcsd-production-swarm.md +108 -0
  52. package/templates/.claude/skills/feature-adr/references/agentic-qe/qe-coverage-analysis.md +193 -0
  53. package/templates/.claude/skills/feature-adr/references/agentic-qe/qe-requirements-validation.md +254 -0
  54. package/templates/.claude/skills/feature-adr/references/agentic-qe/regression-testing.md +233 -0
  55. package/templates/.claude/skills/feature-adr/references/agentic-qe/risk-based-testing.md +212 -0
  56. package/templates/.claude/skills/feature-adr/references/agentic-qe/security-testing.md +311 -0
  57. package/templates/.claude/skills/feature-adr/references/agentic-qe/shift-left-testing.md +231 -0
  58. package/templates/.claude/skills/feature-adr/references/agentic-qe/tdd-london-chicago.md +249 -0
  59. package/templates/.claude/skills/knowledge-extractor/SKILL.md +254 -0
  60. package/templates/.claude/skills/knowledge-extractor/modules/01-extract.md +188 -0
  61. package/templates/.claude/skills/knowledge-extractor/modules/02-classify.md +99 -0
  62. package/templates/.claude/skills/knowledge-extractor/modules/03-gate.md +150 -0
  63. package/templates/.claude/skills/knowledge-extractor/modules/04-integrate.md +159 -0
  64. package/templates/.claude/skills/knowledge-extractor/references/artifact-categories.md +100 -0
  65. package/templates/.claude/skills/knowledge-extractor/references/maturity-model.md +94 -0
  66. package/templates/.claude/skills/knowledge-extractor/references/quality-gates.md +98 -0
  67. package/templates/.claude/skills/knowledge-extractor/templates/artifact-card.md +63 -0
  68. package/templates/.claude/skills/knowledge-extractor/templates/harvest-report.md +102 -0
  69. package/templates/.claude/skills/transcript-site-generator/SKILL.md +287 -0
  70. package/templates/.claude/skills/transcript-site-generator/examples/sample-text-only-site.md +123 -0
  71. package/templates/.claude/skills/transcript-site-generator/examples/sample-transcript-site.md +134 -0
  72. package/templates/.claude/skills/transcript-site-generator/modules/00-input-analysis.md +132 -0
  73. package/templates/.claude/skills/transcript-site-generator/modules/01-content-parsing.md +117 -0
  74. package/templates/.claude/skills/transcript-site-generator/modules/02-site-generation.md +293 -0
  75. package/templates/.claude/skills/transcript-site-generator/modules/03-interactivity.md +406 -0
  76. package/templates/.claude/skills/transcript-site-generator/modules/04-deploy.md +130 -0
  77. package/templates/.claude/skills/transcript-site-generator/modules/05-verification.md +155 -0
  78. package/templates/.claude/skills/transcript-site-generator/references/component-catalog.md +86 -0
  79. package/templates/.claude/skills/transcript-site-generator/references/data-schemas.md +123 -0
  80. package/templates/.claude/skills/transcript-site-generator/references/tech-stack.md +132 -0
  81. package/templates/CLAUDE.md +30 -9
  82. package/templates/docs/continuous-learning-guide.md +622 -0
  83. package/templates/docs/edu-site-generator/01-deployment-guide.md +911 -0
  84. package/templates/docs/edu-site-generator/02-admin-guide.md +901 -0
  85. package/templates/docs/edu-site-generator/03-user-guide.md +1008 -0
  86. package/templates/docs/edu-site-generator/04-infrastructure-requirements.md +489 -0
  87. package/templates/docs/edu-site-generator/05-architecture.md +902 -0
  88. package/templates/docs/edu-site-generator/06-user-flows.md +759 -0
  89. package/templates/docs/improvement-directions.md +8 -4
  90. package/templates/docs/keysarium-core/02-functionality.md +3 -1
  91. package/templates/docs/skills-bto/01-deployment-guide.md +1 -1
  92. package/templates/docs/skills-bto/02-admin-guide.md +4 -1
  93. package/templates/docs/skills-bto/03-user-guide.md +4 -3
  94. package/templates/docs/skills-bto/04-infrastructure-requirements.md +2 -1
  95. package/templates/docs/skills-bto/05-architecture.md +11 -11
  96. package/templates/docs/skills-bto/06-user-flows.md +2 -1
  97. package/templates/docs/transcript-site-generator/01-deployment-guide.md +1042 -0
  98. package/templates/docs/transcript-site-generator/02-admin-guide.md +918 -0
  99. package/templates/docs/transcript-site-generator/03-user-guide.md +925 -0
  100. package/templates/docs/transcript-site-generator/04-infrastructure-requirements.md +482 -0
  101. package/templates/docs/transcript-site-generator/05-architecture.md +1068 -0
  102. package/templates/docs/transcript-site-generator/06-user-flows.md +855 -0
  103. package/templates/lib/dream-engine.md +1 -1
  104. package/templates/lib/memory-protocol.md +6 -2
  105. package/templates/lib/reward-tracker.md +6 -3
@@ -0,0 +1,406 @@
1
+ # Step 3: Interactivity
2
+
3
+ ## Purpose
4
+
5
+ Generate `docs/static/app.js` — vanilla JavaScript implementing all interactive features: search, TOC scroll-spy, dark mode, progress bar, copy-quote, and mobile navigation.
6
+
7
+ ## Protocol
8
+
9
+ ### 1. Generate app.js
10
+
11
+ The JavaScript file follows this structure and implements all features in a single file with no dependencies.
12
+
13
+ ```javascript
14
+ // docs/static/app.js
15
+
16
+ // ═══════════════════════════════════════════════════
17
+ // 0. YOUTUBE API SETUP (must register BEFORE DOMContentLoaded)
18
+ // ═══════════════════════════════════════════════════
19
+ // The YouTube IFrame API calls window.onYouTubeIframeAPIReady as soon as
20
+ // the script loads — which may happen BEFORE DOMContentLoaded fires.
21
+ // Register the callback immediately at the top level to avoid the race.
22
+ let ytPlayer = null;
23
+ const ytIframeEl = document.getElementById('yt-player');
24
+
25
+ if (ytIframeEl) {
26
+ const tag = document.createElement('script');
27
+ tag.src = 'https://www.youtube.com/iframe_api';
28
+ document.head.appendChild(tag);
29
+
30
+ window.onYouTubeIframeAPIReady = function() {
31
+ ytPlayer = new YT.Player('yt-player', {
32
+ events: { onReady: () => console.log('YouTube player ready.') }
33
+ });
34
+ };
35
+ }
36
+
37
+ function seekTo(seconds) {
38
+ if (ytPlayer && typeof ytPlayer.seekTo === 'function') {
39
+ ytPlayer.seekTo(seconds, true);
40
+ ytPlayer.playVideo();
41
+ document.getElementById('yt-player').scrollIntoView({ behavior: 'smooth', block: 'center' });
42
+ } else if (ytIframeEl) {
43
+ // Fallback: postMessage if API not loaded yet
44
+ ytIframeEl.contentWindow.postMessage(JSON.stringify({
45
+ event: 'command', func: 'seekTo', args: [seconds, true]
46
+ }), 'https://www.youtube.com');
47
+ ytIframeEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
48
+ }
49
+ }
50
+
51
+ // Event delegation for timestamp links (data-seek) — no inline onclick needed
52
+ document.addEventListener('click', (e) => {
53
+ const seekEl = e.target.closest('[data-seek]');
54
+ if (seekEl) {
55
+ e.preventDefault();
56
+ seekTo(Number(seekEl.dataset.seek));
57
+ }
58
+ });
59
+
60
+ document.addEventListener('DOMContentLoaded', () => {
61
+
62
+ // ═══════════════════════════════════════════════════
63
+ // 1. DARK MODE
64
+ // ═══════════════════════════════════════════════════
65
+ const themeToggle = document.getElementById('theme-toggle');
66
+ const themeIcon = document.getElementById('theme-icon');
67
+ const html = document.documentElement;
68
+
69
+ // Restore saved theme or detect system preference
70
+ const savedTheme = localStorage.getItem('theme');
71
+ if (savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
72
+ html.classList.add('dark');
73
+ themeIcon.classList.replace('fa-moon', 'fa-sun');
74
+ }
75
+
76
+ themeToggle.addEventListener('click', () => {
77
+ html.classList.toggle('dark');
78
+ const isDark = html.classList.contains('dark');
79
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
80
+ themeIcon.classList.replace(isDark ? 'fa-moon' : 'fa-sun', isDark ? 'fa-sun' : 'fa-moon');
81
+ });
82
+
83
+ // ═══════════════════════════════════════════════════
84
+ // 2. MOBILE NAVIGATION
85
+ // ═══════════════════════════════════════════════════
86
+ const menuToggle = document.getElementById('menu-toggle');
87
+ const sidebar = document.getElementById('sidebar');
88
+
89
+ menuToggle.addEventListener('click', () => {
90
+ sidebar.classList.toggle('-translate-x-full');
91
+ });
92
+
93
+ // Close sidebar when clicking a TOC link on mobile
94
+ sidebar.querySelectorAll('a').forEach(link => {
95
+ link.addEventListener('click', () => {
96
+ if (window.innerWidth < 1024) {
97
+ sidebar.classList.add('-translate-x-full');
98
+ }
99
+ });
100
+ });
101
+
102
+ // Close sidebar when clicking outside on mobile
103
+ document.addEventListener('click', (e) => {
104
+ if (window.innerWidth < 1024 &&
105
+ !sidebar.contains(e.target) &&
106
+ !menuToggle.contains(e.target) &&
107
+ !sidebar.classList.contains('-translate-x-full')) {
108
+ sidebar.classList.add('-translate-x-full');
109
+ }
110
+ });
111
+
112
+ // ═══════════════════════════════════════════════════
113
+ // 3. TOC SCROLL-SPY
114
+ // ═══════════════════════════════════════════════════
115
+ const tocLinks = document.querySelectorAll('.toc-link');
116
+ const sections = document.querySelectorAll('main section[id]');
117
+
118
+ const observerOptions = {
119
+ root: null,
120
+ rootMargin: '-80px 0px -70% 0px',
121
+ threshold: 0
122
+ };
123
+
124
+ const observer = new IntersectionObserver((entries) => {
125
+ entries.forEach(entry => {
126
+ if (entry.isIntersecting) {
127
+ const id = entry.target.id;
128
+ tocLinks.forEach(link => {
129
+ link.classList.toggle('bg-primary-50', link.dataset.section === id);
130
+ link.classList.toggle('dark:bg-primary-900/30', link.dataset.section === id);
131
+ link.classList.toggle('text-primary-700', link.dataset.section === id);
132
+ link.classList.toggle('dark:text-primary-300', link.dataset.section === id);
133
+ link.classList.toggle('font-medium', link.dataset.section === id);
134
+ });
135
+ }
136
+ });
137
+ }, observerOptions);
138
+
139
+ sections.forEach(section => observer.observe(section));
140
+
141
+ // Smooth scroll for TOC links (use scrollIntoView, not hash anchors)
142
+ tocLinks.forEach(link => {
143
+ link.addEventListener('click', (e) => {
144
+ e.preventDefault();
145
+ const targetId = link.dataset.section;
146
+ const target = document.getElementById(targetId);
147
+ if (target) {
148
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
149
+ }
150
+ });
151
+ });
152
+
153
+ // ═══════════════════════════════════════════════════
154
+ // 4. READING PROGRESS BAR
155
+ // ═══════════════════════════════════════════════════
156
+ const progressBar = document.getElementById('progress-bar');
157
+ let ticking = false;
158
+
159
+ window.addEventListener('scroll', () => {
160
+ if (!ticking) {
161
+ requestAnimationFrame(() => {
162
+ const scrollTop = window.scrollY;
163
+ const docHeight = document.documentElement.scrollHeight - window.innerHeight;
164
+ const progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
165
+ progressBar.style.width = progress + '%';
166
+ ticking = false;
167
+ });
168
+ ticking = true;
169
+ }
170
+ });
171
+
172
+ // ═══════════════════════════════════════════════════
173
+ // 5. BACK TO TOP
174
+ // ═══════════════════════════════════════════════════
175
+ const backToTop = document.getElementById('back-to-top');
176
+
177
+ window.addEventListener('scroll', () => {
178
+ if (window.scrollY > 500) {
179
+ backToTop.classList.remove('hidden', 'scale-0');
180
+ backToTop.classList.add('scale-100');
181
+ } else {
182
+ backToTop.classList.remove('scale-100');
183
+ backToTop.classList.add('hidden', 'scale-0');
184
+ }
185
+ });
186
+
187
+ backToTop.addEventListener('click', () => {
188
+ window.scrollTo({ top: 0, behavior: 'smooth' });
189
+ });
190
+
191
+ // ═══════════════════════════════════════════════════
192
+ // 6. SEARCH MODAL
193
+ // ═══════════════════════════════════════════════════
194
+ const searchBtn = document.getElementById('search-btn');
195
+ const searchModal = document.getElementById('search-modal');
196
+ const searchInput = document.getElementById('search-input');
197
+ const searchResults = document.getElementById('search-results');
198
+
199
+ function openSearch() {
200
+ searchModal.classList.remove('hidden');
201
+ searchInput.value = '';
202
+ searchResults.innerHTML = '';
203
+ setTimeout(() => searchInput.focus(), 100);
204
+ }
205
+
206
+ function closeSearch() {
207
+ searchModal.classList.add('hidden');
208
+ }
209
+
210
+ searchBtn.addEventListener('click', openSearch);
211
+ searchModal.addEventListener('click', (e) => {
212
+ if (e.target === searchModal) closeSearch();
213
+ });
214
+
215
+ // Debounced search
216
+ let searchTimeout;
217
+ searchInput.addEventListener('input', () => {
218
+ clearTimeout(searchTimeout);
219
+ searchTimeout = setTimeout(() => performSearch(searchInput.value.trim()), 300);
220
+ });
221
+
222
+ function escapeHtml(text) {
223
+ const div = document.createElement('div');
224
+ div.textContent = text;
225
+ return div.innerHTML;
226
+ }
227
+
228
+ function escapeRegex(str) {
229
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
230
+ }
231
+
232
+ // Split plain text by query matches, escape each segment independently,
233
+ // then wrap matches in <mark>. This prevents highlighting inside HTML
234
+ // entities like &amp; that would be created by escapeHtml.
235
+ // Note: use case-insensitive string comparison instead of regex.test()
236
+ // to avoid the /g flag lastIndex statefulness bug.
237
+ function highlightText(text, query) {
238
+ const regex = new RegExp(`(${escapeRegex(query)})`, 'gi');
239
+ const lowerQuery = query.toLowerCase();
240
+ return text.split(regex).map(part =>
241
+ part.toLowerCase() === lowerQuery
242
+ ? `<mark class="bg-yellow-200 dark:bg-yellow-800 rounded px-0.5">${escapeHtml(part)}</mark>`
243
+ : escapeHtml(part)
244
+ ).join('');
245
+ }
246
+
247
+ function performSearch(query) {
248
+ if (query.length < 2) {
249
+ searchResults.innerHTML = '';
250
+ return;
251
+ }
252
+
253
+ const results = [];
254
+ sections.forEach(section => {
255
+ const title = section.querySelector('h2')?.textContent || '';
256
+ const content = section.querySelector('.prose')?.textContent || '';
257
+ const fullText = title + ' ' + content;
258
+ const lowerQuery = query.toLowerCase();
259
+
260
+ if (fullText.toLowerCase().includes(lowerQuery)) {
261
+ // Extract context around match
262
+ const idx = content.toLowerCase().indexOf(lowerQuery);
263
+ const start = Math.max(0, idx - 60);
264
+ const end = Math.min(content.length, idx + query.length + 60);
265
+ let snippet = (start > 0 ? '...' : '') + content.slice(start, end) + (end < content.length ? '...' : '');
266
+
267
+ results.push({ id: section.id, title, snippet, query });
268
+ }
269
+ });
270
+
271
+ if (results.length === 0) {
272
+ searchResults.innerHTML = `<p class="p-4 text-center text-gray-500">${NO_RESULTS_TEXT}</p>`;
273
+ return;
274
+ }
275
+
276
+ searchResults.innerHTML = results.map(r => {
277
+ // Highlight on PLAIN TEXT first, then escape non-match segments.
278
+ // This avoids matching across HTML entity boundaries (e.g. "&amp;").
279
+ const highlighted = highlightText(r.snippet, r.query);
280
+ return `
281
+ <a href="#${r.id}" class="block p-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" data-search-result="${r.id}">
282
+ <div class="font-medium text-sm text-gray-900 dark:text-white">${escapeHtml(r.title)}</div>
283
+ <div class="text-xs text-gray-500 dark:text-gray-400 mt-1">${highlighted}</div>
284
+ </a>
285
+ `;
286
+ }).join('');
287
+
288
+ // Click handler for search results
289
+ searchResults.querySelectorAll('[data-search-result]').forEach(el => {
290
+ el.addEventListener('click', (e) => {
291
+ e.preventDefault();
292
+ const targetId = el.dataset.searchResult;
293
+ closeSearch();
294
+ const target = document.getElementById(targetId);
295
+ if (target) {
296
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
297
+ // Flash highlight
298
+ target.classList.add('bg-primary-50', 'dark:bg-primary-900/20');
299
+ setTimeout(() => target.classList.remove('bg-primary-50', 'dark:bg-primary-900/20'), 2000);
300
+ }
301
+ });
302
+ });
303
+ }
304
+
305
+ // ═══════════════════════════════════════════════════
306
+ // 7. KEYBOARD SHORTCUTS
307
+ // ═══════════════════════════════════════════════════
308
+ document.addEventListener('keydown', (e) => {
309
+ // Ctrl/Cmd + K → open search
310
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
311
+ e.preventDefault();
312
+ openSearch();
313
+ }
314
+ // Escape → close search
315
+ if (e.key === 'Escape') {
316
+ closeSearch();
317
+ }
318
+ });
319
+
320
+ // ═══════════════════════════════════════════════════
321
+ // 8. COPY QUOTE
322
+ // ═══════════════════════════════════════════════════
323
+ // Event delegation for copy buttons (data-copy) — no inline onclick needed.
324
+ // Section IDs are read from data attributes, never interpolated into JS strings.
325
+ document.addEventListener('click', (e) => {
326
+ const copyBtn = e.target.closest('[data-copy]');
327
+ if (!copyBtn) return;
328
+ const sectionId = copyBtn.dataset.copy;
329
+ const section = document.getElementById(sectionId);
330
+ if (!section) return;
331
+ const prose = section.querySelector('.prose');
332
+ if (!prose) return;
333
+ const text = prose.innerText;
334
+ navigator.clipboard.writeText(text).then(() => {
335
+ const icon = copyBtn.querySelector('i');
336
+ if (icon) {
337
+ icon.classList.replace('fa-copy', 'fa-check');
338
+ icon.classList.add('text-green-500');
339
+ setTimeout(() => {
340
+ icon.classList.replace('fa-check', 'fa-copy');
341
+ icon.classList.remove('text-green-500');
342
+ }, 2000);
343
+ }
344
+ }).catch(() => {
345
+ // Fallback for HTTP or unsupported browsers
346
+ const textarea = document.createElement('textarea');
347
+ textarea.value = text;
348
+ textarea.style.position = 'fixed';
349
+ textarea.style.opacity = '0';
350
+ document.body.appendChild(textarea);
351
+ textarea.select();
352
+ document.execCommand('copy');
353
+ document.body.removeChild(textarea);
354
+ });
355
+ });
356
+
357
+ // ═══════════════════════════════════════════════════
358
+ // 9. YOUTUBE — handled above DOMContentLoaded (see Section 0)
359
+ // ═══════════════════════════════════════════════════
360
+
361
+ // ═══════════════════════════════════════════════════
362
+ // INIT COMPLETE
363
+ // ═══════════════════════════════════════════════════
364
+ console.log('Transcript site loaded successfully.');
365
+ console.log('Tips: Ctrl+K to search, click timestamps to seek video.');
366
+ });
367
+ ```
368
+
369
+ ### 2. Localization Variable
370
+
371
+ At the top of the search section, define the "no results" text based on detected language:
372
+
373
+ ```javascript
374
+ const NO_RESULTS_TEXT = '{no_results_text}'; // "Ничего не найдено" or "No results found"
375
+ ```
376
+
377
+ ### 3. Conditional YouTube Code
378
+
379
+ If no YouTube URL was provided in Step 0:
380
+ - Omit the `seekTo` function
381
+ - Omit the YouTube embed `<div>` from index.html
382
+ - Omit timestamp `<a>` links from section headers
383
+
384
+ ### 4. Performance Notes
385
+
386
+ - `requestAnimationFrame` prevents excessive progress bar updates during scroll
387
+ - `IntersectionObserver` for TOC is more performant than scroll-based position checking
388
+ - Search debounce at 300ms prevents UI jank on fast typing
389
+ - `backdrop-blur-sm` may be expensive on mobile — keep it on header and search modal only
390
+
391
+ ## Output
392
+
393
+ Files created:
394
+ - `docs/static/app.js` (~10-12 KB)
395
+
396
+ ## Checkpoint
397
+
398
+ ```
399
+ Step 3/5: Interactivity Complete
400
+ File: docs/static/app.js ({N} KB)
401
+ Features: search, TOC scroll-spy, dark mode, progress bar, copy-quote, mobile nav
402
+ YouTube sync: {yes | no}
403
+ Keyboard shortcuts: Ctrl+K (search), Esc (close)
404
+
405
+ "ok" to continue | "[feedback]" to adjust
406
+ ```
@@ -0,0 +1,130 @@
1
+ # Step 4: Deployment
2
+
3
+ ## Purpose
4
+
5
+ Configure GitHub Pages deployment and create project README.
6
+
7
+ ## Protocol
8
+
9
+ ### 1. Create GitHub Actions Workflow
10
+
11
+ ```yaml
12
+ # .github/workflows/deploy.yml
13
+ name: Deploy to GitHub Pages
14
+
15
+ on:
16
+ push:
17
+ branches: [main]
18
+ workflow_dispatch:
19
+
20
+ permissions:
21
+ contents: read
22
+ pages: write
23
+ id-token: write
24
+
25
+ concurrency:
26
+ group: "pages"
27
+ cancel-in-progress: true
28
+
29
+ jobs:
30
+ deploy:
31
+ runs-on: ubuntu-latest
32
+ environment:
33
+ name: github-pages
34
+ url: ${{ steps.deployment.outputs.page_url }}
35
+ steps:
36
+ - name: Checkout
37
+ uses: actions/checkout@v4
38
+
39
+ - name: Setup Pages
40
+ uses: actions/configure-pages@v4
41
+
42
+ - name: Upload artifact
43
+ uses: actions/upload-pages-artifact@v3
44
+ with:
45
+ path: 'docs'
46
+
47
+ - name: Deploy to GitHub Pages
48
+ id: deployment
49
+ uses: actions/deploy-pages@v4
50
+ ```
51
+
52
+ **Key points:**
53
+ - No build step needed — `docs/` is served directly
54
+ - `path: 'docs'` points to the static site directory
55
+ - `workflow_dispatch` allows manual deploys from GitHub UI
56
+ - Concurrency group prevents parallel deploys
57
+
58
+ ### 2. Create README.md
59
+
60
+ ```markdown
61
+ # {TITLE}
62
+
63
+ {DESCRIPTION}
64
+
65
+ ## Live Site
66
+
67
+ [{TITLE}](https://{username}.github.io/{repo}/)
68
+
69
+ ## Features
70
+
71
+ - Interactive transcript with section navigation
72
+ - Full-text search (Ctrl+K)
73
+ - Dark/Light mode
74
+ - Reading progress bar
75
+ - Mobile-friendly responsive design
76
+ - SEO optimized (Open Graph, Twitter Cards, JSON-LD)
77
+ {if YouTube:}
78
+ - YouTube video embed with timestamp navigation
79
+ {/if}
80
+
81
+ ## Source
82
+
83
+ {if YouTube:}
84
+ Original video: [{YOUTUBE_TITLE}]({YOUTUBE_URL})
85
+ {/if}
86
+
87
+ Transcript by {AUTHOR}
88
+
89
+ ## Development
90
+
91
+ This is a static site — no build step required.
92
+ Simply edit `docs/index.html` and push to `main`.
93
+
94
+ ## License
95
+
96
+ MIT
97
+ ```
98
+
99
+ ### 3. Repository Settings Reminder
100
+
101
+ After pushing, remind the user:
102
+ ```
103
+ To enable GitHub Pages:
104
+ 1. Go to repository Settings → Pages
105
+ 2. Source: "GitHub Actions" (recommended) or "Deploy from a branch" (main, /docs)
106
+ 3. The workflow will auto-deploy on next push to main
107
+ ```
108
+
109
+ ### 4. Alternative: Branch-based Deploy
110
+
111
+ If the user prefers branch-based deployment instead of Actions:
112
+ - Set GitHub Pages source to: branch `main`, folder `/docs`
113
+ - The `.github/workflows/deploy.yml` is then optional but still recommended for caching
114
+
115
+ ## Output
116
+
117
+ Files created:
118
+ - `.github/workflows/deploy.yml`
119
+ - `README.md`
120
+
121
+ ## Checkpoint
122
+
123
+ ```
124
+ Step 4/5: Deployment Complete
125
+ Files: .github/workflows/deploy.yml, README.md
126
+ Deploy method: GitHub Actions (docs/ folder, zero build)
127
+ Live URL: https://{username}.github.io/{repo}/
128
+
129
+ "ok" to continue | "[feedback]" to adjust
130
+ ```
@@ -0,0 +1,155 @@
1
+ # Step 5: Verification
2
+
3
+ ## Purpose
4
+
5
+ Validate the generated static site for correctness, completeness, and quality.
6
+
7
+ ## Protocol
8
+
9
+ ### 1. File Existence Checks
10
+
11
+ | File | Required | Check |
12
+ |------|----------|-------|
13
+ | `docs/index.html` | Yes | Exists, > 10 KB |
14
+ | `docs/static/app.js` | Yes | Exists, > 5 KB |
15
+ | `docs/static/style.css` | Yes | Exists |
16
+ | `.github/workflows/deploy.yml` | Yes | Exists |
17
+ | `README.md` | Yes | Exists |
18
+
19
+ ### 2. HTML Structure Validation
20
+
21
+ Check `docs/index.html` for:
22
+
23
+ - [ ] `<!DOCTYPE html>` declaration present
24
+ - [ ] `<html lang="...">` with correct language attribute
25
+ - [ ] `<meta charset="UTF-8">` present
26
+ - [ ] `<meta name="viewport">` present (mobile responsive)
27
+ - [ ] `<title>` tag not empty
28
+ - [ ] `<meta name="description">` present and not empty
29
+ - [ ] Open Graph meta tags present (`og:title`, `og:description`, `og:type`)
30
+ - [ ] Twitter Card meta tags present (`twitter:card`, `twitter:title`)
31
+ - [ ] JSON-LD `<script type="application/ld+json">` present with valid structure
32
+ - [ ] Tailwind CSS CDN script tag present
33
+ - [ ] Font Awesome CDN link present
34
+ - [ ] `<script src="static/app.js">` present at end of body
35
+ - [ ] `<link rel="stylesheet" href="static/style.css">` present in head
36
+
37
+ ### 3. Content Validation
38
+
39
+ - [ ] All sections have unique `id` attributes
40
+ - [ ] Section count matches parsed section count from Step 1
41
+ - [ ] TOC links match section IDs (one `<a data-section="...">` per section)
42
+ - [ ] No empty sections (each section has text content)
43
+ - [ ] No unescaped HTML entities in transcript text
44
+ - [ ] No `{placeholder}` or `[TODO]` text remaining
45
+
46
+ ### 4. Interactivity Validation
47
+
48
+ Check `docs/static/app.js` for:
49
+
50
+ - [ ] `DOMContentLoaded` event listener wraps all code
51
+ - [ ] Theme toggle references `#theme-toggle` and `#theme-icon`
52
+ - [ ] Search references `#search-btn`, `#search-modal`, `#search-input`, `#search-results`
53
+ - [ ] TOC scroll-spy uses `IntersectionObserver`
54
+ - [ ] Progress bar references `#progress-bar`
55
+ - [ ] Back-to-top references `#back-to-top`
56
+ - [ ] Event delegation for `[data-copy]` elements (copy via data attributes, no inline onclick)
57
+ - [ ] `escapeHtml` function present (XSS prevention)
58
+ - [ ] `escapeRegex` function present (search regex injection prevention)
59
+ - [ ] `highlightText` function uses string comparison (not regex.test with /g flag)
60
+ - [ ] If YouTube: `seekTo` function defined at top level (before DOMContentLoaded)
61
+ - [ ] If YouTube: event delegation for `[data-seek]` elements (no inline onclick)
62
+ - [ ] If YouTube: `onYouTubeIframeAPIReady` callback defined
63
+ - [ ] No syntax errors (can validate with `node --check docs/static/app.js`)
64
+
65
+ ### 4b. Accessibility Validation
66
+
67
+ - [ ] Skip-nav link (`<a href="#main-content">`) present before header
68
+ - [ ] `<header>` has `role="banner"`
69
+ - [ ] `<main>` has `role="main"` and `id="main-content"`
70
+ - [ ] `<nav>` sidebar has `aria-label`
71
+ - [ ] Search modal has `role="dialog"` and `aria-modal="true"`
72
+ - [ ] Icon-only buttons have accessible text (`aria-label` or visible label)
73
+
74
+ ### 5. Responsive Design Checks
75
+
76
+ - [ ] Mobile menu button has `lg:hidden` class
77
+ - [ ] Sidebar has `lg:translate-x-0` and `-translate-x-full` for toggle
78
+ - [ ] Content area uses `flex-1 min-w-0` to prevent overflow
79
+ - [ ] YouTube embed uses `aspect-video` for responsive sizing
80
+ - [ ] Touch targets are at least `p-2` (minimum 44px tap area)
81
+ - [ ] Text is readable without horizontal scrolling on 320px width
82
+
83
+ ### 6. Dark Mode Checks
84
+
85
+ - [ ] `<html>` has `class` attribute (not `data-theme`) for Tailwind dark mode
86
+ - [ ] All `bg-white` have corresponding `dark:bg-gray-900` (or similar)
87
+ - [ ] All `text-gray-900` have corresponding `dark:text-gray-100` (or similar)
88
+ - [ ] Search modal has dark variants
89
+ - [ ] TOC active state has dark variants
90
+ - [ ] Tailwind config has `darkMode: 'class'`
91
+
92
+ ### 7. SEO Checks
93
+
94
+ - [ ] `og:title` matches `<title>`
95
+ - [ ] `og:description` matches `<meta name="description">`
96
+ - [ ] JSON-LD `headline` matches title
97
+ - [ ] JSON-LD `wordCount` is a number > 0
98
+ - [ ] If YouTube: `og:image` points to valid YouTube thumbnail URL
99
+ - [ ] If YouTube: VideoObject JSON-LD includes `embedUrl`
100
+
101
+ ### 8. Print Checks
102
+
103
+ - [ ] `style.css` has `@media print` rules
104
+ - [ ] `.no-print` class hides navigation, progress bar, buttons
105
+ - [ ] Print colors are black on white (overrides dark mode)
106
+ - [ ] Section breaks avoid splitting mid-content
107
+
108
+ ### 9. Syntax Validation
109
+
110
+ ```bash
111
+ # Validate JS syntax
112
+ node --check docs/static/app.js
113
+
114
+ # Check HTML is well-formed (basic check)
115
+ grep -c '</html>' docs/index.html # Should be 1
116
+ ```
117
+
118
+ ### 10. Browser Preview (Optional)
119
+
120
+ If a local server is available:
121
+ ```bash
122
+ cd docs && python3 -m http.server 8080 &
123
+ echo "Preview at http://localhost:8080"
124
+ ```
125
+
126
+ ## Error Recovery
127
+
128
+ | Error | Fix |
129
+ |-------|-----|
130
+ | Missing section IDs | Regenerate sections with `id="section-{slug}"` |
131
+ | TOC/section mismatch | Rebuild TOC links from actual section elements |
132
+ | JS syntax error | Fix the specific error, re-validate |
133
+ | Missing dark mode classes | Add `dark:` variant for every light class |
134
+ | Broken YouTube embed | Verify VIDEO_ID format (11 chars), check URL encoding |
135
+ | HTML entities in content | Run through `escapeHtml()` |
136
+
137
+ ## Output
138
+
139
+ Verification report:
140
+ ```
141
+ Step 5/5: Verification Complete
142
+
143
+ File checks: {N}/{N} passed
144
+ HTML structure: {N}/{N} passed
145
+ Content: {N}/{N} passed
146
+ Interactivity: {N}/{N} passed
147
+ Responsive: {N}/{N} passed
148
+ Dark mode: {N}/{N} passed
149
+ SEO: {N}/{N} passed
150
+ Print: {N}/{N} passed
151
+
152
+ Site ready for deployment!
153
+ Push to main to trigger GitHub Pages deploy.
154
+ Live URL: https://{username}.github.io/{repo}/
155
+ ```