octocode-cli 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.
- package/README.md +129 -28
- package/out/chunks/chunk-7476PETK.js +309 -0
- package/out/chunks/chunk-CVNNNSMQ.js +26 -0
- package/out/chunks/chunk-OQBJTZWK.js +60 -0
- package/out/chunks/chunk-UCZCF3BQ.js +9 -0
- package/out/chunks/command-help-specs-JZXVSLZ5.js +8 -0
- package/out/chunks/commands-XBFPLHSQ.js +8 -0
- package/out/chunks/help-P7TCOYAJ.js +10 -0
- package/out/chunks/main-help-ULF5PAQY.js +10 -0
- package/out/chunks/prompts-5E6VKRX5.js +8 -0
- package/out/chunks/spinner-URV2OX6O.js +8 -0
- package/out/chunks/tool-command-M6VA7P2F.js +8 -0
- package/out/octocode-cli.js +1 -1
- package/package.json +5 -3
- package/skills/README.md +60 -58
- package/skills/agentic-flow-best-practices/SKILL.md +280 -0
- package/skills/agentic-flow-best-practices/references/agent-collaboration-patterns.md +75 -0
- package/skills/agentic-flow-best-practices/references/pr-review-agent-example.md +47 -0
- package/skills/agentic-flow-best-practices/references/resources.md +112 -0
- package/skills/octocode-brainstorming/.env.example +11 -0
- package/skills/octocode-brainstorming/SKILL.md +262 -0
- package/skills/octocode-brainstorming/scripts/tavily-search.mjs +138 -0
- package/skills/octocode-chrome-devtools/README.md +541 -0
- package/skills/octocode-chrome-devtools/SKILL.md +197 -0
- package/skills/octocode-chrome-devtools/agents/openai.yaml +7 -0
- package/skills/octocode-chrome-devtools/references/CDP_AGENT_REFERENCE.md +401 -0
- package/skills/octocode-chrome-devtools/references/CHROME_FLAGS.md +234 -0
- package/skills/octocode-chrome-devtools/references/INTENTS.md +108 -0
- package/skills/octocode-chrome-devtools/references/INTENTS_AUTH.md +179 -0
- package/skills/octocode-chrome-devtools/references/INTENTS_AUTOMATION.md +214 -0
- package/skills/octocode-chrome-devtools/references/INTENTS_DEBUG.md +329 -0
- package/skills/octocode-chrome-devtools/references/INTENTS_ENVIRONMENT.md +237 -0
- package/skills/octocode-chrome-devtools/references/INTENTS_INSPECT.md +263 -0
- package/skills/octocode-chrome-devtools/references/INTENTS_STORAGE_CONSENT.md +214 -0
- package/skills/octocode-chrome-devtools/references/RECOVERY.md +39 -0
- package/skills/octocode-chrome-devtools/references/SCRIPT_PATTERNS.md +43 -0
- package/skills/octocode-chrome-devtools/references/SCRIPT_PATTERNS_ASYNC_WORKERS.md +345 -0
- package/skills/octocode-chrome-devtools/references/SCRIPT_PATTERNS_BROWSER.md +403 -0
- package/skills/octocode-chrome-devtools/references/SCRIPT_PATTERNS_OBSERVE.md +275 -0
- package/skills/octocode-chrome-devtools/references/SCRIPT_PATTERNS_SPECIAL.md +18 -0
- package/skills/octocode-chrome-devtools/scripts/cdp-runner.mjs +503 -0
- package/skills/octocode-chrome-devtools/scripts/cdp-sandbox.mjs +123 -0
- package/skills/octocode-chrome-devtools/scripts/cdp-template.mjs +81 -0
- package/skills/octocode-chrome-devtools/scripts/octocode-chrome-devtools.vpn.example.json +8 -0
- package/skills/octocode-chrome-devtools/scripts/open-browser.mjs +362 -0
- package/skills/octocode-chrome-devtools/scripts/sourcemap-resolver.mjs +193 -0
- package/skills/octocode-chrome-devtools/scripts/undercover.mjs +226 -0
- package/skills/octocode-design/README.md +2 -2
- package/skills/octocode-documentation-writer/README.md +1 -1
- package/skills/octocode-engineer/README.md +1 -1
- package/skills/octocode-engineer/SKILL.md +137 -306
- package/skills/octocode-engineer/references/cli-reference.md +13 -0
- package/skills/octocode-engineer/references/output-files.md +3 -3
- package/skills/octocode-engineer/scripts/run.js +146 -146
- package/skills/octocode-engineer/src/pipeline/main.ts +1 -17
- package/skills/octocode-engineer/src/pipeline/progress.ts +4 -0
- package/skills/octocode-engineer/src/reporting/summary-md.test.ts +48 -0
- package/skills/octocode-engineer/src/reporting/summary-md.ts +43 -2
- package/skills/octocode-install/SKILL.md +1 -1
- package/skills/octocode-pull-request-reviewer/README.md +5 -5
- package/skills/octocode-pull-request-reviewer/SKILL.md +1 -1
- package/skills/octocode-research/AGENTS.md +1 -1
- package/skills/octocode-research/README.md +2 -2
- package/skills/octocode-research/docs/ARCHITECTURE.md +1 -1
- package/skills/octocode-research/scripts/server.js +184 -239
- package/skills/octocode-research/src/routes/github.ts +4 -21
- package/skills/octocode-research/src/routes/local.ts +4 -21
- package/skills/octocode-research/src/utils/fileContentTransform.ts +44 -0
- package/skills/octocode-search-skill/SKILL.md +337 -0
- package/skills/octocode-search-skill/references/agent-skills-guide.md +177 -0
- package/skills/octocode-search-skill/references/discovery-surfaces.md +162 -0
- package/skills/octocode-search-skill/references/fetch-and-create-locally.md +57 -0
- package/skills/octocode-search-skill/references/install-reference.md +130 -0
- package/skills/octocode-search-skill/references/references-template.md +27 -0
- package/skills/octocode-search-skill/references/references.md +62 -0
- package/skills/octocode-slides/README.md +307 -0
- package/skills/octocode-slides/SKILL.md +410 -0
- package/skills/octocode-slides/references/01-brief.md +156 -0
- package/skills/octocode-slides/references/02-research.md +149 -0
- package/skills/octocode-slides/references/03-outline.md +172 -0
- package/skills/octocode-slides/references/04-design.md +301 -0
- package/skills/octocode-slides/references/05-implementation.md +213 -0
- package/skills/octocode-slides/references/06-review.md +258 -0
- package/skills/octocode-slides/references/animation.md +281 -0
- package/skills/octocode-slides/references/design-system.md +316 -0
- package/skills/octocode-slides/references/html-templates.md +673 -0
- package/skills/octocode-slides/references/image-generation.md +448 -0
- package/skills/octocode-slides/references/resources.md +840 -0
- package/skills/octocode-slides/references/slide-rules.md +541 -0
- package/skills/octocode-slides/references/wireframes.md +727 -0
- package/skills/octocode-slides/scripts/animation.js +182 -0
- package/skills/octocode-slides/scripts/base.css +353 -0
- package/skills/octocode-slides/scripts/base.html +655 -0
- package/skills/octocode-slides/scripts/generate_image.py +221 -0
- package/skills/octocode-slides/scripts/navbridge.js +79 -0
- package/skills/octocode-slides/scripts/presenter.js +316 -0
- package/skills/octocode-slides/scripts/slide.html +248 -0
- package/skills/octocode-stats/SKILL.md +73 -0
- package/skills/octocode-stats/assets/template.html +1332 -0
- package/skills/octocode-stats/scripts/build_dashboard.mjs +407 -0
- package/out/chunks/chunk-LH4AZJPA.js +0 -389
- package/out/chunks/command-help-specs-CQ3RBLP6.js +0 -8
- package/out/chunks/commands-M3QTWKWE.js +0 -51
- package/out/chunks/help-XPXP46ZT.js +0 -10
- package/out/chunks/main-help-HXFAFHPG.js +0 -10
- package/out/chunks/tool-command-VHFLPIHY.js +0 -8
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<!--
|
|
3
|
+
╔══════════════════════════════════════════════════════════════════════════╗
|
|
4
|
+
║ OCTOCODE SLIDES — NAVIGATION CONTROLLER ║
|
|
5
|
+
║ Generated from: skills/octocode-slides/scripts/base.html ║
|
|
6
|
+
║ ║
|
|
7
|
+
║ LLM INSTRUCTIONS (remove all <!-- LLM: --> comments in final output): ║
|
|
8
|
+
║ 1. Replace every <!-- LLM: ... --> placeholder with actual values ║
|
|
9
|
+
║ 2. Fill the const slides = [...] array — one { path, hidden, name } ║
|
|
10
|
+
║ object per slide, in the order you want them shown. ║
|
|
11
|
+
║ • path – path to the slide HTML relative to index.html ║
|
|
12
|
+
║ • name – unique slug used in the URL hash (e.g. 'my-slide') ║
|
|
13
|
+
║ Do NOT use numbers — order is controlled by the array. ║
|
|
14
|
+
║ • hidden – true = skip during playback + hide from overview grid ║
|
|
15
|
+
║ 3. Set the <title> to the deck title ║
|
|
16
|
+
║ 4. Adjust #progress background to match the theme accent color ║
|
|
17
|
+
║ 5. Make sure every slide HTML file includes js/navbridge.js ║
|
|
18
|
+
║ (see scripts/slide.html — the navbridge script tag is already ║
|
|
19
|
+
║ included in the template) ║
|
|
20
|
+
║ ║
|
|
21
|
+
║ KEYBOARD SHORTCUTS: ║
|
|
22
|
+
║ → / ↓ / Space next slide ║
|
|
23
|
+
║ ← / ↑ previous slide ║
|
|
24
|
+
║ Home / End first / last slide ║
|
|
25
|
+
║ F toggle fullscreen ║
|
|
26
|
+
║ G toggle overview grid (thumbnail navigator) ║
|
|
27
|
+
║ Escape close overview ║
|
|
28
|
+
║ B / W black / white screen (press again to dismiss) ║
|
|
29
|
+
║ Scroll wheel next / previous slide ║
|
|
30
|
+
╚══════════════════════════════════════════════════════════════════════════╝
|
|
31
|
+
-->
|
|
32
|
+
<html lang="en">
|
|
33
|
+
<head>
|
|
34
|
+
<meta charset="UTF-8">
|
|
35
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
36
|
+
|
|
37
|
+
<!-- LLM: Set this to the deck title (e.g., "Octocode MCP — 20-min Tech Talk") -->
|
|
38
|
+
<title><!-- LLM: Deck Title --></title>
|
|
39
|
+
|
|
40
|
+
<style>
|
|
41
|
+
/* ── Reset ─────────────────────────────────────────── */
|
|
42
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
43
|
+
|
|
44
|
+
/* ── Full-window letterbox ──────────────────────────── */
|
|
45
|
+
html, body {
|
|
46
|
+
width: 100%; height: 100%;
|
|
47
|
+
/* LLM: Change to theme --bg value (e.g., #0D1117 for Terminal, oklch(16% 0.02 290) for Midnight) */
|
|
48
|
+
background: #000;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
display: grid;
|
|
51
|
+
place-items: center;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ── 1280×720 stage ─────────────────────────────────── */
|
|
55
|
+
/* JS scales this to fill the window while keeping 16:9. */
|
|
56
|
+
#stage {
|
|
57
|
+
width: 1280px;
|
|
58
|
+
height: 720px;
|
|
59
|
+
position: relative;
|
|
60
|
+
transform-origin: center center;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ── Slide cell wrapper ─────────────────────────────── */
|
|
64
|
+
/* All slide cells stack at inset:0. The topmost (last in DOM) cell would
|
|
65
|
+
otherwise swallow every mouse-down — blocking text selection inside the
|
|
66
|
+
active iframe — so inactive cells must not accept pointer events.
|
|
67
|
+
Pair this with .slide-frame[data-active] below; both rules MUST flip
|
|
68
|
+
in lockstep on slide change (`go()` toggles `is-active` + `data-active`). */
|
|
69
|
+
.slide-cell {
|
|
70
|
+
position: absolute;
|
|
71
|
+
inset: 0;
|
|
72
|
+
width: 100%;
|
|
73
|
+
height: 100%;
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
}
|
|
76
|
+
.slide-cell.is-active { pointer-events: auto; }
|
|
77
|
+
|
|
78
|
+
/* ── Slide iframes ──────────────────────────────────── */
|
|
79
|
+
/* Inactive iframes are invisible AND non-interactive. Without
|
|
80
|
+
pointer-events:none they would still capture clicks (preventing the
|
|
81
|
+
active iframe behind them from receiving selection or click events). */
|
|
82
|
+
.slide-frame {
|
|
83
|
+
position: absolute;
|
|
84
|
+
inset: 0;
|
|
85
|
+
width: 100%; height: 100%;
|
|
86
|
+
border: none;
|
|
87
|
+
opacity: 0;
|
|
88
|
+
transition: opacity 150ms ease;
|
|
89
|
+
pointer-events: none;
|
|
90
|
+
}
|
|
91
|
+
/* Active frame is visible and accepts pointer events (enables navbridge,
|
|
92
|
+
text selection, copy/paste, and link clicks inside the slide). */
|
|
93
|
+
.slide-frame[data-active] { opacity: 1; pointer-events: auto; }
|
|
94
|
+
|
|
95
|
+
/* ── Progress bar ───────────────────────────────────── */
|
|
96
|
+
#progress {
|
|
97
|
+
position: fixed;
|
|
98
|
+
bottom: 0; left: 0;
|
|
99
|
+
height: 3px;
|
|
100
|
+
/* LLM: Set to theme --accent (e.g., #39D353 for Terminal, #C9B37A for Midnight) */
|
|
101
|
+
background: rgba(255,255,255,0.5);
|
|
102
|
+
transition: width 200ms ease;
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
z-index: 10;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* ── Slide counter ──────────────────────────────────── */
|
|
108
|
+
#counter {
|
|
109
|
+
position: fixed;
|
|
110
|
+
bottom: 10px; right: 18px;
|
|
111
|
+
font: 11px/1 system-ui, sans-serif;
|
|
112
|
+
color: rgba(255,255,255,0.3);
|
|
113
|
+
letter-spacing: 0.06em;
|
|
114
|
+
pointer-events: none;
|
|
115
|
+
z-index: 10;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* ── HUD (keyboard hint pill) ───────────────────────── */
|
|
119
|
+
/* Fades in on activity, fades out after 1.6 s of idle. */
|
|
120
|
+
#hud {
|
|
121
|
+
position: fixed; right: 18px; top: 18px;
|
|
122
|
+
display: flex; align-items: center; gap: 10px;
|
|
123
|
+
font: 11px/1 system-ui, sans-serif; letter-spacing: 0.06em;
|
|
124
|
+
color: rgba(255,255,255,0.4);
|
|
125
|
+
background: rgba(0,0,0,0.55); border: 1px solid rgba(255,255,255,0.1);
|
|
126
|
+
padding: 6px 12px; border-radius: 999px;
|
|
127
|
+
backdrop-filter: blur(4px);
|
|
128
|
+
opacity: 0; transition: opacity 0.25s ease; pointer-events: none;
|
|
129
|
+
z-index: 50;
|
|
130
|
+
}
|
|
131
|
+
#hud.show { opacity: 1; }
|
|
132
|
+
.hud-sep { color: rgba(255,255,255,0.15); }
|
|
133
|
+
#hud kbd {
|
|
134
|
+
font: inherit; background: rgba(255,255,255,0.08);
|
|
135
|
+
border: 1px solid rgba(255,255,255,0.15); border-radius: 3px;
|
|
136
|
+
padding: 1px 5px; font-size: 10px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* ════════════════════════════════════════════════════
|
|
140
|
+
OVERVIEW MODE (body.overview)
|
|
141
|
+
Press G to open, press G or Escape to close.
|
|
142
|
+
Shows all slides as scaled thumbnails.
|
|
143
|
+
Click any thumbnail to jump to that slide.
|
|
144
|
+
════════════════════════════════════════════════════ */
|
|
145
|
+
|
|
146
|
+
body.overview {
|
|
147
|
+
overflow-y: auto;
|
|
148
|
+
background: #0e0e0e;
|
|
149
|
+
display: block;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
body.overview #stage {
|
|
153
|
+
width: 100%;
|
|
154
|
+
height: auto;
|
|
155
|
+
min-height: 100vh;
|
|
156
|
+
position: static;
|
|
157
|
+
transform: none !important;
|
|
158
|
+
display: grid;
|
|
159
|
+
grid-template-columns: repeat(auto-fill, 256px);
|
|
160
|
+
gap: 1.25rem;
|
|
161
|
+
padding: 2.5rem 2rem 4rem;
|
|
162
|
+
align-content: start;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
body.overview .slide-cell {
|
|
166
|
+
position: relative;
|
|
167
|
+
width: 256px;
|
|
168
|
+
height: 144px;
|
|
169
|
+
overflow: hidden;
|
|
170
|
+
border-radius: 6px;
|
|
171
|
+
border: 2px solid rgba(255,255,255,0.08);
|
|
172
|
+
cursor: pointer;
|
|
173
|
+
transition: border-color 120ms ease, box-shadow 120ms ease;
|
|
174
|
+
}
|
|
175
|
+
body.overview .slide-cell:hover {
|
|
176
|
+
border-color: rgba(255,255,255,0.4);
|
|
177
|
+
}
|
|
178
|
+
body.overview .slide-cell.is-active {
|
|
179
|
+
border-color: rgba(255,255,255,0.75);
|
|
180
|
+
box-shadow: 0 0 0 1px rgba(255,255,255,0.3);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* Scale 1280×720 iframe down to 256×144 (factor 0.2) */
|
|
184
|
+
body.overview .slide-frame {
|
|
185
|
+
position: absolute;
|
|
186
|
+
top: 0; left: 0;
|
|
187
|
+
width: 1280px !important;
|
|
188
|
+
height: 720px !important;
|
|
189
|
+
transform: scale(0.2);
|
|
190
|
+
transform-origin: top left;
|
|
191
|
+
opacity: 1;
|
|
192
|
+
pointer-events: none;
|
|
193
|
+
transition: none;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Slide number badge — only visible in overview */
|
|
197
|
+
.slide-cell .slide-num {
|
|
198
|
+
display: none;
|
|
199
|
+
position: absolute;
|
|
200
|
+
bottom: 4px; right: 6px;
|
|
201
|
+
font: 700 9px/1 system-ui, sans-serif;
|
|
202
|
+
color: rgba(255,255,255,0.45);
|
|
203
|
+
letter-spacing: 0.04em;
|
|
204
|
+
z-index: 10;
|
|
205
|
+
pointer-events: none;
|
|
206
|
+
}
|
|
207
|
+
body.overview .slide-cell .slide-num { display: block; }
|
|
208
|
+
|
|
209
|
+
/* Slide name label in overview — shows the slug under each thumbnail */
|
|
210
|
+
.slide-cell .slide-label {
|
|
211
|
+
display: none;
|
|
212
|
+
position: absolute;
|
|
213
|
+
bottom: 0; left: 0; right: 0;
|
|
214
|
+
padding: 4px 6px 5px;
|
|
215
|
+
background: rgba(0,0,0,0.6);
|
|
216
|
+
font: 9px/1.3 system-ui, sans-serif;
|
|
217
|
+
color: rgba(255,255,255,0.55);
|
|
218
|
+
letter-spacing: 0.02em;
|
|
219
|
+
white-space: nowrap;
|
|
220
|
+
overflow: hidden;
|
|
221
|
+
text-overflow: ellipsis;
|
|
222
|
+
pointer-events: none;
|
|
223
|
+
z-index: 11;
|
|
224
|
+
}
|
|
225
|
+
body.overview .slide-cell .slide-label { display: block; }
|
|
226
|
+
|
|
227
|
+
/* Overview hint strip at top */
|
|
228
|
+
#overview-hint {
|
|
229
|
+
display: none;
|
|
230
|
+
position: fixed;
|
|
231
|
+
top: 0; left: 0; right: 0;
|
|
232
|
+
padding: 0.5rem 1.5rem;
|
|
233
|
+
font: 10px/1.5 system-ui, sans-serif;
|
|
234
|
+
color: rgba(255,255,255,0.25);
|
|
235
|
+
letter-spacing: 0.08em;
|
|
236
|
+
text-align: right;
|
|
237
|
+
pointer-events: none;
|
|
238
|
+
z-index: 200;
|
|
239
|
+
}
|
|
240
|
+
body.overview #overview-hint { display: block; }
|
|
241
|
+
|
|
242
|
+
/* Progress bar, counter, and HUD hidden in overview */
|
|
243
|
+
body.overview #progress,
|
|
244
|
+
body.overview #counter,
|
|
245
|
+
body.overview #hud { display: none; }
|
|
246
|
+
|
|
247
|
+
/* ── Blackout overlay (B = black screen, W = white screen) ───────────── */
|
|
248
|
+
#blackout {
|
|
249
|
+
position: fixed;
|
|
250
|
+
inset: 0;
|
|
251
|
+
z-index: 80;
|
|
252
|
+
pointer-events: none;
|
|
253
|
+
opacity: 0;
|
|
254
|
+
transition: opacity 200ms ease;
|
|
255
|
+
}
|
|
256
|
+
#blackout.is-black { background: #000; opacity: 1; pointer-events: auto; }
|
|
257
|
+
#blackout.is-white { background: #fff; opacity: 1; pointer-events: auto; }
|
|
258
|
+
</style>
|
|
259
|
+
</head>
|
|
260
|
+
<body>
|
|
261
|
+
|
|
262
|
+
<!-- ── Stage ────────────────────────────────────────────────────────────── -->
|
|
263
|
+
<!-- All slide cells (each wrapping an iframe) are injected here by JS. -->
|
|
264
|
+
<div id="stage">
|
|
265
|
+
<!-- LLM: DO NOT add content here manually. JS builds slide cells from slides[]. -->
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<!-- ── UI chrome ─────────────────────────────────────────────────────────── -->
|
|
269
|
+
<div id="progress"></div>
|
|
270
|
+
<div id="counter"></div>
|
|
271
|
+
|
|
272
|
+
<!-- HUD: keyboard hint pill — fades in on activity, fades out after 1.6 s -->
|
|
273
|
+
<div id="hud">
|
|
274
|
+
<span id="hud-count">— / —</span>
|
|
275
|
+
<span class="hud-sep">/</span>
|
|
276
|
+
<span><kbd>←</kbd> <kbd>→</kbd> nav</span>
|
|
277
|
+
<span class="hud-sep">/</span>
|
|
278
|
+
<kbd>G</kbd> grid
|
|
279
|
+
<span class="hud-sep">/</span>
|
|
280
|
+
<kbd>F</kbd> full
|
|
281
|
+
<span class="hud-sep">/</span>
|
|
282
|
+
<kbd>P</kbd> notes
|
|
283
|
+
<span class="hud-sep">/</span>
|
|
284
|
+
<kbd>B</kbd><kbd>W</kbd> blank
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<div id="overview-hint">Overview — click a slide to jump · G or Esc to close</div>
|
|
288
|
+
|
|
289
|
+
<!-- ── Blackout overlay ───────────────────────────────────────────────────── -->
|
|
290
|
+
<!-- B = black screen · W = white screen · press again to dismiss -->
|
|
291
|
+
<div id="blackout" aria-hidden="true"></div>
|
|
292
|
+
|
|
293
|
+
<script src="js/presenter.js"></script>
|
|
294
|
+
<script>
|
|
295
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
296
|
+
// SLIDE MANIFEST
|
|
297
|
+
//
|
|
298
|
+
// Each entry: { path, hidden, name }
|
|
299
|
+
// path – slide HTML path relative to this index.html (forward slashes)
|
|
300
|
+
// name – unique slug for the URL hash (e.g. 'problem' → #problem)
|
|
301
|
+
// Do NOT use numbers — playback order is controlled by this array.
|
|
302
|
+
// hidden – true = skip during playback AND hide from overview grid
|
|
303
|
+
//
|
|
304
|
+
// Filename numbers (01-, 02-…) do NOT drive playback order.
|
|
305
|
+
// This array is the single source of truth for slide order.
|
|
306
|
+
// LLM: Replace the placeholder entries below with the actual slide list.
|
|
307
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
308
|
+
const slides = [
|
|
309
|
+
// LLM: List every slide as { path, hidden, name }, in presentation order.
|
|
310
|
+
// { path: 'slides/title.html', hidden: false, name: 'title' },
|
|
311
|
+
// { path: 'slides/problem.html', hidden: false, name: 'problem' },
|
|
312
|
+
// { path: 'slides/solution.html',hidden: false, name: 'solution' },
|
|
313
|
+
// { path: 'slides/closing.html', hidden: false, name: 'closing' },
|
|
314
|
+
];
|
|
315
|
+
|
|
316
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
317
|
+
// DERIVED STATE — do not edit below this line
|
|
318
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
319
|
+
|
|
320
|
+
/** Visible playlist — what nav, grid, HUD, and URL hash all index into. */
|
|
321
|
+
const playable = slides.filter(s => !s.hidden);
|
|
322
|
+
const total = playable.length;
|
|
323
|
+
const nameToIdx = Object.fromEntries(playable.map((s, i) => [s.name, i]));
|
|
324
|
+
|
|
325
|
+
let current = 0;
|
|
326
|
+
let inOverview = false;
|
|
327
|
+
let presenter = null;
|
|
328
|
+
|
|
329
|
+
const stage = document.getElementById('stage');
|
|
330
|
+
const bar = document.getElementById('progress');
|
|
331
|
+
const counter = document.getElementById('counter');
|
|
332
|
+
const hud = document.getElementById('hud');
|
|
333
|
+
const hudCount = document.getElementById('hud-count');
|
|
334
|
+
const blackoutEl = document.getElementById('blackout');
|
|
335
|
+
let blackoutMode = null; // 'black' | 'white' | null
|
|
336
|
+
|
|
337
|
+
function toggleBlackout(mode) {
|
|
338
|
+
if (blackoutMode === mode) {
|
|
339
|
+
blackoutMode = null;
|
|
340
|
+
blackoutEl.className = '';
|
|
341
|
+
} else {
|
|
342
|
+
blackoutMode = mode;
|
|
343
|
+
blackoutEl.className = mode === 'black' ? 'is-black' : 'is-white';
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
348
|
+
// BUILD SLIDE CELLS
|
|
349
|
+
// Each visible iframe is wrapped in a .slide-cell div.
|
|
350
|
+
// Normal mode : cells are absolute overlays stacked on the stage.
|
|
351
|
+
// Overview mode: cells become 256×144 grid items (iframe scaled 0.2×).
|
|
352
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
353
|
+
playable.forEach((slide, i) => {
|
|
354
|
+
const cell = document.createElement('div');
|
|
355
|
+
cell.className = 'slide-cell';
|
|
356
|
+
cell.addEventListener('click', () => {
|
|
357
|
+
if (inOverview) { closeOverview(); go(i); }
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const f = document.createElement('iframe');
|
|
361
|
+
f.src = slide.path;
|
|
362
|
+
f.className = 'slide-frame';
|
|
363
|
+
f.setAttribute('tabindex', '-1');
|
|
364
|
+
f.setAttribute('title', `Slide ${i + 1}: ${slide.name}`);
|
|
365
|
+
|
|
366
|
+
// Slide number badge (overview only)
|
|
367
|
+
const num = document.createElement('span');
|
|
368
|
+
num.className = 'slide-num';
|
|
369
|
+
num.textContent = String(i + 1).padStart(2, '0');
|
|
370
|
+
|
|
371
|
+
// Name label (overview only)
|
|
372
|
+
const label = document.createElement('span');
|
|
373
|
+
label.className = 'slide-label';
|
|
374
|
+
label.textContent = slide.name.replaceAll('-', ' ');
|
|
375
|
+
|
|
376
|
+
cell.appendChild(f);
|
|
377
|
+
cell.appendChild(num);
|
|
378
|
+
cell.appendChild(label);
|
|
379
|
+
stage.appendChild(cell);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const frames = () => stage.querySelectorAll('.slide-frame');
|
|
383
|
+
const cells = () => stage.querySelectorAll('.slide-cell');
|
|
384
|
+
|
|
385
|
+
if (typeof initPresenter === 'function') {
|
|
386
|
+
presenter = initPresenter(
|
|
387
|
+
() => stage.querySelector('.slide-frame[data-active]'),
|
|
388
|
+
playable,
|
|
389
|
+
() => current
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
394
|
+
// SCALE STAGE TO WINDOW
|
|
395
|
+
// Maintains 1280×720 (16:9) at any window size.
|
|
396
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
397
|
+
function scaleStage() {
|
|
398
|
+
if (inOverview) return;
|
|
399
|
+
const s = Math.min(
|
|
400
|
+
window.innerWidth / 1280,
|
|
401
|
+
window.innerHeight / 720
|
|
402
|
+
);
|
|
403
|
+
stage.style.transform = `scale(${s})`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const ro = new ResizeObserver(scaleStage);
|
|
407
|
+
ro.observe(document.documentElement);
|
|
408
|
+
scaleStage();
|
|
409
|
+
|
|
410
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
411
|
+
// URL HASH → INDEX
|
|
412
|
+
// Accepts a slide name (#problem) or a legacy 1-based number (#5).
|
|
413
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
414
|
+
function indexFromHash(h) {
|
|
415
|
+
const raw = (h || '').replace(/^#/, '');
|
|
416
|
+
if (!raw) return 0;
|
|
417
|
+
const decoded = (() => { try { return decodeURIComponent(raw); } catch (_) { return raw; } })();
|
|
418
|
+
if (Object.prototype.hasOwnProperty.call(nameToIdx, decoded)) return nameToIdx[decoded];
|
|
419
|
+
const n = parseInt(decoded, 10);
|
|
420
|
+
if (Number.isFinite(n) && n >= 1) return n - 1;
|
|
421
|
+
return 0;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function clampIndex(n) {
|
|
425
|
+
if (total === 0) return 0;
|
|
426
|
+
return Math.max(0, Math.min(total - 1, n));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
430
|
+
// NAVIGATION
|
|
431
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
432
|
+
function go(idx) {
|
|
433
|
+
const allFrames = frames();
|
|
434
|
+
const allCells = cells();
|
|
435
|
+
if (idx < 0 || idx >= total || !allFrames.length) return;
|
|
436
|
+
|
|
437
|
+
allFrames[current]?.removeAttribute('data-active');
|
|
438
|
+
allCells[current]?.classList.remove('is-active');
|
|
439
|
+
|
|
440
|
+
current = idx;
|
|
441
|
+
|
|
442
|
+
allFrames[current].setAttribute('data-active', '');
|
|
443
|
+
allCells[current].classList.add('is-active');
|
|
444
|
+
|
|
445
|
+
// ── Transfer focus to the newly active slide ────────────────────────────
|
|
446
|
+
// Without this, focus stays on the last-clicked iframe. Two failure modes:
|
|
447
|
+
// 1. Stale animation.js on the old (invisible) slide silently consumes
|
|
448
|
+
// keystrokes — the user sees nothing happen.
|
|
449
|
+
// 2. Stale navbridge on the old slide forwards keys straight to the parent
|
|
450
|
+
// (passthrough:true), bypassing the new slide's animation.js entirely.
|
|
451
|
+
// requestAnimationFrame lets the opacity transition start before we steal
|
|
452
|
+
// focus; the try/catch handles the initial go(0) call before iframes load.
|
|
453
|
+
;(function (f) {
|
|
454
|
+
requestAnimationFrame(function () {
|
|
455
|
+
try { if (f.contentWindow) f.contentWindow.focus(); } catch (_) {}
|
|
456
|
+
});
|
|
457
|
+
})(allFrames[current]);
|
|
458
|
+
|
|
459
|
+
const slide = playable[current];
|
|
460
|
+
const pct = ((current + 1) / total) * 100;
|
|
461
|
+
bar.style.width = pct + '%';
|
|
462
|
+
counter.textContent = `${current + 1} / ${total}`;
|
|
463
|
+
if (hudCount) hudCount.textContent = `${String(current + 1).padStart(2, '0')} / ${total}`;
|
|
464
|
+
|
|
465
|
+
// Name-based hash — stable across slide reorders
|
|
466
|
+
history.replaceState(null, '', '#' + encodeURIComponent(slide.name));
|
|
467
|
+
presenter?.onSlideChange();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
471
|
+
// HUD — auto-hide keyboard hint pill
|
|
472
|
+
// Show on any activity; fade after 1.6 s of idle.
|
|
473
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
474
|
+
let hudTimer;
|
|
475
|
+
function showHud() {
|
|
476
|
+
hud.classList.add('show');
|
|
477
|
+
clearTimeout(hudTimer);
|
|
478
|
+
hudTimer = setTimeout(() => hud.classList.remove('show'), 1600);
|
|
479
|
+
}
|
|
480
|
+
window.addEventListener('mousemove', showHud);
|
|
481
|
+
|
|
482
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
483
|
+
// OVERVIEW MODE
|
|
484
|
+
// G key opens the thumbnail grid. G or Escape closes it.
|
|
485
|
+
// Clicking a thumbnail closes overview and jumps to that slide.
|
|
486
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
487
|
+
function openOverview() {
|
|
488
|
+
inOverview = true;
|
|
489
|
+
document.body.classList.add('overview');
|
|
490
|
+
document.body.style.overflow = '';
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function closeOverview() {
|
|
494
|
+
inOverview = false;
|
|
495
|
+
document.body.classList.remove('overview');
|
|
496
|
+
requestAnimationFrame(scaleStage);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
500
|
+
// KEYBOARD — single point of truth
|
|
501
|
+
// • Parent window focused: keydown listener below fires directly.
|
|
502
|
+
// • Iframe focused: js/navbridge.js inside the slide posts a message,
|
|
503
|
+
// which the window message listener converts into a handleKey() call.
|
|
504
|
+
// Do NOT attach a second keydown listener to the iframe — that would
|
|
505
|
+
// double-fire and advance two slides per key press.
|
|
506
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
507
|
+
function handleKey(e) {
|
|
508
|
+
if (inOverview) {
|
|
509
|
+
if (e.key === 'Escape' || e.key === 'g' || e.key === 'G') closeOverview();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ── Step-animation routing ──────────────────────────────────────────────
|
|
514
|
+
// When the parent window has focus (initial load, browser chrome refocus,
|
|
515
|
+
// overview close, or other parent-level interaction), nav keys fire here
|
|
516
|
+
// directly and bypass animation.js inside the iframe entirely. Fix: forward
|
|
517
|
+
// those keys into the active iframe so
|
|
518
|
+
// animation.js can intercept them in capture phase. animation.js consumes the
|
|
519
|
+
// key (stopImmediatePropagation) if a step is pending; navbridge inside the
|
|
520
|
+
// iframe then posts back only once all steps are exhausted, at which point
|
|
521
|
+
// the message handler below calls handleKey with passthrough:true and normal
|
|
522
|
+
// slide navigation resumes.
|
|
523
|
+
// passthrough:true is set only by the message handler — it marks a reply from
|
|
524
|
+
// navbridge meaning "nothing left to consume, please advance/retreat."
|
|
525
|
+
if (!e.passthrough) {
|
|
526
|
+
var _stepKeys = { ArrowRight: 1, ArrowDown: 1, ' ': 1, ArrowLeft: 1, ArrowUp: 1 };
|
|
527
|
+
if (_stepKeys[e.key]) {
|
|
528
|
+
var _af = stage.querySelector('.slide-frame[data-active]');
|
|
529
|
+
if (_af) {
|
|
530
|
+
try { _af.contentWindow.postMessage({ type: 'octocode-slides:key', key: e.key }, '*'); }
|
|
531
|
+
catch (_) {}
|
|
532
|
+
}
|
|
533
|
+
showHud();
|
|
534
|
+
if (e.preventDefault) e.preventDefault();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
let handled = true;
|
|
540
|
+
switch (e.key) {
|
|
541
|
+
case 'ArrowRight':
|
|
542
|
+
case 'ArrowDown':
|
|
543
|
+
case ' ':
|
|
544
|
+
go(current + 1);
|
|
545
|
+
break;
|
|
546
|
+
case 'ArrowLeft':
|
|
547
|
+
case 'ArrowUp':
|
|
548
|
+
go(current - 1);
|
|
549
|
+
break;
|
|
550
|
+
case 'Home': go(0); break;
|
|
551
|
+
case 'End': go(total - 1); break;
|
|
552
|
+
case 'f':
|
|
553
|
+
case 'F':
|
|
554
|
+
document.fullscreenElement
|
|
555
|
+
? document.exitFullscreen?.()
|
|
556
|
+
: document.documentElement.requestFullscreen?.();
|
|
557
|
+
break;
|
|
558
|
+
case 'g':
|
|
559
|
+
case 'G':
|
|
560
|
+
openOverview();
|
|
561
|
+
break;
|
|
562
|
+
case 'p':
|
|
563
|
+
case 'P':
|
|
564
|
+
presenter?.open();
|
|
565
|
+
break;
|
|
566
|
+
case 'b':
|
|
567
|
+
case 'B':
|
|
568
|
+
toggleBlackout('black');
|
|
569
|
+
break;
|
|
570
|
+
case 'w':
|
|
571
|
+
case 'W':
|
|
572
|
+
toggleBlackout('white');
|
|
573
|
+
break;
|
|
574
|
+
default:
|
|
575
|
+
handled = false;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (handled) {
|
|
579
|
+
showHud();
|
|
580
|
+
if (e.preventDefault) e.preventDefault();
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
document.addEventListener('keydown', handleKey, true);
|
|
585
|
+
|
|
586
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
587
|
+
// NAVBRIDGE — postMessage receiver
|
|
588
|
+
// js/navbridge.js inside each slide iframe forwards nav keys via postMessage
|
|
589
|
+
// when the iframe has focus (user clicked inside the slide).
|
|
590
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
591
|
+
window.addEventListener('message', function (event) {
|
|
592
|
+
var data = event.data;
|
|
593
|
+
if (!data || typeof data !== 'object') return;
|
|
594
|
+
if (data.type === 'octocode-slides:nav' && data.key) {
|
|
595
|
+
// passthrough:true tells handleKey this reply came from navbridge inside the
|
|
596
|
+
// iframe — animation.js had nothing left to consume, so navigate normally.
|
|
597
|
+
handleKey({ key: data.key, passthrough: true, preventDefault: function () {} });
|
|
598
|
+
} else if (data.type === 'octocode-slides:activity') {
|
|
599
|
+
showHud();
|
|
600
|
+
} else if (data.type === 'octocode-slides:presenter-goto') {
|
|
601
|
+
go(clampIndex(data.index));
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
606
|
+
// TOUCH / SWIPE (left swipe = next, right swipe = previous)
|
|
607
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
608
|
+
let touchStartX = null;
|
|
609
|
+
|
|
610
|
+
document.addEventListener('touchstart', e => {
|
|
611
|
+
if (!inOverview) touchStartX = e.touches[0].clientX;
|
|
612
|
+
}, { passive: true });
|
|
613
|
+
|
|
614
|
+
document.addEventListener('touchend', e => {
|
|
615
|
+
if (touchStartX === null || inOverview) return;
|
|
616
|
+
const diff = touchStartX - e.changedTouches[0].clientX;
|
|
617
|
+
if (Math.abs(diff) > 40) go(current + (diff > 0 ? 1 : -1));
|
|
618
|
+
touchStartX = null;
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
622
|
+
// WHEEL NAVIGATION (scroll down = next, scroll up = previous)
|
|
623
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
624
|
+
let wheelTimer = null;
|
|
625
|
+
document.addEventListener('wheel', function(e) {
|
|
626
|
+
if (inOverview) return;
|
|
627
|
+
if (Math.abs(e.deltaY) < 20) return;
|
|
628
|
+
clearTimeout(wheelTimer);
|
|
629
|
+
wheelTimer = setTimeout(function() {
|
|
630
|
+
go(current + (e.deltaY > 0 ? 1 : -1));
|
|
631
|
+
}, 60);
|
|
632
|
+
}, { passive: true });
|
|
633
|
+
|
|
634
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
635
|
+
// HASH NAVIGATION
|
|
636
|
+
// index.html#my-slide jumps to that slide directly.
|
|
637
|
+
// Accepts name slugs (#problem) and legacy 1-based numbers (#5).
|
|
638
|
+
// URL is updated automatically on every slide change via history.replaceState.
|
|
639
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
640
|
+
window.addEventListener('hashchange', function () {
|
|
641
|
+
const next = clampIndex(indexFromHash(location.hash));
|
|
642
|
+
if (next !== current) go(next);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
646
|
+
// INIT — respect hash on first load, else start at slide 1
|
|
647
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
648
|
+
// Do not pre-mark slide 1 active; go() is the single activation path so
|
|
649
|
+
// direct hash loads (#problem) never leave two visible iframes.
|
|
650
|
+
go(clampIndex(indexFromHash(location.hash)));
|
|
651
|
+
showHud();
|
|
652
|
+
</script>
|
|
653
|
+
|
|
654
|
+
</body>
|
|
655
|
+
</html>
|