@crockery/fellowship-components 0.1.0 → 0.4.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 (57) hide show
  1. package/README.md +10 -3
  2. package/RELEASING.md +9 -45
  3. package/dist/chunks/data-source-XbksqUrw.js +9 -0
  4. package/dist/chunks/data-source-XbksqUrw.js.map +1 -0
  5. package/dist/chunks/decorate-B_ZTN9li.js.map +1 -1
  6. package/dist/chunks/dungeon-cast-browser-CSnu3gC5.js +705 -0
  7. package/dist/chunks/dungeon-cast-browser-CSnu3gC5.js.map +1 -0
  8. package/dist/chunks/{dungeon-map-IPN6sEDj.js → dungeon-map-CutIcCVP.js} +93 -119
  9. package/dist/chunks/dungeon-map-CutIcCVP.js.map +1 -0
  10. package/dist/chunks/{talent-calculator-Ch1Pbbba.js → talent-calculator-Dxuc2dZ8.js} +75 -105
  11. package/dist/chunks/talent-calculator-Dxuc2dZ8.js.map +1 -0
  12. package/dist/data/cast-data-source.d.ts +5 -0
  13. package/dist/data/cast-data-source.d.ts.map +1 -0
  14. package/dist/data/data-source.d.ts +7 -16
  15. package/dist/data/data-source.d.ts.map +1 -1
  16. package/dist/data/package-cast-data-source.d.ts +8 -0
  17. package/dist/data/package-cast-data-source.d.ts.map +1 -0
  18. package/dist/data/package-data-source.d.ts +3 -1
  19. package/dist/data/package-data-source.d.ts.map +1 -1
  20. package/dist/data/package-talent-data-source.d.ts +2 -0
  21. package/dist/data/package-talent-data-source.d.ts.map +1 -1
  22. package/dist/data/talent-data-source.d.ts +4 -10
  23. package/dist/data/talent-data-source.d.ts.map +1 -1
  24. package/dist/dungeon-cast-browser/dungeon-cast-browser.d.ts +36 -0
  25. package/dist/dungeon-cast-browser/dungeon-cast-browser.d.ts.map +1 -0
  26. package/dist/dungeon-cast-browser/dungeon-cast-browser.styles.d.ts +2 -0
  27. package/dist/dungeon-cast-browser/dungeon-cast-browser.styles.d.ts.map +1 -0
  28. package/dist/dungeon-cast-browser/index.d.ts +4 -0
  29. package/dist/dungeon-cast-browser/index.d.ts.map +1 -0
  30. package/dist/dungeon-cast-browser/render-model.d.ts +5 -0
  31. package/dist/dungeon-cast-browser/render-model.d.ts.map +1 -0
  32. package/dist/dungeon-cast-browser/types.d.ts +60 -0
  33. package/dist/dungeon-cast-browser/types.d.ts.map +1 -0
  34. package/dist/dungeon-cast-browser.js +7 -0
  35. package/dist/dungeon-cast-browser.js.map +1 -0
  36. package/dist/dungeon-map/dungeon-map.d.ts.map +1 -1
  37. package/dist/dungeon-map.js +1 -1
  38. package/dist/index.d.ts +6 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +8 -5
  41. package/dist/react/dungeon-cast-browser.d.ts +12 -0
  42. package/dist/react/dungeon-cast-browser.d.ts.map +1 -0
  43. package/dist/react/dungeon-cast-browser.js +19 -0
  44. package/dist/react/dungeon-cast-browser.js.map +1 -0
  45. package/dist/react/dungeon-map.js +1 -1
  46. package/dist/react/index.d.ts +2 -0
  47. package/dist/react/index.d.ts.map +1 -1
  48. package/dist/react/talent-calculator.js +1 -1
  49. package/dist/react.js +4 -3
  50. package/dist/talent-calculator/talent-calculator.d.ts.map +1 -1
  51. package/dist/talent-calculator.js +1 -1
  52. package/package.json +35 -25
  53. package/src/dungeon-cast-browser/README.md +22 -0
  54. package/dist/chunks/data-source-CoIFvV72.js +0 -10
  55. package/dist/chunks/data-source-CoIFvV72.js.map +0 -1
  56. package/dist/chunks/dungeon-map-IPN6sEDj.js.map +0 -1
  57. package/dist/chunks/talent-calculator-Ch1Pbbba.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dungeon-map-CutIcCVP.js","names":["#zoom","#x","#y","#viewportWidth","#viewportHeight","#mapWidth","#mapHeight","#clampPan","#selectMap","#viewport","#commitViewport","#isInteractiveEvent","#pointers","#lastPointer","#lastPinchDistance","#lastPinchCenter","#resizeObserver","#handleResize","#abortController","#flushRendererErrors","#loadDungeon","#prepareSelectedMap","#rendererFailures","#prepareRendererContent","#prepareMapLabels","#selectedMapTabId","#viewerLabel","#handleWheel","#handleViewportKeydown","#handlePointerDown","#handlePointerMove","#handlePointerEnd","#mapLabel","#renderGroups","#renderMapSelector","#renderControls","#renderStatus","#loadToken","#renderToken","#activeSource","#setError","#drawMap","#queueRendererError","#renderEnemyAnchor","#groupKey","#renderGroup","#renderDefaultGroup","#renderDefaultEnemy","#handleMapSelectorKeydown","#pendingRendererErrors"],"sources":["../../src/data/package-data-source.ts","../../src/dungeon-map/dungeon-map.styles.ts","../../src/dungeon-map/layout.ts","../../src/dungeon-map/render-model.ts","../../src/dungeon-map/viewport-controller.ts","../../src/dungeon-map/dungeon-map.ts"],"sourcesContent":["import { createPackageFellowshipDataClient } from \"@crockery/fellowship-data/client\";\n\nimport type { FellowshipDataSource } from \"./data-source.js\";\n\n/** @deprecated Create a package Fellowship data client instead. */\nexport interface PackageFellowshipDataSourceOptions {\n readonly assetBaseUrl?: string;\n}\n\n/** @deprecated Use `createPackageFellowshipDataClient` from `@crockery/fellowship-data/client`. */\nexport const createPackageFellowshipDataSource = (\n options: PackageFellowshipDataSourceOptions = {},\n): FellowshipDataSource => {\n const client = createPackageFellowshipDataClient(options);\n return {\n loadDungeon: (dungeonId, signal) => client.loadDungeon(dungeonId, signal),\n resolveAssetUrl: (asset) => client.resolveAssetUrl(asset),\n };\n};\n","import { css } from \"lit\";\n\nexport const dungeonMapStyles = css`\n :host {\n --fellowship-map-soot: var(--_fellowship-field-canvas);\n --fellowship-map-ink: var(--_fellowship-field-ink);\n --fellowship-map-muted-ink: var(--_fellowship-field-muted);\n --fellowship-map-border: var(--_fellowship-field-border);\n --fellowship-map-focus: var(--_fellowship-field-focus);\n --fellowship-map-rune: var(--_fellowship-field-accent);\n --fellowship-map-ember: var(--_fellowship-field-ember);\n --fellowship-map-control-surface: color-mix(\n in srgb,\n var(--_fellowship-field-surface-raised) 92%,\n transparent\n );\n --fellowship-map-enemy-size: 32px;\n --fellowship-map-radius: var(--_fellowship-field-radius);\n --fellowship-map-font-family: var(--_fellowship-field-font-family);\n --fellowship-map-tactical-font-family: var(--_fellowship-field-mono-font-family);\n\n display: block;\n inline-size: 100%;\n min-block-size: 240px;\n aspect-ratio: 2 / 1;\n container-type: inline-size;\n color: var(--fellowship-map-ink);\n font-family: var(--fellowship-map-font-family);\n -webkit-font-smoothing: antialiased;\n }\n\n :host([hidden]) {\n display: none;\n }\n\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n .viewer,\n .viewport {\n inline-size: 100%;\n block-size: 100%;\n min-block-size: inherit;\n }\n\n .viewer {\n position: relative;\n overflow: hidden;\n border: 1px solid var(--fellowship-map-border);\n border-radius: calc(var(--fellowship-map-radius) + 2px);\n background: var(--fellowship-map-soot);\n }\n\n .viewport {\n position: relative;\n overflow: hidden;\n outline: none;\n cursor: grab;\n touch-action: none;\n }\n\n .viewport[data-panning=\"true\"] {\n cursor: grabbing;\n }\n\n .viewport:focus-visible {\n box-shadow: inset 0 0 0 2px var(--fellowship-map-focus);\n }\n\n .stage {\n position: absolute;\n inset: 0 auto auto 0;\n transform-origin: 0 0;\n will-change: transform;\n }\n\n .map-canvas,\n .overlay {\n position: absolute;\n inset: 0;\n inline-size: 100%;\n block-size: 100%;\n }\n\n .map-canvas {\n display: block;\n background: #d5ceb1;\n box-shadow: inset 0 0 0 1px rgb(0 0 0 / 10%);\n }\n\n .overlay {\n pointer-events: none;\n }\n\n .group-anchor {\n position: absolute;\n pointer-events: none;\n }\n\n .default-group,\n .custom-group-host {\n position: absolute;\n inset: 0;\n }\n\n .group-ring {\n position: absolute;\n inset: 0;\n border: calc(1px * var(--fellowship-map-inverse-scale, 1)) solid transparent;\n border-radius: 50%;\n background: transparent;\n transition:\n border-color 140ms cubic-bezier(0.23, 1, 0.32, 1),\n background-color 140ms cubic-bezier(0.23, 1, 0.32, 1);\n }\n\n .group-anchor:has(.enemy-anchor:hover) .group-ring,\n .group-anchor:focus-within .group-ring {\n border-color: var(--fellowship-map-rune);\n background: color-mix(in srgb, var(--fellowship-map-rune) 8%, transparent);\n }\n\n .group-label {\n position: absolute;\n inset: 0 auto auto 50%;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n min-block-size: 22px;\n padding: 0 8px 0 12px;\n border-radius: 4px;\n background: var(--fellowship-map-control-surface);\n color: var(--fellowship-map-ink);\n box-shadow: inset 0 0 0 1px var(--fellowship-map-border);\n font-family: var(--fellowship-map-tactical-font-family);\n font-size: 10px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.04em;\n line-height: 1;\n white-space: nowrap;\n transform: translate(-50%, -45%) scale(var(--fellowship-map-inverse-scale, 1));\n transform-origin: center;\n }\n\n .group-label::before {\n position: absolute;\n inset: 5px auto 5px 0;\n width: 2px;\n background: var(--fellowship-map-rune);\n content: \"\";\n }\n\n .enemy-anchor {\n position: absolute;\n z-index: 2;\n inline-size: 0;\n block-size: 0;\n pointer-events: auto;\n }\n\n .enemy-visual {\n position: absolute;\n inset: 0 auto auto 0;\n transform: translate(-50%, -50%) scale(var(--fellowship-map-inverse-scale, 1));\n transform-origin: center;\n }\n\n .enemy-marker {\n position: relative;\n display: grid;\n inline-size: var(--fellowship-map-enemy-size);\n block-size: var(--fellowship-map-enemy-size);\n place-items: center;\n overflow: hidden;\n border: 2px solid var(--fellowship-map-rune);\n border-radius: 50%;\n background: var(--_fellowship-field-surface-raised);\n color: var(--fellowship-map-ink);\n font-family: var(--fellowship-map-tactical-font-family);\n font-size: 10px;\n font-weight: 600;\n box-shadow: 0 0 0 1px rgb(0 0 0 / 68%);\n transition:\n border-color 140ms cubic-bezier(0.23, 1, 0.32, 1),\n transform 140ms cubic-bezier(0.23, 1, 0.32, 1);\n }\n\n .enemy-marker[data-boss=\"true\"] {\n inline-size: calc(var(--fellowship-map-enemy-size) + 4px);\n block-size: calc(var(--fellowship-map-enemy-size) + 4px);\n border-color: var(--fellowship-map-ember);\n }\n\n .enemy-anchor:hover .enemy-marker {\n transform: scale(1.08);\n }\n\n .enemy-image {\n inline-size: 100%;\n block-size: 100%;\n object-fit: cover;\n }\n\n .controls,\n .map-selector {\n position: absolute;\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 4px;\n border-radius: calc(var(--fellowship-map-radius) + 2px);\n background: var(--fellowship-map-control-surface);\n box-shadow: inset 0 0 0 1px var(--fellowship-map-border);\n backdrop-filter: blur(12px);\n }\n\n .map-selector {\n inset: 12px auto auto 12px;\n max-inline-size: calc(100% - 172px);\n overflow-x: auto;\n scrollbar-width: none;\n }\n\n .map-selector::-webkit-scrollbar {\n display: none;\n }\n\n .controls {\n inset: 12px 12px auto auto;\n }\n\n .control-button,\n .map-selector-button {\n position: relative;\n min-inline-size: 44px;\n min-block-size: 44px;\n border: 0;\n border-radius: 4px;\n background: transparent;\n color: var(--fellowship-map-muted-ink);\n font: inherit;\n font-size: 13px;\n font-weight: 600;\n line-height: 1;\n cursor: pointer;\n transition:\n color 140ms cubic-bezier(0.23, 1, 0.32, 1),\n background-color 140ms cubic-bezier(0.23, 1, 0.32, 1),\n transform 120ms cubic-bezier(0.23, 1, 0.32, 1);\n }\n\n .control-button {\n font-family: var(--fellowship-map-tactical-font-family);\n font-size: 16px;\n font-weight: 500;\n }\n\n .map-selector-button {\n padding-inline: 12px;\n letter-spacing: 0.02em;\n white-space: nowrap;\n }\n\n .map-selector-button[aria-selected=\"true\"] {\n background: rgb(255 255 255 / 6%);\n color: var(--fellowship-map-ink);\n }\n\n .map-selector-button[aria-selected=\"true\"]::after {\n position: absolute;\n inset: auto 12px 0;\n height: 2px;\n background: var(--fellowship-map-rune);\n content: \"\";\n }\n\n .control-button:focus-visible,\n .map-selector-button:focus-visible,\n .retry-button:focus-visible {\n outline: 2px solid var(--fellowship-map-focus);\n outline-offset: 2px;\n }\n\n .control-button:active,\n .map-selector-button:active,\n .retry-button:active {\n transform: scale(0.97);\n }\n\n .status {\n position: absolute;\n z-index: 30;\n inset: 0;\n display: grid;\n padding: 24px;\n place-items: center;\n background: color-mix(in srgb, var(--fellowship-map-soot) 84%, transparent);\n text-align: center;\n }\n\n .status-card {\n display: grid;\n max-inline-size: 376px;\n gap: 8px;\n justify-items: center;\n padding: 24px;\n border-radius: calc(var(--fellowship-map-radius) + 2px);\n background: var(--fellowship-map-control-surface);\n box-shadow: inset 0 0 0 1px var(--fellowship-map-border);\n }\n\n .status-mark {\n position: relative;\n display: grid;\n inline-size: 40px;\n block-size: 24px;\n place-items: center;\n color: var(--fellowship-map-rune);\n font-family: var(--fellowship-map-tactical-font-family);\n font-size: 12px;\n }\n\n .status-mark::before,\n .status-mark::after {\n position: absolute;\n width: 8px;\n height: 1px;\n background: var(--fellowship-map-border);\n content: \"\";\n }\n\n .status-mark::before {\n left: 0;\n }\n\n .status-mark::after {\n right: 0;\n }\n\n .status-title {\n margin: 4px 0 0;\n color: var(--fellowship-map-ink);\n font-size: 18px;\n font-weight: 600;\n line-height: 24px;\n text-wrap: balance;\n }\n\n .status-copy {\n margin: 0;\n color: var(--fellowship-map-muted-ink);\n font-size: 14px;\n line-height: 1.5;\n text-wrap: pretty;\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-map-border);\n border-radius: 4px;\n background: var(--_fellowship-field-inset);\n color: var(--fellowship-map-ink);\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n }\n\n .empty-badge {\n position: absolute;\n z-index: 20;\n inset: auto auto 12px 12px;\n padding: 8px;\n border-radius: 4px;\n background: var(--fellowship-map-control-surface);\n color: var(--fellowship-map-muted-ink);\n box-shadow: inset 0 0 0 1px var(--fellowship-map-border);\n font-size: 12px;\n }\n\n .visually-hidden {\n position: absolute;\n inline-size: 1px;\n block-size: 1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n }\n\n @media (hover: hover) {\n .control-button:hover,\n .map-selector-button:hover {\n background: rgb(255 255 255 / 6%);\n color: var(--fellowship-map-ink);\n }\n\n .retry-button:hover {\n border-color: color-mix(in srgb, var(--fellowship-map-rune) 48%, transparent);\n color: var(--fellowship-map-focus);\n }\n }\n\n @container (max-width: 560px) {\n .map-selector,\n .controls {\n inset-block-start: 8px;\n }\n\n .map-selector {\n inset-inline-start: 8px;\n max-inline-size: calc(100% - 164px);\n }\n\n .controls {\n inset-inline-end: 8px;\n }\n\n .map-selector-button {\n padding-inline: 8px;\n }\n\n .map-selector-button[aria-selected=\"true\"]::after {\n inset-inline: 8px;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .group-ring,\n .enemy-marker,\n .control-button,\n .map-selector-button,\n .retry-button {\n transition: none;\n }\n\n .control-button:active,\n .map-selector-button:active,\n .retry-button:active {\n transform: none;\n }\n }\n`;\n","import type { Position2D } from \"@crockery/fellowship-data\";\n\nimport type { DungeonMapEnemyModel, DungeonMapGroupModel, NormalizedRect } from \"./types.js\";\n\nconst VISUAL_PADDING_X = 0.018;\nconst VISUAL_PADDING_Y = 0.036;\nconst MIN_VISUAL_WIDTH = 0.06;\nconst MIN_VISUAL_HEIGHT = 0.12;\n\nconst clamp = (value: number, minimum: number, maximum: number): number =>\n Math.min(maximum, Math.max(minimum, value));\n\nconst calculateBounds = (enemies: readonly DungeonMapEnemyModel[]): NormalizedRect => {\n const xs = enemies.map(({ position }) => position.x);\n const ys = enemies.map(({ position }) => position.y);\n const minimumX = Math.min(...xs);\n const maximumX = Math.max(...xs);\n const minimumY = Math.min(...ys);\n const maximumY = Math.max(...ys);\n\n return {\n x: minimumX,\n y: minimumY,\n width: maximumX - minimumX,\n height: maximumY - minimumY,\n };\n};\n\nconst expandBounds = (bounds: NormalizedRect): NormalizedRect => {\n const centerX = bounds.x + bounds.width / 2;\n const centerY = bounds.y + bounds.height / 2;\n const width = Math.max(MIN_VISUAL_WIDTH, bounds.width + VISUAL_PADDING_X * 2);\n const height = Math.max(MIN_VISUAL_HEIGHT, bounds.height + VISUAL_PADDING_Y * 2);\n const x = clamp(centerX - width / 2, 0, Math.max(0, 1 - width));\n const y = clamp(centerY - height / 2, 0, Math.max(0, 1 - height));\n\n return { x, y, width: Math.min(width, 1), height: Math.min(height, 1) };\n};\n\nconst groupCenter = (enemies: readonly DungeonMapEnemyModel[]): Position2D => ({\n x: enemies.reduce((sum, enemy) => sum + enemy.position.x, 0) / enemies.length,\n y: enemies.reduce((sum, enemy) => sum + enemy.position.y, 0) / enemies.length,\n});\n\nconst groupScore = (enemies: readonly DungeonMapEnemyModel[]): number | null => {\n const scores = enemies\n .map(({ enemy }) => enemy?.dungeonScore)\n .filter((score): score is number => typeof score === \"number\");\n return scores.length > 0 ? scores.reduce((sum, score) => sum + score, 0) : null;\n};\n\nexport const createDungeonMapGroupModel = (\n id: number | null,\n enemies: readonly DungeonMapEnemyModel[],\n): DungeonMapGroupModel => {\n const enemyBounds = calculateBounds(enemies);\n return {\n id,\n enemies,\n enemyBounds,\n visualBounds: expandBounds(enemyBounds),\n center: groupCenter(enemies),\n dungeonScore: groupScore(enemies),\n };\n};\n","import { findImage } from \"@crockery/fellowship-data\";\nimport type { Dungeon, ImageAssetRef, Position2D } from \"@crockery/fellowship-data\";\n\nimport type { DungeonMapData, FellowshipDataSource } from \"../data/data-source.js\";\nimport { createDungeonMapGroupModel } from \"./layout.js\";\nimport type { DungeonMapEnemyModel, DungeonMapRenderModel, DungeonMapWarning } from \"./types.js\";\n\nexport const isValidMapPosition = (position: Position2D | null): position is Position2D =>\n position !== null &&\n Number.isFinite(position.x) &&\n Number.isFinite(position.y) &&\n position.x >= 0 &&\n position.x <= 1 &&\n position.y >= 0 &&\n position.y <= 1;\n\nexport const createDungeonMapRenderModel = (\n data: DungeonMapData,\n mapImage: ImageAssetRef,\n source: FellowshipDataSource,\n): DungeonMapRenderModel => {\n const warnings: DungeonMapWarning[] = [];\n const groups = new Map<string, { id: number | null; enemies: DungeonMapEnemyModel[] }>();\n\n data.dungeon.enemies.forEach((placement, placementIndex) => {\n if (placement.mapImageId !== mapImage.id) {\n return;\n }\n\n if (!isValidMapPosition(placement.mapPosition)) {\n warnings.push({\n code: \"invalid-map-position\",\n message: `Skipped ${placement.id}: its map position is missing or outside the map.`,\n placementIndex,\n enemyId: placement.id,\n });\n return;\n }\n\n const enemy = data.enemies.get(placement.id) ?? null;\n if (!enemy) {\n warnings.push({\n code: \"missing-enemy\",\n message: `No enemy metadata exists for ${placement.id}.`,\n placementIndex,\n enemyId: placement.id,\n });\n }\n\n const image = enemy ? findImage(enemy.images, { type: \"enemy\", size: \"200\" }) : null;\n let imageUrl: string | null = null;\n if (image) {\n try {\n imageUrl = source.resolveAssetUrl(image);\n } catch (error: unknown) {\n warnings.push({\n code: \"asset-url-error\",\n message: `Could not resolve the image URL for ${placement.id}: ${String(error)}.`,\n placementIndex,\n enemyId: placement.id,\n });\n }\n } else if (enemy) {\n warnings.push({\n code: \"missing-enemy-image\",\n message: `No 200px enemy image exists for ${placement.id}.`,\n placementIndex,\n enemyId: placement.id,\n });\n }\n\n const model: DungeonMapEnemyModel = {\n instanceId: `${data.dungeon.id}:${mapImage.id}:${placementIndex}`,\n enemy,\n placement,\n position: placement.mapPosition,\n imageUrl,\n };\n const key =\n placement.groupId === null ? `singleton:${placementIndex}` : `group:${placement.groupId}`;\n const group = groups.get(key) ?? { id: placement.groupId, enemies: [] };\n group.enemies.push(model);\n groups.set(key, group);\n });\n\n return {\n dungeon: data.dungeon,\n mapImage,\n groups: [...groups.values()].map(({ id, enemies }) => createDungeonMapGroupModel(id, enemies)),\n warnings,\n };\n};\n\nexport const humanizeMapLabel = (mapImage: ImageAssetRef, dungeon: Dungeon): string => {\n const rawName = mapImage.id.split(\":\").at(-1) ?? mapImage.id;\n const withoutPrefix = rawName.replace(/^T_(?:Map_)?/i, \"\").replace(/^map[-_]/i, \"\");\n const words = withoutPrefix\n .replaceAll(/[_-]+/g, \" \")\n .replace(/([a-z\\d])([A-Z])/g, \"$1 $2\")\n .trim();\n const dungeonName = dungeon.id.split(\".\").at(-1)?.toLowerCase();\n const parts = words.split(/\\s+/).filter((part) => part.toLowerCase() !== dungeonName);\n const label = parts.join(\" \") || words || \"Map\";\n return label.replace(/\\b[a-z]/g, (letter) => letter.toUpperCase());\n};\n","import type { ViewportState } from \"./types.js\";\n\nconst clamp = (value: number, minimum: number, maximum: number): number =>\n Math.min(maximum, Math.max(minimum, value));\n\nexport class ViewportController {\n #viewportWidth = 0;\n #viewportHeight = 0;\n #mapWidth = 0;\n #mapHeight = 0;\n #zoom = 1;\n #x = 0;\n #y = 0;\n\n maxZoom = 8;\n\n get state(): ViewportState {\n return { zoom: this.#zoom, scale: this.scale, x: this.#x, y: this.#y };\n }\n\n get scale(): number {\n return this.fitScale * this.#zoom;\n }\n\n get fitScale(): number {\n if (!this.#viewportWidth || !this.#viewportHeight || !this.#mapWidth || !this.#mapHeight) {\n return 1;\n }\n return Math.min(this.#viewportWidth / this.#mapWidth, this.#viewportHeight / this.#mapHeight);\n }\n\n setDimensions(\n viewportWidth: number,\n viewportHeight: number,\n mapWidth: number,\n mapHeight: number,\n ): ViewportState {\n const previousScale = this.scale;\n const previousCenterX = this.#mapWidth\n ? (this.#viewportWidth / 2 - this.#x) / previousScale / this.#mapWidth\n : 0.5;\n const previousCenterY = this.#mapHeight\n ? (this.#viewportHeight / 2 - this.#y) / previousScale / this.#mapHeight\n : 0.5;\n\n this.#viewportWidth = Math.max(0, viewportWidth);\n this.#viewportHeight = Math.max(0, viewportHeight);\n this.#mapWidth = Math.max(0, mapWidth);\n this.#mapHeight = Math.max(0, mapHeight);\n\n this.#x = this.#viewportWidth / 2 - previousCenterX * this.#mapWidth * this.scale;\n this.#y = this.#viewportHeight / 2 - previousCenterY * this.#mapHeight * this.scale;\n this.#clampPan();\n return this.state;\n }\n\n fit(): ViewportState {\n this.#zoom = 1;\n this.#x = (this.#viewportWidth - this.#mapWidth * this.scale) / 2;\n this.#y = (this.#viewportHeight - this.#mapHeight * this.scale) / 2;\n return this.state;\n }\n\n zoomAt(\n factor: number,\n pointX = this.#viewportWidth / 2,\n pointY = this.#viewportHeight / 2,\n ): ViewportState {\n const previousScale = this.scale;\n const mapX = (pointX - this.#x) / previousScale;\n const mapY = (pointY - this.#y) / previousScale;\n this.#zoom = clamp(this.#zoom * factor, 1, Math.max(1, this.maxZoom));\n this.#x = pointX - mapX * this.scale;\n this.#y = pointY - mapY * this.scale;\n this.#clampPan();\n return this.state;\n }\n\n panBy(deltaX: number, deltaY: number): ViewportState {\n this.#x += deltaX;\n this.#y += deltaY;\n this.#clampPan();\n return this.state;\n }\n\n #clampPan(): void {\n const scaledWidth = this.#mapWidth * this.scale;\n const scaledHeight = this.#mapHeight * this.scale;\n\n this.#x =\n scaledWidth <= this.#viewportWidth\n ? (this.#viewportWidth - scaledWidth) / 2\n : clamp(this.#x, this.#viewportWidth - scaledWidth, 0);\n this.#y =\n scaledHeight <= this.#viewportHeight\n ? (this.#viewportHeight - scaledHeight) / 2\n : clamp(this.#y, this.#viewportHeight - scaledHeight, 0);\n }\n}\n","import type { ImageAssetRef } from \"@crockery/fellowship-data\";\nimport { html, LitElement, nothing, type PropertyValues, type TemplateResult } from \"lit\";\nimport { customElement, property, query, state } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\n\nimport {\n assertSupportedFellowshipDataSchema,\n FellowshipDataError,\n type DungeonMapData,\n type FellowshipDataSource,\n} from \"../data/data-source.js\";\nimport { createPackageFellowshipDataSource } from \"../data/package-data-source.js\";\nimport { fieldManualStyles } from \"../styles/field-manual.styles.js\";\nimport { installFieldManualFonts } from \"../styles/field-manual-fonts.js\";\nimport { dungeonMapStyles } from \"./dungeon-map.styles.js\";\nimport { createDungeonMapRenderModel, humanizeMapLabel } from \"./render-model.js\";\nimport type {\n DungeonMapEnemyModel,\n DungeonMapGroupModel,\n DungeonMapRenderModel,\n FellowshipMapErrorDetail,\n FellowshipMapImageChangeDetail,\n FellowshipMapReadyDetail,\n FellowshipMapViewportChangeDetail,\n FellowshipMapWarningDetail,\n LitEnemyRenderer,\n LitGroupRenderer,\n MapLabelFormatter,\n ViewportChangeSource,\n ViewportState,\n} from \"./types.js\";\nimport { ViewportController } from \"./viewport-controller.js\";\n\ntype ComponentStatus = \"empty\" | \"loading\" | \"ready\" | \"error\";\n\ninterface PointerPosition {\n readonly x: number;\n readonly y: number;\n}\n\nconst eventOptions = { bubbles: true, composed: true } as const;\n\n/**\n * @fires fellowship-map-ready - Fired after dungeon data and the map image are ready.\n * @fires fellowship-map-error - Fired when map data, imagery, or a renderer fails.\n * @fires fellowship-map-warning - Fired for recoverable dungeon data issues.\n * @fires fellowship-map-image-change - Fired when the selected map changes.\n * @fires fellowship-map-viewport-change - Fired when zoom or pan state changes.\n */\n@customElement(\"fellowship-dungeon-map\")\nexport class FellowshipDungeonMapElement extends LitElement {\n static override styles = [fieldManualStyles, dungeonMapStyles];\n\n @property({ type: String, attribute: \"dungeon-id\" }) dungeonId = \"\";\n @property({ type: String, attribute: \"map-image-id\" }) mapImageId: string | null = null;\n @property({ type: String, attribute: \"asset-base-url\" }) assetBaseUrl: string | null = null;\n @property({ attribute: false }) dataSource: FellowshipDataSource | null = null;\n @property({ attribute: false }) renderEnemy: LitEnemyRenderer | null = null;\n @property({ attribute: false }) renderGroup: LitGroupRenderer | null = null;\n @property({ attribute: false }) mapLabelFormatter: MapLabelFormatter | null = null;\n @property({ type: Boolean, attribute: \"show-controls\" }) showControls = true;\n @property({ type: Boolean, attribute: \"show-map-selector\" }) showMapSelector = true;\n @property({ type: Number, attribute: \"max-zoom\" }) maxZoom = 8;\n\n /** Internal bridge used by the React wrapper. */\n @property({ type: Boolean, attribute: false }) externalOverlay = false;\n\n @state() private _status: ComponentStatus = \"empty\";\n @state() private _error: FellowshipMapErrorDetail | null = null;\n @state() private _data: DungeonMapData | null = null;\n @state() private _model: DungeonMapRenderModel | null = null;\n @state() private _selectedMap: ImageAssetRef | null = null;\n @state() private _viewportState: ViewportState = { zoom: 1, scale: 1, x: 0, y: 0 };\n @state() private _isPanning = false;\n @state() private _enemyRenderContent = new Map<string, TemplateResult | typeof nothing>();\n @state() private _groupRenderContent = new Map<string, TemplateResult | typeof nothing>();\n @state() private _mapLabels = new Map<string, string>();\n\n @query(\".viewport\") private _viewportElement?: HTMLElement;\n @query(\"canvas\") private _canvasElement?: HTMLCanvasElement;\n\n #activeSource: FellowshipDataSource | null = null;\n #abortController: AbortController | null = null;\n #loadToken = 0;\n #renderToken = 0;\n #resizeObserver: ResizeObserver | null = null;\n #viewport = new ViewportController();\n #pointers = new Map<number, PointerPosition>();\n #lastPointer: PointerPosition | null = null;\n #lastPinchDistance = 0;\n #lastPinchCenter: PointerPosition | null = null;\n #rendererFailures = new Set<string>();\n #pendingRendererErrors: FellowshipMapErrorDetail[] = [];\n\n override connectedCallback(): void {\n super.connectedCallback();\n installFieldManualFonts(this.ownerDocument);\n this.#resizeObserver ??= new ResizeObserver(() => this.#handleResize());\n }\n\n override disconnectedCallback(): void {\n this.#abortController?.abort();\n this.#resizeObserver?.disconnect();\n super.disconnectedCallback();\n }\n\n protected override firstUpdated(changed: PropertyValues<this>): void {\n super.firstUpdated(changed);\n const viewport = this._viewportElement;\n if (viewport) {\n this.#resizeObserver?.observe(viewport);\n }\n }\n\n protected override updated(changed: PropertyValues<this>): void {\n super.updated(changed);\n this.#flushRendererErrors();\n\n if (changed.has(\"dungeonId\") || changed.has(\"dataSource\") || changed.has(\"assetBaseUrl\")) {\n queueMicrotask(() => {\n if (this.isConnected) {\n void this.#loadDungeon();\n }\n });\n return;\n }\n\n if (changed.has(\"mapImageId\") && this._data) {\n queueMicrotask(() => {\n if (this.isConnected) {\n void this.#prepareSelectedMap(true);\n }\n });\n }\n\n if (changed.has(\"maxZoom\") && changed.get(\"maxZoom\") !== undefined) {\n this.#viewport.maxZoom = Math.max(1, this.maxZoom);\n this.#viewport.zoomAt(1);\n queueMicrotask(() => this.#commitViewport(\"button\"));\n }\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n super.willUpdate(changed);\n\n if (\n changed.has(\"renderEnemy\") ||\n changed.has(\"renderGroup\") ||\n changed.has(\"mapLabelFormatter\")\n ) {\n this.#rendererFailures.clear();\n }\n if (changed.has(\"_model\") || changed.has(\"renderEnemy\") || changed.has(\"renderGroup\")) {\n this.#prepareRendererContent();\n }\n if (changed.has(\"_data\") || changed.has(\"mapLabelFormatter\")) {\n this.#prepareMapLabels();\n }\n }\n\n fitToView(): void {\n this.#viewport.fit();\n this.#commitViewport(\"fit\");\n }\n\n zoomIn(): void {\n this.#viewport.zoomAt(1.35);\n this.#commitViewport(\"button\");\n }\n\n zoomOut(): void {\n this.#viewport.zoomAt(1 / 1.35);\n this.#commitViewport(\"button\");\n }\n\n retry(): void {\n void this.#loadDungeon();\n }\n\n get renderModel(): DungeonMapRenderModel | null {\n return this._model;\n }\n\n override render(): TemplateResult {\n const model = this._model;\n const viewport = this._viewportState;\n const selectedMapTabId = this.#selectedMapTabId;\n const stageStyle = model\n ? {\n width: `${model.mapImage.width}px`,\n height: `${model.mapImage.height}px`,\n transform: `translate3d(${viewport.x}px, ${viewport.y}px, 0) scale(${viewport.scale})`,\n \"--fellowship-map-inverse-scale\": String(1 / viewport.scale),\n }\n : {};\n\n return html`\n <section class=\"viewer\" aria-label=${this.#viewerLabel}>\n <div\n class=\"viewport\"\n part=\"viewport\"\n role=\"region\"\n aria-label=\"Interactive dungeon map\"\n tabindex=\"0\"\n aria-describedby=\"map-instructions\"\n data-panning=${String(this._isPanning)}\n @wheel=${this.#handleWheel}\n @keydown=${this.#handleViewportKeydown}\n @pointerdown=${this.#handlePointerDown}\n @pointermove=${this.#handlePointerMove}\n @pointerup=${this.#handlePointerEnd}\n @pointercancel=${this.#handlePointerEnd}\n >\n ${\n model\n ? html`\n <div\n class=\"stage\"\n id=\"map-stage\"\n role=${selectedMapTabId === nothing ? nothing : \"tabpanel\"}\n aria-labelledby=${selectedMapTabId}\n style=${styleMap(stageStyle)}\n >\n <canvas\n class=\"map-canvas\"\n part=\"map-canvas\"\n role=\"img\"\n aria-label=${`${model.dungeon.name ?? model.dungeon.id}, ${this.#mapLabel(model.mapImage)}`}\n .width=${model.mapImage.width}\n .height=${model.mapImage.height}\n ></canvas>\n <div class=\"overlay\" part=\"overlay\">\n ${\n this.externalOverlay\n ? html`<slot name=\"overlay\"></slot>`\n : this.#renderGroups(model)\n }\n </div>\n </div>\n ${this.#renderMapSelector(model)} ${this.#renderControls()}\n ${\n model.groups.length === 0 && this._status === \"ready\"\n ? html`<div class=\"empty-badge\">No enemy placements on this map</div>`\n : nothing\n }\n `\n : nothing\n }\n ${this.#renderStatus()}\n <p class=\"visually-hidden\" id=\"map-instructions\">\n Drag to pan. Use the mouse wheel, plus and minus keys, or zoom controls to zoom. Press\n zero to fit the full map.\n </p>\n </div>\n </section>\n `;\n }\n\n get #viewerLabel(): string {\n if (this._data) {\n return `${this._data.dungeon.name ?? this._data.dungeon.id} dungeon map`;\n }\n return \"Fellowship dungeon map\";\n }\n\n get #selectedMapTabId(): string | typeof nothing {\n if (\n !this.showMapSelector ||\n !this._model ||\n this._model.dungeon.mapImages.length <= 1 ||\n !this._selectedMap\n ) {\n return nothing;\n }\n const index = this._model.dungeon.mapImages.findIndex(\n (mapImage) => mapImage.id === this._selectedMap?.id,\n );\n return index >= 0 ? `map-tab-${index}` : nothing;\n }\n\n async #loadDungeon(): Promise<void> {\n const dungeonId = this.dungeonId.trim();\n const token = ++this.#loadToken;\n this.#renderToken += 1;\n this.#abortController?.abort();\n this.#abortController = new AbortController();\n this._data = null;\n this._model = null;\n this._selectedMap = null;\n this._error = null;\n this.#rendererFailures.clear();\n\n if (!dungeonId) {\n this._status = \"empty\";\n return;\n }\n\n this._status = \"loading\";\n const source =\n this.dataSource ??\n createPackageFellowshipDataSource(\n this.assetBaseUrl ? { assetBaseUrl: this.assetBaseUrl } : {},\n );\n this.#activeSource = source;\n\n try {\n const data = await source.loadDungeon(dungeonId, this.#abortController.signal);\n if (token !== this.#loadToken || this.#abortController.signal.aborted) {\n return;\n }\n assertSupportedFellowshipDataSchema(data.manifest.schemaVersion);\n this._data = data;\n await this.#prepareSelectedMap(true);\n } catch (error: unknown) {\n if (token !== this.#loadToken || this.#abortController.signal.aborted) {\n return;\n }\n this.#setError(error);\n }\n }\n\n async #prepareSelectedMap(resetView: boolean): Promise<void> {\n const data = this._data;\n const source = this.#activeSource;\n if (!data || !source) {\n return;\n }\n\n const mapImage = this.mapImageId\n ? data.dungeon.mapImages.find(({ id }) => id === this.mapImageId)\n : data.dungeon.mapImages[0];\n\n if (!mapImage) {\n this.#setError({\n code: \"map-not-found\",\n message: this.mapImageId\n ? `No map image with ID “${this.mapImageId}” exists for this dungeon.`\n : \"This dungeon does not include a map image.\",\n });\n return;\n }\n\n const renderToken = ++this.#renderToken;\n const model = createDungeonMapRenderModel(data, mapImage, source);\n this._model = model;\n this._selectedMap = mapImage;\n this._status = \"loading\";\n this._error = null;\n await this.updateComplete;\n\n const viewport = this._viewportElement;\n if (viewport) {\n this.#viewport.maxZoom = Math.max(1, this.maxZoom);\n this.#viewport.setDimensions(\n viewport.clientWidth,\n viewport.clientHeight,\n mapImage.width,\n mapImage.height,\n );\n if (resetView) {\n this.#viewport.fit();\n }\n this._viewportState = this.#viewport.state;\n await this.updateComplete;\n }\n\n try {\n await this.#drawMap(mapImage, source, renderToken);\n if (renderToken !== this.#renderToken) {\n return;\n }\n this._status = \"ready\";\n this.dispatchEvent(\n new CustomEvent<FellowshipMapReadyDetail>(\"fellowship-map-ready\", {\n ...eventOptions,\n detail: { model, data },\n }),\n );\n if (model.warnings.length > 0) {\n this.dispatchEvent(\n new CustomEvent<FellowshipMapWarningDetail>(\"fellowship-map-warning\", {\n ...eventOptions,\n detail: { warnings: model.warnings, model },\n }),\n );\n }\n } catch (error: unknown) {\n if (renderToken === this.#renderToken) {\n this.#setError({\n code: \"map-image-error\",\n message: `Unable to load the dungeon map image: ${String(error)}.`,\n cause: error,\n });\n }\n }\n }\n\n async #drawMap(\n mapImage: ImageAssetRef,\n source: FellowshipDataSource,\n renderToken: number,\n ): Promise<void> {\n const image = new Image();\n image.crossOrigin = \"anonymous\";\n image.src = source.resolveAssetUrl(mapImage);\n await image.decode();\n if (renderToken !== this.#renderToken) {\n return;\n }\n const canvas = this._canvasElement;\n const context = canvas?.getContext(\"2d\");\n if (!canvas || !context) {\n throw new Error(\"A 2D canvas context is unavailable.\");\n }\n context.clearRect(0, 0, canvas.width, canvas.height);\n context.drawImage(image, 0, 0, mapImage.width, mapImage.height);\n }\n\n #setError(error: unknown): void {\n const detail: FellowshipMapErrorDetail =\n typeof error === \"object\" && error !== null && \"code\" in error && \"message\" in error\n ? {\n code: String(error.code),\n message: String(error.message),\n ...(error instanceof FellowshipDataError && error.cause !== undefined\n ? { cause: error.cause }\n : {}),\n }\n : { code: \"unknown\", message: error instanceof Error ? error.message : String(error) };\n this._error = detail;\n this._status = \"error\";\n this.dispatchEvent(\n new CustomEvent<FellowshipMapErrorDetail>(\"fellowship-map-error\", {\n ...eventOptions,\n detail,\n }),\n );\n }\n\n #prepareRendererContent(): void {\n const model = this._model;\n const enemyContent = new Map<string, TemplateResult | typeof nothing>();\n const groupContent = new Map<string, TemplateResult | typeof nothing>();\n\n if (!model) {\n this._enemyRenderContent = enemyContent;\n this._groupRenderContent = groupContent;\n return;\n }\n\n if (this.renderEnemy) {\n for (const group of model.groups) {\n for (const enemy of group.enemies) {\n try {\n enemyContent.set(\n enemy.instanceId,\n this.renderEnemy({\n model: enemy,\n group,\n dungeon: model.dungeon,\n mapImage: model.mapImage,\n }),\n );\n } catch (error: unknown) {\n this.#queueRendererError(`enemy:${enemy.instanceId}`, \"enemy-renderer-error\", error);\n }\n }\n }\n }\n this._enemyRenderContent = enemyContent;\n\n if (this.renderGroup) {\n for (const group of model.groups) {\n const children = group.enemies.map((enemy) => this.#renderEnemyAnchor(group, enemy));\n const key = this.#groupKey(model, group);\n try {\n groupContent.set(\n key,\n this.renderGroup({\n group,\n children,\n dungeon: model.dungeon,\n mapImage: model.mapImage,\n }),\n );\n } catch (error: unknown) {\n this.#queueRendererError(`group:${key}`, \"group-renderer-error\", error);\n }\n }\n }\n this._groupRenderContent = groupContent;\n }\n\n #prepareMapLabels(): void {\n const labels = new Map<string, string>();\n const dungeon = this._data?.dungeon;\n if (dungeon) {\n for (const mapImage of dungeon.mapImages) {\n try {\n labels.set(\n mapImage.id,\n this.mapLabelFormatter?.(mapImage, dungeon) ?? humanizeMapLabel(mapImage, dungeon),\n );\n } catch (error: unknown) {\n this.#queueRendererError(`map-label:${mapImage.id}`, \"map-label-formatter-error\", error);\n labels.set(mapImage.id, humanizeMapLabel(mapImage, dungeon));\n }\n }\n }\n this._mapLabels = labels;\n }\n\n #renderGroups(model: DungeonMapRenderModel): unknown {\n return repeat(\n model.groups,\n (group) => this.#groupKey(model, group),\n (group) => this.#renderGroup(model, group),\n );\n }\n\n #groupKey(model: DungeonMapRenderModel, group: DungeonMapGroupModel): string {\n return `${model.mapImage.id}:${String(group.id)}:${group.enemies[0]?.instanceId ?? \"empty\"}`;\n }\n\n #renderGroup(model: DungeonMapRenderModel, group: DungeonMapGroupModel): TemplateResult {\n const bounds = group.visualBounds;\n const style = {\n left: `${bounds.x * 100}%`,\n top: `${bounds.y * 100}%`,\n width: `${bounds.width * 100}%`,\n height: `${bounds.height * 100}%`,\n };\n const key = this.#groupKey(model, group);\n const content =\n this.renderGroup && this._groupRenderContent.has(key)\n ? (this._groupRenderContent.get(key) ?? nothing)\n : this.#renderDefaultGroup(group);\n\n return html` <div class=\"group-anchor\" part=\"group\" style=${styleMap(style)}>${content}</div> `;\n }\n\n #renderDefaultGroup(group: DungeonMapGroupModel): TemplateResult {\n const label = `G${group.id ?? \"–\"}${group.dungeonScore === null ? \"\" : ` · ${group.dungeonScore}`}`;\n return html`\n <div class=\"default-group\">\n <div class=\"group-ring\" aria-hidden=\"true\"></div>\n <span class=\"group-label\">${label}</span>\n ${repeat(\n group.enemies,\n (enemy) => enemy.instanceId,\n (enemy) => this.#renderEnemyAnchor(group, enemy),\n )}\n </div>\n `;\n }\n\n #renderEnemyAnchor(group: DungeonMapGroupModel, enemy: DungeonMapEnemyModel): TemplateResult {\n const bounds = group.visualBounds;\n const style = {\n left: `${((enemy.position.x - bounds.x) / bounds.width) * 100}%`,\n top: `${((enemy.position.y - bounds.y) / bounds.height) * 100}%`,\n };\n const content =\n this.renderEnemy && this._enemyRenderContent.has(enemy.instanceId)\n ? (this._enemyRenderContent.get(enemy.instanceId) ?? nothing)\n : this.#renderDefaultEnemy(enemy);\n const name = enemy.enemy?.name ?? enemy.placement.id;\n\n return html`\n <div\n class=\"enemy-anchor\"\n part=\"enemy\"\n style=${styleMap(style)}\n role=\"img\"\n aria-label=${name}\n title=${name}\n >\n <div class=\"enemy-visual\">${content}</div>\n </div>\n `;\n }\n\n #renderDefaultEnemy(enemy: DungeonMapEnemyModel): TemplateResult {\n const name = enemy.enemy?.name ?? enemy.placement.id;\n const fallback = name\n .split(/\\s+/)\n .map((part) => part[0])\n .join(\"\")\n .slice(0, 2)\n .toUpperCase();\n return html`\n <span class=\"enemy-marker\" data-boss=${String(enemy.enemy?.isBoss === true)}>\n ${\n enemy.imageUrl\n ? html`<img class=\"enemy-image\" src=${enemy.imageUrl} alt=\"\" draggable=\"false\" />`\n : fallback\n }\n </span>\n `;\n }\n\n #renderMapSelector(model: DungeonMapRenderModel): TemplateResult | typeof nothing {\n const maps = model.dungeon.mapImages;\n if (!this.showMapSelector || maps.length <= 1) {\n return nothing;\n }\n\n return html`\n <div class=\"map-selector\" part=\"map-selector\" role=\"tablist\" aria-label=\"Dungeon maps\">\n ${repeat(\n maps,\n (mapImage) => mapImage.id,\n (mapImage, index) => html`\n <button\n class=\"map-selector-button\"\n id=${`map-tab-${index}`}\n part=\"map-selector-button\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"map-stage\"\n aria-selected=${String(mapImage.id === this._selectedMap?.id)}\n tabindex=${mapImage.id === this._selectedMap?.id ? \"0\" : \"-1\"}\n data-map-index=${String(index)}\n @click=${() => this.#selectMap(mapImage)}\n @keydown=${this.#handleMapSelectorKeydown}\n >\n ${this.#mapLabel(mapImage)}\n </button>\n `,\n )}\n </div>\n `;\n }\n\n #renderControls(): TemplateResult | typeof nothing {\n if (!this.showControls || !this._model) {\n return nothing;\n }\n return html`\n <div class=\"controls\" part=\"controls\" role=\"group\" aria-label=\"Map zoom controls\">\n <button\n class=\"control-button\"\n part=\"control-button\"\n type=\"button\"\n aria-label=\"Zoom out\"\n title=\"Zoom out\"\n @click=${this.zoomOut}\n >\n −\n </button>\n <button\n class=\"control-button\"\n part=\"control-button\"\n type=\"button\"\n aria-label=\"Fit map\"\n title=\"Fit map\"\n @click=${this.fitToView}\n >\n ◇\n </button>\n <button\n class=\"control-button\"\n part=\"control-button\"\n type=\"button\"\n aria-label=\"Zoom in\"\n title=\"Zoom in\"\n @click=${this.zoomIn}\n >\n +\n </button>\n </div>\n `;\n }\n\n #renderStatus(): TemplateResult | typeof nothing {\n if (this._status === \"ready\") {\n return nothing;\n }\n\n const loading = this._status === \"loading\";\n const empty = this._status === \"empty\";\n const title = loading ? \"Charting dungeon\" : empty ? \"Choose a dungeon\" : \"Map unavailable\";\n const copy = loading\n ? \"Loading Fellowship data and preparing the tactical layer.\"\n : empty\n ? \"Set the dungeon-id attribute or dungeonId property to render a map.\"\n : (this._error?.message ?? \"An unexpected map error occurred.\");\n\n return html`\n <div class=\"status\" part=\"status\" role=${this._status === \"error\" ? \"alert\" : \"status\"}>\n <div class=\"status-card\">\n <div class=\"status-mark\" aria-hidden=\"true\"><span>${loading ? \"…\" : \"✦\"}</span></div>\n <p class=\"status-title\">${title}</p>\n <p class=\"status-copy\">${copy}</p>\n ${\n this._status === \"error\"\n ? html`<button class=\"retry-button\" type=\"button\" @click=${this.retry}>Retry</button>`\n : nothing\n }\n </div>\n </div>\n `;\n }\n\n #mapLabel(mapImage: ImageAssetRef): string {\n return this._mapLabels.get(mapImage.id) ?? \"Map\";\n }\n\n #selectMap(mapImage: ImageAssetRef): void {\n if (mapImage.id === this._selectedMap?.id) {\n return;\n }\n this.mapImageId = mapImage.id;\n this.dispatchEvent(\n new CustomEvent<FellowshipMapImageChangeDetail>(\"fellowship-map-image-change\", {\n ...eventOptions,\n detail: { mapImageId: mapImage.id, mapImage },\n }),\n );\n }\n\n #handleMapSelectorKeydown = (event: KeyboardEvent): void => {\n if (![\"ArrowLeft\", \"ArrowRight\", \"Home\", \"End\"].includes(event.key)) {\n return;\n }\n const maps = this._data?.dungeon.mapImages ?? [];\n const currentIndex = maps.findIndex(({ id }) => id === this._selectedMap?.id);\n const nextIndex =\n event.key === \"Home\"\n ? 0\n : event.key === \"End\"\n ? maps.length - 1\n : event.key === \"ArrowRight\"\n ? (currentIndex + 1) % maps.length\n : (currentIndex - 1 + maps.length) % maps.length;\n const nextMap = maps[nextIndex];\n if (nextMap) {\n event.preventDefault();\n this.#selectMap(nextMap);\n void this.updateComplete.then(() => {\n this.renderRoot\n .querySelector<HTMLButtonElement>(`[data-map-index=\"${nextIndex}\"]`)\n ?.focus();\n });\n }\n };\n\n #handleWheel = (event: WheelEvent): void => {\n if (!this._model || this._status !== \"ready\") {\n return;\n }\n event.preventDefault();\n const rect = this._viewportElement?.getBoundingClientRect();\n if (!rect) {\n return;\n }\n const factor = Math.exp(-event.deltaY * 0.0015);\n this.#viewport.zoomAt(factor, event.clientX - rect.left, event.clientY - rect.top);\n this.#commitViewport(\"wheel\");\n };\n\n #handleViewportKeydown = (event: KeyboardEvent): void => {\n if (event.target !== this._viewportElement || !this._model) {\n return;\n }\n const panAmount = 56;\n if (event.key === \"+\" || event.key === \"=\") {\n event.preventDefault();\n this.#viewport.zoomAt(1.35);\n } else if (event.key === \"-\") {\n event.preventDefault();\n this.#viewport.zoomAt(1 / 1.35);\n } else if (event.key === \"0\") {\n event.preventDefault();\n this.#viewport.fit();\n } else if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n this.#viewport.panBy(panAmount, 0);\n } else if (event.key === \"ArrowRight\") {\n event.preventDefault();\n this.#viewport.panBy(-panAmount, 0);\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n this.#viewport.panBy(0, panAmount);\n } else if (event.key === \"ArrowDown\") {\n event.preventDefault();\n this.#viewport.panBy(0, -panAmount);\n } else {\n return;\n }\n this.#commitViewport(\"keyboard\");\n };\n\n #handlePointerDown = (event: PointerEvent): void => {\n if (!this._model || this.#isInteractiveEvent(event)) {\n return;\n }\n const viewport = this._viewportElement;\n const rect = viewport?.getBoundingClientRect();\n if (!viewport || !rect) {\n return;\n }\n viewport.setPointerCapture(event.pointerId);\n const point = { x: event.clientX - rect.left, y: event.clientY - rect.top };\n this.#pointers.set(event.pointerId, point);\n this._isPanning = true;\n if (this.#pointers.size === 1) {\n this.#lastPointer = point;\n } else if (this.#pointers.size === 2) {\n const [first, second] = [...this.#pointers.values()];\n if (first && second) {\n this.#lastPinchDistance = Math.hypot(second.x - first.x, second.y - first.y);\n this.#lastPinchCenter = { x: (first.x + second.x) / 2, y: (first.y + second.y) / 2 };\n }\n }\n };\n\n #handlePointerMove = (event: PointerEvent): void => {\n if (!this.#pointers.has(event.pointerId)) {\n return;\n }\n const rect = this._viewportElement?.getBoundingClientRect();\n if (!rect) {\n return;\n }\n const point = { x: event.clientX - rect.left, y: event.clientY - rect.top };\n this.#pointers.set(event.pointerId, point);\n\n if (this.#pointers.size === 1 && this.#lastPointer) {\n this.#viewport.panBy(point.x - this.#lastPointer.x, point.y - this.#lastPointer.y);\n this.#lastPointer = point;\n this.#commitViewport(\"pan\");\n return;\n }\n\n if (this.#pointers.size === 2) {\n const [first, second] = [...this.#pointers.values()];\n if (!first || !second) {\n return;\n }\n const distance = Math.hypot(second.x - first.x, second.y - first.y);\n const center = { x: (first.x + second.x) / 2, y: (first.y + second.y) / 2 };\n if (this.#lastPinchDistance > 0) {\n this.#viewport.zoomAt(distance / this.#lastPinchDistance, center.x, center.y);\n }\n if (this.#lastPinchCenter) {\n this.#viewport.panBy(\n center.x - this.#lastPinchCenter.x,\n center.y - this.#lastPinchCenter.y,\n );\n }\n this.#lastPinchDistance = distance;\n this.#lastPinchCenter = center;\n this.#commitViewport(\"pinch\");\n }\n };\n\n #handlePointerEnd = (event: PointerEvent): void => {\n this.#pointers.delete(event.pointerId);\n if (this.#pointers.size === 1) {\n this.#lastPointer = [...this.#pointers.values()][0] ?? null;\n } else if (this.#pointers.size === 0) {\n this.#lastPointer = null;\n this.#lastPinchCenter = null;\n this.#lastPinchDistance = 0;\n this._isPanning = false;\n }\n };\n\n #isInteractiveEvent(event: PointerEvent): boolean {\n return event\n .composedPath()\n .some(\n (node) =>\n node instanceof HTMLElement &&\n node.matches(\"button, a, input, select, textarea, [contenteditable='true']\"),\n );\n }\n\n #handleResize(): void {\n const viewport = this._viewportElement;\n const mapImage = this._selectedMap;\n if (!viewport || !mapImage) {\n return;\n }\n this.#viewport.setDimensions(\n viewport.clientWidth,\n viewport.clientHeight,\n mapImage.width,\n mapImage.height,\n );\n this.#commitViewport(\"resize\");\n }\n\n #commitViewport(source: ViewportChangeSource): void {\n this._viewportState = this.#viewport.state;\n this.dispatchEvent(\n new CustomEvent<FellowshipMapViewportChangeDetail>(\"fellowship-map-viewport-change\", {\n ...eventOptions,\n detail: { viewport: this.#viewport.state, source },\n }),\n );\n }\n\n #queueRendererError(key: string, code: string, error: unknown): void {\n if (this.#rendererFailures.has(key)) {\n return;\n }\n this.#rendererFailures.add(key);\n this.#pendingRendererErrors.push({\n code,\n message: error instanceof Error ? error.message : String(error),\n cause: error,\n });\n }\n\n #flushRendererErrors(): void {\n if (this.#pendingRendererErrors.length === 0) {\n return;\n }\n const errors = this.#pendingRendererErrors;\n this.#pendingRendererErrors = [];\n for (const detail of errors) {\n this.dispatchEvent(\n new CustomEvent<FellowshipMapErrorDetail>(\"fellowship-map-error\", {\n ...eventOptions,\n detail,\n }),\n );\n }\n }\n}\n\nexport const defineFellowshipDungeonMap = (): void => {\n if (\n typeof globalThis.customElements !== \"undefined\" &&\n !globalThis.customElements.get(\"fellowship-dungeon-map\")\n ) {\n globalThis.customElements.define(\"fellowship-dungeon-map\", FellowshipDungeonMapElement);\n }\n};\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"fellowship-dungeon-map\": FellowshipDungeonMapElement;\n }\n}\n"],"mappings":"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"}
