@forwardimpact/pathway 0.1.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.
Files changed (227) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +104 -0
  3. package/app/commands/agent.js +430 -0
  4. package/app/commands/behaviour.js +61 -0
  5. package/app/commands/command-factory.js +211 -0
  6. package/app/commands/discipline.js +58 -0
  7. package/app/commands/driver.js +94 -0
  8. package/app/commands/grade.js +60 -0
  9. package/app/commands/index.js +20 -0
  10. package/app/commands/init.js +67 -0
  11. package/app/commands/interview.js +68 -0
  12. package/app/commands/job.js +157 -0
  13. package/app/commands/progress.js +77 -0
  14. package/app/commands/questions.js +179 -0
  15. package/app/commands/serve.js +143 -0
  16. package/app/commands/site.js +121 -0
  17. package/app/commands/skill.js +76 -0
  18. package/app/commands/stage.js +129 -0
  19. package/app/commands/track.js +70 -0
  20. package/app/components/action-buttons.js +66 -0
  21. package/app/components/behaviour-profile.js +53 -0
  22. package/app/components/builder.js +341 -0
  23. package/app/components/card.js +98 -0
  24. package/app/components/checklist.js +145 -0
  25. package/app/components/comparison-radar.js +237 -0
  26. package/app/components/detail.js +230 -0
  27. package/app/components/error-page.js +72 -0
  28. package/app/components/grid.js +109 -0
  29. package/app/components/list.js +120 -0
  30. package/app/components/modifier-table.js +142 -0
  31. package/app/components/nav.js +64 -0
  32. package/app/components/progression-table.js +320 -0
  33. package/app/components/radar-chart.js +102 -0
  34. package/app/components/skill-matrix.js +97 -0
  35. package/app/css/base.css +56 -0
  36. package/app/css/bundles/app.css +40 -0
  37. package/app/css/bundles/handout.css +43 -0
  38. package/app/css/bundles/slides.css +40 -0
  39. package/app/css/components/badges.css +215 -0
  40. package/app/css/components/buttons.css +101 -0
  41. package/app/css/components/forms.css +105 -0
  42. package/app/css/components/layout.css +209 -0
  43. package/app/css/components/nav.css +166 -0
  44. package/app/css/components/progress.css +166 -0
  45. package/app/css/components/states.css +82 -0
  46. package/app/css/components/surfaces.css +243 -0
  47. package/app/css/components/tables.css +362 -0
  48. package/app/css/components/typography.css +122 -0
  49. package/app/css/components/utilities.css +41 -0
  50. package/app/css/pages/agent-builder.css +391 -0
  51. package/app/css/pages/assessment-results.css +453 -0
  52. package/app/css/pages/detail.css +59 -0
  53. package/app/css/pages/interview-builder.css +148 -0
  54. package/app/css/pages/job-builder.css +134 -0
  55. package/app/css/pages/landing.css +92 -0
  56. package/app/css/pages/lifecycle.css +118 -0
  57. package/app/css/pages/progress-builder.css +274 -0
  58. package/app/css/pages/self-assessment.css +502 -0
  59. package/app/css/reset.css +50 -0
  60. package/app/css/tokens.css +153 -0
  61. package/app/css/views/handout.css +30 -0
  62. package/app/css/views/print.css +608 -0
  63. package/app/css/views/slide-animations.css +113 -0
  64. package/app/css/views/slide-base.css +330 -0
  65. package/app/css/views/slide-sections.css +597 -0
  66. package/app/css/views/slide-tables.css +275 -0
  67. package/app/formatters/agent/dom.js +540 -0
  68. package/app/formatters/agent/profile.js +133 -0
  69. package/app/formatters/agent/skill.js +58 -0
  70. package/app/formatters/behaviour/dom.js +91 -0
  71. package/app/formatters/behaviour/markdown.js +54 -0
  72. package/app/formatters/behaviour/shared.js +64 -0
  73. package/app/formatters/discipline/dom.js +187 -0
  74. package/app/formatters/discipline/markdown.js +87 -0
  75. package/app/formatters/discipline/shared.js +131 -0
  76. package/app/formatters/driver/dom.js +103 -0
  77. package/app/formatters/driver/shared.js +92 -0
  78. package/app/formatters/grade/dom.js +208 -0
  79. package/app/formatters/grade/markdown.js +94 -0
  80. package/app/formatters/grade/shared.js +86 -0
  81. package/app/formatters/index.js +50 -0
  82. package/app/formatters/interview/dom.js +97 -0
  83. package/app/formatters/interview/markdown.js +66 -0
  84. package/app/formatters/interview/shared.js +332 -0
  85. package/app/formatters/job/description.js +176 -0
  86. package/app/formatters/job/dom.js +411 -0
  87. package/app/formatters/job/markdown.js +102 -0
  88. package/app/formatters/progress/dom.js +135 -0
  89. package/app/formatters/progress/markdown.js +86 -0
  90. package/app/formatters/progress/shared.js +339 -0
  91. package/app/formatters/questions/json.js +43 -0
  92. package/app/formatters/questions/markdown.js +303 -0
  93. package/app/formatters/questions/shared.js +274 -0
  94. package/app/formatters/questions/yaml.js +76 -0
  95. package/app/formatters/shared.js +71 -0
  96. package/app/formatters/skill/dom.js +168 -0
  97. package/app/formatters/skill/markdown.js +109 -0
  98. package/app/formatters/skill/shared.js +125 -0
  99. package/app/formatters/stage/dom.js +135 -0
  100. package/app/formatters/stage/index.js +12 -0
  101. package/app/formatters/stage/shared.js +111 -0
  102. package/app/formatters/track/dom.js +128 -0
  103. package/app/formatters/track/markdown.js +105 -0
  104. package/app/formatters/track/shared.js +181 -0
  105. package/app/handout-main.js +421 -0
  106. package/app/handout.html +21 -0
  107. package/app/index.html +59 -0
  108. package/app/lib/card-mappers.js +173 -0
  109. package/app/lib/cli-output.js +270 -0
  110. package/app/lib/error-boundary.js +70 -0
  111. package/app/lib/errors.js +49 -0
  112. package/app/lib/form-controls.js +47 -0
  113. package/app/lib/job-cache.js +86 -0
  114. package/app/lib/markdown.js +114 -0
  115. package/app/lib/radar.js +866 -0
  116. package/app/lib/reactive.js +77 -0
  117. package/app/lib/render.js +212 -0
  118. package/app/lib/router-core.js +160 -0
  119. package/app/lib/router-pages.js +16 -0
  120. package/app/lib/router-slides.js +202 -0
  121. package/app/lib/state.js +148 -0
  122. package/app/lib/utils.js +14 -0
  123. package/app/lib/yaml-loader.js +327 -0
  124. package/app/main.js +213 -0
  125. package/app/model/agent.js +702 -0
  126. package/app/model/checklist.js +137 -0
  127. package/app/model/derivation.js +699 -0
  128. package/app/model/index-generator.js +71 -0
  129. package/app/model/interview.js +539 -0
  130. package/app/model/job.js +222 -0
  131. package/app/model/levels.js +591 -0
  132. package/app/model/loader.js +564 -0
  133. package/app/model/matching.js +858 -0
  134. package/app/model/modifiers.js +158 -0
  135. package/app/model/profile.js +266 -0
  136. package/app/model/progression.js +507 -0
  137. package/app/model/validation.js +1385 -0
  138. package/app/pages/agent-builder.js +823 -0
  139. package/app/pages/assessment-results.js +507 -0
  140. package/app/pages/behaviour.js +70 -0
  141. package/app/pages/discipline.js +71 -0
  142. package/app/pages/driver.js +106 -0
  143. package/app/pages/grade.js +117 -0
  144. package/app/pages/interview-builder.js +50 -0
  145. package/app/pages/interview.js +304 -0
  146. package/app/pages/job-builder.js +50 -0
  147. package/app/pages/job.js +58 -0
  148. package/app/pages/landing.js +305 -0
  149. package/app/pages/progress-builder.js +58 -0
  150. package/app/pages/progress.js +495 -0
  151. package/app/pages/self-assessment.js +729 -0
  152. package/app/pages/skill.js +113 -0
  153. package/app/pages/stage.js +231 -0
  154. package/app/pages/track.js +69 -0
  155. package/app/slide-main.js +360 -0
  156. package/app/slides/behaviour.js +38 -0
  157. package/app/slides/chapter.js +82 -0
  158. package/app/slides/discipline.js +40 -0
  159. package/app/slides/driver.js +39 -0
  160. package/app/slides/grade.js +32 -0
  161. package/app/slides/index.js +198 -0
  162. package/app/slides/interview.js +58 -0
  163. package/app/slides/job.js +55 -0
  164. package/app/slides/overview.js +126 -0
  165. package/app/slides/progress.js +83 -0
  166. package/app/slides/skill.js +40 -0
  167. package/app/slides/track.js +39 -0
  168. package/app/slides.html +56 -0
  169. package/app/types.js +147 -0
  170. package/bin/pathway.js +489 -0
  171. package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
  172. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
  173. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
  174. package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
  175. package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
  176. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
  177. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
  178. package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
  179. package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
  180. package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
  181. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
  182. package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
  183. package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
  184. package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
  185. package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
  186. package/examples/agents/.vscode/settings.json +8 -0
  187. package/examples/behaviours/_index.yaml +8 -0
  188. package/examples/behaviours/outcome_ownership.yaml +44 -0
  189. package/examples/behaviours/polymathic_knowledge.yaml +42 -0
  190. package/examples/behaviours/precise_communication.yaml +40 -0
  191. package/examples/behaviours/relentless_curiosity.yaml +38 -0
  192. package/examples/behaviours/systems_thinking.yaml +41 -0
  193. package/examples/capabilities/_index.yaml +8 -0
  194. package/examples/capabilities/business.yaml +251 -0
  195. package/examples/capabilities/delivery.yaml +352 -0
  196. package/examples/capabilities/people.yaml +100 -0
  197. package/examples/capabilities/reliability.yaml +318 -0
  198. package/examples/capabilities/scale.yaml +394 -0
  199. package/examples/disciplines/_index.yaml +5 -0
  200. package/examples/disciplines/data_engineering.yaml +76 -0
  201. package/examples/disciplines/software_engineering.yaml +76 -0
  202. package/examples/drivers.yaml +205 -0
  203. package/examples/framework.yaml +58 -0
  204. package/examples/grades.yaml +118 -0
  205. package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
  206. package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
  207. package/examples/questions/behaviours/precise_communication.yaml +55 -0
  208. package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
  209. package/examples/questions/behaviours/systems_thinking.yaml +53 -0
  210. package/examples/questions/skills/architecture_design.yaml +54 -0
  211. package/examples/questions/skills/cloud_platforms.yaml +48 -0
  212. package/examples/questions/skills/code_quality.yaml +49 -0
  213. package/examples/questions/skills/data_modeling.yaml +46 -0
  214. package/examples/questions/skills/devops.yaml +47 -0
  215. package/examples/questions/skills/full_stack_development.yaml +48 -0
  216. package/examples/questions/skills/sre_practices.yaml +44 -0
  217. package/examples/questions/skills/stakeholder_management.yaml +49 -0
  218. package/examples/questions/skills/team_collaboration.yaml +43 -0
  219. package/examples/questions/skills/technical_writing.yaml +43 -0
  220. package/examples/self-assessments.yaml +66 -0
  221. package/examples/stages.yaml +76 -0
  222. package/examples/tracks/_index.yaml +6 -0
  223. package/examples/tracks/manager.yaml +53 -0
  224. package/examples/tracks/platform.yaml +54 -0
  225. package/examples/tracks/sre.yaml +58 -0
  226. package/examples/vscode-settings.yaml +22 -0
  227. package/package.json +68 -0
