@crockery/fellowship-components 0.4.0 → 1.0.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 (134) hide show
  1. package/README.md +40 -26
  2. package/RELEASING.md +15 -14
  3. package/THIRD_PARTY_LICENSES.md +12 -0
  4. package/dist/assets/dm-mono-latin-400-normal-4GdczIuU.woff2 +0 -0
  5. package/dist/assets/dm-mono-latin-500-normal-DRMDZjhP.woff2 +0 -0
  6. package/dist/assets/syne-latin-700-normal-AF3Rs61n.woff2 +0 -0
  7. package/dist/assets/syne-latin-800-normal-EehdNYzx.woff2 +0 -0
  8. package/dist/chunks/decorate-DyMgX_Uh.js +9 -0
  9. package/dist/chunks/{dungeon-cast-browser-CSnu3gC5.js → dungeon-cast-browser-DdG8klca.js} +62 -60
  10. package/dist/chunks/{dungeon-map-CutIcCVP.js → dungeon-map-D0-DAjZ9.js} +130 -133
  11. package/dist/chunks/{decorate-B_ZTN9li.js → field-manual-fonts-DTFloVFe.js} +18 -25
  12. package/dist/chunks/gear-planner-Bgt77Ovf.js +1696 -0
  13. package/dist/chunks/{talent-calculator-Dxuc2dZ8.js → talent-calculator-DbU6P1AP.js} +36 -37
  14. package/dist/chunks/tooltip-C3SSJw-t.js +482 -0
  15. package/dist/data/cast-data-source.d.ts +1 -2
  16. package/dist/data/data-source.d.ts +1 -2
  17. package/dist/data/package-cast-data-source.d.ts +1 -6
  18. package/dist/data/package-data-source.d.ts +1 -6
  19. package/dist/data/package-talent-data-source.d.ts +1 -6
  20. package/dist/data/talent-data-source.d.ts +1 -2
  21. package/dist/dungeon-cast-browser/dungeon-cast-browser.d.ts +2 -2
  22. package/dist/dungeon-cast-browser/dungeon-cast-browser.styles.d.ts +0 -1
  23. package/dist/dungeon-cast-browser/index.d.ts +0 -1
  24. package/dist/dungeon-cast-browser/render-model.d.ts +3 -3
  25. package/dist/dungeon-cast-browser/types.d.ts +2 -3
  26. package/dist/dungeon-cast-browser.js +1 -3
  27. package/dist/dungeon-map/dungeon-map.d.ts +2 -2
  28. package/dist/dungeon-map/dungeon-map.styles.d.ts +0 -1
  29. package/dist/dungeon-map/index.d.ts +0 -1
  30. package/dist/dungeon-map/layout.d.ts +0 -1
  31. package/dist/dungeon-map/render-model.d.ts +3 -4
  32. package/dist/dungeon-map/types.d.ts +2 -3
  33. package/dist/dungeon-map/viewport-controller.d.ts +0 -1
  34. package/dist/dungeon-map.js +1 -3
  35. package/dist/gear-planner/gear-planner.d.ts +36 -0
  36. package/dist/gear-planner/gear-planner.styles.d.ts +1 -0
  37. package/dist/gear-planner/gear-position.d.ts +19 -0
  38. package/dist/gear-planner/index.d.ts +2 -0
  39. package/dist/gear-planner/item-workbench.d.ts +18 -0
  40. package/dist/gear-planner/item-workbench.styles.d.ts +1 -0
  41. package/dist/gear-planner/types.d.ts +24 -0
  42. package/dist/gear-planner.js +5 -0
  43. package/dist/index.d.ts +6 -6
  44. package/dist/index.js +9 -7
  45. package/dist/react/dungeon-cast-browser.d.ts +8 -4
  46. package/dist/react/dungeon-cast-browser.js +11 -6
  47. package/dist/react/dungeon-map.d.ts +3 -2
  48. package/dist/react/dungeon-map.js +1 -3
  49. package/dist/react/gear-planner.d.ts +14 -0
  50. package/dist/react/gear-planner.js +23 -0
  51. package/dist/react/index.d.ts +2 -1
  52. package/dist/react/talent-calculator.d.ts +3 -2
  53. package/dist/react/talent-calculator.js +1 -3
  54. package/dist/react/tooltip.d.ts +0 -1
  55. package/dist/react/tooltip.js +1 -3
  56. package/dist/react.js +2 -1
  57. package/dist/styles/dungeon-poster-fonts.d.ts +2 -0
  58. package/dist/styles/dungeon-poster.styles.d.ts +7 -0
  59. package/dist/styles/field-manual-fonts.d.ts +0 -1
  60. package/dist/styles/field-manual.styles.d.ts +0 -1
  61. package/dist/talent-calculator/index.d.ts +0 -1
  62. package/dist/talent-calculator/render-model.d.ts +3 -3
  63. package/dist/talent-calculator/selection-state.d.ts +0 -1
  64. package/dist/talent-calculator/talent-calculator.d.ts +2 -2
  65. package/dist/talent-calculator/talent-calculator.styles.d.ts +0 -1
  66. package/dist/talent-calculator/types.d.ts +2 -3
  67. package/dist/talent-calculator.js +1 -3
  68. package/dist/tooltip/index.d.ts +0 -1
  69. package/dist/tooltip/positioning.d.ts +0 -1
  70. package/dist/tooltip/rich-text.d.ts +0 -1
  71. package/dist/tooltip/tooltip.d.ts +0 -1
  72. package/dist/tooltip/tooltip.styles.d.ts +0 -1
  73. package/dist/tooltip/types.d.ts +0 -1
  74. package/dist/tooltip.js +3 -5
  75. package/package.json +21 -15
  76. package/src/dungeon-cast-browser/README.md +10 -4
  77. package/src/dungeon-map/README.md +5 -6
  78. package/src/gear-planner/README.md +44 -0
  79. package/src/talent-calculator/README.md +5 -6
  80. package/src/tooltip/README.md +5 -6
  81. package/dist/chunks/data-source-XbksqUrw.js +0 -9
  82. package/dist/chunks/data-source-XbksqUrw.js.map +0 -1
  83. package/dist/chunks/decorate-B_ZTN9li.js.map +0 -1
  84. package/dist/chunks/dungeon-cast-browser-CSnu3gC5.js.map +0 -1
  85. package/dist/chunks/dungeon-map-CutIcCVP.js.map +0 -1
  86. package/dist/chunks/talent-calculator-Dxuc2dZ8.js.map +0 -1
  87. package/dist/chunks/tooltip-DoQVSnOn.js +0 -412
  88. package/dist/chunks/tooltip-DoQVSnOn.js.map +0 -1
  89. package/dist/data/cast-data-source.d.ts.map +0 -1
  90. package/dist/data/data-source.d.ts.map +0 -1
  91. package/dist/data/package-cast-data-source.d.ts.map +0 -1
  92. package/dist/data/package-data-source.d.ts.map +0 -1
  93. package/dist/data/package-talent-data-source.d.ts.map +0 -1
  94. package/dist/data/talent-data-source.d.ts.map +0 -1
  95. package/dist/dungeon-cast-browser/dungeon-cast-browser.d.ts.map +0 -1
  96. package/dist/dungeon-cast-browser/dungeon-cast-browser.styles.d.ts.map +0 -1
  97. package/dist/dungeon-cast-browser/index.d.ts.map +0 -1
  98. package/dist/dungeon-cast-browser/render-model.d.ts.map +0 -1
  99. package/dist/dungeon-cast-browser/types.d.ts.map +0 -1
  100. package/dist/dungeon-cast-browser.js.map +0 -1
  101. package/dist/dungeon-map/dungeon-map.d.ts.map +0 -1
  102. package/dist/dungeon-map/dungeon-map.styles.d.ts.map +0 -1
  103. package/dist/dungeon-map/index.d.ts.map +0 -1
  104. package/dist/dungeon-map/layout.d.ts.map +0 -1
  105. package/dist/dungeon-map/render-model.d.ts.map +0 -1
  106. package/dist/dungeon-map/types.d.ts.map +0 -1
  107. package/dist/dungeon-map/viewport-controller.d.ts.map +0 -1
  108. package/dist/dungeon-map.js.map +0 -1
  109. package/dist/index.d.ts.map +0 -1
  110. package/dist/react/dungeon-cast-browser.d.ts.map +0 -1
  111. package/dist/react/dungeon-cast-browser.js.map +0 -1
  112. package/dist/react/dungeon-map.d.ts.map +0 -1
  113. package/dist/react/dungeon-map.js.map +0 -1
  114. package/dist/react/index.d.ts.map +0 -1
  115. package/dist/react/talent-calculator.d.ts.map +0 -1
  116. package/dist/react/talent-calculator.js.map +0 -1
  117. package/dist/react/tooltip.d.ts.map +0 -1
  118. package/dist/react/tooltip.js.map +0 -1
  119. package/dist/styles/field-manual-fonts.d.ts.map +0 -1
  120. package/dist/styles/field-manual.styles.d.ts.map +0 -1
  121. package/dist/talent-calculator/index.d.ts.map +0 -1
  122. package/dist/talent-calculator/render-model.d.ts.map +0 -1
  123. package/dist/talent-calculator/selection-state.d.ts.map +0 -1
  124. package/dist/talent-calculator/talent-calculator.d.ts.map +0 -1
  125. package/dist/talent-calculator/talent-calculator.styles.d.ts.map +0 -1
  126. package/dist/talent-calculator/types.d.ts.map +0 -1
  127. package/dist/talent-calculator.js.map +0 -1
  128. package/dist/tooltip/index.d.ts.map +0 -1
  129. package/dist/tooltip/positioning.d.ts.map +0 -1
  130. package/dist/tooltip/rich-text.d.ts.map +0 -1
  131. package/dist/tooltip/tooltip.d.ts.map +0 -1
  132. package/dist/tooltip/tooltip.styles.d.ts.map +0 -1
  133. package/dist/tooltip/types.d.ts.map +0 -1
  134. package/dist/tooltip.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import type { Hero, Talent } from "@crockery/fellowship-data";