@@ -1,115 +1,85 @@
1
- import { t as e } from "./data-source-CoIFvV72.js";
2
- import { n as t, r as n, t as r } from "./decorate-B_ZTN9li.js";
3
- import { a as i, n as a } from "./tooltip-DoQVSnOn.js";
4
- import { findImage as o, getAssetUrl as s } from "@crockery/fellowship-data";
1
+ import { n as e, t } from "./data-source-XbksqUrw.js";
2
+ import { n, r, t as i } from "./decorate-B_ZTN9li.js";
3
+ import { a, n as o } from "./tooltip-DoQVSnOn.js";
4
+ import { createPackageFellowshipDataClient as s } from "@crockery/fellowship-data/client";
5
5
  import { LitElement as c, css as l, html as u, nothing as d } from "lit";
6
6
  import { customElement as f, property as p, state as m } from "lit/decorators.js";
7
7
  import { repeat as h } from "lit/directives/repeat.js";
8
+ import { findImage as g } from "@crockery/fellowship-data";
8
9
  //#region src/data/package-talent-data-source.ts
9
- var g, _ = (t) => {
10
- if (t?.aborted) throw new e("aborted", "The hero talent data request was aborted.", t.reason);
11
- }, v = () => (g ??= Promise.all([
12
- import("@crockery/fellowship-data/heroes"),
13
- import("@crockery/fellowship-data/talents"),
14
- import("@crockery/fellowship-data/manifest")
15
- ]).then(([t, n, r]) => {
16
- let i = r.default;
17
- if (i.schemaVersion !== 1) throw new e("unsupported-schema", `Unsupported fellowship-data schema version: ${String(i.schemaVersion)}.`);
18
- let a = /* @__PURE__ */ new Map();
19
- for (let e of n.default) {
20
- let t = a.get(e.parentId) ?? [];
21
- t.push(e), a.set(e.parentId, t);
22
- }
10
+ var _ = (e = {}) => {
11
+ let t = s(e);
23
12
  return {
24
- manifest: i,
25
- heroes: new Map(t.default.map((e) => [e.id, e])),
26
- talentsByHero: new Map([...a].map(([e, t]) => [e, t]))
13
+ loadHeroTalents: (e, n) => t.loadHeroTalents(e, n),
14
+ resolveAssetUrl: (e) => t.resolveAssetUrl(e)
27
15
  };
28
- }).catch((t) => {
29
- throw g = void 0, t instanceof e ? t : new e("data-load-failed", "Unable to load hero talents from @crockery/fellowship-data.", t);
30
- }), g), y = (t = {}) => ({
31
- async loadHeroTalents(t, n) {
32
- _(n);
33
- let r = await v();
34
- _(n);
35
- let i = r.heroes.get(t);
36
- if (!i) throw new e("hero-not-found", `No hero with ID “${t}” exists in @crockery/fellowship-data.`);
37
- return {
38
- manifest: r.manifest,
39
- hero: i,
40
- talents: r.talentsByHero.get(t) ?? []
41
- };
42
- },
43
- resolveAssetUrl(e) {
44
- return t.assetBaseUrl ? s(e, { baseUrl: t.assetBaseUrl }) : s(e);
45
- }
46
- }), b = [
16
+ }, v = [
47
17
  "I",
48
18
  "II",
49
19
  "III",
50
20
  "IV",
51
21
  "V",
52
22
  "VI"
53
- ], x = (e) => b[e - 1] ?? String(e), S = (e, t, n) => ({
23
+ ], y = (e) => v[e - 1] ?? String(e), b = (e, t, n) => ({
54
24
  code: e,
55
25
  message: n,
56
26
  talentId: t.id
57
- }), C = (e, t) => {
27
+ }), x = (e, t) => {
58
28
  let n = [], r = /* @__PURE__ */ new Set(), i = [];
59
29
  for (let a of e.talents) {
60
30
  if (a.row === null && a.column === null) continue;
61
31
  if (!Number.isInteger(a.row) || !Number.isInteger(a.column) || a.row === null || a.column === null || a.row < 1 || a.row > 6 || a.column < 1 || a.column > 3) {
62
- n.push(S("invalid-position", a, `Talent “${a.id}” has an invalid row or column and was skipped.`));
32
+ n.push(b("invalid-position", a, `Talent “${a.id}” has an invalid row or column and was skipped.`));
63
33
  continue;
64
34
  }
65
35
  let e = `${a.row}:${a.column}`;
66
36
  if (r.has(e)) {
67
- n.push(S("duplicate-position", a, `Talent “${a.id}” duplicates row ${a.row}, column ${a.column} and was skipped.`));
37
+ n.push(b("duplicate-position", a, `Talent “${a.id}” duplicates row ${a.row}, column ${a.column} and was skipped.`));
68
38
  continue;
69
39
  }
70
40
  r.add(e);
71
- let s = Number.isInteger(a.pointCost) && (a.pointCost ?? 0) > 0 ? a.pointCost : null;
72
- s === null && n.push(S("invalid-cost", a, `Talent “${a.id}” has an invalid point cost.`)), a.name || n.push(S("missing-name", a, `Talent “${a.id}” has no display name.`)), a.description || n.push(S("missing-description", a, `Talent “${a.id}” has no description.`));
73
- let c = o(a.images, {
41
+ let o = Number.isInteger(a.pointCost) && (a.pointCost ?? 0) > 0 ? a.pointCost : null;
42
+ o === null && n.push(b("invalid-cost", a, `Talent “${a.id}” has an invalid point cost.`)), a.name || n.push(b("missing-name", a, `Talent “${a.id}” has no display name.`)), a.description || n.push(b("missing-description", a, `Talent “${a.id}” has no description.`));
43
+ let s = g(a.images, {
74
44
  type: "talent",
75
45
  size: "200"
76
- }) ?? o(a.images, {
46
+ }) ?? g(a.images, {
77
47
  type: "talent",
78
48
  size: "full"
79
- }), l = null;
80
- if (!c) n.push(S("missing-image", a, `Talent “${a.id}” has no icon.`));
49
+ }), c = null;
50
+ if (!s) n.push(b("missing-image", a, `Talent “${a.id}” has no icon.`));
81
51
  else try {
82
- l = t.resolveAssetUrl(c);
52
+ c = t.resolveAssetUrl(s);
83
53
  } catch (e) {
84
- n.push(S("asset-url-error", a, `The icon URL for talent “${a.id}” could not be resolved: ${String(e)}.`));
54
+ n.push(b("asset-url-error", a, `The icon URL for talent “${a.id}” could not be resolved: ${String(e)}.`));
85
55
  }
86
56
  i.push({
87
57
  talent: a,
88
58
  row: a.row,
89
59
  column: a.column,
90
- pointCost: s,
91
- imageUrl: l
60
+ pointCost: o,
61
+ imageUrl: c
92
62
  });
93
63
  }
94
64
  i.sort((e, t) => e.row - t.row || e.column - t.column);
95
- let a = new Map(i.map((e) => [e.talent.id, e])), s = [...new Set(i.map(({ row: e }) => e))].map((e) => ({
65
+ let a = new Map(i.map((e) => [e.talent.id, e])), o = [...new Set(i.map(({ row: e }) => e))].map((e) => ({
96
66
  row: e,
97
- label: x(e),
67
+ label: y(e),
98
68
  talents: i.filter((t) => t.row === e)
99
69
  }));
100
70
  return {
101
71
  hero: e.hero,
102
- rows: s,
72
+ rows: o,
103
73
  talentById: a,
104
74
  warnings: n
105
75
  };
106
- }, w = 14, T = (e) => Math.min(14, Math.max(0, Math.round(Number.isFinite(e) ? e : 14))), E = (e, t) => {
76
+ }, S = 14, C = (e) => Math.min(14, Math.max(0, Math.round(Number.isFinite(e) ? e : 14))), w = (e, t) => {
107
77
  let n = new Set(t), r = [...e.talentById.values()].filter(({ talent: e }) => n.has(e.id)).map(({ talent: e }) => e.id), i = new Set(r);
108
78
  return {
109
79
  selectedTalentIds: r,
110
80
  removedTalentIds: [...new Set(t)].filter((e) => !i.has(e))
111
81
  };
112
- }, D = (e, t, n) => {
82
+ }, T = (e, t, n) => {
113
83
  let r = new Set(t), i = [...e.talentById.values()].reduce((e, t) => r.has(t.talent.id) && t.pointCost !== null ? e + t.pointCost : e, 0), a = n - i;
114
84
  return {
115
85
  selectedTalentIds: [...e.talentById.values()].filter(({ talent: e }) => r.has(e.id)).map(({ talent: e }) => e.id),
@@ -117,7 +87,7 @@ var g, _ = (t) => {
117
87
  remainingPoints: a,
118
88
  overBudget: a < 0
119
89
  };
120
- }, O = (e, t) => e !== null && !t.overBudget && e <= t.remainingPoints, k = l`
90
+ }, E = (e, t) => e !== null && !t.overBudget && e <= t.remainingPoints, D = l`
121
91
  :host {
122
92
  --fellowship-talent-background: var(--_fellowship-field-canvas);
123
93
  --fellowship-talent-surface: var(--_fellowship-field-surface);
@@ -665,20 +635,20 @@ var g, _ = (t) => {
665
635
  transform: none;
666
636
  }
667
637
  }
668
- `, A = {
638
+ `, O = {
669
639
  bubbles: !0,
670
640
  composed: !0
671
- }, j = (e, t) => e.length !== t?.length || e.some((e, n) => e !== t[n]), M = (e) => `talent:${e}`, N = (e) => ({
641
+ }, k = (e, t) => e.length !== t?.length || e.some((e, n) => e !== t[n]), A = (e) => `talent:${e}`, j = (e) => ({
672
642
  selectedTalentIds: [],
673
643
  spentPoints: 0,
674
644
  remainingPoints: e,
675
645
  overBudget: !1
676
- }), P = class extends c {
646
+ }), M = class extends c {
677
647
  constructor(...e) {
678
- super(...e), this.heroId = "", this.talentPoints = 14, this.selectedTalentIds = [], this.assetBaseUrl = null, this.dataSource = null, this.renderTalent = null, this.showPointControls = !0, this.showTooltips = !0, this._status = "empty", this._error = null, this._model = null, this._failedImages = /* @__PURE__ */ new Set(), this._announcement = "", this._renderSelectionState = N(14), this._talentPresentation = /* @__PURE__ */ new Map(), this._talentRenderContent = /* @__PURE__ */ new Map(), this.#e = null, this.#t = 0, this.#n = null, this.#r = !1, this.#i = /* @__PURE__ */ new Set(), this.#a = [];
648
+ super(...e), this.heroId = "", this.talentPoints = 14, this.selectedTalentIds = [], this.assetBaseUrl = null, this.dataSource = null, this.renderTalent = null, this.showPointControls = !0, this.showTooltips = !0, this._status = "empty", this._error = null, this._model = null, this._failedImages = /* @__PURE__ */ new Set(), this._announcement = "", this._renderSelectionState = j(14), this._talentPresentation = /* @__PURE__ */ new Map(), this._talentRenderContent = /* @__PURE__ */ new Map(), this.#e = null, this.#t = 0, this.#n = null, this.#r = !1, this.#i = /* @__PURE__ */ new Set(), this.#a = [];
679
649
  }
680
650
  static {
681
- this.styles = [n, k];
651
+ this.styles = [r, D];
682
652
  }
683
653
  #e;
684
654
  #t;
@@ -687,7 +657,7 @@ var g, _ = (t) => {
687
657
  #i;
688
658
  #a;
689
659
  connectedCallback() {
690
- super.connectedCallback(), t(this.ownerDocument);
660
+ super.connectedCallback(), n(this.ownerDocument);
691
661
  }
692
662
  disconnectedCallback() {
693
663
  this.#e?.abort(), super.disconnectedCallback();
@@ -700,7 +670,7 @@ var g, _ = (t) => {
700
670
  return;
701
671
  }
702
672
  if (e.has("talentPoints")) {
703
- let t = T(this.talentPoints);
673
+ let t = C(this.talentPoints);
704
674
  if (t !== this.talentPoints) {
705
675
  this.#r = !0, this.talentPoints = t, this.#v("normalization");
706
676
  return;
@@ -712,20 +682,20 @@ var g, _ = (t) => {
712
682
  willUpdate(e) {
713
683
  super.willUpdate(e);
714
684
  let t = e.has("_model") || e.has("selectedTalentIds") || e.has("talentPoints");
715
- t && (this._renderSelectionState = this._model ? D(this._model, this.selectedTalentIds, this.talentPoints) : N(this.talentPoints)), e.has("renderTalent") && this.#i.clear(), (t || e.has("renderTalent")) && this.#l(this._renderSelectionState);
685
+ t && (this._renderSelectionState = this._model ? T(this._model, this.selectedTalentIds, this.talentPoints) : j(this.talentPoints)), e.has("renderTalent") && this.#i.clear(), (t || e.has("renderTalent")) && this.#l(this._renderSelectionState);
716
686
  }
717
687
  get renderModel() {
718
688
  return this._model;
719
689
  }
720
690
  get selectionState() {
721
- return this._model ? D(this._model, this.selectedTalentIds, this.talentPoints) : N(this.talentPoints);
691
+ return this._model ? T(this._model, this.selectedTalentIds, this.talentPoints) : j(this.talentPoints);
722
692
  }
723
693
  reset() {
724
694
  this.selectedTalentIds.length !== 0 && (this.selectedTalentIds = [], this.#_("reset"));
725
695
  }
726
696
  selectTalent(e) {
727
697
  let t = this._model?.talentById.get(e), n = this.selectionState;
728
- return !t || n.selectedTalentIds.includes(e) || !O(t.pointCost, n) ? !1 : (this.selectedTalentIds = [...n.selectedTalentIds, e], this.#_("api"), !0);
698
+ return !t || n.selectedTalentIds.includes(e) || !E(t.pointCost, n) ? !1 : (this.selectedTalentIds = [...n.selectedTalentIds, e], this.#_("api"), !0);
729
699
  }
730
700
  deselectTalent(e) {
731
701
  return this.selectionState.selectedTalentIds.includes(e) ? (this.selectedTalentIds = this.selectionState.selectedTalentIds.filter((t) => t !== e), this.#_("api"), !0) : !1;
@@ -864,9 +834,9 @@ var g, _ = (t) => {
864
834
  `;
865
835
  }
866
836
  #l(e) {
867
- let t = this.renderTalent, n = this._model, r = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map();
837
+ let t = this.renderTalent, n = this._model, r = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map();
868
838
  if (n) for (let o of n.talentById.values()) {
869
- let s = e.selectedTalentIds.includes(o.talent.id), c = O(o.pointCost, e), l = !s && !c, u = o.talent.name ?? o.talent.id, d = o.pointCost === null ? "This talent has no valid point cost." : l ? `Requires ${o.pointCost} talent points; ${Math.max(0, e.remainingPoints)} remaining.` : `${o.pointCost} talent ${o.pointCost === 1 ? "point" : "points"}.`, f = {
839
+ let s = e.selectedTalentIds.includes(o.talent.id), c = E(o.pointCost, e), l = !s && !c, u = o.talent.name ?? o.talent.id, d = o.pointCost === null ? "This talent has no valid point cost." : l ? `Requires ${o.pointCost} talent points; ${Math.max(0, e.remainingPoints)} remaining.` : `${o.pointCost} talent ${o.pointCost === 1 ? "point" : "points"}.`, f = {
870
840
  talent: o,
871
841
  hero: n.hero,
872
842
  selected: s,
@@ -876,12 +846,12 @@ var g, _ = (t) => {
876
846
  remainingPoints: e.remainingPoints,
877
847
  talentPoints: this.talentPoints
878
848
  };
879
- if (a.set(o.talent.id, {
849
+ if (i.set(o.talent.id, {
880
850
  context: f,
881
851
  name: u,
882
852
  descriptionId: `talent-description-${o.row}-${o.column}`,
883
853
  affordability: d,
884
- description: [i(o.talent.description ?? ""), d].filter(Boolean).join(" ")
854
+ description: [a(o.talent.description ?? ""), d].filter(Boolean).join(" ")
885
855
  }), t) try {
886
856
  r.set(o.talent.id, t(f));
887
857
  } catch (e) {
@@ -892,11 +862,11 @@ var g, _ = (t) => {
892
862
  }));
893
863
  }
894
864
  }
895
- this._talentPresentation = a, this._talentRenderContent = r;
865
+ this._talentPresentation = i, this._talentRenderContent = r;
896
866
  }
897
867
  #u(e) {
898
868
  let t = e.talent, n = t.talent.name ?? t.talent.id, r = this.renderTalent && this._talentRenderContent.has(t.talent.id) ? this._talentRenderContent.get(t.talent.id) : u`
899
- <slot name=${M(t.talent.id)}>
869
+ <slot name=${A(t.talent.id)}>
900
870
  <span class="talent-name" part="talent-name" title=${n}>${n}</span>
901
871
  </slot>
902
872
  `;
@@ -940,18 +910,18 @@ var g, _ = (t) => {
940
910
  return;
941
911
  }
942
912
  this._status = "loading";
943
- let r = this.dataSource ?? y(this.assetBaseUrl ? { assetBaseUrl: this.assetBaseUrl } : {});
913
+ let r = this.dataSource ?? _(this.assetBaseUrl ? { assetBaseUrl: this.assetBaseUrl } : {});
944
914
  try {
945
915
  let i = await r.loadHeroTalents(t, this.#e.signal);
946
916
  if (n !== this.#t || this.#e.signal.aborted) return;
947
- if (i.manifest.schemaVersion !== 1) throw new e("unsupported-schema", `Unsupported fellowship-data schema version: ${String(i.manifest.schemaVersion)}.`);
948
- let a = C(i, r);
917
+ e(i.manifest.schemaVersion);
918
+ let a = x(i, r);
949
919
  this._model = a, this._status = "ready";
950
920
  let o = this.#m(!1);
951
921
  await this.updateComplete;
952
922
  let s = this.selectionState;
953
923
  this.dispatchEvent(new CustomEvent("fellowship-talents-ready", {
954
- ...A,
924
+ ...O,
955
925
  detail: {
956
926
  data: i,
957
927
  model: a,
@@ -965,7 +935,7 @@ var g, _ = (t) => {
965
935
  }
966
936
  #m(e) {
967
937
  if (!this._model) return !1;
968
- let t = E(this._model, this.selectedTalentIds);
938
+ let t = w(this._model, this.selectedTalentIds);
969
939
  return t.selectedTalentIds.length !== this.selectedTalentIds.length || t.selectedTalentIds.some((e, t) => e !== this.selectedTalentIds[t]) ? (this.selectedTalentIds = t.selectedTalentIds, t.removedTalentIds.length > 0 && this.#x([{
970
940
  code: "invalid-selection",
971
941
  message: `Removed unavailable talent selections: ${t.removedTalentIds.join(", ")}.`,
@@ -984,13 +954,13 @@ var g, _ = (t) => {
984
954
  this.selectedTalentIds = [...this.selectionState.selectedTalentIds, e.talent.id], this.#_("user");
985
955
  }
986
956
  #g(e, t) {
987
- let n = T(e), r = this.selectionState;
957
+ let n = C(e), r = this.selectionState;
988
958
  n === this.talentPoints || n < r.spentPoints || (this.talentPoints = n, this.#r = !0, this.#v(t));
989
959
  }
990
960
  #_(e) {
991
- let t = this._model ? D(this._model, this.selectedTalentIds, this.talentPoints) : N(this.talentPoints);
961
+ let t = this._model ? T(this._model, this.selectedTalentIds, this.talentPoints) : j(this.talentPoints);
992
962
  this.dispatchEvent(new CustomEvent("fellowship-talent-selection-change", {
993
- ...A,
963
+ ...O,
994
964
  detail: {
995
965
  ...t,
996
966
  source: e
@@ -1000,7 +970,7 @@ var g, _ = (t) => {
1000
970
  #v(e) {
1001
971
  let t = this.selectionState;
1002
972
  this.dispatchEvent(new CustomEvent("fellowship-talent-points-change", {
1003
- ...A,
973
+ ...O,
1004
974
  detail: {
1005
975
  ...t,
1006
976
  talentPoints: this.talentPoints,
@@ -1031,7 +1001,7 @@ var g, _ = (t) => {
1031
1001
  }
1032
1002
  #x(e) {
1033
1003
  this.dispatchEvent(new CustomEvent("fellowship-talents-warning", {
1034
- ...A,
1004
+ ...O,
1035
1005
  detail: {
1036
1006
  warnings: e,
1037
1007
  model: this._model
@@ -1043,44 +1013,44 @@ var g, _ = (t) => {
1043
1013
  let e = this.#a;
1044
1014
  this.#a = [], this.#x(e);
1045
1015
  }
1046
- #C(t) {
1047
- let n = typeof t == "object" && t && "code" in t && "message" in t ? {
1048
- code: String(t.code),
1049
- message: String(t.message),
1050
- ...t instanceof e && t.cause !== void 0 ? { cause: t.cause } : {}
1016
+ #C(e) {
1017
+ let n = typeof e == "object" && e && "code" in e && "message" in e ? {
1018
+ code: String(e.code),
1019
+ message: String(e.message),
1020
+ ...e instanceof t && e.cause !== void 0 ? { cause: e.cause } : {}
1051
1021
  } : {
1052
1022
  code: "unknown",
1053
- message: t instanceof Error ? t.message : String(t)
1023
+ message: e instanceof Error ? e.message : String(e)
1054
1024
  };
1055
1025
  this._error = n, this._status = "error", this.dispatchEvent(new CustomEvent("fellowship-talents-error", {
1056
- ...A,
1026
+ ...O,
1057
1027
  detail: n
1058
1028
  }));
1059
1029
  }
1060
1030
  };
1061
- r([p({
1031
+ i([p({
1062
1032
  type: String,
1063
1033
  attribute: "hero-id"
1064
- })], P.prototype, "heroId", void 0), r([p({
1034
+ })], M.prototype, "heroId", void 0), i([p({
1065
1035
  type: Number,
1066
1036
  attribute: "talent-points"
1067
- })], P.prototype, "talentPoints", void 0), r([p({
1037
+ })], M.prototype, "talentPoints", void 0), i([p({
1068
1038
  attribute: !1,
1069
- hasChanged: j
1070
- })], P.prototype, "selectedTalentIds", void 0), r([p({
1039
+ hasChanged: k
1040
+ })], M.prototype, "selectedTalentIds", void 0), i([p({
1071
1041
  type: String,
1072
1042
  attribute: "asset-base-url"
1073
- })], P.prototype, "assetBaseUrl", void 0), r([p({ attribute: !1 })], P.prototype, "dataSource", void 0), r([p({ attribute: !1 })], P.prototype, "renderTalent", void 0), r([p({
1043
+ })], M.prototype, "assetBaseUrl", void 0), i([p({ attribute: !1 })], M.prototype, "dataSource", void 0), i([p({ attribute: !1 })], M.prototype, "renderTalent", void 0), i([p({
1074
1044
  type: Boolean,
1075
1045
  attribute: "show-point-controls"
1076
- })], P.prototype, "showPointControls", void 0), r([p({
1046
+ })], M.prototype, "showPointControls", void 0), i([p({
1077
1047
  type: Boolean,
1078
1048
  attribute: "show-tooltips"
1079
- })], P.prototype, "showTooltips", void 0), r([m()], P.prototype, "_status", void 0), r([m()], P.prototype, "_error", void 0), r([m()], P.prototype, "_model", void 0), r([m()], P.prototype, "_failedImages", void 0), r([m()], P.prototype, "_announcement", void 0), r([m()], P.prototype, "_renderSelectionState", void 0), r([m()], P.prototype, "_talentPresentation", void 0), r([m()], P.prototype, "_talentRenderContent", void 0), P = r([f("fellowship-talent-calculator")], P);
1080
- var F = () => {
1081
- a(), customElements.get("fellowship-talent-calculator") || customElements.define("fellowship-talent-calculator", P);
1049
+ })], M.prototype, "showTooltips", void 0), i([m()], M.prototype, "_status", void 0), i([m()], M.prototype, "_error", void 0), i([m()], M.prototype, "_model", void 0), i([m()], M.prototype, "_failedImages", void 0), i([m()], M.prototype, "_announcement", void 0), i([m()], M.prototype, "_renderSelectionState", void 0), i([m()], M.prototype, "_talentPresentation", void 0), i([m()], M.prototype, "_talentRenderContent", void 0), M = i([f("fellowship-talent-calculator")], M);
1050
+ var N = () => {
1051
+ o(), customElements.get("fellowship-talent-calculator") || customElements.define("fellowship-talent-calculator", M);
1082
1052
  };
1083
1053
  //#endregion
1084
- export { O as a, T as c, y as d, w as i, C as l, F as n, D as o, M as r, E as s, P as t, x as u };
1054
+ export { E as a, C as c, _ as d, S as i, x as l, N as n, T as o, A as r, w as s, M as t, y as u };
1085
1055
 
1086
- //# sourceMappingURL=talent-calculator-Ch1Pbbba.js.map
1056
+ //# sourceMappingURL=talent-calculator-Dxuc2dZ8.js.map