@graphlearning/shell 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/dist/ConceptApp.d.ts +2 -1
- package/dist/CourseIndex.d.ts +2 -1
- package/dist/SiteHeader.d.ts +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +247 -198
- package/dist/index.js.map +1 -1
- package/dist/styles.css +171 -17
- package/package.json +24 -4
- package/scripts/_paths.mjs +72 -0
- package/scripts/capture-shots.mjs +17 -0
- package/scripts/gen-audio-manifest.mjs +57 -0
- package/scripts/gen-descriptions.mjs +155 -0
- package/scripts/record-course.mjs +316 -0
- package/scripts/record-reels.mjs +247 -0
- package/scripts/shots-4k.mjs +54 -0
- package/scripts/thumb-template.html +52 -0
- package/scripts/thumb.mjs +232 -0
package/README.md
CHANGED
|
@@ -29,6 +29,31 @@ import { getScene } from './scenes'
|
|
|
29
29
|
That is the whole integration. A content repo's `src/` is then `content/`, `scenes/`, `main.tsx`
|
|
30
30
|
and `theme.css`.
|
|
31
31
|
|
|
32
|
+
## The site bar
|
|
33
|
+
|
|
34
|
+
`ConceptApp` renders the platform's header — brand, section nav, an empty actions slot — above its
|
|
35
|
+
catalog page, and nowhere else. It is a port of the bar at graphl.in, class for class, so a reader
|
|
36
|
+
crossing from the catalog into a concept app never leaves "the site". Nothing to pass: a content
|
|
37
|
+
repo is a course, which is the default.
|
|
38
|
+
|
|
39
|
+
It is deliberately platform-coloured, not `--brand`: looking identical on all eight sites is the
|
|
40
|
+
whole reason it exists. It renders on the catalog page ONLY — never on a section, which is a video
|
|
41
|
+
frame — so the recorder cannot see it.
|
|
42
|
+
|
|
43
|
+
An app that is not a course (`python-lab` is a lab) says so, and one that is not a `ConceptApp` at
|
|
44
|
+
all imports the bar directly — the one part of this package that is exported on its own:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<ConceptApp … kind="labs" /> // marks Labs as the current nav item
|
|
48
|
+
|
|
49
|
+
import { SiteHeader } from '@graphlearning/shell'
|
|
50
|
+
<SiteHeader kind="labs" actions={<ThemeToggle />} />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`ui-graphl` is buildless vanilla and this is a React package, so the bar exists twice by necessity.
|
|
54
|
+
One design, two implementations — **when you change one, change the other**, or they drift and the
|
|
55
|
+
seam shows on every crossing.
|
|
56
|
+
|
|
32
57
|
## The three contracts
|
|
33
58
|
|
|
34
59
|
**1. Import order.** The three stylesheets cascade: engine, shell, theme. The theme must come last
|
|
@@ -77,3 +102,43 @@ regression is visible here before it reaches any content repo.
|
|
|
77
102
|
second react-flow store) inside this package. The content repo pins the engine itself and must get
|
|
78
103
|
exactly one copy — and a wide peer range (`>=0.5.0 <1.0.0`) means an engine minor does not force a
|
|
79
104
|
republish of this package.
|
|
105
|
+
|
|
106
|
+
## The toolchain
|
|
107
|
+
|
|
108
|
+
The capture / record / publish scripts ship with this package too — they were byte-identical in all
|
|
109
|
+
seven repos and they drive the route contract above, so they version with the shell that defines it.
|
|
110
|
+
They are `bin` entries, wired through each repo's `npm run`:
|
|
111
|
+
|
|
112
|
+
| command | what it does |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `npm run record` | one course → a 3840×2160 MP4 |
|
|
115
|
+
| `npm run record:reels` | one course → portrait reels |
|
|
116
|
+
| `npm run shots:4k` | one PNG per section, no ffmpeg |
|
|
117
|
+
| `npm run thumb` | a branded YouTube thumbnail |
|
|
118
|
+
| `npm run gen:desc` | video descriptions + chapters |
|
|
119
|
+
| `npm run gen:audio` | the narration manifest |
|
|
120
|
+
|
|
121
|
+
**Where things live.** Scripts resolve two roots explicitly (`scripts/_paths.mjs`), because from
|
|
122
|
+
`node_modules` they can no longer use their own directory:
|
|
123
|
+
|
|
124
|
+
- `repoDir` = `process.cwd()` — the content repo. `npm run` sets cwd to the package root.
|
|
125
|
+
- `dataDir` = `repoDir/scripts` — the repo's script data (`concept.json`, `titles.json`,
|
|
126
|
+
`audio-manifest.json`) **and every output** (`out/`, `segments/`, `.tmp/`), at exactly the paths
|
|
127
|
+
they used before the extraction, so existing `.gitignore` entries still hold.
|
|
128
|
+
- `pkgDir` — machinery that ships here (`thumb-template.html`).
|
|
129
|
+
|
|
130
|
+
**Peer resolution.** `puppeteer` and `esbuild` are optional peers, loaded via `loadPeer()` which
|
|
131
|
+
resolves them **from the repo**, not from this package. A bare `import('puppeteer')` resolves by
|
|
132
|
+
walking up from the importing file, which under a local `file:` install lands in `ui-shell/` and
|
|
133
|
+
never reaches the repo's `node_modules`.
|
|
134
|
+
|
|
135
|
+
**`scripts/concept.json`** is the per-repo publishing identity — the values that used to be
|
|
136
|
+
hardcoded defaults in each repo's copy, which is precisely why they drifted:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{ "concept": "Apache Spark", "kicker": "SPARK", "site": "https://graphl.in",
|
|
140
|
+
"appPath": "/apache-spark", "hashtags": "#ApacheSpark …", "panelBg": "radial-gradient(…)" }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`concept` is the name used in descriptions; `kicker` is the thumbnail panel's label and defaults to
|
|
144
|
+
`concept` — they differ only where a long name will not fit the panel (apache-spark).
|
package/dist/ConceptApp.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Course, SceneLookup } from './types';
|
|
2
|
-
export declare function ConceptApp({ subject, eyebrow, courses: COURSES, getScene, audioBase, }: {
|
|
2
|
+
export declare function ConceptApp({ subject, eyebrow, courses: COURSES, getScene, audioBase, kind, }: {
|
|
3
3
|
subject: string;
|
|
4
4
|
courses: Record<string, Course>;
|
|
5
5
|
eyebrow?: string;
|
|
6
6
|
getScene: SceneLookup;
|
|
7
7
|
audioBase?: string;
|
|
8
|
+
kind?: string;
|
|
8
9
|
}): import("react").JSX.Element;
|
package/dist/CourseIndex.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,269 +1,318 @@
|
|
|
1
|
-
import { jsx as e, jsxs as
|
|
2
|
-
import { useRef as
|
|
3
|
-
import { SceneView as
|
|
4
|
-
import { Home as
|
|
1
|
+
import { jsx as e, jsxs as i, Fragment as I } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as L, useState as b, useLayoutEffect as D, useEffect as N, useMemo as E, useCallback as z } from "react";
|
|
3
|
+
import { SceneView as S } from "@graphlearning/flow";
|
|
4
|
+
import { Home as G, Volume2 as K, VolumeX as V, ChevronLeft as R, ChevronRight as W } from "lucide-react";
|
|
5
5
|
import X from "react-markdown";
|
|
6
6
|
import F from "remark-gfm";
|
|
7
|
-
function
|
|
8
|
-
return `${a.id}-${
|
|
7
|
+
function j(a, c) {
|
|
8
|
+
return `${a.id}-${c.id}`;
|
|
9
9
|
}
|
|
10
|
-
function
|
|
11
|
-
return a.sections.map((
|
|
10
|
+
function P(a) {
|
|
11
|
+
return a.sections.map((c) => ({ section: c, slug: j(a, c) }));
|
|
12
12
|
}
|
|
13
|
-
const
|
|
13
|
+
const B = 806;
|
|
14
14
|
function O() {
|
|
15
|
-
const a =
|
|
16
|
-
return
|
|
17
|
-
const
|
|
18
|
-
if (!
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
return
|
|
23
|
-
}, []), { ref: a, zoom:
|
|
15
|
+
const a = L(null), [c, s] = b(1);
|
|
16
|
+
return D(() => {
|
|
17
|
+
const l = a.current;
|
|
18
|
+
if (!l) return;
|
|
19
|
+
const h = () => s(l.clientWidth / B);
|
|
20
|
+
h();
|
|
21
|
+
const f = new ResizeObserver(h);
|
|
22
|
+
return f.observe(l), () => f.disconnect();
|
|
23
|
+
}, []), { ref: a, zoom: c };
|
|
24
24
|
}
|
|
25
|
-
function
|
|
26
|
-
const { ref:
|
|
27
|
-
return /* @__PURE__ */ e("aside", { ref:
|
|
25
|
+
function Z({ slide: a, open: c }) {
|
|
26
|
+
const { ref: s, zoom: l } = O();
|
|
27
|
+
return /* @__PURE__ */ e("aside", { ref: s, className: `slide-panel${c ? " slide-panel--open" : ""}`, children: /* @__PURE__ */ e("div", { className: "slide-panel__scaler", style: { width: B, zoom: l }, children: /* @__PURE__ */ e(X, { remarkPlugins: [F], children: a }) }) });
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function q({
|
|
30
30
|
section: a,
|
|
31
|
-
getScene:
|
|
32
|
-
capture:
|
|
33
|
-
eyebrow:
|
|
34
|
-
index:
|
|
35
|
-
total:
|
|
36
|
-
onHome:
|
|
37
|
-
onPrev:
|
|
38
|
-
onNext:
|
|
39
|
-
narrating:
|
|
40
|
-
onToggleNarration:
|
|
31
|
+
getScene: c,
|
|
32
|
+
capture: s = !1,
|
|
33
|
+
eyebrow: l,
|
|
34
|
+
index: h = 0,
|
|
35
|
+
total: f = 0,
|
|
36
|
+
onHome: o,
|
|
37
|
+
onPrev: _,
|
|
38
|
+
onNext: u,
|
|
39
|
+
narrating: n = !1,
|
|
40
|
+
onToggleNarration: p
|
|
41
41
|
}) {
|
|
42
|
-
const [
|
|
43
|
-
return
|
|
44
|
-
/* @__PURE__ */
|
|
45
|
-
|
|
42
|
+
const [d, m] = b(!1), v = c(a.scene);
|
|
43
|
+
return v ? /* @__PURE__ */ i("div", { className: "stage stage--section", children: [
|
|
44
|
+
/* @__PURE__ */ i("header", { className: "reel-head", children: [
|
|
45
|
+
l && /* @__PURE__ */ e("button", { className: "reel-head__eyebrow", onClick: o, "aria-label": "Back to catalog (Esc)", title: "Back to catalog (Esc)", children: l }),
|
|
46
46
|
/* @__PURE__ */ e("h1", { className: "reel-head__title", children: a.title })
|
|
47
47
|
] }),
|
|
48
|
-
!
|
|
49
|
-
/* @__PURE__ */
|
|
50
|
-
/* @__PURE__ */ e("button", { className: "reel-foot__ctrl", onClick:
|
|
48
|
+
!s && /* @__PURE__ */ i("footer", { className: "reel-foot reel-foot--controls", children: [
|
|
49
|
+
/* @__PURE__ */ i("span", { className: "reel-foot__grp", children: [
|
|
50
|
+
/* @__PURE__ */ e("button", { className: "reel-foot__ctrl", onClick: o, "aria-label": "Back to catalog (Esc)", title: "Back to catalog (Esc)", children: /* @__PURE__ */ e(G, { size: 17 }) }),
|
|
51
51
|
/* @__PURE__ */ e(
|
|
52
52
|
"button",
|
|
53
53
|
{
|
|
54
54
|
className: "reel-foot__ctrl",
|
|
55
|
-
onClick:
|
|
56
|
-
"aria-label":
|
|
57
|
-
title:
|
|
58
|
-
children:
|
|
55
|
+
onClick: p,
|
|
56
|
+
"aria-label": n ? "Pause narration (Space)" : "Play narration (Space)",
|
|
57
|
+
title: n ? "Pause narration (Space)" : "Play narration (Space)",
|
|
58
|
+
children: n ? /* @__PURE__ */ e(K, { size: 18 }) : /* @__PURE__ */ e(V, { size: 18 })
|
|
59
59
|
}
|
|
60
60
|
)
|
|
61
61
|
] }),
|
|
62
|
-
/* @__PURE__ */
|
|
63
|
-
/* @__PURE__ */ e("button", { className: "reel-foot__ctrl", onClick:
|
|
64
|
-
/* @__PURE__ */
|
|
65
|
-
|
|
62
|
+
/* @__PURE__ */ i("span", { className: "reel-foot__nav", children: [
|
|
63
|
+
/* @__PURE__ */ e("button", { className: "reel-foot__ctrl", onClick: _, "aria-label": "Previous section (←)", title: "Previous section (←)", children: /* @__PURE__ */ e(R, { size: 19 }) }),
|
|
64
|
+
/* @__PURE__ */ i("span", { className: "reel-foot__count reel-foot__count--live", children: [
|
|
65
|
+
h + 1,
|
|
66
66
|
" / ",
|
|
67
|
-
|
|
67
|
+
f
|
|
68
68
|
] }),
|
|
69
|
-
/* @__PURE__ */ e("button", { className: "reel-foot__ctrl", onClick:
|
|
69
|
+
/* @__PURE__ */ e("button", { className: "reel-foot__ctrl", onClick: u, "aria-label": "Next section (→)", title: "Next section (→)", children: /* @__PURE__ */ e(W, { size: 19 }) })
|
|
70
70
|
] })
|
|
71
71
|
] }),
|
|
72
|
-
/* @__PURE__ */ e("div", { className: "scene-area", children: /* @__PURE__ */ e(
|
|
73
|
-
!
|
|
72
|
+
/* @__PURE__ */ e("div", { className: "scene-area", children: /* @__PURE__ */ e(S, { scene: v, focusId: a.focus }) }),
|
|
73
|
+
!s && /* @__PURE__ */ e(
|
|
74
74
|
"button",
|
|
75
75
|
{
|
|
76
76
|
className: "slide-toggle",
|
|
77
|
-
onClick: () =>
|
|
78
|
-
"aria-label":
|
|
79
|
-
children:
|
|
77
|
+
onClick: () => m((w) => !w),
|
|
78
|
+
"aria-label": d ? "Hide slide" : "Show slide",
|
|
79
|
+
children: d ? "›" : "‹"
|
|
80
80
|
}
|
|
81
81
|
),
|
|
82
|
-
/* @__PURE__ */ e(
|
|
83
|
-
] }) : /* @__PURE__ */
|
|
82
|
+
/* @__PURE__ */ e(Z, { slide: a.slide, open: d })
|
|
83
|
+
] }) : /* @__PURE__ */ i("div", { className: "stage stage--missing", children: [
|
|
84
84
|
"no scene: ",
|
|
85
85
|
a.scene
|
|
86
86
|
] });
|
|
87
87
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return /* @__PURE__ */ h("li", { className: `idx-card${o ? " idx-card--open" : ""}`, children: [
|
|
101
|
-
/* @__PURE__ */ h(
|
|
102
|
-
"button",
|
|
103
|
-
{
|
|
104
|
-
className: "idx-card__head",
|
|
105
|
-
onClick: () => p(c.id),
|
|
106
|
-
"aria-expanded": o,
|
|
107
|
-
children: [
|
|
108
|
-
/* @__PURE__ */ e("span", { className: "idx-card__num", children: String(f + 1).padStart(2, "0") }),
|
|
109
|
-
/* @__PURE__ */ e("span", { className: "idx-card__title", children: c.title }),
|
|
110
|
-
/* @__PURE__ */ h("span", { className: "idx-card__count", children: [
|
|
111
|
-
l.length,
|
|
112
|
-
" sections"
|
|
113
|
-
] }),
|
|
114
|
-
/* @__PURE__ */ e("span", { className: "idx-card__toggle", "aria-hidden": "true", children: "⌄" })
|
|
115
|
-
]
|
|
116
|
-
}
|
|
117
|
-
),
|
|
118
|
-
o && /* @__PURE__ */ e("ol", { className: "idx-sec", children: l.map(({ section: t, slug: u }, m) => /* @__PURE__ */ e("li", { className: "idx-sec__row", children: /* @__PURE__ */ h("a", { href: `#/${u}`, children: [
|
|
119
|
-
/* @__PURE__ */ e("span", { className: "idx-sec__num", children: m + 1 }),
|
|
120
|
-
/* @__PURE__ */ e("span", { className: "idx-sec__title", children: t.title }),
|
|
121
|
-
/* @__PURE__ */ e("span", { className: "idx-sec__chev", "aria-hidden": "true", children: "›" })
|
|
122
|
-
] }) }, u)) })
|
|
123
|
-
] }, c.id);
|
|
124
|
-
}) }),
|
|
125
|
-
/* @__PURE__ */ e("footer", { className: "idx__foot", children: /* @__PURE__ */ h("span", { className: "idx__hint", children: [
|
|
126
|
-
/* @__PURE__ */ e("kbd", { children: "→" }),
|
|
127
|
-
" next · ",
|
|
128
|
-
/* @__PURE__ */ e("kbd", { children: "←" }),
|
|
129
|
-
" prev · ",
|
|
130
|
-
/* @__PURE__ */ e("kbd", { children: "Space" }),
|
|
131
|
-
" narration"
|
|
132
|
-
] }) })
|
|
88
|
+
const J = [
|
|
89
|
+
{ id: "courses", label: "Courses" },
|
|
90
|
+
{ id: "labs", label: "Labs" },
|
|
91
|
+
{ id: "coach", label: "Coach" }
|
|
92
|
+
];
|
|
93
|
+
function Q() {
|
|
94
|
+
return /* @__PURE__ */ i("svg", { className: "site__logo", width: "28", height: "28", viewBox: "0 0 512 512", "aria-hidden": "true", children: [
|
|
95
|
+
/* @__PURE__ */ e("rect", { width: "512", height: "512", rx: "80", fill: "#e8804f" }),
|
|
96
|
+
/* @__PURE__ */ i("g", { fill: "none", stroke: "#ffffff", strokeWidth: "58", strokeLinecap: "butt", strokeLinejoin: "miter", children: [
|
|
97
|
+
/* @__PURE__ */ e("path", { d: "M 373 200 A 130 130 0 1 0 373 312" }),
|
|
98
|
+
/* @__PURE__ */ e("path", { d: "M 283 312 L 373 312 L 373 410" })
|
|
99
|
+
] })
|
|
133
100
|
] });
|
|
134
101
|
}
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
102
|
+
function Y({
|
|
103
|
+
kind: a = "courses",
|
|
104
|
+
nav: c = J,
|
|
105
|
+
home: s = "/",
|
|
106
|
+
actions: l
|
|
107
|
+
}) {
|
|
108
|
+
return /* @__PURE__ */ e("header", { className: "site", children: /* @__PURE__ */ i("div", { className: "site__inner", children: [
|
|
109
|
+
/* @__PURE__ */ i("a", { className: "site__brand", href: s, children: [
|
|
110
|
+
/* @__PURE__ */ e(Q, {}),
|
|
111
|
+
/* @__PURE__ */ e("span", { className: "site__name", children: "GraphL" })
|
|
112
|
+
] }),
|
|
113
|
+
/* @__PURE__ */ e("nav", { className: "site__nav", "aria-label": "Sections", children: c.map((h) => /* @__PURE__ */ e(
|
|
114
|
+
"a",
|
|
115
|
+
{
|
|
116
|
+
className: "site__link",
|
|
117
|
+
href: `${s}#${h.id}`,
|
|
118
|
+
...h.id === a ? { "aria-current": "page" } : {},
|
|
119
|
+
children: h.label
|
|
120
|
+
},
|
|
121
|
+
h.id
|
|
122
|
+
)) }),
|
|
123
|
+
/* @__PURE__ */ e("div", { className: "site__actions", children: l })
|
|
124
|
+
] }) });
|
|
125
|
+
}
|
|
126
|
+
function U({
|
|
127
|
+
courses: a,
|
|
128
|
+
subject: c,
|
|
129
|
+
kind: s
|
|
130
|
+
}) {
|
|
131
|
+
const [l, h] = b(() => /* @__PURE__ */ new Set()), f = (o) => h((_) => {
|
|
132
|
+
const u = new Set(_);
|
|
133
|
+
return u.has(o) ? u.delete(o) : u.add(o), u;
|
|
134
|
+
});
|
|
135
|
+
return (
|
|
136
|
+
// The site bar is a SIBLING of .idx, not a child: it is full-bleed (its rule spans the
|
|
137
|
+
// viewport) while .idx is a 940px centred column. Both use the same width and gutters, so the
|
|
138
|
+
// brand lines up with the card edge below it.
|
|
139
|
+
/* @__PURE__ */ i(I, { children: [
|
|
140
|
+
/* @__PURE__ */ e(Y, { kind: s }),
|
|
141
|
+
/* @__PURE__ */ i("div", { className: "idx", children: [
|
|
142
|
+
/* @__PURE__ */ e("header", { className: "idx__head", children: /* @__PURE__ */ e("h1", { className: "idx__subject", children: c }) }),
|
|
143
|
+
a.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: a.map((o, _) => {
|
|
144
|
+
const u = P(o), n = l.has(o.id);
|
|
145
|
+
return /* @__PURE__ */ i("li", { className: `idx-card${n ? " idx-card--open" : ""}`, children: [
|
|
146
|
+
/* @__PURE__ */ i(
|
|
147
|
+
"button",
|
|
148
|
+
{
|
|
149
|
+
className: "idx-card__head",
|
|
150
|
+
onClick: () => f(o.id),
|
|
151
|
+
"aria-expanded": n,
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ e("span", { className: "idx-card__num", children: String(_ + 1).padStart(2, "0") }),
|
|
154
|
+
/* @__PURE__ */ e("span", { className: "idx-card__title", children: o.title }),
|
|
155
|
+
/* @__PURE__ */ i("span", { className: "idx-card__count", children: [
|
|
156
|
+
u.length,
|
|
157
|
+
" sections"
|
|
158
|
+
] }),
|
|
159
|
+
/* @__PURE__ */ e("span", { className: "idx-card__toggle", "aria-hidden": "true", children: "⌄" })
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
),
|
|
163
|
+
n && /* @__PURE__ */ e("ol", { className: "idx-sec", children: u.map(({ section: p, slug: d }, m) => /* @__PURE__ */ e("li", { className: "idx-sec__row", children: /* @__PURE__ */ i("a", { href: `#/${d}`, children: [
|
|
164
|
+
/* @__PURE__ */ e("span", { className: "idx-sec__num", children: m + 1 }),
|
|
165
|
+
/* @__PURE__ */ e("span", { className: "idx-sec__title", children: p.title }),
|
|
166
|
+
/* @__PURE__ */ e("span", { className: "idx-sec__chev", "aria-hidden": "true", children: "›" })
|
|
167
|
+
] }) }, d)) })
|
|
168
|
+
] }, o.id);
|
|
169
|
+
}) }),
|
|
170
|
+
/* @__PURE__ */ e("footer", { className: "idx__foot", children: /* @__PURE__ */ i("span", { className: "idx__hint", children: [
|
|
171
|
+
/* @__PURE__ */ e("kbd", { children: "→" }),
|
|
172
|
+
" next · ",
|
|
173
|
+
/* @__PURE__ */ e("kbd", { children: "←" }),
|
|
174
|
+
" prev · ",
|
|
175
|
+
/* @__PURE__ */ e("kbd", { children: "Space" }),
|
|
176
|
+
" narration"
|
|
177
|
+
] }) })
|
|
178
|
+
] })
|
|
179
|
+
] })
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
function ee(a, c) {
|
|
183
|
+
const s = L(null), l = L(c);
|
|
184
|
+
l.current = c;
|
|
185
|
+
const [h, f] = b(!1), o = L(h);
|
|
186
|
+
return o.current = h, N(() => {
|
|
187
|
+
const n = new Audio();
|
|
188
|
+
return n.addEventListener("ended", () => l.current()), n.addEventListener("error", () => f(!1)), s.current = n, () => n.pause();
|
|
189
|
+
}, []), N(() => {
|
|
190
|
+
const n = s.current;
|
|
191
|
+
n && (n.pause(), a ? (n.src = a, n.currentTime = 0, o.current && n.play().catch(() => {
|
|
192
|
+
})) : (n.removeAttribute("src"), n.load(), f(!1)));
|
|
193
|
+
}, [a]), { playing: h, toggle: () => {
|
|
194
|
+
const n = s.current;
|
|
195
|
+
!n || !n.getAttribute("src") || (o.current ? (n.pause(), f(!1)) : (n.play().catch(() => {
|
|
196
|
+
}), f(!0)));
|
|
150
197
|
}, stop: () => {
|
|
151
|
-
var
|
|
152
|
-
(
|
|
198
|
+
var n;
|
|
199
|
+
(n = s.current) == null || n.pause(), f(!1);
|
|
153
200
|
} };
|
|
154
201
|
}
|
|
155
|
-
function
|
|
202
|
+
function ce({
|
|
156
203
|
subject: a,
|
|
157
|
-
eyebrow:
|
|
158
|
-
courses:
|
|
159
|
-
getScene:
|
|
160
|
-
audioBase:
|
|
204
|
+
eyebrow: c,
|
|
205
|
+
courses: s,
|
|
206
|
+
getScene: l,
|
|
207
|
+
audioBase: h = "/",
|
|
208
|
+
kind: f = "courses"
|
|
161
209
|
}) {
|
|
162
|
-
const [
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
return window.addEventListener("hashchange",
|
|
210
|
+
const [o, _] = b(() => location.hash);
|
|
211
|
+
N(() => {
|
|
212
|
+
const t = () => _(location.hash);
|
|
213
|
+
return window.addEventListener("hashchange", t), () => window.removeEventListener("hashchange", t);
|
|
166
214
|
}, []);
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
for (const
|
|
170
|
-
if (
|
|
171
|
-
return Object.values(
|
|
172
|
-
}, [
|
|
173
|
-
|
|
174
|
-
if (
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
}, [
|
|
178
|
-
|
|
179
|
-
plan: () =>
|
|
180
|
-
slug:
|
|
181
|
-
course:
|
|
215
|
+
const u = new URLSearchParams(location.search).get("capture") === "1", n = o.replace(/^#\/?/, ""), p = E(() => {
|
|
216
|
+
if (s[n]) return s[n];
|
|
217
|
+
for (const t of Object.values(s))
|
|
218
|
+
if (t.sections.some((r) => j(t, r) === n)) return t;
|
|
219
|
+
return Object.values(s)[0];
|
|
220
|
+
}, [n, s]), d = E(() => P(p), [p]), m = E(() => Object.values(s).flatMap(P), [s]);
|
|
221
|
+
N(() => {
|
|
222
|
+
if (u || !s[n]) return;
|
|
223
|
+
const t = d[0];
|
|
224
|
+
t && (history.replaceState(null, "", `${location.pathname}${location.search}#/${t.slug}`), _(`#/${t.slug}`));
|
|
225
|
+
}, [u, n, d]), N(() => {
|
|
226
|
+
u && (window.__scene = {
|
|
227
|
+
plan: () => d.map(({ slug: t, section: r }) => ({
|
|
228
|
+
slug: t,
|
|
229
|
+
course: p.id,
|
|
182
230
|
// audio lives at audio/<course>/<id>.wav
|
|
183
|
-
id:
|
|
184
|
-
scene:
|
|
185
|
-
focus:
|
|
231
|
+
id: r.id,
|
|
232
|
+
scene: r.scene,
|
|
233
|
+
focus: r.focus ?? null
|
|
186
234
|
}))
|
|
187
235
|
});
|
|
188
|
-
}, [
|
|
189
|
-
const v =
|
|
190
|
-
(
|
|
191
|
-
const
|
|
236
|
+
}, [u, d, p]);
|
|
237
|
+
const v = z(
|
|
238
|
+
(t) => {
|
|
239
|
+
const r = location.hash.replace(/^#\/?/, ""), g = m.findIndex(({ slug: y }) => y === r);
|
|
192
240
|
if (g !== -1) {
|
|
193
|
-
const
|
|
194
|
-
location.hash =
|
|
241
|
+
const y = m[g + t];
|
|
242
|
+
location.hash = y ? `#/${y.slug}` : "";
|
|
195
243
|
return;
|
|
196
244
|
}
|
|
197
|
-
if (
|
|
198
|
-
|
|
245
|
+
if (r === "") {
|
|
246
|
+
t === 1 && m[0] && (location.hash = `#/${m[0].slug}`);
|
|
199
247
|
return;
|
|
200
248
|
}
|
|
201
|
-
const
|
|
202
|
-
|
|
249
|
+
const k = t === 1 ? d[0] : d[d.length - 1];
|
|
250
|
+
k && (location.hash = `#/${k.slug}`);
|
|
203
251
|
},
|
|
204
|
-
[
|
|
252
|
+
[d, m]
|
|
205
253
|
);
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
254
|
+
N(() => {
|
|
255
|
+
const t = (r) => {
|
|
256
|
+
r.key !== "ArrowRight" && r.key !== "ArrowLeft" || (r.preventDefault(), v(r.key === "ArrowRight" ? 1 : -1));
|
|
209
257
|
};
|
|
210
|
-
return window.addEventListener("keydown",
|
|
258
|
+
return window.addEventListener("keydown", t), () => window.removeEventListener("keydown", t);
|
|
211
259
|
}, [v]);
|
|
212
|
-
const
|
|
260
|
+
const w = z(() => {
|
|
213
261
|
location.hash ? location.hash = "" : location.pathname !== "/" && (location.href = "/");
|
|
214
262
|
}, []);
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
|
|
263
|
+
N(() => {
|
|
264
|
+
const t = (r) => {
|
|
265
|
+
r.key === "Escape" && w();
|
|
218
266
|
};
|
|
219
|
-
return window.addEventListener("keydown",
|
|
220
|
-
}, [
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
g ? location.hash = `#/${g.slug}` :
|
|
267
|
+
return window.addEventListener("keydown", t), () => window.removeEventListener("keydown", t);
|
|
268
|
+
}, [w]);
|
|
269
|
+
const x = d.find(({ slug: t }) => t === n), H = x ? `${h}audio/${p.id}/${x.section.id}.wav` : void 0, { playing: M, toggle: A, stop: T } = ee(H, () => {
|
|
270
|
+
const t = location.hash.replace(/^#\/?/, ""), r = m.findIndex(({ slug: k }) => k === t), g = m[r + 1];
|
|
271
|
+
g ? location.hash = `#/${g.slug}` : T();
|
|
224
272
|
});
|
|
225
|
-
if (
|
|
226
|
-
const
|
|
227
|
-
if (
|
|
228
|
-
const g =
|
|
229
|
-
g && (g.tagName === "INPUT" || g.tagName === "TEXTAREA" || g.isContentEditable) || (
|
|
273
|
+
if (N(() => {
|
|
274
|
+
const t = (r) => {
|
|
275
|
+
if (r.key !== " " && r.code !== "Space") return;
|
|
276
|
+
const g = r.target;
|
|
277
|
+
g && (g.tagName === "INPUT" || g.tagName === "TEXTAREA" || g.isContentEditable) || (r.preventDefault(), A());
|
|
230
278
|
};
|
|
231
|
-
return window.addEventListener("keydown",
|
|
232
|
-
}), !
|
|
233
|
-
let $,
|
|
234
|
-
if (
|
|
235
|
-
const
|
|
279
|
+
return window.addEventListener("keydown", t), () => window.removeEventListener("keydown", t);
|
|
280
|
+
}), !n) return /* @__PURE__ */ e(U, { courses: Object.values(s), subject: a, kind: f });
|
|
281
|
+
let $, C = null;
|
|
282
|
+
if (x) {
|
|
283
|
+
const t = d.findIndex(({ slug: r }) => r === n);
|
|
236
284
|
$ = /* @__PURE__ */ e(
|
|
237
|
-
|
|
285
|
+
q,
|
|
238
286
|
{
|
|
239
|
-
section:
|
|
240
|
-
getScene:
|
|
241
|
-
capture:
|
|
242
|
-
eyebrow: `${
|
|
243
|
-
index:
|
|
244
|
-
total:
|
|
245
|
-
onHome:
|
|
287
|
+
section: x.section,
|
|
288
|
+
getScene: l,
|
|
289
|
+
capture: u,
|
|
290
|
+
eyebrow: `${c ?? a.toUpperCase()} · ${p.id.toUpperCase()}`,
|
|
291
|
+
index: t,
|
|
292
|
+
total: d.length,
|
|
293
|
+
onHome: w,
|
|
246
294
|
onPrev: () => v(-1),
|
|
247
295
|
onNext: () => v(1),
|
|
248
|
-
narrating:
|
|
249
|
-
onToggleNarration:
|
|
296
|
+
narrating: M,
|
|
297
|
+
onToggleNarration: A
|
|
250
298
|
}
|
|
251
299
|
);
|
|
252
300
|
} else {
|
|
253
|
-
const
|
|
254
|
-
$ =
|
|
301
|
+
const t = l(n);
|
|
302
|
+
$ = t ? /* @__PURE__ */ e("div", { className: "stage", children: /* @__PURE__ */ e(S, { scene: t }) }) : /* @__PURE__ */ i("div", { className: "stage stage--missing", children: [
|
|
255
303
|
"no scene or slug: ",
|
|
256
|
-
|
|
257
|
-
] }),
|
|
304
|
+
n
|
|
305
|
+
] }), C = u ? null : /* @__PURE__ */ e("button", { className: "brand-home", onClick: w, "aria-label": "Back to catalog (Esc)", title: "Back to catalog (Esc)", children: "GraphL" });
|
|
258
306
|
}
|
|
259
|
-
return /* @__PURE__ */
|
|
260
|
-
|
|
307
|
+
return /* @__PURE__ */ i(I, { children: [
|
|
308
|
+
C,
|
|
261
309
|
$
|
|
262
310
|
] });
|
|
263
311
|
}
|
|
264
312
|
export {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
313
|
+
ce as ConceptApp,
|
|
314
|
+
Y as SiteHeader,
|
|
315
|
+
P as allSections,
|
|
316
|
+
j as slugOf
|
|
268
317
|
};
|
|
269
318
|
//# sourceMappingURL=index.js.map
|