1
+ import type { Hero, ImageAssetUrlResolver, Talent } from "@crockery/fellowship-data";
2
2
  import type { nothing, TemplateResult } from "lit";
3
3
  import type { FellowshipTalentDataSource, TalentCalculatorData } from "../data/talent-data-source.js";
4
4
  export type TalentCalculatorWarningCode = "invalid-position" | "duplicate-position" | "invalid-cost" | "missing-name" | "missing-description" | "missing-image" | "asset-url-error" | "asset-image-error" | "renderer-error" | "invalid-selection" | "over-budget";
@@ -74,7 +74,7 @@ export interface FellowshipTalentCalculatorProperties {
74
74
  heroId: string;
75
75
  talentPoints: number;
76
76
  selectedTalentIds: readonly string[];
77
- assetBaseUrl: string | null;
77
+ resolveAssetUrl: ImageAssetUrlResolver;
78
78
  dataSource: FellowshipTalentDataSource | null;
79
79
  renderTalent: LitTalentRenderer | null;
80
80
  showPointControls: boolean;
@@ -89,4 +89,3 @@ declare global {
89
89
  "fellowship-talent-points-change": TalentPointsChangeEvent;
90
90
  }
91
91
  }
92
- //# sourceMappingURL=types.d.ts.map
@@ -1,7 +1,5 @@
1
- import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "./chunks/talent-calculator-Dxuc2dZ8.js";
1
+ import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "./chunks/talent-calculator-DbU6P1AP.js";
2
2
  //#region src/talent-calculator/index.ts
3
3
  i();
4
4
  //#endregion
5
5
  export { c as FellowshipTalentCalculatorElement, n as MAX_TALENT_POINTS, e as canSelectTalent, r as createTalentCalculatorRenderModel, a as createTalentCalculatorSelectionState, i as defineFellowshipTalentCalculator, s as normalizeSelectedTalentIds, t as normalizeTalentPoints, l as talentRowLabel, o as talentSlotName };
6
-
7
- //# sourceMappingURL=talent-calculator.js.map
@@ -3,4 +3,3 @@ export type { TooltipPosition, TooltipPositionOptions, TooltipRect } from "./pos
3
3
  export { resolveFellowshipRichText, stripFellowshipRichText } from "./rich-text.js";
4
4
  export { FellowshipTooltipElement, defineFellowshipTooltip } from "./tooltip.js";
5
5
  export type { FellowshipRichTextDocument, FellowshipRichTextSegment, FellowshipTextTone, FellowshipTooltipPlacement, FellowshipTooltipToggleDetail, FellowshipTooltipToggleEvent, ResolvedTooltipPlacement, } from "./types.js";
6
- //# sourceMappingURL=index.d.ts.map
@@ -20,4 +20,3 @@ export interface TooltipPositionOptions {
20
20
  readonly margin?: number;
21
21
  }
22
22
  export declare const calculateTooltipPosition: (trigger: TooltipRect, tooltip: Pick<TooltipRect, "width" | "height">, options: TooltipPositionOptions) => TooltipPosition;
23
- //# sourceMappingURL=positioning.d.ts.map
@@ -1,4 +1,3 @@
1
1
  import type { FellowshipRichTextDocument } from "./types.js";
2
2
  export declare const resolveFellowshipRichText: (value: string) => FellowshipRichTextDocument;
3
3
  export declare const stripFellowshipRichText: (value: string) => string;
4
- //# sourceMappingURL=rich-text.d.ts.map
@@ -30,4 +30,3 @@ declare global {
30
30
  "fellowship-tooltip": FellowshipTooltipElement;
31
31
  }
32
32
  }
33
- //# sourceMappingURL=tooltip.d.ts.map
@@ -1,2 +1 @@
1
1
  export declare const tooltipStyles: import("lit").CSSResult;
2
- //# sourceMappingURL=tooltip.styles.d.ts.map
@@ -16,4 +16,3 @@ declare global {
16
16
  "fellowship-tooltip-toggle": FellowshipTooltipToggleEvent;
17
17
  }
18
18
  }
19
- //# sourceMappingURL=types.d.ts.map
package/dist/tooltip.js CHANGED
@@ -1,7 +1,5 @@
1
- import { a as e, i as t, n, r, t as i } from "./chunks/tooltip-DoQVSnOn.js";
1
+ import { n as e, o as t, r as n, s as r, t as i } from "./chunks/tooltip-C3SSJw-t.js";
2
2
  //#region src/tooltip/index.ts
3
- n();
3
+ e();
4
4
  //#endregion
5
- export { i as FellowshipTooltipElement, r as calculateTooltipPosition, n as defineFellowshipTooltip, t as resolveFellowshipRichText, e as stripFellowshipRichText };
6
-
7
- //# sourceMappingURL=tooltip.js.map
5
+ export { i as FellowshipTooltipElement, n as calculateTooltipPosition, e as defineFellowshipTooltip, t as resolveFellowshipRichText, r as stripFellowshipRichText };
package/package.json CHANGED
@@ -1,15 +1,12 @@
1
1
  {
2
2
  "name": "@crockery/fellowship-components",
3
- "version": "0.4.0",
3
+ "version": "1.0.0",
4
4
  "description": "Lit web components for rendering Fellowship game data.",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "git+https://github.com/Crockery/fellowship-components.git"
7
+ "url": "git+https://github.com/Crockery/fellowscript.git",
8
+ "directory": "packages/fellowship-components"
8
9
  },
9
- "bugs": {
10
- "url": "https://github.com/Crockery/fellowship-components/issues"
11
- },
12
- "homepage": "https://github.com/Crockery/fellowship-components#readme",
13
10
  "keywords": [
14
11
  "fellowship",
15
12
  "web-components",
@@ -28,6 +25,7 @@
28
25
  "./dist/dungeon-map.js",
29
26
  "./dist/talent-calculator.js",
30
27
  "./dist/tooltip.js",
28
+ "./dist/gear-planner.js",
31
29
  "./dist/react.js",
32
30
  "./dist/react/*.js"
33
31
  ],
@@ -60,6 +58,11 @@
60
58
  "import": "./dist/tooltip.js",
61
59
  "default": "./dist/tooltip.js"
62
60
  },
61
+ "./gear-planner": {
62
+ "types": "./dist/gear-planner/index.d.ts",
63
+ "import": "./dist/gear-planner.js",
64
+ "default": "./dist/gear-planner.js"
65
+ },
63
66
  "./react": {
64
67
  "types": "./dist/react/index.d.ts",
65
68
  "import": "./dist/react.js",
@@ -85,6 +88,11 @@
85
88
  "import": "./dist/react/tooltip.js",
86
89
  "default": "./dist/react/tooltip.js"
87
90
  },
