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