@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.
- package/LICENSE +201 -0
- package/README.md +104 -0
- package/app/commands/agent.js +430 -0
- package/app/commands/behaviour.js +61 -0
- package/app/commands/command-factory.js +211 -0
- package/app/commands/discipline.js +58 -0
- package/app/commands/driver.js +94 -0
- package/app/commands/grade.js +60 -0
- package/app/commands/index.js +20 -0
- package/app/commands/init.js +67 -0
- package/app/commands/interview.js +68 -0
- package/app/commands/job.js +157 -0
- package/app/commands/progress.js +77 -0
- package/app/commands/questions.js +179 -0
- package/app/commands/serve.js +143 -0
- package/app/commands/site.js +121 -0
- package/app/commands/skill.js +76 -0
- package/app/commands/stage.js +129 -0
- package/app/commands/track.js +70 -0
- package/app/components/action-buttons.js +66 -0
- package/app/components/behaviour-profile.js +53 -0
- package/app/components/builder.js +341 -0
- package/app/components/card.js +98 -0
- package/app/components/checklist.js +145 -0
- package/app/components/comparison-radar.js +237 -0
- package/app/components/detail.js +230 -0
- package/app/components/error-page.js +72 -0
- package/app/components/grid.js +109 -0
- package/app/components/list.js +120 -0
- package/app/components/modifier-table.js +142 -0
- package/app/components/nav.js +64 -0
- package/app/components/progression-table.js +320 -0
- package/app/components/radar-chart.js +102 -0
- package/app/components/skill-matrix.js +97 -0
- package/app/css/base.css +56 -0
- package/app/css/bundles/app.css +40 -0
- package/app/css/bundles/handout.css +43 -0
- package/app/css/bundles/slides.css +40 -0
- package/app/css/components/badges.css +215 -0
- package/app/css/components/buttons.css +101 -0
- package/app/css/components/forms.css +105 -0
- package/app/css/components/layout.css +209 -0
- package/app/css/components/nav.css +166 -0
- package/app/css/components/progress.css +166 -0
- package/app/css/components/states.css +82 -0
- package/app/css/components/surfaces.css +243 -0
- package/app/css/components/tables.css +362 -0
- package/app/css/components/typography.css +122 -0
- package/app/css/components/utilities.css +41 -0
- package/app/css/pages/agent-builder.css +391 -0
- package/app/css/pages/assessment-results.css +453 -0
- package/app/css/pages/detail.css +59 -0
- package/app/css/pages/interview-builder.css +148 -0
- package/app/css/pages/job-builder.css +134 -0
- package/app/css/pages/landing.css +92 -0
- package/app/css/pages/lifecycle.css +118 -0
- package/app/css/pages/progress-builder.css +274 -0
- package/app/css/pages/self-assessment.css +502 -0
- package/app/css/reset.css +50 -0
- package/app/css/tokens.css +153 -0
- package/app/css/views/handout.css +30 -0
- package/app/css/views/print.css +608 -0
- package/app/css/views/slide-animations.css +113 -0
- package/app/css/views/slide-base.css +330 -0
- package/app/css/views/slide-sections.css +597 -0
- package/app/css/views/slide-tables.css +275 -0
- package/app/formatters/agent/dom.js +540 -0
- package/app/formatters/agent/profile.js +133 -0
- package/app/formatters/agent/skill.js +58 -0
- package/app/formatters/behaviour/dom.js +91 -0
- package/app/formatters/behaviour/markdown.js +54 -0
- package/app/formatters/behaviour/shared.js +64 -0
- package/app/formatters/discipline/dom.js +187 -0
- package/app/formatters/discipline/markdown.js +87 -0
- package/app/formatters/discipline/shared.js +131 -0
- package/app/formatters/driver/dom.js +103 -0
- package/app/formatters/driver/shared.js +92 -0
- package/app/formatters/grade/dom.js +208 -0
- package/app/formatters/grade/markdown.js +94 -0
- package/app/formatters/grade/shared.js +86 -0
- package/app/formatters/index.js +50 -0
- package/app/formatters/interview/dom.js +97 -0
- package/app/formatters/interview/markdown.js +66 -0
- package/app/formatters/interview/shared.js +332 -0
- package/app/formatters/job/description.js +176 -0
- package/app/formatters/job/dom.js +411 -0
- package/app/formatters/job/markdown.js +102 -0
- package/app/formatters/progress/dom.js +135 -0
- package/app/formatters/progress/markdown.js +86 -0
- package/app/formatters/progress/shared.js +339 -0
- package/app/formatters/questions/json.js +43 -0
- package/app/formatters/questions/markdown.js +303 -0
- package/app/formatters/questions/shared.js +274 -0
- package/app/formatters/questions/yaml.js +76 -0
- package/app/formatters/shared.js +71 -0
- package/app/formatters/skill/dom.js +168 -0
- package/app/formatters/skill/markdown.js +109 -0
- package/app/formatters/skill/shared.js +125 -0
- package/app/formatters/stage/dom.js +135 -0
- package/app/formatters/stage/index.js +12 -0
- package/app/formatters/stage/shared.js +111 -0
- package/app/formatters/track/dom.js +128 -0
- package/app/formatters/track/markdown.js +105 -0
- package/app/formatters/track/shared.js +181 -0
- package/app/handout-main.js +421 -0
- package/app/handout.html +21 -0
- package/app/index.html +59 -0
- package/app/lib/card-mappers.js +173 -0
- package/app/lib/cli-output.js +270 -0
- package/app/lib/error-boundary.js +70 -0
- package/app/lib/errors.js +49 -0
- package/app/lib/form-controls.js +47 -0
- package/app/lib/job-cache.js +86 -0
- package/app/lib/markdown.js +114 -0
- package/app/lib/radar.js +866 -0
- package/app/lib/reactive.js +77 -0
- package/app/lib/render.js +212 -0
- package/app/lib/router-core.js +160 -0
- package/app/lib/router-pages.js +16 -0
- package/app/lib/router-slides.js +202 -0
- package/app/lib/state.js +148 -0
- package/app/lib/utils.js +14 -0
- package/app/lib/yaml-loader.js +327 -0
- package/app/main.js +213 -0
- package/app/model/agent.js +702 -0
- package/app/model/checklist.js +137 -0
- package/app/model/derivation.js +699 -0
- package/app/model/index-generator.js +71 -0
- package/app/model/interview.js +539 -0
- package/app/model/job.js +222 -0
- package/app/model/levels.js +591 -0
- package/app/model/loader.js +564 -0
- package/app/model/matching.js +858 -0
- package/app/model/modifiers.js +158 -0
- package/app/model/profile.js +266 -0
- package/app/model/progression.js +507 -0
- package/app/model/validation.js +1385 -0
- package/app/pages/agent-builder.js +823 -0
- package/app/pages/assessment-results.js +507 -0
- package/app/pages/behaviour.js +70 -0
- package/app/pages/discipline.js +71 -0
- package/app/pages/driver.js +106 -0
- package/app/pages/grade.js +117 -0
- package/app/pages/interview-builder.js +50 -0
- package/app/pages/interview.js +304 -0
- package/app/pages/job-builder.js +50 -0
- package/app/pages/job.js +58 -0
- package/app/pages/landing.js +305 -0
- package/app/pages/progress-builder.js +58 -0
- package/app/pages/progress.js +495 -0
- package/app/pages/self-assessment.js +729 -0
- package/app/pages/skill.js +113 -0
- package/app/pages/stage.js +231 -0
- package/app/pages/track.js +69 -0
- package/app/slide-main.js +360 -0
- package/app/slides/behaviour.js +38 -0
- package/app/slides/chapter.js +82 -0
- package/app/slides/discipline.js +40 -0
- package/app/slides/driver.js +39 -0
- package/app/slides/grade.js +32 -0
- package/app/slides/index.js +198 -0
- package/app/slides/interview.js +58 -0
- package/app/slides/job.js +55 -0
- package/app/slides/overview.js +126 -0
- package/app/slides/progress.js +83 -0
- package/app/slides/skill.js +40 -0
- package/app/slides/track.js +39 -0
- package/app/slides.html +56 -0
- package/app/types.js +147 -0
- package/bin/pathway.js +489 -0
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
- package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
- package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
- package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
- package/examples/agents/.vscode/settings.json +8 -0
- package/examples/behaviours/_index.yaml +8 -0
- package/examples/behaviours/outcome_ownership.yaml +44 -0
- package/examples/behaviours/polymathic_knowledge.yaml +42 -0
- package/examples/behaviours/precise_communication.yaml +40 -0
- package/examples/behaviours/relentless_curiosity.yaml +38 -0
- package/examples/behaviours/systems_thinking.yaml +41 -0
- package/examples/capabilities/_index.yaml +8 -0
- package/examples/capabilities/business.yaml +251 -0
- package/examples/capabilities/delivery.yaml +352 -0
- package/examples/capabilities/people.yaml +100 -0
- package/examples/capabilities/reliability.yaml +318 -0
- package/examples/capabilities/scale.yaml +394 -0
- package/examples/disciplines/_index.yaml +5 -0
- package/examples/disciplines/data_engineering.yaml +76 -0
- package/examples/disciplines/software_engineering.yaml +76 -0
- package/examples/drivers.yaml +205 -0
- package/examples/framework.yaml +58 -0
- package/examples/grades.yaml +118 -0
- package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
- package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
- package/examples/questions/behaviours/precise_communication.yaml +55 -0
- package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
- package/examples/questions/behaviours/systems_thinking.yaml +53 -0
- package/examples/questions/skills/architecture_design.yaml +54 -0
- package/examples/questions/skills/cloud_platforms.yaml +48 -0
- package/examples/questions/skills/code_quality.yaml +49 -0
- package/examples/questions/skills/data_modeling.yaml +46 -0
- package/examples/questions/skills/devops.yaml +47 -0
- package/examples/questions/skills/full_stack_development.yaml +48 -0
- package/examples/questions/skills/sre_practices.yaml +44 -0
- package/examples/questions/skills/stakeholder_management.yaml +49 -0
- package/examples/questions/skills/team_collaboration.yaml +43 -0
- package/examples/questions/skills/technical_writing.yaml +43 -0
- package/examples/self-assessments.yaml +66 -0
- package/examples/stages.yaml +76 -0
- package/examples/tracks/_index.yaml +6 -0
- package/examples/tracks/manager.yaml +53 -0
- package/examples/tracks/platform.yaml +54 -0
- package/examples/tracks/sre.yaml +58 -0
- package/examples/vscode-settings.yaml +22 -0
- package/package.json +68 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid components and utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides reusable grid components that wrap the CSS grid utilities.
|
|
5
|
+
* All grids use the unified CSS classes: auto-grid-xs, auto-grid-sm, auto-grid-md, auto-grid-lg
|
|
6
|
+
*
|
|
7
|
+
* Grid sizes:
|
|
8
|
+
* - xs: 150px min column width (compact stats, small cards)
|
|
9
|
+
* - sm: 200px min column width (form controls, medium cards)
|
|
10
|
+
* - md: 300px min column width (detail items, content cards)
|
|
11
|
+
* - lg: 400px min column width (large content like radar charts)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { div } from "../lib/render.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Grid size options
|
|
18
|
+
* @typedef {'xs' | 'sm' | 'md' | 'lg'} GridSize
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Gap size options
|
|
23
|
+
* @typedef {'sm' | 'md' | 'lg' | 'xl'} GapSize
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create an auto-fit grid container
|
|
28
|
+
* @param {GridSize} size - Grid size variant (xs, sm, md, lg)
|
|
29
|
+
* @param {HTMLElement[]} children - Child elements to place in grid
|
|
30
|
+
* @param {Object} [options] - Optional configuration
|
|
31
|
+
* @param {string} [options.className] - Additional CSS classes
|
|
32
|
+
* @param {GapSize} [options.gap] - Override default gap (sm, md, lg, xl)
|
|
33
|
+
* @returns {HTMLElement}
|
|
34
|
+
*/
|
|
35
|
+
export function createAutoGrid(size, children, options = {}) {
|
|
36
|
+
const { className = "", gap } = options;
|
|
37
|
+
const classes = [`auto-grid-${size}`, gap ? `gap-${gap}` : "", className]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join(" ");
|
|
40
|
+
|
|
41
|
+
return div({ className: classes }, ...children);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create a fixed-column grid container
|
|
46
|
+
* @param {2 | 3 | 6} columns - Number of columns
|
|
47
|
+
* @param {HTMLElement[]} children - Child elements to place in grid
|
|
48
|
+
* @param {Object} [options] - Optional configuration
|
|
49
|
+
* @param {string} [options.className] - Additional CSS classes
|
|
50
|
+
* @returns {HTMLElement}
|
|
51
|
+
*/
|
|
52
|
+
export function createFixedGrid(columns, children, options = {}) {
|
|
53
|
+
const { className = "" } = options;
|
|
54
|
+
const classes = ["grid", `grid-${columns}`, className]
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.join(" ");
|
|
57
|
+
|
|
58
|
+
return div({ className: classes }, ...children);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create a grid for form selectors (discipline/grade/track dropdowns)
|
|
63
|
+
* Uses auto-grid-sm (200px min)
|
|
64
|
+
* @param {HTMLElement[]} children - Form control elements
|
|
65
|
+
* @returns {HTMLElement}
|
|
66
|
+
*/
|
|
67
|
+
export function createSelectorGrid(children) {
|
|
68
|
+
return createAutoGrid("sm", children, { gap: "lg" });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create a grid for detail items (key-value pairs)
|
|
73
|
+
* Uses auto-grid-md (300px min)
|
|
74
|
+
* @param {HTMLElement[]} children - Detail item elements
|
|
75
|
+
* @returns {HTMLElement}
|
|
76
|
+
*/
|
|
77
|
+
export function createDetailGrid(children) {
|
|
78
|
+
return createAutoGrid("md", children);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a grid for radar charts or large content
|
|
83
|
+
* Uses auto-grid-lg (400px min)
|
|
84
|
+
* @param {HTMLElement[]} children - Large content elements
|
|
85
|
+
* @returns {HTMLElement}
|
|
86
|
+
*/
|
|
87
|
+
export function createRadarGrid(children) {
|
|
88
|
+
return createAutoGrid("lg", children);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a grid for compact stats
|
|
93
|
+
* Uses auto-grid-xs (150px min)
|
|
94
|
+
* @param {HTMLElement[]} children - Stat elements
|
|
95
|
+
* @returns {HTMLElement}
|
|
96
|
+
*/
|
|
97
|
+
export function createStatsGrid(children) {
|
|
98
|
+
return createAutoGrid("xs", children);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create a grid for card-like items (tips, expectations)
|
|
103
|
+
* Uses auto-grid-sm (200px min)
|
|
104
|
+
* @param {HTMLElement[]} children - Card elements
|
|
105
|
+
* @returns {HTMLElement}
|
|
106
|
+
*/
|
|
107
|
+
export function createCardGrid(children) {
|
|
108
|
+
return createAutoGrid("sm", children);
|
|
109
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable list component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { div, h2, input, select, option } from "../lib/render.js";
|
|
6
|
+
import { createCard, createBadge } from "./card.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a search/filter bar
|
|
10
|
+
* @param {Object} options
|
|
11
|
+
* @param {Function} options.onSearch - Search callback
|
|
12
|
+
* @param {Array<{value: string, label: string}>} [options.filterOptions] - Filter dropdown options
|
|
13
|
+
* @param {Function} [options.onFilter] - Filter callback
|
|
14
|
+
* @param {string} [options.searchPlaceholder='Search...']
|
|
15
|
+
* @param {string} [options.filterPlaceholder='All']
|
|
16
|
+
* @returns {HTMLElement}
|
|
17
|
+
*/
|
|
18
|
+
export function createSearchBar({
|
|
19
|
+
onSearch,
|
|
20
|
+
filterOptions,
|
|
21
|
+
onFilter,
|
|
22
|
+
searchPlaceholder = "Search...",
|
|
23
|
+
filterPlaceholder = "All",
|
|
24
|
+
}) {
|
|
25
|
+
const searchInput = input({
|
|
26
|
+
type: "text",
|
|
27
|
+
className: "form-input",
|
|
28
|
+
placeholder: searchPlaceholder,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
searchInput.addEventListener("input", (e) => {
|
|
32
|
+
onSearch(e.target.value);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const children = [searchInput];
|
|
36
|
+
|
|
37
|
+
if (filterOptions && onFilter) {
|
|
38
|
+
const filterSelect = select(
|
|
39
|
+
{ className: "form-select" },
|
|
40
|
+
option({ value: "" }, filterPlaceholder),
|
|
41
|
+
...filterOptions.map((opt) => option({ value: opt.value }, opt.label)),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
filterSelect.addEventListener("change", (e) => {
|
|
45
|
+
onFilter(e.target.value);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
children.push(filterSelect);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return div({ className: "search-bar" }, ...children);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a list of cards
|
|
56
|
+
* @param {Array} items - Items to render
|
|
57
|
+
* @param {Function} renderItem - Function to render each item as a card config
|
|
58
|
+
* @param {string} [emptyMessage='No items found']
|
|
59
|
+
* @returns {HTMLElement}
|
|
60
|
+
*/
|
|
61
|
+
export function createCardList(
|
|
62
|
+
items,
|
|
63
|
+
renderItem,
|
|
64
|
+
emptyMessage = "No items found",
|
|
65
|
+
) {
|
|
66
|
+
if (!items || items.length === 0) {
|
|
67
|
+
return div(
|
|
68
|
+
{ className: "empty-state" },
|
|
69
|
+
div({ className: "empty-message" }, emptyMessage),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const cards = items.map((item) => {
|
|
74
|
+
const config = renderItem(item);
|
|
75
|
+
return createCard(config);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return div({ className: "grid grid-3" }, ...cards);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a grouped list (like skills by capability)
|
|
83
|
+
* @param {Object} groups - Object with group names as keys and arrays as values
|
|
84
|
+
* @param {Function} renderItem - Function to render each item
|
|
85
|
+
* @param {Function} [renderGroupHeader] - Function to render group header
|
|
86
|
+
* @returns {HTMLElement}
|
|
87
|
+
*/
|
|
88
|
+
export function createGroupedList(groups, renderItem, renderGroupHeader) {
|
|
89
|
+
const sections = Object.entries(groups).map(([groupName, items]) => {
|
|
90
|
+
const header = renderGroupHeader
|
|
91
|
+
? renderGroupHeader(groupName, items.length)
|
|
92
|
+
: div(
|
|
93
|
+
{ className: "capability-header" },
|
|
94
|
+
h2({ className: "capability-title" }, formatGroupName(groupName)),
|
|
95
|
+
createBadge(`${items.length}`, "default"),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const cards = items.map((item) => {
|
|
99
|
+
const config = renderItem(item);
|
|
100
|
+
return createCard(config);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return div(
|
|
104
|
+
{ className: "capability-group" },
|
|
105
|
+
header,
|
|
106
|
+
div({ className: "grid grid-3" }, ...cards),
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return div({ className: "grouped-list" }, ...sections);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Format a group name for display
|
|
115
|
+
* @param {string} name
|
|
116
|
+
* @returns {string}
|
|
117
|
+
*/
|
|
118
|
+
function formatGroupName(name) {
|
|
119
|
+
return name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
120
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modifier table component for displaying skill/behaviour modifiers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
div,
|
|
7
|
+
span,
|
|
8
|
+
table,
|
|
9
|
+
thead,
|
|
10
|
+
tbody,
|
|
11
|
+
tr,
|
|
12
|
+
th,
|
|
13
|
+
td,
|
|
14
|
+
a,
|
|
15
|
+
} from "../lib/render.js";
|
|
16
|
+
import { createBadge } from "./card.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a modifier badge based on value
|
|
20
|
+
* @param {number} modifier - The modifier value
|
|
21
|
+
* @returns {HTMLElement}
|
|
22
|
+
*/
|
|
23
|
+
function createModifierBadge(modifier) {
|
|
24
|
+
if (modifier > 0) {
|
|
25
|
+
return createBadge(`+${modifier}`, "broad");
|
|
26
|
+
} else if (modifier < 0) {
|
|
27
|
+
return createBadge(`${modifier}`, "negative");
|
|
28
|
+
}
|
|
29
|
+
return createBadge(`${modifier}`, "secondary");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a table displaying modifiers for skills or behaviours
|
|
34
|
+
* @param {Object} options - Configuration options
|
|
35
|
+
* @param {Array} options.modifiers - Array of {id, name, modifier} objects
|
|
36
|
+
* @param {string} options.basePath - Base path for links (e.g., '/skill', '/behaviour')
|
|
37
|
+
* @param {string} options.itemLabel - Label for the first column (e.g., 'Skill', 'Behaviour')
|
|
38
|
+
* @returns {HTMLElement}
|
|
39
|
+
*/
|
|
40
|
+
export function createModifierTable({ modifiers, basePath, itemLabel }) {
|
|
41
|
+
const rows = modifiers.map((m) =>
|
|
42
|
+
tr(
|
|
43
|
+
{},
|
|
44
|
+
td({}, a({ href: `#${basePath}/${m.id}` }, m.name)),
|
|
45
|
+
td({}, createModifierBadge(m.modifier)),
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return div(
|
|
50
|
+
{ className: "table-container" },
|
|
51
|
+
table(
|
|
52
|
+
{ className: "table modifier-table" },
|
|
53
|
+
thead({}, tr({}, th({}, itemLabel), th({}, "Modifier"))),
|
|
54
|
+
tbody({}, ...rows),
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a behaviour modifiers table
|
|
61
|
+
* @param {Array} modifiers - Array of {id, name, modifier} objects
|
|
62
|
+
* @returns {HTMLElement}
|
|
63
|
+
*/
|
|
64
|
+
export function createBehaviourModifierTable(modifiers) {
|
|
65
|
+
return createModifierTable({
|
|
66
|
+
modifiers,
|
|
67
|
+
basePath: "/behaviour",
|
|
68
|
+
itemLabel: "Behaviour",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create a skill modifiers table
|
|
74
|
+
* @param {Array} modifiers - Array of {id, name, modifier} objects
|
|
75
|
+
* @returns {HTMLElement}
|
|
76
|
+
*/
|
|
77
|
+
export function createSkillModifierTable(modifiers) {
|
|
78
|
+
return createModifierTable({
|
|
79
|
+
modifiers,
|
|
80
|
+
basePath: "/skill",
|
|
81
|
+
itemLabel: "Skill",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create a skill modifiers table with capability support
|
|
87
|
+
* Used for tracks which can have capability-level modifiers with nested skill links
|
|
88
|
+
* @param {Array} modifiers - Array of {id, name, modifier, isCapability?, skills?} objects
|
|
89
|
+
* @returns {HTMLElement}
|
|
90
|
+
*/
|
|
91
|
+
export function createSkillModifierTableWithCapabilities(modifiers) {
|
|
92
|
+
const rows = modifiers.map((m) => {
|
|
93
|
+
if (m.isCapability && m.skills) {
|
|
94
|
+
// Capability row: show capability name (not linked) with nested skill list
|
|
95
|
+
const skillLinks = m.skills.map((skill, index) => {
|
|
96
|
+
if (index < m.skills.length - 1) {
|
|
97
|
+
return span({}, a({ href: `#/skill/${skill.id}` }, skill.name), ", ");
|
|
98
|
+
}
|
|
99
|
+
return a({ href: `#/skill/${skill.id}` }, skill.name);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return tr(
|
|
103
|
+
{},
|
|
104
|
+
td(
|
|
105
|
+
{},
|
|
106
|
+
div({ style: "font-weight: 500;" }, m.name),
|
|
107
|
+
div(
|
|
108
|
+
{
|
|
109
|
+
style:
|
|
110
|
+
"font-size: 0.85em; color: var(--text-secondary); margin-top: 0.25rem;",
|
|
111
|
+
},
|
|
112
|
+
...skillLinks,
|
|
113
|
+
),
|
|
114
|
+
),
|
|
115
|
+
td({}, createModifierBadge(m.modifier)),
|
|
116
|
+
);
|
|
117
|
+
} else if (m.isCapability) {
|
|
118
|
+
// Capability row without nested skills: show simple text
|
|
119
|
+
return tr(
|
|
120
|
+
{},
|
|
121
|
+
td({}, `All ${m.name} skills`),
|
|
122
|
+
td({}, createModifierBadge(m.modifier)),
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
// Individual skill row: show linked skill name
|
|
126
|
+
return tr(
|
|
127
|
+
{},
|
|
128
|
+
td({}, a({ href: `#/skill/${m.id}` }, m.name)),
|
|
129
|
+
td({}, createModifierBadge(m.modifier)),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return div(
|
|
135
|
+
{ className: "table-container" },
|
|
136
|
+
table(
|
|
137
|
+
{ className: "table modifier-table" },
|
|
138
|
+
thead({}, tr({}, th({}, "Skill"), th({}, "Modifier"))),
|
|
139
|
+
tbody({}, ...rows),
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigation component helpers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { div, a } from "../lib/render.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Update the active navigation link
|
|
9
|
+
* @param {string} path - Current path
|
|
10
|
+
*/
|
|
11
|
+
export function updateActiveNav(path) {
|
|
12
|
+
const links = document.querySelectorAll("#nav-links a");
|
|
13
|
+
links.forEach((link) => {
|
|
14
|
+
const href = link.getAttribute("href").slice(1); // Remove #
|
|
15
|
+
const isActive = path === href || (href !== "/" && path.startsWith(href));
|
|
16
|
+
link.classList.toggle("active", isActive);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a back link
|
|
22
|
+
* @param {string} href - Link destination
|
|
23
|
+
* @param {string} [text='Back'] - Link text
|
|
24
|
+
* @returns {HTMLElement}
|
|
25
|
+
*/
|
|
26
|
+
export function createBackLink(href, text = "← Back") {
|
|
27
|
+
return a({ href: `#${href}`, className: "back-link" }, text);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create breadcrumbs
|
|
32
|
+
* @param {Array<{label: string, href?: string}>} items
|
|
33
|
+
* @returns {HTMLElement}
|
|
34
|
+
*/
|
|
35
|
+
export function createBreadcrumbs(items) {
|
|
36
|
+
const crumbs = items.map((item, index) => {
|
|
37
|
+
const isLast = index === items.length - 1;
|
|
38
|
+
if (isLast || !item.href) {
|
|
39
|
+
return span({ className: "breadcrumb-item" }, item.label);
|
|
40
|
+
}
|
|
41
|
+
return a(
|
|
42
|
+
{ href: `#${item.href}`, className: "breadcrumb-item" },
|
|
43
|
+
item.label,
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const separator = " / ";
|
|
48
|
+
const children = [];
|
|
49
|
+
crumbs.forEach((crumb, i) => {
|
|
50
|
+
children.push(crumb);
|
|
51
|
+
if (i < crumbs.length - 1) {
|
|
52
|
+
children.push(document.createTextNode(separator));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return div({ className: "breadcrumbs" }, ...children);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function span(attrs, text) {
|
|
60
|
+
const el = document.createElement("span");
|
|
61
|
+
if (attrs.className) el.className = attrs.className;
|
|
62
|
+
if (text) el.textContent = text;
|
|
63
|
+
return el;
|
|
64
|
+
}
|