91
+ "./react/gear-planner": {
92
+ "types": "./dist/react/gear-planner.d.ts",
93
+ "import": "./dist/react/gear-planner.js",
94
+ "default": "./dist/react/gear-planner.js"
95
+ },
88
96
  "./package.json": "./package.json"
89
97
  },
90
98
  "files": [
@@ -99,12 +107,12 @@
99
107
  "access": "public"
100
108
  },
101
109
  "engines": {
102
- "node": "^20.19.0 || >=22.12.0"
110
+ "node": ">=24"
103
111
  },
104
112
  "dependencies": {
105
- "@crockery/fellowship-data": "^1.3.0",
106
113
  "@lit/react": "^1.0.8",
107
- "lit": "^3.3.3"
114
+ "lit": "^3.3.3",
115
+ "@crockery/fellowship-data": "^3.0.0"
108
116
  },
109
117
  "peerDependencies": {
110
118
  "react": ">=18 <20",
@@ -120,10 +128,11 @@
120
128
  },
121
129
  "devDependencies": {
122
130
  "@arethetypeswrong/cli": "^0.18.5",
123
- "@biomejs/biome": "^2.5.3",
124
- "@changesets/cli": "^2.31.0",
131
+ "@biomejs/biome": "2.5.3",
132
+ "@fontsource/dm-mono": "5.2.7",
125
133
  "@fontsource/ibm-plex-mono": "5.2.7",
126
134
  "@fontsource/ibm-plex-sans-condensed": "5.2.8",
135
+ "@fontsource/syne": "5.3.0",
127
136
  "@ladle/react": "^5.1.1",
128
137
  "@types/react": "^18.3.24",
129
138
  "@types/react-dom": "^18.3.7",
@@ -151,9 +160,6 @@
151
160
  "ladle": "ladle serve",
152
161
  "ladle:build": "ladle build",
153
162
  "package:lint": "publint --strict --pack npm && attw --pack . --profile esm-only",
154
- "pack:check": "pnpm build && node scripts/check-build.mjs && pnpm package:lint && pnpm pack --dry-run",
155
- "changeset": "changeset",
156
- "version-packages": "changeset version",
157
- "release": "changeset publish"
163
+ "pack:check": "pnpm build && node scripts/check-build.mjs && node scripts/check-package-files.mjs && pnpm package:lint"
158
164
  }
159
165
  }
@@ -4,15 +4,21 @@
4
4
  cast-bearing abilities. Enemies without an actionable Interrupt or Stop response are omitted;
5
5
  non-actionable casts remain visible beneath otherwise eligible enemies.
6
6
 
7
- ```html
8
- <fellowship-dungeon-cast-browser dungeon-id="Level.ID.DemonicRitual">
9
- </fellowship-dungeon-cast-browser>
7
+ ```ts
8
+ import "@crockery/fellowship-components/dungeon-cast-browser";
9
+
10
+ const browser = document.querySelector("fellowship-dungeon-cast-browser");
11
+ browser.dungeonId = "Level.ID.DemonicRitual";
12
+ browser.resolveAssetUrl = (asset) =>
13
+ new URL(asset.path, "https://static.example.com/fellowship/").href;
10
14
  ```
11
15
 
12
16
  The component emits composed `fellowship-cast-browser-ready`,
13
17
  `fellowship-cast-browser-error`, and `fellowship-cast-selected` events. Selection is controlled:
14
18
  set `selectedCast` after receiving a selection event. Use `configuredCasts` to mark rules that
15
- already exist, and replace `dataSource` to provide game data from another host.
19
+ already exist, and replace `dataSource` to provide game data from another host. The
20
+ non-attribute `resolveAssetUrl` property is required; missing callbacks enter the
21
+ error state with code `asset-resolver-missing`.
16
22
 
17
23
  Set `configuredFilter` to `"configured"` or `"unconfigured"` to narrow the visible cast rows;
18
24
  the default is `"all"`. Set `fill` when the browser should occupy a constrained-height workspace
@@ -66,10 +66,11 @@ map loading, canvas drawing, positioning, and viewport interactions.
66
66
 
67
67
  ## Data and assets
68
68
 
69
- Set `assetBaseUrl` to self-host package images:
69
+ Provide the application-owned image resolver separately from the data source:
70
70
 
71
71
  ```ts
72
- map.assetBaseUrl = "https://static.example.com/fellowship-data/v1.0.0/";
72
+ map.resolveAssetUrl = (asset) =>
73
+ new URL(asset.path, "https://static.example.com/fellowship/").href;
73
74
  ```
74
75
 
75
76
  Replace `dataSource` for filtered, experimental, remote, or application-owned data:
@@ -83,9 +84,6 @@ const source: FellowshipDataSource = {
83
84
  async loadDungeon(dungeonId, signal) {
84
85
  return applicationData.loadDungeon(dungeonId, signal);
85
86
  },
86
- resolveAssetUrl(asset) {
87
- return new URL(asset.path, "https://static.example.com/").href;
88
- },
89
87
  };
90
88
 
91
89
  map.dataSource = source;
@@ -97,7 +95,8 @@ map.dataSource = source;
97
95
 
98
96
  - `dungeonId: string`
99
97
  - `mapImageId: string | null`
100
- - `assetBaseUrl: string | null` — `null` uses the package-pinned CDN.
98
+ - `resolveAssetUrl: ImageAssetUrlResolver` — required and assigned as a
99
+ JavaScript property.
101
100
  - `dataSource: FellowshipDataSource | null` — `null` uses the package provider.
102
101
  - `renderEnemy: LitEnemyRenderer | null`
103
102
  - `renderGroup: LitGroupRenderer | null`
@@ -0,0 +1,44 @@
1
+ # Gear Planner
2
+
3
+ `<fellowship-gear-planner>` is a controlled, responsive editor for serializable
4
+ Fellowship loadouts.
5
+
6
+ ```ts
7
+ import gearPlannerData, {
8
+ createEmptyGearPlannerBuild,
9
+ } from "@crockery/fellowship-data/gear-planner";
10
+ import "@crockery/fellowship-components/gear-planner";
11
+
12
+ const planner = document.querySelector("fellowship-gear-planner");
13
+ planner.data = gearPlannerData;
14
+ planner.build = createEmptyGearPlannerBuild(
15
+ gearPlannerData,
16
+ gearPlannerData.heroes[0].hero.id,
17
+ );
18
+ planner.resolveAssetUrl = (asset) =>
19
+ new URL(asset.path, "https://static.example.com/fellowship/").href;
20
+ planner.addEventListener("fellowship-gear-build-change", (event) => {
21
+ planner.build = event.detail.build;
22
+ });
23
+ ```
24
+
25
+ The element never persists or mutates `build`. Its public properties are
26
+ `data`, `build`, `loading`, `error`, and the required `resolveAssetUrl`
27
+ callback.
28
+ `fellowship-gear-build-change` bubbles and is composed; its detail includes the
29
+ complete proposed build, change source, and affected position.
30
+ `fellowship-gear-planner-error` reports recoverable presentation failures.
31
+
32
+ The React wrapper is exported from
33
+ `@crockery/fellowship-components/react/gear-planner` as `GearPlanner`, with
34
+ `onBuildChange` and `onPlannerError` event props.
35
+
36
+ At 1200px and above, the component renders summary and slot rails around a
37
+ persistent workbench. Below 1200px it uses Traits & gems, Gear, and Bonuses tabs,
38
+ with configuration in a full-height focus-contained editor. Item choices are
39
+ filtered by the Data package, all changes are immediate, and unsupported
40
+ mechanics remain visibly excluded from exact totals.
41
+
42
+ The planner uses the public `--fellowship-poster-*` Dungeon Poster tokens and
43
+ bundled Syne/DM Mono faces. It maintains 44px controls, visible focus, reduced
44
+ motion, canonical mobile position order, and accessible equipped-item tooltips.
@@ -102,22 +102,20 @@ continues to own button semantics and selection state.
102
102
 
103
103
  ## Data and assets
104
104
 
105
- Self-host images with `assetBaseUrl`, or replace the complete provider:
105
+ Provide an image resolver and optionally replace the data provider:
106
106
 
107
107
  ```ts
108
108
  import type { FellowshipTalentDataSource } from "@crockery/fellowship-components/talent-calculator";