@@ -0,0 +1,421 @@
1
+ /**
2
+ * Handout View Main Entry Point
3
+ *
4
+ * Displays all slides of a category on a single page for printing handouts.
5
+ * Routes:
6
+ * / - Index with links to all categories
7
+ * /driver - All driver slides
8
+ * /skill - All skill slides
9
+ * /behaviour - All behaviour slides
10
+ * /job - All discipline, track, and grade slides
11
+ */
12
+
13
+ import { setData, getState } from "./lib/state.js";
14
+ import { loadAllData } from "./lib/yaml-loader.js";
15
+ import {
16
+ div,
17
+ h1,
18
+ p,
19
+ a,
20
+ ul,
21
+ li,
22
+ span,
23
+ heading1,
24
+ heading2,
25
+ } from "./lib/render.js";
26
+
27
+ // Import model functions
28
+ import { getCapabilityOrder, getConceptEmoji } from "./model/levels.js";
29
+
30
+ // Import formatters
31
+ import {
32
+ driverToDOM,
33
+ skillToDOM,
34
+ behaviourToDOM,
35
+ disciplineToDOM,
36
+ gradeToDOM,
37
+ trackToDOM,
38
+ } from "./formatters/index.js";
39
+ import { sortTracksByType } from "./formatters/track/shared.js";
40
+
41
+ /**
42
+ * Create a chapter cover page
43
+ * @param {Object} params
44
+ * @param {string} params.emoji - Chapter emoji
45
+ * @param {string} params.title - Chapter title
46
+ * @param {string} params.description - Chapter description
47
+ * @returns {HTMLElement}
48
+ */
49
+ function createChapterCover({ emoji, title, description }) {
50
+ return div(
51
+ { className: "chapter-cover" },
52
+ h1(
53
+ { className: "chapter-title" },
54
+ emoji,
55
+ " ",
56
+ span({ className: "gradient-text" }, title),
57
+ ),
58
+ p({ className: "chapter-description" }, description.trim()),
59
+ );
60
+ }
61
+
62
+ /**
63
+ * Get handout content container
64
+ * @returns {HTMLElement}
65
+ */
66
+ function getHandoutContent() {
67
+ return document.getElementById("handout-content");
68
+ }
69
+
70
+ /**
71
+ * Hide loading indicator
72
+ */
73
+ function hideLoading() {
74
+ const loading = document.getElementById("slide-loading");
75
+ if (loading) {
76
+ loading.classList.add("hidden");
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Show loading indicator
82
+ */
83
+ function showLoading() {
84
+ const loading = document.getElementById("slide-loading");
85
+ if (loading) {
86
+ loading.classList.remove("hidden");
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Render content to handout container
92
+ * @param {HTMLElement} content
93
+ */
94
+ function renderHandout(content) {
95
+ const container = getHandoutContent();
96
+ container.innerHTML = "";
97
+ container.appendChild(content);
98
+ hideLoading();
99
+ }
100
+
101
+ /**
102
+ * Render the handout index page
103
+ * @param {Object} data
104
+ */
105
+ function renderIndex(data) {
106
+ const { framework } = data;
107
+
108
+ const content = div(
109
+ { className: "slide slide-index" },
110
+ div(
111
+ { className: "page-header" },
112
+ heading1(
113
+ { className: "page-title" },
114
+ `${framework.emoji} ${framework.title} Handouts`,
115
+ ),
116
+ p(
117
+ { className: "page-description" },
118
+ "Printable handouts with all items in each category on a single page.",
119
+ ),
120
+ ),
121
+
122
+ div(
123
+ { className: "slide-section" },
124
+ heading2(
125
+ { className: "slide-section-title" },
126
+ "📄 ",
127
+ span({ className: "gradient-text" }, "Available Handouts"),
128
+ ),
129
+ ul(
130
+ { className: "related-list" },
131
+ li(
132
+ {},
133
+ a(
134
+ { href: "#/job" },
135
+ `${getConceptEmoji(framework, "job")} ${framework.entityDefinitions.job.title}`,
136
+ ),
137
+ " - ",
138
+ `${data.disciplines.length} disciplines, ${data.tracks.length} tracks, ${data.grades.length} grades`,
139
+ ),
140
+ li(
141
+ {},
142
+ a(
143
+ { href: "#/skill" },
144
+ `${getConceptEmoji(framework, "skill")} ${framework.entityDefinitions.skill.title}`,
145
+ ),
146
+ " - ",
147
+ `${data.skills.length} skill definitions`,
148
+ ),
149
+ li(
150
+ {},
151
+ a(
152
+ { href: "#/behaviour" },
153
+ `${getConceptEmoji(framework, "behaviour")} ${framework.entityDefinitions.behaviour.title}`,
154
+ ),
155
+ " - ",
156
+ `${data.behaviours.length} behaviour definitions`,
157
+ ),
158
+ li(
159
+ {},
160
+ a(
161
+ { href: "#/driver" },
162
+ `${getConceptEmoji(framework, "driver")} ${framework.entityDefinitions.driver.title}`,
163
+ ),
164
+ " - ",
165
+ `${data.drivers.length} driver definitions`,
166
+ ),
167
+ ),
168
+ ),
169
+ );
170
+
171
+ renderHandout(content);
172
+ }
173
+
174
+ /**
175
+ * Render all driver slides
176
+ * @param {Object} data
177
+ */
178
+ function renderDriverHandout(data) {
179
+ const { framework } = data;
180
+
181
+ const slides = data.drivers.map((driver) => {
182
+ return driverToDOM(driver, {
183
+ skills: data.skills,
184
+ behaviours: data.behaviours,
185
+ framework: data.framework,
186
+ showBackLink: false,
187
+ });
188
+ });
189
+
190
+ const content = div(
191
+ {},
192
+ createChapterCover({
193
+ emoji: getConceptEmoji(framework, "driver"),
194
+ title: framework.entityDefinitions.driver.title,
195
+ description: framework.entityDefinitions.driver.description,
196
+ }),
197
+ ...slides,
198
+ );
199
+
200
+ renderHandout(content);
201
+ }
202
+
203
+ /**
204
+ * Render all skill slides
205
+ * @param {Object} data
206
+ */
207
+ function renderSkillHandout(data) {
208
+ const { framework } = data;
209
+
210
+ // Get capability order from data
211
+ const capabilityOrder = getCapabilityOrder(data.capabilities);
212
+
213
+ // Sort skills by capability order, then by name within capability
214
+ const sortedSkills = [...data.skills].sort((a, b) => {
215
+ const aIndex = capabilityOrder.indexOf(a.capability);
216
+ const bIndex = capabilityOrder.indexOf(b.capability);
217
+ if (aIndex !== bIndex) {
218
+ return aIndex - bIndex;
219
+ }
220
+ return a.name.localeCompare(b.name);
221
+ });
222
+
223
+ const slides = sortedSkills.map((skill) => {
224
+ return skillToDOM(skill, {
225
+ disciplines: data.disciplines,
226
+ tracks: data.tracks,
227
+ drivers: data.drivers,
228
+ capabilities: data.capabilities,
229
+ showBackLink: false,
230
+ });
231
+ });
232
+
233
+ const content = div(
234
+ {},
235
+ createChapterCover({
236
+ emoji: getConceptEmoji(framework, "skill"),
237
+ title: framework.entityDefinitions.skill.title,
238
+ description: framework.entityDefinitions.skill.description,
239
+ }),
240
+ ...slides,
241
+ );
242
+
243
+ renderHandout(content);
244
+ }
245
+
246
+ /**
247
+ * Render all behaviour slides
248
+ * @param {Object} data
249
+ */
250
+ function renderBehaviourHandout(data) {
251
+ const { framework } = data;
252
+
253
+ const slides = data.behaviours.map((behaviour) => {
254
+ return behaviourToDOM(behaviour, {
255
+ drivers: data.drivers,
256
+ framework: data.framework,
257
+ showBackLink: false,
258
+ });
259
+ });
260
+
261
+ const content = div(
262
+ {},
263
+ createChapterCover({
264
+ emoji: getConceptEmoji(framework, "behaviour"),
265
+ title: framework.entityDefinitions.behaviour.title,
266
+ description: framework.entityDefinitions.behaviour.description,
267
+ }),
268
+ ...slides,
269
+ );
270
+
271
+ renderHandout(content);
272
+ }
273
+
274
+ /**
275
+ * Render all job component slides (disciplines, grades, tracks)
276
+ * @param {Object} data
277
+ */
278
+ function renderJobHandout(data) {
279
+ const { framework } = data;
280
+
281
+ const disciplineSlides = data.disciplines.map((discipline) => {
282
+ return disciplineToDOM(discipline, {
283
+ skills: data.skills,
284
+ behaviours: data.behaviours,
285
+ framework: data.framework,
286
+ showBackLink: false,
287
+ showBehaviourModifiers: false,
288
+ });
289
+ });
290
+
291
+ const gradeSlides = data.grades.map((grade) => {
292
+ return gradeToDOM(grade, {
293
+ framework: data.framework,
294
+ showBackLink: false,
295
+ });
296
+ });
297
+
298
+ const trackSlides = sortTracksByType(data.tracks).map((track) => {
299
+ return trackToDOM(track, {
300
+ skills: data.skills,
301
+ behaviours: data.behaviours,
302
+ disciplines: data.disciplines,
303
+ framework: data.framework,
304
+ });
305
+ });
306
+
307
+ const content = div(
308
+ {},
309
+ // Disciplines chapter
310
+ createChapterCover({
311
+ emoji: getConceptEmoji(framework, "discipline"),
312
+ title: framework.entityDefinitions.discipline.title,
313
+ description: framework.entityDefinitions.discipline.description,
314
+ }),
315
+ ...disciplineSlides,
316
+
317
+ // Tracks chapter
318
+ createChapterCover({
319
+ emoji: getConceptEmoji(framework, "track"),
320
+ title: framework.entityDefinitions.track.title,
321
+ description: framework.entityDefinitions.track.description,
322
+ }),
323
+ ...trackSlides,
324
+
325
+ // Grades chapter
326
+ createChapterCover({
327
+ emoji: getConceptEmoji(framework, "grade"),
328
+ title: framework.entityDefinitions.grade.title,
329
+ description: framework.entityDefinitions.grade.description,
330
+ }),
331
+ ...gradeSlides,
332
+ );
333
+
334
+ renderHandout(content);
335
+ }
336
+
337
+ /**
338
+ * Handle routing based on hash
339
+ */
340
+ function handleRoute() {
341
+ const data = getState().data;
342
+ const hash = window.location.hash || "#/";
343
+ const path = hash.slice(1); // Remove #
344
+
345
+ switch (path) {
346
+ case "/":
347
+ case "":
348
+ renderIndex(data);
349
+ break;
350
+ case "/driver":
351
+ renderDriverHandout(data);
352
+ break;
353
+ case "/skill":
354
+ renderSkillHandout(data);
355
+ break;
356
+ case "/behaviour":
357
+ renderBehaviourHandout(data);
358
+ break;
359
+ case "/job":
360
+ renderJobHandout(data);
361
+ break;
362
+ default:
363
+ renderIndex(data);
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Populate the page brand header with framework title and hashtag
369
+ * @param {Object} framework - Framework data from YAML
370
+ */
371
+ function populateBrandHeader(framework) {
372
+ const header = document.getElementById("page-brand-header");
373
+ if (!header) return;
374
+
375
+ // Update document title
376
+ document.title = `${framework.title} - Handout View`;
377
+
378
+ header.innerHTML = "";
379
+ header.appendChild(
380
+ a(
381
+ { className: "brand-title", href: "#/" },
382
+ `${framework.emoji} ${framework.title}`,
383
+ ),
384
+ );
385
+ header.appendChild(span({ className: "brand-tag" }, framework.tag));
386
+ header.style.display = "";
387
+ }
388
+
389
+ /**
390
+ * Initialize the handout viewer
391
+ */
392
+ async function init() {
393
+ showLoading();
394
+
395
+ try {
396
+ const data = await loadAllData();
397
+ setData(data);
398
+
399
+ // Populate brand header
400
+ populateBrandHeader(data.framework);
401
+
402
+ // Set up hash change listener
403
+ window.addEventListener("hashchange", handleRoute);
404
+
405
+ // Initial render
406
+ handleRoute();
407
+ } catch (error) {
408
+ console.error("Failed to initialize handout viewer:", error);
409
+ const container = getHandoutContent();
410
+ container.innerHTML = `
411
+ <div class="slide-error">
412
+ <h1>Initialization Error</h1>
413
+ <p>${error.message}</p>
414
+ </div>
415
+ `;
416
+ hideLoading();
417
+ }
418
+ }
419
+
420
+ // Start the application
421
+ init();
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Engineering Pathway - Handout View</title>
7
+ <link rel="stylesheet" href="css/bundles/handout.css" />
8
+ </head>
9
+ <body class="slide-view handout-view">
10
+ <header
11
+ id="page-brand-header"
12
+ class="page-brand-header"
13
+ style="display: none"
14
+ ></header>
15
+ <div id="slide-loading" class="slide-loading">
16
+ <p>Loading...</p>
17
+ </div>
18
+ <main id="handout-content"></main>
19
+ <script type="module" src="handout-main.js"></script>
20
+ </body>
21
+ </html>
package/app/index.html ADDED
@@ -0,0 +1,59 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Engineering Pathway</title>
7
+ <link rel="stylesheet" href="css/bundles/app.css" />
8
+ </head>
9
+ <body>
10
+ <div id="app">
11
+ <nav id="app-nav">
12
+ <div class="nav-brand">
13
+ <a href="#/">Engineering Pathway</a>
14
+ <span class="brand-tag">#BenchTools</span>
15
+ </div>
16
+ <button
17
+ class="nav-toggle"
18
+ id="nav-toggle"
19
+ aria-label="Toggle navigation"
20
+ >
21
+ <span></span>
22
+ <span></span>
23
+ <span></span>
24
+ </button>
25
+ <ul class="nav-links" id="nav-links">
26
+ <li><a href="#/discipline">Disciplines</a></li>
27
+ <li><a href="#/track">Tracks</a></li>
28
+ <li><a href="#/grade">Grades</a></li>
29
+ <li><a href="#/skill">Skills</a></li>
30
+ <li><a href="#/behaviour">Behaviours</a></li>
31
+ <li><a href="#/stage">Stages</a></li>
32
+ <li><a href="#/driver">Drivers</a></li>
33
+ <li><a href="#/job-builder" class="nav-cta">Build a Job</a></li>
34
+ <li><a href="#/agent-builder" class="nav-cta">Build an Agent</a></li>
35
+ <li><a href="#/interview-prep" class="nav-cta">Interview Prep</a></li>
36
+ <li>
37
+ <a href="#/career-progress" class="nav-cta">Career Progress</a>
38
+ </li>
39
+ <li>
40
+ <a href="#/self-assessment" class="nav-cta">Self-Assessment</a>
41
+ </li>
42
+ </ul>
43
+ </nav>
44
+
45
+ <main id="app-content">
46
+ <div class="loading">
47
+ <div class="loading-spinner"></div>
48
+ <p>Loading...</p>
49
+ </div>
50
+ </main>
51
+
52
+ <footer id="app-footer">
53
+ <p>Engineering Pathway</p>
54
+ </footer>
55
+ </div>
56
+
57
+ <script type="module" src="main.js"></script>
58
+ </body>
59
+ </html>
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Card Mapper Functions
3
+ *
4
+ * Reusable functions that map entities to card configurations.
5
+ * Used by both regular pages and overview slides.
6
+ */
7
+
8
+ import { createBadge } from "../components/card.js";
9
+ import { formatLevel } from "./render.js";
10
+ import { getCapabilityEmoji } from "../model/levels.js";
11
+
12
+ /**
13
+ * Map discipline to card config
14
+ * @param {Object} discipline
15
+ * @returns {Object}
16
+ */
17
+ export function disciplineToCardConfig(discipline) {
18
+ return {
19
+ title: discipline.name,
20
+ description: discipline.truncatedDescription,
21
+ href: `/discipline/${discipline.id}`,
22
+ meta: [
23
+ createBadge(`${discipline.coreSkillsCount} core`, "primary"),
24
+ createBadge(
25
+ `${discipline.supportingSkillsCount} supporting`,
26
+ "secondary",
27
+ ),
28
+ createBadge(`${discipline.broadSkillsCount} broad`, "broad"),
29
+ ],
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Map skill to card config
35
+ * @param {Object} skill
36
+ * @param {Array} capabilities
37
+ * @returns {Object}
38
+ */
39
+ export function skillToCardConfig(skill, capabilities) {
40
+ return {
41
+ title: skill.name,
42
+ description: skill.truncatedDescription,
43
+ href: `/skill/${skill.id}`,
44
+ badges: [
45
+ createBadge(
46
+ formatCapability(skill.capability, capabilities),
47
+ skill.capability,
48
+ ),
49
+ ],
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Map behaviour to card config
55
+ * @param {Object} behaviour
56
+ * @returns {Object}
57
+ */
58
+ export function behaviourToCardConfig(behaviour) {
59
+ return {
60
+ title: behaviour.name,
61
+ description: behaviour.truncatedDescription,
62
+ href: `/behaviour/${behaviour.id}`,
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Map driver to card config
68
+ * @param {Object} driver
69
+ * @returns {Object}
70
+ */
71
+ export function driverToCardConfig(driver) {
72
+ return {
73
+ title: driver.name,
74
+ description: driver.truncatedDescription,
75
+ href: `/driver/${driver.id}`,
76
+ meta: [
77
+ createBadge(`${driver.contributingSkillsCount} skills`, "default"),
78
+ createBadge(
79
+ `${driver.contributingBehavioursCount} behaviours`,
80
+ "primary",
81
+ ),
82
+ ],
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Map grade to card config (for timeline)
88
+ * @param {Object} grade
89
+ * @returns {Object}
90
+ */
91
+ export function gradeToCardConfig(grade) {
92
+ return {
93
+ title: grade.displayName,
94
+ description: grade.scope || grade.truncatedDescription,
95
+ href: `/grade/${grade.id}`,
96
+ badges: [createBadge(grade.id, "default")],
97
+ meta: [
98
+ createBadge(
99
+ `Primary: ${formatLevel(grade.baseSkillLevels?.primary)}`,
100
+ "primary",
101
+ ),
102
+ createBadge(
103
+ `Secondary: ${formatLevel(grade.baseSkillLevels?.secondary)}`,
104
+ "secondary",
105
+ ),
106
+ createBadge(
107
+ `Broad: ${formatLevel(grade.baseSkillLevels?.broad)}`,
108
+ "broad",
109
+ ),
110
+ ],
111
+ yearsExperience: grade.yearsExperience,
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Map track to card config
117
+ * @param {Object} track
118
+ * @returns {Object}
119
+ */
120
+ export function trackToCardConfig(track) {
121
+ const badges = [];
122
+ if (track.isProfessional) {
123
+ badges.push(createBadge("Professional", "secondary"));
124
+ }
125
+ if (track.isManagement) {
126
+ badges.push(createBadge("Management", "default"));
127
+ }
128
+
129
+ return {
130
+ title: track.name,
131
+ description: track.truncatedDescription,
132
+ href: `/track/${track.id}`,
133
+ meta: badges,
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Map job combination to card config
139
+ * @param {Object} job
140
+ * @returns {Object}
141
+ */
142
+ export function jobToCardConfig(job) {
143
+ return {
144
+ title: job.title,
145
+ description: `${job.discipline.specialization || job.discipline.name} at ${job.grade.professionalTitle} level on ${job.track.name} track`,
146
+ href: `/job/${job.discipline.id}/${job.track.id}/${job.grade.id}`,
147
+ badges: [createBadge(job.grade.id, "default")],
148
+ meta: [createBadge(job.track.name, "secondary")],
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Format capability for display
154
+ * @param {string} capability
155
+ * @param {Array} capabilities
156
+ * @returns {string}
157
+ */
158
+ function formatCapability(capability, capabilities) {
159
+ const capabilityLabels = {
160
+ delivery: "Delivery",
161
+ scale: "Scale",
162
+ reliability: "Reliability",
163
+ data: "Data",
164
+ ai: "AI",
165
+ process: "Process",
166
+ business: "Business",
167
+ people: "People",
168
+ documentation: "Documentation",
169
+ };
170
+ const label = capabilityLabels[capability] || formatLevel(capability);
171
+ const emoji = getCapabilityEmoji(capabilities, capability);
172
+ return `${emoji} ${label}`;
173
+ }