@graphlearning/shell 0.10.0 → 0.10.2

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