109
109
 
110
110
  declare const applicationTalentData: FellowshipTalentDataSource;
111
111
 
112
- calculator.assetBaseUrl = "https://static.example.com/fellowship-data/v1.0.0/";
112
+ calculator.resolveAssetUrl = (asset) =>
113
+ new URL(asset.path, "https://static.example.com/fellowship/").href;
113
114
 
114
115
  const source: FellowshipTalentDataSource = {
115
116
  async loadHeroTalents(heroId, signal) {
116
117
  return applicationTalentData.loadHeroTalents(heroId, signal);
117
118
  },
118
- resolveAssetUrl(asset) {
119
- return new URL(asset.path, "https://static.example.com/").href;
120
- },
121
119
  };
122
120
 
123
121
  calculator.dataSource = source;
@@ -131,7 +129,8 @@ calculator.dataSource = source;
131
129
  - `talentPoints: number` — defaults to `14`, normalized from `0` through
132
130
  `MAX_TALENT_POINTS`.
133
131
  - `selectedTalentIds: readonly string[]`
134
- - `assetBaseUrl: string | null`
132
+ - `resolveAssetUrl: ImageAssetUrlResolver` required and assigned as a
133
+ JavaScript property.
135
134
  - `dataSource: FellowshipTalentDataSource | null`
136
135
  - `renderTalent: LitTalentRenderer | null`
137
136
  - `showPointControls: boolean` — defaults to `true`.
@@ -48,8 +48,9 @@ Pass a React node to the wrapper's `content` prop to fill the named content slot
48
48
  - Positioning maintains an 8px trigger gap and viewport collision margin.
49
49
  - The native Popover API is used when available, with a fixed-position fallback.
50
50
 
51
- Tooltip content is noninteractive and uses `role="tooltip"`. The first slotted trigger receives a
52
- plain-text `aria-description` while connected; any existing value is restored afterward.
51
+ Tooltip content is noninteractive and uses `role="tooltip"`. The first slotted
52
+ trigger receives a stable `aria-describedby` relationship to the tooltip
53
+ surface while connected; any existing relationship is preserved and restored.
53
54
 
54
55
  ## API
55
56
 
