@graphlearning/shell 0.6.0 → 0.8.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/dist/index.js CHANGED
@@ -1,384 +1,452 @@
1
- import { jsx as e, jsxs as d, Fragment as M } from "react/jsx-runtime";
2
- import { useRef as S, useState as x, useLayoutEffect as q, useEffect as N, useCallback as T, useMemo as C } from "react";
3
- import { SceneView as H } from "@graphlearning/flow";
4
- import { Home as W, Volume2 as X, VolumeX as O, Moon as Y, Sun as Z, ChevronLeft as J, ChevronRight as Q } from "lucide-react";
5
- import U from "react-markdown";
6
- import ee from "remark-gfm";
7
- function K(n, r) {
8
- return `${n.id}-${r.id}`;
1
+ import { jsx as t, jsxs as h, Fragment as H } from "react/jsx-runtime";
2
+ import { useRef as T, useState as b, useLayoutEffect as O, useEffect as k, useCallback as I, useMemo as M } from "react";
3
+ import { SceneView as R } from "@graphlearning/flow";
4
+ import { Home as Y, Volume2 as Z, VolumeX as J, Moon as Q, Sun as U, ChevronLeft as ee, ChevronRight as te } from "lucide-react";
5
+ import ne from "react-markdown";
6
+ import ae from "remark-gfm";
7
+ function X(e, a) {
8
+ return `${e.id}-${a.id}`;
9
9
  }
10
- function z(n) {
11
- return n.sections.map((r) => ({ section: r, slug: K(n, r) }));
10
+ function D(e) {
11
+ return e.sections.map((a) => ({ section: a, slug: X(e, a) }));
12
12
  }
13
- const F = 806;
14
- function te() {
15
- const n = S(null), [r, s] = x(1);
16
- return q(() => {
17
- const o = n.current;
13
+ const G = 806;
14
+ function re() {
15
+ const e = T(null), [a, s] = b(1);
16
+ return O(() => {
17
+ const o = e.current;
18
18
  if (!o) return;
19
- const l = () => s(o.clientWidth / F);
20
- l();
21
- const u = new ResizeObserver(l);
22
- return u.observe(o), () => u.disconnect();
23
- }, []), { ref: n, zoom: r };
19
+ const d = () => s(o.clientWidth / G);
20
+ d();
21
+ const g = new ResizeObserver(d);
22
+ return g.observe(o), () => g.disconnect();
23
+ }, []), { ref: e, zoom: a };
24
+ }
25
+ function se({ slide: e, open: a }) {
26
+ const { ref: s, zoom: o } = re();
27
+ return /* @__PURE__ */ t("aside", { ref: s, className: `slide-panel${a ? " slide-panel--open" : ""}`, children: /* @__PURE__ */ t("div", { className: "slide-panel__scaler", style: { width: G, zoom: o }, children: /* @__PURE__ */ t(ne, { remarkPlugins: [ae], children: e }) }) });
28
+ }
29
+ const z = 10;
30
+ function ie({ audio: e }) {
31
+ const [a, s] = b(0), [o, d] = b(0), g = T(null);
32
+ if (k(() => {
33
+ if (!e) return;
34
+ const n = () => {
35
+ s(e.currentTime), d(Number.isFinite(e.duration) ? e.duration : 0);
36
+ };
37
+ n();
38
+ const u = ["loadedmetadata", "durationchange", "timeupdate", "seeked", "ended", "emptied", "error"];
39
+ return u.forEach((r) => e.addEventListener(r, n)), () => u.forEach((r) => e.removeEventListener(r, n));
40
+ }, [e]), !e || !o) return null;
41
+ const f = (n) => {
42
+ const u = g.current;
43
+ if (!u) return;
44
+ const r = u.getBoundingClientRect(), m = Math.min(1, Math.max(0, (n - r.left) / r.width));
45
+ e.currentTime = m * o, s(e.currentTime);
46
+ }, l = (n) => {
47
+ const u = (r) => {
48
+ n.preventDefault(), n.stopPropagation(), e.currentTime = Math.min(o, Math.max(0, e.currentTime + r)), s(e.currentTime);
49
+ };
50
+ n.key === "ArrowLeft" ? u(-z) : n.key === "ArrowRight" ? u(z) : n.key === "Home" ? u(-o) : n.key === "End" && u(o);
51
+ }, p = `${Math.min(100, a / o * 100)}%`;
52
+ return /* @__PURE__ */ h("span", { className: "reel-foot__audio", children: [
53
+ /* @__PURE__ */ t("span", { className: "reel-foot__count reel-foot__time", children: A(a) }),
54
+ /* @__PURE__ */ h(
55
+ "div",
56
+ {
57
+ ref: g,
58
+ className: "reel-foot__track",
59
+ role: "slider",
60
+ tabIndex: 0,
61
+ "aria-label": "Narration position",
62
+ "aria-valuemin": 0,
63
+ "aria-valuemax": Math.round(o),
64
+ "aria-valuenow": Math.round(a),
65
+ "aria-valuetext": `${A(a)} of ${A(o)}`,
66
+ onKeyDown: l,
67
+ onPointerDown: (n) => {
68
+ n.currentTarget.setPointerCapture(n.pointerId), f(n.clientX);
69
+ },
70
+ onPointerMove: (n) => {
71
+ n.currentTarget.hasPointerCapture(n.pointerId) && f(n.clientX);
72
+ },
73
+ children: [
74
+ /* @__PURE__ */ t("span", { className: "reel-foot__fill", style: { width: p } }),
75
+ /* @__PURE__ */ t("span", { className: "reel-foot__thumb", style: { left: p } })
76
+ ]
77
+ }
78
+ ),
79
+ /* @__PURE__ */ t("span", { className: "reel-foot__count reel-foot__time", children: A(o) })
80
+ ] });
24
81
  }
25
- function ne({ slide: n, open: r }) {
26
- const { ref: s, zoom: o } = te();
27
- return /* @__PURE__ */ e("aside", { ref: s, className: `slide-panel${r ? " slide-panel--open" : ""}`, children: /* @__PURE__ */ e("div", { className: "slide-panel__scaler", style: { width: F, zoom: o }, children: /* @__PURE__ */ e(U, { remarkPlugins: [ee], children: n }) }) });
82
+ function A(e) {
83
+ const a = Number.isFinite(e) && e > 0 ? e : 0;
84
+ return `${Math.floor(a / 60)}:${String(Math.floor(a % 60)).padStart(2, "0")}`;
28
85
  }
29
- function ae({
30
- section: n,
31
- getScene: r,
86
+ function oe({
87
+ section: e,
88
+ getScene: a,
32
89
  capture: s = !1,
33
90
  eyebrow: o,
34
- index: l = 0,
35
- total: u = 0,
36
- onHome: m,
37
- onPrev: c,
38
- onNext: v,
39
- narrating: t = !1,
40
- onToggleNarration: w,
41
- theme: k = "dark",
42
- onToggleTheme: h
91
+ index: d = 0,
92
+ total: g = 0,
93
+ onHome: f,
94
+ onPrev: l,
95
+ onNext: p,
96
+ narrating: n = !1,
97
+ onToggleNarration: u,
98
+ narrationAudio: r = null,
99
+ theme: m = "dark",
100
+ onToggleTheme: v
43
101
  }) {
44
- const [g, f] = x(!1), p = r(n.scene);
45
- return p ? /* @__PURE__ */ d("div", { className: "stage stage--section", children: [
46
- /* @__PURE__ */ d("header", { className: "reel-head", children: [
47
- o && /* @__PURE__ */ e("button", { className: "reel-head__eyebrow", onClick: m, "aria-label": "Back to catalog (Esc)", title: "Back to catalog (Esc)", children: o }),
48
- /* @__PURE__ */ e("h1", { className: "reel-head__title", children: n.title })
102
+ const [_, w] = b(!1), E = a(e.scene);
103
+ return E ? /* @__PURE__ */ h("div", { className: "stage stage--section", children: [
104
+ /* @__PURE__ */ h("header", { className: "reel-head", children: [
105
+ o && /* @__PURE__ */ t("button", { className: "reel-head__eyebrow", onClick: f, "aria-label": "Back to catalog (Esc)", title: "Back to catalog (Esc)", children: o }),
106
+ /* @__PURE__ */ t("h1", { className: "reel-head__title", children: e.title })
49
107
  ] }),
50
- !s && /* @__PURE__ */ d("footer", { className: "reel-foot reel-foot--controls", children: [
51
- /* @__PURE__ */ d("span", { className: "reel-foot__grp", children: [
52
- /* @__PURE__ */ e("button", { className: "reel-foot__ctrl", onClick: m, "aria-label": "Back to catalog (Esc)", title: "Back to catalog (Esc)", children: /* @__PURE__ */ e(W, { size: 17 }) }),
53
- /* @__PURE__ */ e(
108
+ !s && /* @__PURE__ */ h("footer", { className: "reel-foot reel-foot--controls", children: [
109
+ /* @__PURE__ */ h("span", { className: "reel-foot__grp", children: [
110
+ /* @__PURE__ */ t("button", { className: "reel-foot__ctrl", onClick: f, "aria-label": "Back to catalog (Esc)", title: "Back to catalog (Esc)", children: /* @__PURE__ */ t(Y, { size: 17 }) }),
111
+ /* @__PURE__ */ t(
54
112
  "button",
55
113
  {
56
114
  className: "reel-foot__ctrl",
57
- onClick: w,
58
- "aria-label": t ? "Pause narration (Space)" : "Play narration (Space)",
59
- title: t ? "Pause narration (Space)" : "Play narration (Space)",
60
- children: t ? /* @__PURE__ */ e(X, { size: 18 }) : /* @__PURE__ */ e(O, { size: 18 })
115
+ onClick: u,
116
+ "aria-label": n ? "Pause narration (Space)" : "Play narration (Space)",
117
+ title: n ? "Pause narration (Space)" : "Play narration (Space)",
118
+ children: n ? /* @__PURE__ */ t(Z, { size: 18 }) : /* @__PURE__ */ t(J, { size: 18 })
61
119
  }
62
120
  ),
63
- h && /* @__PURE__ */ e(
121
+ v && /* @__PURE__ */ t(
64
122
  "button",
65
123
  {
66
124
  className: "reel-foot__ctrl",
67
- onClick: h,
68
- "aria-label": k === "dark" ? "Dark theme. Switch to light theme." : "Light theme. Switch to dark theme.",
69
- title: k === "dark" ? "Dark theme. Switch to light theme." : "Light theme. Switch to dark theme.",
70
- children: k === "dark" ? /* @__PURE__ */ e(Y, { size: 17 }) : /* @__PURE__ */ e(Z, { size: 17 })
125
+ onClick: v,
126
+ "aria-label": m === "dark" ? "Dark theme. Switch to light theme." : "Light theme. Switch to dark theme.",
127
+ title: m === "dark" ? "Dark theme. Switch to light theme." : "Light theme. Switch to dark theme.",
128
+ children: m === "dark" ? /* @__PURE__ */ t(Q, { size: 17 }) : /* @__PURE__ */ t(U, { size: 17 })
71
129
  }
72
130
  )
73
131
  ] }),
74
- /* @__PURE__ */ d("span", { className: "reel-foot__nav", children: [
75
- /* @__PURE__ */ e("button", { className: "reel-foot__ctrl", onClick: c, "aria-label": "Previous section (←)", title: "Previous section (←)", children: /* @__PURE__ */ e(J, { size: 19 }) }),
76
- /* @__PURE__ */ d("span", { className: "reel-foot__count reel-foot__count--live", children: [
77
- l + 1,
132
+ /* @__PURE__ */ t(ie, { audio: r }),
133
+ /* @__PURE__ */ h("span", { className: "reel-foot__nav", children: [
134
+ /* @__PURE__ */ t("button", { className: "reel-foot__ctrl", onClick: l, "aria-label": "Previous section (Shift+←)", title: "Previous section (Shift+←)", children: /* @__PURE__ */ t(ee, { size: 19 }) }),
135
+ /* @__PURE__ */ h("span", { className: "reel-foot__count reel-foot__count--live", children: [
136
+ d + 1,
78
137
  " / ",
79
- u
138
+ g
80
139
  ] }),
81
- /* @__PURE__ */ e("button", { className: "reel-foot__ctrl", onClick: v, "aria-label": "Next section (→)", title: "Next section (→)", children: /* @__PURE__ */ e(Q, { size: 19 }) })
140
+ /* @__PURE__ */ t("button", { className: "reel-foot__ctrl", onClick: p, "aria-label": "Next section (Shift+→)", title: "Next section (Shift+→)", children: /* @__PURE__ */ t(te, { size: 19 }) })
82
141
  ] })
83
142
  ] }),
84
- /* @__PURE__ */ e("div", { className: "scene-area", children: /* @__PURE__ */ e(H, { scene: p, focusId: n.focus, theme: k }) }),
85
- !s && /* @__PURE__ */ e(
143
+ /* @__PURE__ */ t("div", { className: "scene-area", children: /* @__PURE__ */ t(R, { scene: E, focusId: e.focus, theme: m }) }),
144
+ !s && /* @__PURE__ */ t(
86
145
  "button",
87
146
  {
88
147
  className: "slide-toggle",
89
- onClick: () => f((b) => !b),
90
- "aria-label": g ? "Hide slide" : "Show slide",
91
- children: g ? "›" : "‹"
148
+ onClick: () => w((S) => !S),
149
+ "aria-label": _ ? "Hide slide" : "Show slide",
150
+ children: _ ? "›" : "‹"
92
151
  }
93
152
  ),
94
- /* @__PURE__ */ e(ne, { slide: n.slide, open: g })
95
- ] }) : /* @__PURE__ */ d("div", { className: "stage stage--missing", children: [
153
+ /* @__PURE__ */ t(se, { slide: e.slide, open: _ })
154
+ ] }) : /* @__PURE__ */ h("div", { className: "stage stage--missing", children: [
96
155
  "no scene: ",
97
- n.scene
156
+ e.scene
98
157
  ] });
99
158
  }
100
- const se = [
159
+ const le = [
101
160
  { id: "courses", label: "Courses" },
102
161
  { id: "labs", label: "Labs" },
103
162
  { id: "coach", label: "Coach" }
104
163
  ];
105
- function re() {
106
- return /* @__PURE__ */ d("svg", { className: "site__logo", width: "28", height: "28", viewBox: "0 0 512 512", "aria-hidden": "true", children: [
107
- /* @__PURE__ */ e("rect", { width: "512", height: "512", rx: "80", fill: "#e8804f" }),
108
- /* @__PURE__ */ d("g", { fill: "none", stroke: "#ffffff", strokeWidth: "58", strokeLinecap: "butt", strokeLinejoin: "miter", children: [
109
- /* @__PURE__ */ e("path", { d: "M 373 200 A 130 130 0 1 0 373 312" }),
110
- /* @__PURE__ */ e("path", { d: "M 283 312 L 373 312 L 373 410" })
164
+ function ce() {
165
+ return /* @__PURE__ */ h("svg", { className: "site__logo", width: "28", height: "28", viewBox: "0 0 512 512", "aria-hidden": "true", children: [
166
+ /* @__PURE__ */ t("rect", { width: "512", height: "512", rx: "80", fill: "#e8804f" }),
167
+ /* @__PURE__ */ h("g", { fill: "none", stroke: "#ffffff", strokeWidth: "58", strokeLinecap: "butt", strokeLinejoin: "miter", children: [
168
+ /* @__PURE__ */ t("path", { d: "M 373 200 A 130 130 0 1 0 373 312" }),
169
+ /* @__PURE__ */ t("path", { d: "M 283 312 L 373 312 L 373 410" })
111
170
  ] })
112
171
  ] });
113
172
  }
114
- function oe({
115
- kind: n = "courses",
116
- nav: r = se,
173
+ function de({
174
+ kind: e = "courses",
175
+ nav: a = le,
117
176
  home: s = "/",
118
177
  actions: o
119
178
  }) {
120
- return /* @__PURE__ */ e("header", { className: "site", children: /* @__PURE__ */ d("div", { className: "site__inner", children: [
121
- /* @__PURE__ */ d("a", { className: "site__brand", href: s, children: [
122
- /* @__PURE__ */ e(re, {}),
123
- /* @__PURE__ */ e("span", { className: "site__name", children: "GraphL" })
179
+ return /* @__PURE__ */ t("header", { className: "site", children: /* @__PURE__ */ h("div", { className: "site__inner", children: [
180
+ /* @__PURE__ */ h("a", { className: "site__brand", href: s, children: [
181
+ /* @__PURE__ */ t(ce, {}),
182
+ /* @__PURE__ */ t("span", { className: "site__name", children: "GraphL" })
124
183
  ] }),
125
- /* @__PURE__ */ e("nav", { className: "site__nav", "aria-label": "Sections", children: r.map((l) => /* @__PURE__ */ e(
184
+ /* @__PURE__ */ t("nav", { className: "site__nav", "aria-label": "Sections", children: a.map((d) => /* @__PURE__ */ t(
126
185
  "a",
127
186
  {
128
187
  className: "site__link",
129
- href: `${s}#${l.id}`,
130
- ...l.id === n ? { "aria-current": "page" } : {},
131
- children: l.label
188
+ href: `${s}#${d.id}`,
189
+ ...d.id === e ? { "aria-current": "page" } : {},
190
+ children: d.label
132
191
  },
133
- l.id
192
+ d.id
134
193
  )) }),
135
- /* @__PURE__ */ e("div", { className: "site__actions", children: o })
194
+ /* @__PURE__ */ t("div", { className: "site__actions", children: o })
136
195
  ] }) });
137
196
  }
138
- function ie({
139
- courses: n,
140
- subject: r,
197
+ function he({
198
+ courses: e,
199
+ subject: a,
141
200
  kind: s,
142
201
  actions: o
143
202
  }) {
144
- const [l, u] = x(() => /* @__PURE__ */ new Set()), m = (c) => u((v) => {
145
- const t = new Set(v);
146
- return t.has(c) ? t.delete(c) : t.add(c), t;
203
+ const [d, g] = b(() => /* @__PURE__ */ new Set()), f = (l) => g((p) => {
204
+ const n = new Set(p);
205
+ return n.has(l) ? n.delete(l) : n.add(l), n;
147
206
  });
148
207
  return (
149
208
  // The site bar is a SIBLING of .idx, not a child: it is full-bleed (its rule spans the
150
209
  // viewport) while .idx is a 940px centred column. Both use the same width and gutters, so the
151
210
  // brand lines up with the card edge below it.
152
- /* @__PURE__ */ d(M, { children: [
153
- /* @__PURE__ */ e(oe, { kind: s, actions: o }),
154
- /* @__PURE__ */ d("div", { className: "idx", children: [
155
- /* @__PURE__ */ e("header", { className: "idx__head", children: /* @__PURE__ */ e("h1", { className: "idx__subject", children: r }) }),
156
- n.length === 0 ? /* @__PURE__ */ e("p", { className: "idx__empty", children: "No courses authored yet — the app shell is ready." }) : /* @__PURE__ */ e("ol", { className: "idx__grid", children: n.map((c, v) => {
157
- const t = z(c), w = l.has(c.id);
158
- return /* @__PURE__ */ d("li", { className: `idx-card${w ? " idx-card--open" : ""}`, children: [
159
- /* @__PURE__ */ d(
211
+ /* @__PURE__ */ h(H, { children: [
212
+ /* @__PURE__ */ t(de, { kind: s, actions: o }),
213
+ /* @__PURE__ */ h("div", { className: "idx", children: [
214
+ /* @__PURE__ */ t("header", { className: "idx__head", children: /* @__PURE__ */ t("h1", { className: "idx__subject", children: a }) }),
215
+ e.length === 0 ? /* @__PURE__ */ t("p", { className: "idx__empty", children: "No courses authored yet — the app shell is ready." }) : /* @__PURE__ */ t("ol", { className: "idx__grid", children: e.map((l, p) => {
216
+ const n = D(l), u = d.has(l.id);
217
+ return /* @__PURE__ */ h("li", { className: `idx-card${u ? " idx-card--open" : ""}`, children: [
218
+ /* @__PURE__ */ h(
160
219
  "button",
161
220
  {
162
221
  className: "idx-card__head",
163
- onClick: () => m(c.id),
164
- "aria-expanded": w,
222
+ onClick: () => f(l.id),
223
+ "aria-expanded": u,
165
224
  children: [
166
- /* @__PURE__ */ e("span", { className: "idx-card__num", children: String(v + 1).padStart(2, "0") }),
167
- /* @__PURE__ */ e("span", { className: "idx-card__title", children: c.title }),
168
- /* @__PURE__ */ d("span", { className: "idx-card__count", children: [
169
- t.length,
225
+ /* @__PURE__ */ t("span", { className: "idx-card__num", children: String(p + 1).padStart(2, "0") }),
226
+ /* @__PURE__ */ t("span", { className: "idx-card__title", children: l.title }),
227
+ /* @__PURE__ */ h("span", { className: "idx-card__count", children: [
228
+ n.length,
170
229
  " sections"
171
230
  ] }),
172
- /* @__PURE__ */ e("span", { className: "idx-card__toggle", "aria-hidden": "true", children: "⌄" })
231
+ /* @__PURE__ */ t("span", { className: "idx-card__toggle", "aria-hidden": "true", children: "⌄" })
173
232
  ]
174
233
  }
175
234
  ),
176
- w && /* @__PURE__ */ e("ol", { className: "idx-sec", children: t.map(({ section: k, slug: h }, g) => /* @__PURE__ */ e("li", { className: "idx-sec__row", children: /* @__PURE__ */ d("a", { href: `#/${h}`, children: [
177
- /* @__PURE__ */ e("span", { className: "idx-sec__num", children: g + 1 }),
178
- /* @__PURE__ */ e("span", { className: "idx-sec__title", children: k.title }),
179
- /* @__PURE__ */ e("span", { className: "idx-sec__chev", "aria-hidden": "true", children: "›" })
180
- ] }) }, h)) })
181
- ] }, c.id);
235
+ u && /* @__PURE__ */ t("ol", { className: "idx-sec", children: n.map(({ section: r, slug: m }, v) => /* @__PURE__ */ t("li", { className: "idx-sec__row", children: /* @__PURE__ */ h("a", { href: `#/${m}`, children: [
236
+ /* @__PURE__ */ t("span", { className: "idx-sec__num", children: v + 1 }),
237
+ /* @__PURE__ */ t("span", { className: "idx-sec__title", children: r.title }),
238
+ /* @__PURE__ */ t("span", { className: "idx-sec__chev", "aria-hidden": "true", children: "›" })
239
+ ] }) }, m)) })
240
+ ] }, l.id);
182
241
  }) }),
183
- /* @__PURE__ */ e("footer", { className: "idx__foot", children: /* @__PURE__ */ d("span", { className: "idx__hint", children: [
184
- /* @__PURE__ */ e("kbd", { children: "→" }),
242
+ /* @__PURE__ */ t("footer", { className: "idx__foot", children: /* @__PURE__ */ h("span", { className: "idx__hint", children: [
243
+ /* @__PURE__ */ t("kbd", { children: "→" }),
185
244
  " next · ",
186
- /* @__PURE__ */ e("kbd", { children: "←" }),
245
+ /* @__PURE__ */ t("kbd", { children: "←" }),
187
246
  " prev · ",
188
- /* @__PURE__ */ e("kbd", { children: "Space" }),
247
+ /* @__PURE__ */ t("kbd", { children: "Space" }),
189
248
  " narration"
190
249
  ] }) })
191
250
  ] })
192
251
  ] })
193
252
  );
194
253
  }
195
- function le(n, r) {
196
- const s = S(null), o = S(r);
197
- o.current = r;
198
- const [l, u] = x(!1), m = S(l);
199
- return m.current = l, N(() => {
200
- const t = new Audio();
201
- return t.addEventListener("ended", () => o.current()), t.addEventListener("error", () => u(!1)), s.current = t, () => t.pause();
202
- }, []), N(() => {
203
- const t = s.current;
204
- t && (t.pause(), n ? (t.src = n, t.currentTime = 0, m.current && t.play().catch(() => {
205
- })) : (t.removeAttribute("src"), t.load(), u(!1)));
206
- }, [n]), { playing: l, toggle: () => {
207
- const t = s.current;
208
- !t || !t.getAttribute("src") || (m.current ? (t.pause(), u(!1)) : (t.play().catch(() => {
209
- }), u(!0)));
254
+ function ue(e, a) {
255
+ const s = T(null), [o, d] = b(null), g = T(a);
256
+ g.current = a;
257
+ const [f, l] = b(!1), p = T(f);
258
+ return p.current = f, k(() => {
259
+ const r = new Audio();
260
+ return r.addEventListener("ended", () => g.current()), r.addEventListener("error", () => l(!1)), s.current = r, d(r), () => r.pause();
261
+ }, []), k(() => {
262
+ const r = s.current;
263
+ r && (r.pause(), e ? (r.src = e, r.currentTime = 0, p.current && r.play().catch(() => {
264
+ })) : (r.removeAttribute("src"), r.load(), l(!1)));
265
+ }, [e]), { playing: f, toggle: () => {
266
+ const r = s.current;
267
+ !r || !r.getAttribute("src") || (p.current ? (r.pause(), l(!1)) : (r.play().catch(() => {
268
+ }), l(!0)));
210
269
  }, stop: () => {
211
- var t;
212
- (t = s.current) == null || t.pause(), u(!1);
213
- } };
270
+ var r;
271
+ (r = s.current) == null || r.pause(), l(!1);
272
+ }, audio: o };
214
273
  }
215
- const I = "graphl:theme";
216
- function D() {
274
+ const B = "graphl:theme";
275
+ function F() {
217
276
  try {
218
- const n = localStorage.getItem(I);
219
- return n === "light" || n === "dark" ? n : null;
277
+ const e = localStorage.getItem(B);
278
+ return e === "light" || e === "dark" ? e : null;
220
279
  } catch {
221
280
  return null;
222
281
  }
223
282
  }
224
- function ce(n) {
283
+ function me(e) {
225
284
  try {
226
- localStorage.setItem(I, n);
285
+ localStorage.setItem(B, e);
227
286
  } catch {
228
287
  }
229
288
  }
230
- const de = (n, r, s) => s || !r ? n : r;
231
- function he(n, r) {
232
- const [s, o] = x(() => r ? null : D()), l = de(n, s, r);
233
- N(() => (document.documentElement.setAttribute("data-theme", l), () => document.documentElement.removeAttribute("data-theme")), [l]), N(() => {
234
- if (r) return;
235
- const m = (c) => {
236
- (c.key === I || c.key === null) && o(D());
289
+ const fe = (e, a, s) => s || !a ? e : a;
290
+ function ge(e, a) {
291
+ const [s, o] = b(() => a ? null : F()), d = fe(e, s, a);
292
+ k(() => (document.documentElement.setAttribute("data-theme", d), () => document.documentElement.removeAttribute("data-theme")), [d]), k(() => {
293
+ if (a) return;
294
+ const f = (l) => {
295
+ (l.key === B || l.key === null) && o(F());
237
296
  };
238
- return window.addEventListener("storage", m), () => window.removeEventListener("storage", m);
239
- }, [r]);
240
- const u = T(() => {
241
- const m = l === "dark" ? "light" : "dark";
242
- ce(m);
243
- const c = document.documentElement;
244
- c.classList.add("theme-swap"), o(m), requestAnimationFrame(() => requestAnimationFrame(() => c.classList.remove("theme-swap")));
245
- }, [l]);
246
- return { theme: l, toggle: u };
297
+ return window.addEventListener("storage", f), () => window.removeEventListener("storage", f);
298
+ }, [a]);
299
+ const g = I(() => {
300
+ const f = d === "dark" ? "light" : "dark";
301
+ me(f);
302
+ const l = document.documentElement;
303
+ l.classList.add("theme-swap"), o(f), requestAnimationFrame(() => requestAnimationFrame(() => l.classList.remove("theme-swap")));
304
+ }, [d]);
305
+ return { theme: d, toggle: g };
247
306
  }
248
307
  const P = {
249
308
  dark: { glyph: "☾", label: "Dark theme" },
250
309
  light: { glyph: "☀", label: "Light theme" }
251
310
  };
252
- function ue({ theme: n, onToggle: r }) {
253
- const s = n === "dark" ? "light" : "dark", o = `${P[n].label}. Switch to ${P[s].label.toLowerCase()}.`;
254
- return /* @__PURE__ */ e("button", { className: "site__icon", type: "button", onClick: r, "aria-label": o, title: o, children: P[n].glyph });
311
+ function pe({ theme: e, onToggle: a }) {
312
+ const s = e === "dark" ? "light" : "dark", o = `${P[e].label}. Switch to ${P[s].label.toLowerCase()}.`;
313
+ return /* @__PURE__ */ t("button", { className: "site__icon", type: "button", onClick: a, "aria-label": o, title: o, children: P[e].glyph });
255
314
  }
256
- function ve({
257
- subject: n,
258
- eyebrow: r,
315
+ function ye({
316
+ subject: e,
317
+ eyebrow: a,
259
318
  courses: s,
260
319
  getScene: o,
261
- audioBase: l = "/",
262
- kind: u = "courses",
263
- theme: m = "dark"
320
+ audioBase: d = "/",
321
+ kind: g = "courses",
322
+ theme: f = "dark"
264
323
  }) {
265
- const [c, v] = x(() => location.hash);
266
- N(() => {
267
- const a = () => v(location.hash);
268
- return window.addEventListener("hashchange", a), () => window.removeEventListener("hashchange", a);
324
+ const [l, p] = b(() => location.hash);
325
+ k(() => {
326
+ const i = () => p(location.hash);
327
+ return window.addEventListener("hashchange", i), () => window.removeEventListener("hashchange", i);
269
328
  }, []);
270
- const t = new URLSearchParams(location.search).get("capture") === "1", { theme: w, toggle: k } = he(m, t), h = c.replace(/^#\/?/, ""), g = C(() => {
271
- if (s[h]) return s[h];
272
- for (const a of Object.values(s))
273
- if (a.sections.some((i) => K(a, i) === h)) return a;
329
+ const n = new URLSearchParams(location.search).get("capture") === "1", { theme: u, toggle: r } = ge(f, n), m = l.replace(/^#\/?/, ""), v = M(() => {
330
+ if (s[m]) return s[m];
331
+ for (const i of Object.values(s))
332
+ if (i.sections.some((c) => X(i, c) === m)) return i;
274
333
  return Object.values(s)[0];
275
- }, [h, s]), f = C(() => z(g), [g]), p = C(() => Object.values(s).flatMap(z), [s]);
276
- N(() => {
277
- if (t || !s[h]) return;
278
- const a = f[0];
279
- a && (history.replaceState(null, "", `${location.pathname}${location.search}#/${a.slug}`), v(`#/${a.slug}`));
280
- }, [t, h, f]), N(() => {
281
- t && (window.__scene = {
282
- plan: () => f.map(({ slug: a, section: i }) => ({
283
- slug: a,
284
- course: g.id,
334
+ }, [m, s]), _ = M(() => D(v), [v]), w = M(() => Object.values(s).flatMap(D), [s]);
335
+ k(() => {
336
+ if (n || !s[m]) return;
337
+ const i = _[0];
338
+ i && (history.replaceState(null, "", `${location.pathname}${location.search}#/${i.slug}`), p(`#/${i.slug}`));
339
+ }, [n, m, _]), k(() => {
340
+ n && (window.__scene = {
341
+ plan: () => _.map(({ slug: i, section: c }) => ({
342
+ slug: i,
343
+ course: v.id,
285
344
  // audio lives at audio/<course>/<id>.wav
286
- id: i.id,
287
- scene: i.scene,
288
- focus: i.focus ?? null
345
+ id: c.id,
346
+ scene: c.scene,
347
+ focus: c.focus ?? null
289
348
  }))
290
349
  });
291
- }, [t, f, g]);
292
- const b = T(
293
- (a) => {
294
- const i = location.hash.replace(/^#\/?/, ""), _ = p.findIndex(({ slug: $ }) => $ === i);
295
- if (_ !== -1) {
296
- const $ = p[_ + a];
297
- location.hash = $ ? `#/${$.slug}` : "";
350
+ }, [n, _, v]);
351
+ const E = I(
352
+ (i) => {
353
+ const c = location.hash.replace(/^#\/?/, ""), N = w.findIndex(({ slug: x }) => x === c);
354
+ if (N !== -1) {
355
+ const x = w[N + i];
356
+ location.hash = x ? `#/${x.slug}` : "";
298
357
  return;
299
358
  }
300
- if (i === "") {
301
- a === 1 && p[0] && (location.hash = `#/${p[0].slug}`);
359
+ if (c === "") {
360
+ i === 1 && w[0] && (location.hash = `#/${w[0].slug}`);
302
361
  return;
303
362
  }
304
- const E = a === 1 ? f[0] : f[f.length - 1];
305
- E && (location.hash = `#/${E.slug}`);
363
+ const L = i === 1 ? _[0] : _[_.length - 1];
364
+ L && (location.hash = `#/${L.slug}`);
306
365
  },
307
- [f, p]
308
- );
309
- N(() => {
310
- const a = (i) => {
311
- i.key !== "ArrowRight" && i.key !== "ArrowLeft" || (i.preventDefault(), b(i.key === "ArrowRight" ? 1 : -1));
312
- };
313
- return window.addEventListener("keydown", a), () => window.removeEventListener("keydown", a);
314
- }, [b]);
315
- const y = T(() => {
366
+ [_, w]
367
+ ), S = I(() => {
316
368
  location.hash ? location.hash = "" : location.pathname !== "/" && (location.href = "/");
317
369
  }, []);
318
- N(() => {
319
- const a = (i) => {
320
- i.key === "Escape" && y();
370
+ k(() => {
371
+ const i = (c) => {
372
+ c.key === "Escape" && S();
321
373
  };
322
- return window.addEventListener("keydown", a), () => window.removeEventListener("keydown", a);
323
- }, [y]);
324
- const L = f.find(({ slug: a }) => a === h), G = L ? `${l}audio/${g.id}/${L.section.id}.wav` : void 0, { playing: V, toggle: j, stop: R } = le(G, () => {
325
- const a = location.hash.replace(/^#\/?/, ""), i = p.findIndex(({ slug: E }) => E === a), _ = p[i + 1];
326
- _ ? location.hash = `#/${_.slug}` : R();
374
+ return window.addEventListener("keydown", i), () => window.removeEventListener("keydown", i);
375
+ }, [S]);
376
+ const $ = _.find(({ slug: i }) => i === m), V = $ ? `${d}audio/${v.id}/${$.section.id}.wav` : void 0, { playing: q, toggle: K, stop: W, audio: y } = ue(V, () => {
377
+ const i = location.hash.replace(/^#\/?/, ""), c = w.findIndex(({ slug: L }) => L === i), N = w[c + 1];
378
+ N ? location.hash = `#/${N.slug}` : W();
327
379
  });
328
- if (N(() => {
329
- const a = (i) => {
330
- if (i.key !== " " && i.code !== "Space") return;
331
- const _ = i.target;
332
- _ && (_.tagName === "INPUT" || _.tagName === "TEXTAREA" || _.isContentEditable) || (i.preventDefault(), j());
380
+ if (k(() => {
381
+ const i = (c) => {
382
+ if (c.key !== "ArrowRight" && c.key !== "ArrowLeft") return;
383
+ const N = c.target;
384
+ if (N && (N.tagName === "INPUT" || N.tagName === "TEXTAREA" || N.isContentEditable)) return;
385
+ const L = c.key === "ArrowRight" ? 1 : -1;
386
+ c.preventDefault();
387
+ const x = (y == null ? void 0 : y.duration) ?? 0;
388
+ if (y && !c.shiftKey && Number.isFinite(x) && x > 0) {
389
+ y.currentTime = Math.min(x, Math.max(0, y.currentTime + L * z));
390
+ return;
391
+ }
392
+ E(L);
393
+ };
394
+ return window.addEventListener("keydown", i), () => window.removeEventListener("keydown", i);
395
+ }, [E, y]), k(() => {
396
+ const i = (c) => {
397
+ if (c.key !== " " && c.code !== "Space") return;
398
+ const N = c.target;
399
+ N && (N.tagName === "INPUT" || N.tagName === "TEXTAREA" || N.isContentEditable) || (c.preventDefault(), K());
333
400
  };
334
- return window.addEventListener("keydown", a), () => window.removeEventListener("keydown", a);
335
- }), !h)
336
- return /* @__PURE__ */ e(
337
- ie,
401
+ return window.addEventListener("keydown", i), () => window.removeEventListener("keydown", i);
402
+ }), !m)
403
+ return /* @__PURE__ */ t(
404
+ he,
338
405
  {
339
406
  courses: Object.values(s),
340
- subject: n,
341
- kind: u,
342
- actions: t ? null : /* @__PURE__ */ e(ue, { theme: w, onToggle: k })
407
+ subject: e,
408
+ kind: g,
409
+ actions: n ? null : /* @__PURE__ */ t(pe, { theme: u, onToggle: r })
343
410
  }
344
411
  );
345
- let A, B = null;
346
- if (L) {
347
- const a = f.findIndex(({ slug: i }) => i === h);
348
- A = /* @__PURE__ */ e(
349
- ae,
412
+ let C, j = null;
413
+ if ($) {
414
+ const i = _.findIndex(({ slug: c }) => c === m);
415
+ C = /* @__PURE__ */ t(
416
+ oe,
350
417
  {
351
- section: L.section,
418
+ section: $.section,
352
419
  getScene: o,
353
- capture: t,
354
- eyebrow: `${r ?? n.toUpperCase()} · ${g.id.toUpperCase()}`,
355
- index: a,
356
- total: f.length,
357
- onHome: y,
358
- onPrev: () => b(-1),
359
- onNext: () => b(1),
360
- narrating: V,
361
- onToggleNarration: j,
362
- theme: w,
363
- onToggleTheme: t ? void 0 : k
420
+ capture: n,
421
+ eyebrow: `${a ?? e.toUpperCase()} · ${v.id.toUpperCase()}`,
422
+ index: i,
423
+ total: _.length,
424
+ onHome: S,
425
+ onPrev: () => E(-1),
426
+ onNext: () => E(1),
427
+ narrating: q,
428
+ onToggleNarration: K,
429
+ narrationAudio: y,
430
+ theme: u,
431
+ onToggleTheme: n ? void 0 : r
364
432
  }
365
433
  );
366
434
  } else {
367
- const a = o(h);
368
- A = a ? /* @__PURE__ */ e("div", { className: "stage", children: /* @__PURE__ */ e(H, { scene: a, theme: w }) }) : /* @__PURE__ */ d("div", { className: "stage stage--missing", children: [
435
+ const i = o(m);
436
+ C = i ? /* @__PURE__ */ t("div", { className: "stage", children: /* @__PURE__ */ t(R, { scene: i, theme: u }) }) : /* @__PURE__ */ h("div", { className: "stage stage--missing", children: [
369
437
  "no scene or slug: ",
370
- h
371
- ] }), B = t ? null : /* @__PURE__ */ e("button", { className: "brand-home", onClick: y, "aria-label": "Back to catalog (Esc)", title: "Back to catalog (Esc)", children: "GraphL" });
438
+ m
439
+ ] }), j = n ? null : /* @__PURE__ */ t("button", { className: "brand-home", onClick: S, "aria-label": "Back to catalog (Esc)", title: "Back to catalog (Esc)", children: "GraphL" });
372
440
  }
373
- return /* @__PURE__ */ d(M, { children: [
374
- B,
375
- A
441
+ return /* @__PURE__ */ h(H, { children: [
442
+ j,
443
+ C
376
444
  ] });
377
445
  }
378
446
  export {
379
- ve as ConceptApp,
380
- oe as SiteHeader,
381
- z as allSections,
382
- K as slugOf
447
+ ye as ConceptApp,
448
+ de as SiteHeader,
449
+ D as allSections,
450
+ X as slugOf
383
451
  };
384
452
  //# sourceMappingURL=index.js.map