@@ -101,10 +102,8 @@ import {
101
102
 
102
103
  ## Theming
103
104
 
104
- The tooltip inherits the shared `--fellowship-field-*` theme and exposes the established
105
- `--fellowship-tooltip-*` overrides. Its desaturated semantic defaults are ability `#76C5D2`,
106
- warning `#D4A761`, effect `#AFA1CA`, resource `#7CB2C2`, healing `#82B997`, Meiko primary
107
- `#C892A0`, and Meiko secondary `#8DBAB7`.
105
+ The tooltip inherits the shared `--fellowship-poster-*` Dungeon Poster
106
+ foundation and exposes the established `--fellowship-tooltip-*` overrides.
108
107
 
109
108
  CSS parts: `trigger`, `surface`, `arrow`, `heading`, `content`, and `paragraph`.
110
109
 
@@ -1,9 +0,0 @@
1
- import { FellowshipDataError as e } from "@crockery/fellowship-data/client";
2
- //#region src/data/data-source.ts
3
- var t = 2, n = (n) => {
4
- if (n !== t) throw new e("unsupported-schema", `Unsupported fellowship-data schema version: ${String(n)}.`);
5
- };
6
- //#endregion
7
- export { n, e as t };
8
-
9
- //# sourceMappingURL=data-source-XbksqUrw.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"data-source-XbksqUrw.js","names":[],"sources":["../../src/data/data-source.ts"],"sourcesContent":["import { FellowshipDataError } from \"@crockery/fellowship-data/client\";\nexport { FellowshipDataError };\nexport type {\n DungeonMapData,\n FellowshipDataErrorCode,\n} from \"@crockery/fellowship-data/client\";\n\nimport type { FellowshipDataClient } from \"@crockery/fellowship-data/client\";\n\nconst supportedSchemaVersion = 2;\n\nexport const assertSupportedFellowshipDataSchema = (schemaVersion: number): void => {\n if (schemaVersion === supportedSchemaVersion) return;\n\n throw new FellowshipDataError(\n \"unsupported-schema\",\n `Unsupported fellowship-data schema version: ${String(schemaVersion)}.`,\n );\n};\n\n/** @deprecated Use the corresponding `FellowshipDataClient` methods. */\nexport type FellowshipDataSource = Pick<FellowshipDataClient, \"loadDungeon\" | \"resolveAssetUrl\">;\n"],"mappings":";;AASA,IAAM,IAAyB,GAElB,KAAuC,MAAgC;CAC9E,UAAkB,GAEtB,MAAM,IAAI,EACR,sBACA,+CAA+C,OAAO,CAAa,EAAE,EACvE;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"decorate-B_ZTN9li.js","names":[],"sources":["../../src/styles/field-manual.styles.ts","../../node_modules/.pnpm/@fontsource+ibm-plex-mono@5.2.7/node_modules/@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-500-normal.woff2?fellowship-font-url","../../node_modules/.pnpm/@fontsource+ibm-plex-mono@5.2.7/node_modules/@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-600-normal.woff2?fellowship-font-url","../../node_modules/.pnpm/@fontsource+ibm-plex-sans-condensed@5.2.8/node_modules/@fontsource/ibm-plex-sans-condensed/files/ibm-plex-sans-condensed-latin-400-normal.woff2?fellowship-font-url","../../node_modules/.pnpm/@fontsource+ibm-plex-sans-condensed@5.2.8/node_modules/@fontsource/ibm-plex-sans-condensed/files/ibm-plex-sans-condensed-latin-500-normal.woff2?fellowship-font-url","../../node_modules/.pnpm/@fontsource+ibm-plex-sans-condensed@5.2.8/node_modules/@fontsource/ibm-plex-sans-condensed/files/ibm-plex-sans-condensed-latin-600-normal.woff2?fellowship-font-url","../../node_modules/.pnpm/@fontsource+ibm-plex-sans-condensed@5.2.8/node_modules/@fontsource/ibm-plex-sans-condensed/files/ibm-plex-sans-condensed-latin-700-normal.woff2?fellowship-font-url","../../src/styles/field-manual-fonts.ts"],"sourcesContent":["import { css } from \"lit\";\n\n/** Shared internal aliases for the public Expedition Field Manual theme tokens. */\nexport const fieldManualStyles = css`\n :host {\n --_fellowship-field-canvas: var(--fellowship-field-canvas, #0b0f13);\n --_fellowship-field-surface: var(--fellowship-field-surface, #10161c);\n --_fellowship-field-surface-raised: var(--fellowship-field-surface-raised, #151c23);\n --_fellowship-field-inset: var(--fellowship-field-inset, #080c10);\n --_fellowship-field-ink: var(--fellowship-field-ink, #f1eee5);\n --_fellowship-field-muted: var(--fellowship-field-muted, #b7b4aa);\n --_fellowship-field-tertiary: var(--fellowship-field-tertiary, #858a8e);\n --_fellowship-field-border: var(\n --fellowship-field-border,\n rgb(241 238 229 / 10%)\n );\n --_fellowship-field-border-strong: var(\n --fellowship-field-border-strong,\n rgb(241 238 229 / 18%)\n );\n --_fellowship-field-accent: var(--fellowship-field-accent, #69c4d4);\n --_fellowship-field-focus: var(--fellowship-field-focus, #a8e6ee);\n --_fellowship-field-ember: var(--fellowship-field-ember, #d9784c);\n --_fellowship-field-brass: var(--fellowship-field-brass, #c1a36a);\n --_fellowship-field-font-family: var(\n --fellowship-field-font-family,\n \"Fellowship Field\",\n \"Arial Narrow\",\n sans-serif\n );\n --_fellowship-field-mono-font-family: var(\n --fellowship-field-mono-font-family,\n \"Fellowship Tactical\",\n ui-monospace,\n monospace\n );\n --_fellowship-field-radius: var(--fellowship-field-radius, 6px);\n }\n`;\n","export default import.meta.ROLLUP_FILE_URL_FKNdLzKnlftW3t_gzo81yw;","export default import.meta.ROLLUP_FILE_URL_P4DPfwIRieI8P6q8r4JZRw;","export default import.meta.ROLLUP_FILE_URL_WPGYjwIlzWVNM1bYhOc83w;","export default import.meta.ROLLUP_FILE_URL_cn7IAAGY294pWGc7MpElVg;","export default import.meta.ROLLUP_FILE_URL_8HSUfh7cJHNFxCkInNzNXw;","export default import.meta.ROLLUP_FILE_URL_VRAku6fjghkApIISiBWPzg;","import mono500Url from \"@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-500-normal.woff2?url\";\nimport mono600Url from \"@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-600-normal.woff2?url\";\nimport field400Url from \"@fontsource/ibm-plex-sans-condensed/files/ibm-plex-sans-condensed-latin-400-normal.woff2?url\";\nimport field500Url from \"@fontsource/ibm-plex-sans-condensed/files/ibm-plex-sans-condensed-latin-500-normal.woff2?url\";\nimport field600Url from \"@fontsource/ibm-plex-sans-condensed/files/ibm-plex-sans-condensed-latin-600-normal.woff2?url\";\nimport field700Url from \"@fontsource/ibm-plex-sans-condensed/files/ibm-plex-sans-condensed-latin-700-normal.woff2?url\";\n\ninterface FieldManualFontAsset {\n readonly family: \"Fellowship Field\" | \"Fellowship Tactical\";\n readonly weight: \"400\" | \"500\" | \"600\" | \"700\";\n readonly url: string;\n}\n\nconst fieldManualFontAssets: readonly FieldManualFontAsset[] = [\n { family: \"Fellowship Field\", weight: \"400\", url: field400Url },\n { family: \"Fellowship Field\", weight: \"500\", url: field500Url },\n { family: \"Fellowship Field\", weight: \"600\", url: field600Url },\n { family: \"Fellowship Field\", weight: \"700\", url: field700Url },\n { family: \"Fellowship Tactical\", weight: \"500\", url: mono500Url },\n { family: \"Fellowship Tactical\", weight: \"600\", url: mono600Url },\n];\n\nconst installedDocuments = new WeakSet<Document>();\n\n/** Makes the bundled field-manual faces available to a component's owner document. */\nexport const installFieldManualFonts = (document: Document): void => {\n if (installedDocuments.has(document)) {\n return;\n }\n\n const FontFaceConstructor = document.defaultView?.FontFace;\n if (!FontFaceConstructor || !document.fonts) {\n return;\n }\n\n const faces = fieldManualFontAssets.map(\n ({ family, weight, url }) =>\n new FontFaceConstructor(family, `url(${JSON.stringify(url)}) format(\"woff2\")`, {\n display: \"swap\",\n style: \"normal\",\n weight,\n }),\n );\n for (const face of faces) {\n document.fonts.add(face);\n }\n installedDocuments.add(document);\n};\n"],"x_google_ignoreList":[1,2,3,4,5,6],"mappings":";;AAGA,IAAa,IAAoB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GCHpC,IAAA,IAAA,IAAA,2DAAA,OAAA,KAAA,GAAA,CAAA,CAAA,MCAA,IAAA,IAAA,IAAA,2DAAA,OAAA,KAAA,GAAA,CAAA,CAAA,MCAA,IAAA,IAAA,IAAA,qEAAA,OAAA,KAAA,GAAA,CAAA,CAAA,MCAA,IAAA,IAAA,IAAA,qEAAA,OAAA,KAAA,GAAA,CAAA,CAAA,MCAA,IAAA,IAAA,IAAA,qEAAA,OAAA,KAAA,GAAA,CAAA,CAAA,MCAA,IAAA,IAAA,IAAA,qEAAA,OAAA,KAAA,GAAA,CAAA,CAAA,MCaM,IAAyD;CAC7D;EAAE,QAAQ;EAAoB,QAAQ;EAAO,KAAK;CAAY;CAC9D;EAAE,QAAQ;EAAoB,QAAQ;EAAO,KAAK;CAAY;CAC9D;EAAE,QAAQ;EAAoB,QAAQ;EAAO,KAAK;CAAY;CAC9D;EAAE,QAAQ;EAAoB,QAAQ;EAAO,KAAK;CAAY;CAC9D;EAAE,QAAQ;EAAuB,QAAQ;EAAO,KAAK;CAAW;CAChE;EAAE,QAAQ;EAAuB,QAAQ;EAAO,KAAK;CAAW;AAClE,GAEM,oBAAqB,IAAI,QAAkB,GAGpC,KAA2B,MAA6B;CACnE,IAAI,EAAmB,IAAI,CAAQ,GACjC;CAGF,IAAM,IAAsB,EAAS,aAAa;CAClD,IAAI,CAAC,KAAuB,CAAC,EAAS,OACpC;CAGF,IAAM,IAAQ,EAAsB,KACjC,EAAE,WAAQ,WAAQ,aACjB,IAAI,EAAoB,GAAQ,OAAO,KAAK,UAAU,CAAG,EAAE,oBAAoB;EAC7E,SAAS;EACT,OAAO;EACP;CACF,CAAC,CACL;CACA,KAAK,IAAM,KAAQ,GACjB,EAAS,MAAM,IAAI,CAAI;CAEzB,EAAmB,IAAI,CAAQ;AACjC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"dungeon-cast-browser-CSnu3gC5.js","names":["#abortController","#loadDungeonCasts","#renderStatus","#visibleEnemies","#handleSearch","#renderEnemy","#renderEmptySearch","#loadToken","#sameIdentity","#renderCast","#selectCast","#handleCastKeydown","#handleAbilityImageError","#statusCard"],"sources":["../../src/data/package-cast-data-source.ts","../../src/dungeon-cast-browser/dungeon-cast-browser.styles.ts","../../src/dungeon-cast-browser/render-model.ts","../../src/dungeon-cast-browser/dungeon-cast-browser.ts"],"sourcesContent":["import { createPackageFellowshipDataClient } from \"@crockery/fellowship-data/client\";\n\nimport type { FellowshipCastDataSource } from \"./cast-data-source.js\";\n\n/** @deprecated Create a package Fellowship data client instead. */\nexport interface PackageFellowshipCastDataSourceOptions {\n readonly assetBaseUrl?: string;\n}\n\n/** @deprecated Use `createPackageFellowshipDataClient` from `@crockery/fellowship-data/client`. */\nexport const createPackageFellowshipCastDataSource = (\n options: PackageFellowshipCastDataSourceOptions = {},\n): FellowshipCastDataSource => {\n const client = createPackageFellowshipDataClient(options);\n return {\n loadDungeonCasts: (dungeonId, signal) => client.loadDungeonCasts(dungeonId, signal),\n resolveAssetUrl: (asset) => client.resolveAssetUrl(asset),\n };\n};\n","import { css } from \"lit\";\n\nexport const dungeonCastBrowserStyles = css`\n :host {\n --fellowship-cast-canvas: var(--_fellowship-field-canvas);\n --fellowship-cast-surface: var(--_fellowship-field-surface);\n --fellowship-cast-raised: var(--_fellowship-field-surface-raised);\n --fellowship-cast-ink: var(--_fellowship-field-ink);\n --fellowship-cast-muted: var(--_fellowship-field-muted);\n --fellowship-cast-tertiary: var(--_fellowship-field-tertiary);\n --fellowship-cast-border: var(--_fellowship-field-border);\n --fellowship-cast-border-strong: var(--_fellowship-field-border-strong);\n --fellowship-cast-interrupt: var(--_fellowship-field-accent);\n --fellowship-cast-stop: var(--_fellowship-field-brass);\n --fellowship-cast-focus: var(--_fellowship-field-focus);\n --fellowship-cast-font-family: var(--_fellowship-field-font-family);\n --fellowship-cast-tactical-font-family: var(--_fellowship-field-mono-font-family);\n\n display: block;\n inline-size: 100%;\n min-block-size: 320px;\n color: var(--fellowship-cast-ink);\n font-family: var(--fellowship-cast-font-family);\n container-type: inline-size;\n -webkit-font-smoothing: antialiased;\n }\n\n :host([hidden]) {\n display: none;\n }\n\n :host([fill]) {\n min-block-size: 0;\n block-size: 100%;\n overflow: hidden;\n }\n\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n .browser {\n min-block-size: inherit;\n overflow: hidden;\n border: 1px solid var(--fellowship-cast-border);\n border-radius: calc(var(--_fellowship-field-radius) + 2px);\n background: var(--fellowship-cast-canvas);\n }\n\n :host([fill]) .browser {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n min-block-size: 0;\n block-size: 100%;\n }\n\n .toolbar {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 12px;\n align-items: end;\n padding: 16px;\n border-block-end: 1px solid var(--fellowship-cast-border);\n background: var(--fellowship-cast-surface);\n }\n\n .eyebrow,\n .summary,\n .enemy-id,\n .ability-id,\n .capability,\n .marker {\n font-family: var(--fellowship-cast-tactical-font-family);\n }\n\n .eyebrow {\n margin: 0 0 4px;\n color: var(--fellowship-cast-interrupt);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n }\n\n h2 {\n margin: 0;\n font-size: clamp(18px, 3cqi, 24px);\n font-weight: 600;\n letter-spacing: 0.01em;\n line-height: 1.08;\n }\n\n .summary {\n margin: 6px 0 0;\n color: var(--fellowship-cast-tertiary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.04em;\n }\n\n .search-label {\n display: grid;\n gap: 4px;\n min-inline-size: min(240px, 38cqi);\n color: var(--fellowship-cast-muted);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.04em;\n }\n\n .search-input {\n min-block-size: 44px;\n inline-size: 100%;\n border: 1px solid var(--fellowship-cast-border-strong);\n border-radius: var(--_fellowship-field-radius);\n background: var(--_fellowship-field-inset);\n color: var(--fellowship-cast-ink);\n font: inherit;\n font-size: 14px;\n padding-inline: 12px;\n }\n\n .search-input::placeholder {\n color: var(--fellowship-cast-tertiary);\n }\n\n .search-input:focus-visible,\n .cast-row:focus-visible,\n .retry-button:focus-visible {\n outline: 2px solid var(--fellowship-cast-focus);\n outline-offset: 2px;\n }\n\n .enemy-list {\n display: grid;\n }\n\n :host([fill]) .enemy-list {\n min-block-size: 0;\n align-content: start;\n overflow-y: auto;\n overscroll-behavior: contain;\n }\n\n .enemy {\n min-inline-size: 0;\n border-block-end: 1px solid var(--fellowship-cast-border);\n }\n\n .enemy:last-child {\n border-block-end: 0;\n }\n\n .enemy-header {\n display: grid;\n grid-template-columns: 4px minmax(0, 1fr) auto;\n gap: 12px;\n align-items: center;\n min-block-size: 52px;\n padding: 8px 16px;\n background: color-mix(in srgb, var(--fellowship-cast-raised) 72%, transparent);\n }\n\n .enemy-rule {\n align-self: stretch;\n background: var(--fellowship-cast-interrupt);\n }\n\n .enemy-name {\n overflow: hidden;\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n line-height: 1.15;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .enemy-id {\n overflow: hidden;\n margin: 3px 0 0;\n color: var(--fellowship-cast-tertiary);\n font-size: 10px;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .enemy-count {\n color: var(--fellowship-cast-muted);\n font-family: var(--fellowship-cast-tactical-font-family);\n font-size: 10px;\n font-variant-numeric: tabular-nums;\n }\n\n .cast-list {\n display: grid;\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n .cast-list > li + li {\n border-block-start: 1px solid var(--fellowship-cast-border);\n }\n\n .cast-row {\n position: relative;\n display: grid;\n grid-template-columns: 40px minmax(0, 1fr) auto auto;\n gap: 12px;\n align-items: center;\n inline-size: 100%;\n min-block-size: 64px;\n padding: 8px 16px 8px 28px;\n border: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: start;\n cursor: pointer;\n transition: background-color 140ms cubic-bezier(0.23, 1, 0.32, 1);\n }\n\n .cast-row:not(:disabled):hover {\n background: color-mix(in srgb, var(--fellowship-cast-interrupt) 7%, transparent);\n }\n\n .cast-row[aria-pressed=\"true\"] {\n background: color-mix(in srgb, var(--fellowship-cast-interrupt) 11%, transparent);\n }\n\n .cast-row[aria-pressed=\"true\"]::before {\n position: absolute;\n inset: 12px auto 12px 0;\n inline-size: 3px;\n background: var(--fellowship-cast-interrupt);\n content: \"\";\n }\n\n .cast-row:disabled {\n color: var(--fellowship-cast-tertiary);\n cursor: not-allowed;\n }\n\n .ability-image,\n .ability-fallback {\n inline-size: 40px;\n block-size: 40px;\n border: 1px solid var(--fellowship-cast-border-strong);\n border-radius: 4px;\n background: var(--fellowship-cast-raised);\n }\n\n .ability-image {\n display: block;\n object-fit: cover;\n }\n\n .ability-fallback {\n display: grid;\n place-items: center;\n color: var(--fellowship-cast-muted);\n font-family: var(--fellowship-cast-tactical-font-family);\n font-size: 11px;\n font-weight: 600;\n }\n\n .ability-image[hidden],\n .ability-fallback[hidden] {\n display: none;\n }\n\n .ability-copy {\n min-inline-size: 0;\n }\n\n .ability-name {\n display: block;\n overflow: hidden;\n font-size: 14px;\n font-weight: 600;\n line-height: 1.15;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .ability-id {\n display: block;\n overflow: hidden;\n margin-block-start: 4px;\n color: var(--fellowship-cast-tertiary);\n font-size: 10px;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .capability {\n min-inline-size: 102px;\n padding: 5px 8px;\n border: 1px solid currentColor;\n border-radius: 999px;\n color: var(--fellowship-cast-interrupt);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.04em;\n text-align: center;\n white-space: nowrap;\n }\n\n .capability[data-kind=\"varies\"] {\n border-style: dashed;\n }\n\n .capability[data-kind=\"stop\"] {\n color: var(--fellowship-cast-stop);\n }\n\n .capability[data-kind=\"none\"] {\n color: var(--fellowship-cast-tertiary);\n }\n\n .marker {\n min-inline-size: 72px;\n color: var(--fellowship-cast-stop);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.06em;\n text-align: end;\n text-transform: uppercase;\n }\n\n .marker[data-empty=\"true\"] {\n visibility: hidden;\n }\n\n .status {\n display: grid;\n min-block-size: 280px;\n padding: 32px;\n place-items: center;\n text-align: center;\n }\n\n :host([fill]) .status {\n min-block-size: 0;\n }\n\n .status-card {\n display: grid;\n max-inline-size: 360px;\n gap: 8px;\n justify-items: center;\n }\n\n .status-mark {\n inline-size: 32px;\n block-size: 4px;\n margin-block-end: 8px;\n background: var(--fellowship-cast-interrupt);\n }\n\n .status-title {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n }\n\n .status-copy {\n margin: 0;\n color: var(--fellowship-cast-muted);\n font-size: 14px;\n line-height: 1.45;\n }\n\n .retry-button {\n min-block-size: 44px;\n margin-block-start: 8px;\n padding-inline: 16px;\n border: 1px solid var(--fellowship-cast-interrupt);\n border-radius: var(--_fellowship-field-radius);\n background: transparent;\n color: var(--fellowship-cast-ink);\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n }\n\n @container (max-width: 560px) {\n .toolbar {\n grid-template-columns: 1fr;\n }\n\n .search-label {\n min-inline-size: 0;\n }\n\n .cast-row {\n grid-template-columns: 40px minmax(0, 1fr) auto;\n padding-inline: 16px;\n }\n\n .marker {\n position: absolute;\n inset: 5px 8px auto auto;\n min-inline-size: 0;\n font-size: 8px;\n }\n\n .ability-id,\n .enemy-id {\n display: none;\n }\n\n .capability {\n min-inline-size: 0;\n padding-inline: 6px;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .cast-row {\n transition: none;\n }\n }\n`;\n","import type { Ability, ImageAssetRef } from \"@crockery/fellowship-data\";\n\nimport type { DungeonCastData, FellowshipCastDataSource } from \"../data/cast-data-source.js\";\nimport type {\n CastCapabilityLabel,\n DungeonCastBrowserModel,\n DungeonCastEnemyModel,\n DungeonCastModel,\n FellowshipCastIdentity,\n} from \"./types.js\";\n\nexport const castIdentityKey = ({ enemyId, abilityId }: FellowshipCastIdentity): string =>\n `${enemyId}\\u0000${abilityId}`;\n\nconst abilityImage = (ability: Ability): ImageAssetRef | null =>\n ability.images.find(({ size }) => size === \"200\") ?? ability.images[0] ?? null;\n\nconst capabilityLabel = (\n canInterrupt: boolean,\n interruptVaries: boolean,\n canStop: boolean,\n): CastCapabilityLabel => {\n if (interruptVaries) return \"Interrupt varies\";\n if (canInterrupt) return \"Interrupt\";\n if (canStop) return \"Stop\";\n return \"No response\";\n};\n\nexport const createDungeonCastBrowserModel = (\n data: DungeonCastData,\n source: FellowshipCastDataSource,\n): DungeonCastBrowserModel => {\n const seenEnemyIds = new Set<string>();\n const enemies: DungeonCastEnemyModel[] = [];\n\n for (const placement of data.dungeon.enemies) {\n if (seenEnemyIds.has(placement.id)) continue;\n seenEnemyIds.add(placement.id);\n\n const enemy = data.enemies.get(placement.id);\n if (!enemy) continue;\n\n const casts: DungeonCastModel[] = (data.abilitiesByEnemy.get(enemy.id) ?? [])\n .filter((ability) => ability.castInterruptibility !== undefined)\n .map((ability) => {\n const canInterrupt = ability.castInterruptibility !== \"never\";\n const interruptVaries = ability.castInterruptibility === \"sometimes\";\n const canStop = enemy.canBeControlled;\n const image = abilityImage(ability);\n let imageUrl: string | null = null;\n if (image) {\n try {\n imageUrl = source.resolveAssetUrl(image);\n } catch {\n imageUrl = null;\n }\n }\n return {\n identity: { enemyId: enemy.id, abilityId: ability.id },\n ability,\n enemy,\n canInterrupt,\n interruptVaries,\n canStop,\n actionable: canInterrupt || canStop,\n capabilityLabel: capabilityLabel(canInterrupt, interruptVaries, canStop),\n imageUrl,\n };\n });\n\n if (casts.some(({ actionable }) => actionable)) enemies.push({ enemy, casts });\n }\n\n return {\n dungeon: data.dungeon,\n enemies,\n castCount: enemies.reduce((total, enemy) => total + enemy.casts.length, 0),\n };\n};\n","import { html, LitElement, nothing, type PropertyValues, type TemplateResult } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\n\nimport type { FellowshipCastDataSource } from \"../data/cast-data-source.js\";\nimport { assertSupportedFellowshipDataSchema, FellowshipDataError } from \"../data/data-source.js\";\nimport { createPackageFellowshipCastDataSource } from \"../data/package-cast-data-source.js\";\nimport { installFieldManualFonts } from \"../styles/field-manual-fonts.js\";\nimport { fieldManualStyles } from \"../styles/field-manual.styles.js\";\nimport { dungeonCastBrowserStyles } from \"./dungeon-cast-browser.styles.js\";\nimport { castIdentityKey, createDungeonCastBrowserModel } from \"./render-model.js\";\nimport type {\n ConfiguredCastFilter,\n DungeonCastBrowserModel,\n DungeonCastEnemyModel,\n DungeonCastModel,\n FellowshipCastBrowserErrorDetail,\n FellowshipCastBrowserReadyDetail,\n FellowshipCastIdentity,\n FellowshipCastSelectedDetail,\n} from \"./types.js\";\n\ntype ComponentStatus = \"empty\" | \"loading\" | \"ready\" | \"error\";\n\nconst eventOptions = { bubbles: true, composed: true } as const;\n\n/**\n * @fires fellowship-cast-browser-ready - Fired after dungeon casts have been loaded and joined.\n * @fires fellowship-cast-browser-error - Fired when dungeon cast data cannot be loaded.\n * @fires fellowship-cast-selected - Fired when a user selects an actionable cast.\n */\n@customElement(\"fellowship-dungeon-cast-browser\")\nexport class FellowshipDungeonCastBrowserElement extends LitElement {\n static override styles = [fieldManualStyles, dungeonCastBrowserStyles];\n\n @property({ type: String, attribute: \"dungeon-id\" }) dungeonId = \"\";\n @property({ attribute: false }) selectedCast: FellowshipCastIdentity | null = null;\n @property({ attribute: false }) configuredCasts: readonly FellowshipCastIdentity[] = [];\n @property({ type: String, attribute: \"configured-filter\" })\n configuredFilter: ConfiguredCastFilter = \"all\";\n @property({ type: Boolean, reflect: true }) fill = false;\n @property({ type: String, attribute: \"asset-base-url\" }) assetBaseUrl: string | null = null;\n @property({ attribute: false }) dataSource: FellowshipCastDataSource | null = null;\n\n @state() private _status: ComponentStatus = \"empty\";\n @state() private _error: FellowshipCastBrowserErrorDetail | null = null;\n @state() private _model: DungeonCastBrowserModel | null = null;\n @state() private _query = \"\";\n\n #abortController: AbortController | null = null;\n #loadToken = 0;\n\n override connectedCallback(): void {\n super.connectedCallback();\n installFieldManualFonts(this.ownerDocument);\n }\n\n override disconnectedCallback(): void {\n this.#abortController?.abort();\n super.disconnectedCallback();\n }\n\n protected override updated(changed: PropertyValues<this>): void {\n super.updated(changed);\n if (changed.has(\"dungeonId\") || changed.has(\"dataSource\") || changed.has(\"assetBaseUrl\")) {\n queueMicrotask(() => {\n if (this.isConnected) void this.#loadDungeonCasts();\n });\n }\n }\n\n retry(): void {\n void this.#loadDungeonCasts();\n }\n\n get renderModel(): DungeonCastBrowserModel | null {\n return this._model;\n }\n\n override render(): TemplateResult {\n if (this._status !== \"ready\" || !this._model) {\n return html`<section class=\"browser\" aria-label=\"Dungeon cast browser\">${this.#renderStatus()}</section>`;\n }\n\n const visibleEnemies = this.#visibleEnemies(this._model);\n return html`\n <section class=\"browser\" aria-label=${`${this._model.dungeon.name ?? this._model.dungeon.id} casts`}>\n <header class=\"toolbar\" part=\"toolbar\">\n <div>\n <p class=\"eyebrow\">Dungeon cast index</p>\n <h2>${this._model.dungeon.name ?? this._model.dungeon.id}</h2>\n <p class=\"summary\">\n ${this._model.enemies.length} enemies · ${this._model.castCount} cast-bearing abilities\n </p>\n </div>\n <label class=\"search-label\">\n Search casts\n <input\n class=\"search-input\"\n type=\"search\"\n placeholder=\"Enemy or ability\"\n .value=${this._query}\n @input=${this.#handleSearch}\n />\n </label>\n </header>\n ${\n visibleEnemies.length > 0\n ? html`<div class=\"enemy-list\" part=\"enemy-list\">${repeat(\n visibleEnemies,\n ({ enemy }) => enemy.id,\n (enemy) => this.#renderEnemy(enemy),\n )}</div>`\n : this.#renderEmptySearch()\n }\n </section>\n `;\n }\n\n async #loadDungeonCasts(): Promise<void> {\n const dungeonId = this.dungeonId.trim();\n const token = ++this.#loadToken;\n this.#abortController?.abort();\n this.#abortController = new AbortController();\n this._model = null;\n this._error = null;\n this._query = \"\";\n\n if (!dungeonId) {\n this._status = \"empty\";\n return;\n }\n\n this._status = \"loading\";\n const source =\n this.dataSource ??\n createPackageFellowshipCastDataSource(\n this.assetBaseUrl ? { assetBaseUrl: this.assetBaseUrl } : {},\n );\n\n try {\n const data = await source.loadDungeonCasts(dungeonId, this.#abortController.signal);\n if (token !== this.#loadToken || this.#abortController.signal.aborted) return;\n assertSupportedFellowshipDataSchema(data.manifest.schemaVersion);\n const model = createDungeonCastBrowserModel(data, source);\n this._model = model;\n this._status = \"ready\";\n this.dispatchEvent(\n new CustomEvent<FellowshipCastBrowserReadyDetail>(\"fellowship-cast-browser-ready\", {\n ...eventOptions,\n detail: { model, data },\n }),\n );\n } catch (error: unknown) {\n if (token !== this.#loadToken || this.#abortController.signal.aborted) return;\n const detail: FellowshipCastBrowserErrorDetail = {\n code:\n error instanceof FellowshipDataError\n ? error.code\n : ((error as { code?: string }).code ?? \"data-load-failed\"),\n message: error instanceof Error ? error.message : String(error),\n ...(error instanceof Error && error.cause !== undefined ? { cause: error.cause } : {}),\n };\n this._error = detail;\n this._status = \"error\";\n this.dispatchEvent(\n new CustomEvent<FellowshipCastBrowserErrorDetail>(\"fellowship-cast-browser-error\", {\n ...eventOptions,\n detail,\n }),\n );\n }\n }\n\n #visibleEnemies(model: DungeonCastBrowserModel): readonly DungeonCastEnemyModel[] {\n const query = this._query.trim().toLocaleLowerCase();\n const configuredFilter =\n this.configuredFilter === \"configured\" || this.configuredFilter === \"unconfigured\"\n ? this.configuredFilter\n : \"all\";\n return model.enemies.flatMap((enemy) => {\n const enemyMatches = `${enemy.enemy.name ?? \"\"} ${enemy.enemy.id}`\n .toLocaleLowerCase()\n .includes(query);\n const casts = enemy.casts.filter((cast) => {\n const queryMatches =\n !query ||\n enemyMatches ||\n `${cast.ability.name ?? \"\"} ${cast.ability.id}`.toLocaleLowerCase().includes(query);\n if (!queryMatches) return false;\n const configured = this.configuredCasts.some((identity) =>\n this.#sameIdentity(identity, cast.identity),\n );\n return (\n configuredFilter === \"all\" ||\n (configuredFilter === \"configured\" && configured) ||\n (configuredFilter === \"unconfigured\" && !configured)\n );\n });\n return casts.length > 0 ? [{ ...enemy, casts }] : [];\n });\n }\n\n #renderEnemy(model: DungeonCastEnemyModel): TemplateResult {\n return html`\n <article class=\"enemy\" part=\"enemy\">\n <header class=\"enemy-header\" part=\"enemy-header\">\n <span class=\"enemy-rule\" aria-hidden=\"true\"></span>\n <div>\n <h3 class=\"enemy-name\">${model.enemy.name ?? model.enemy.id}</h3>\n <p class=\"enemy-id\">${model.enemy.id}</p>\n </div>\n <span class=\"enemy-count\">${model.casts.length} ${model.casts.length === 1 ? \"cast\" : \"casts\"}</span>\n </header>\n <ul class=\"cast-list\">\n ${repeat(\n model.casts,\n ({ identity }) => castIdentityKey(identity),\n (cast) => html`<li>${this.#renderCast(cast)}</li>`,\n )}\n </ul>\n </article>\n `;\n }\n\n #renderCast(cast: DungeonCastModel): TemplateResult {\n const selected = this.#sameIdentity(this.selectedCast, cast.identity);\n const configured = this.configuredCasts.some((identity) =>\n this.#sameIdentity(identity, cast.identity),\n );\n const capabilityKind = cast.interruptVaries\n ? \"varies\"\n : cast.canInterrupt\n ? \"interrupt\"\n : cast.canStop\n ? \"stop\"\n : \"none\";\n const fallback = (cast.ability.name ?? cast.ability.id)\n .split(/\\s+/)\n .map((part) => part[0])\n .join(\"\")\n .slice(0, 2)\n .toUpperCase();\n\n return html`\n <button\n class=\"cast-row\"\n part=\"cast\"\n type=\"button\"\n ?disabled=${!cast.actionable}\n aria-pressed=${String(selected)}\n data-configured=${String(configured)}\n @click=${() => this.#selectCast(cast)}\n @keydown=${this.#handleCastKeydown}\n >\n ${\n cast.imageUrl\n ? html`<img\n class=\"ability-image\"\n part=\"cast-icon\"\n src=${cast.imageUrl}\n alt=\"\"\n @error=${this.#handleAbilityImageError}\n /><span\n class=\"ability-fallback\"\n part=\"cast-icon-fallback\"\n aria-hidden=\"true\"\n hidden\n >${fallback}</span\n >`\n : html`<span class=\"ability-fallback\" part=\"cast-icon-fallback\" aria-hidden=\"true\">${fallback}</span>`\n }\n <span class=\"ability-copy\">\n <span class=\"ability-name\">${cast.ability.name ?? cast.ability.id}</span>\n <span class=\"ability-id\">${cast.ability.id}</span>\n </span>\n <span class=\"capability\" data-kind=${capabilityKind}>${cast.capabilityLabel}</span>\n <span class=\"marker\" data-empty=${String(!configured)}>${configured ? \"Configured\" : nothing}</span>\n </button>\n `;\n }\n\n #renderStatus(): TemplateResult {\n if (this._status === \"loading\") {\n return this.#statusCard(\n \"Loading cast index\",\n \"Joining dungeon enemies with their cast data.\",\n );\n }\n if (this._status === \"error\") {\n return html`\n <div class=\"status\" role=\"alert\">\n <div class=\"status-card\">\n <span class=\"status-mark\" aria-hidden=\"true\"></span>\n <h2 class=\"status-title\">Cast data unavailable</h2>\n <p class=\"status-copy\">${this._error?.message ?? \"The dungeon cast index could not be loaded.\"}</p>\n <button class=\"retry-button\" type=\"button\" @click=${this.retry}>Retry</button>\n </div>\n </div>\n `;\n }\n if (this._status === \"ready\") {\n return this.#statusCard(\n \"No actionable casts\",\n \"This dungeon has no enemies with casts that can be interrupted or stopped.\",\n );\n }\n return this.#statusCard(\"Select a dungeon\", \"Choose a dungeon to inspect enemy casts.\");\n }\n\n #renderEmptySearch(): TemplateResult {\n return this.#statusCard(\n \"No matching casts\",\n `No enemy or ability matches “${this._query.trim()}”.`,\n );\n }\n\n #statusCard(title: string, copy: string): TemplateResult {\n return html`\n <div class=\"status\">\n <div class=\"status-card\">\n <span class=\"status-mark\" aria-hidden=\"true\"></span>\n <h2 class=\"status-title\">${title}</h2>\n <p class=\"status-copy\">${copy}</p>\n </div>\n </div>\n `;\n }\n\n #sameIdentity(left: FellowshipCastIdentity | null, right: FellowshipCastIdentity): boolean {\n return left?.enemyId === right.enemyId && left.abilityId === right.abilityId;\n }\n\n #selectCast(cast: DungeonCastModel): void {\n if (!cast.actionable) return;\n this.dispatchEvent(\n new CustomEvent<FellowshipCastSelectedDetail>(\"fellowship-cast-selected\", {\n ...eventOptions,\n detail: { cast },\n }),\n );\n }\n\n #handleSearch = (event: InputEvent): void => {\n this._query = (event.currentTarget as HTMLInputElement).value;\n };\n\n #handleAbilityImageError = (event: Event): void => {\n const image = event.currentTarget as HTMLImageElement;\n image.hidden = true;\n const fallback = image.nextElementSibling;\n if (fallback instanceof HTMLElement) fallback.hidden = false;\n };\n\n #handleCastKeydown = (event: KeyboardEvent): void => {\n const movement =\n event.key === \"ArrowDown\" || event.key === \"ArrowRight\"\n ? 1\n : event.key === \"ArrowUp\" || event.key === \"ArrowLeft\"\n ? -1\n : 0;\n if (movement === 0 && event.key !== \"Home\" && event.key !== \"End\") return;\n const buttons = [\n ...this.renderRoot.querySelectorAll<HTMLButtonElement>(\".cast-row:not(:disabled)\"),\n ];\n if (buttons.length === 0) return;\n const current = buttons.indexOf(event.currentTarget as HTMLButtonElement);\n const nextIndex =\n event.key === \"Home\"\n ? 0\n : event.key === \"End\"\n ? buttons.length - 1\n : (current + movement + buttons.length) % buttons.length;\n buttons[nextIndex]?.focus();\n event.preventDefault();\n };\n}\n\nexport const defineFellowshipDungeonCastBrowser = (): void => {\n if (\n typeof globalThis.customElements !== \"undefined\" &&\n !globalThis.customElements.get(\"fellowship-dungeon-cast-browser\")\n ) {\n globalThis.customElements.define(\n \"fellowship-dungeon-cast-browser\",\n FellowshipDungeonCastBrowserElement,\n );\n }\n};\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"fellowship-dungeon-cast-browser\": FellowshipDungeonCastBrowserElement;\n }\n}\n"],"mappings":"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"}