@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.
- package/README.md +40 -26
- package/RELEASING.md +15 -14
- package/THIRD_PARTY_LICENSES.md +12 -0
- package/dist/assets/dm-mono-latin-400-normal-4GdczIuU.woff2 +0 -0
- package/dist/assets/dm-mono-latin-500-normal-DRMDZjhP.woff2 +0 -0
- package/dist/assets/syne-latin-700-normal-AF3Rs61n.woff2 +0 -0
- package/dist/assets/syne-latin-800-normal-EehdNYzx.woff2 +0 -0
- package/dist/chunks/decorate-DyMgX_Uh.js +9 -0
- package/dist/chunks/{dungeon-cast-browser-CSnu3gC5.js → dungeon-cast-browser-DdG8klca.js} +62 -60
- package/dist/chunks/{dungeon-map-CutIcCVP.js → dungeon-map-D0-DAjZ9.js} +130 -133
- package/dist/chunks/{decorate-B_ZTN9li.js → field-manual-fonts-DTFloVFe.js} +18 -25
- package/dist/chunks/gear-planner-Bgt77Ovf.js +1696 -0
- package/dist/chunks/{talent-calculator-Dxuc2dZ8.js → talent-calculator-DbU6P1AP.js} +36 -37
- package/dist/chunks/tooltip-C3SSJw-t.js +482 -0
- package/dist/data/cast-data-source.d.ts +1 -2
- package/dist/data/data-source.d.ts +1 -2
- package/dist/data/package-cast-data-source.d.ts +1 -6
- package/dist/data/package-data-source.d.ts +1 -6
- package/dist/data/package-talent-data-source.d.ts +1 -6
- package/dist/data/talent-data-source.d.ts +1 -2
- package/dist/dungeon-cast-browser/dungeon-cast-browser.d.ts +2 -2
- package/dist/dungeon-cast-browser/dungeon-cast-browser.styles.d.ts +0 -1
- package/dist/dungeon-cast-browser/index.d.ts +0 -1
- package/dist/dungeon-cast-browser/render-model.d.ts +3 -3
- package/dist/dungeon-cast-browser/types.d.ts +2 -3
- package/dist/dungeon-cast-browser.js +1 -3
- package/dist/dungeon-map/dungeon-map.d.ts +2 -2
- package/dist/dungeon-map/dungeon-map.styles.d.ts +0 -1
- package/dist/dungeon-map/index.d.ts +0 -1
- package/dist/dungeon-map/layout.d.ts +0 -1
- package/dist/dungeon-map/render-model.d.ts +3 -4
- package/dist/dungeon-map/types.d.ts +2 -3
- package/dist/dungeon-map/viewport-controller.d.ts +0 -1
- package/dist/dungeon-map.js +1 -3
- package/dist/gear-planner/gear-planner.d.ts +36 -0
- package/dist/gear-planner/gear-planner.styles.d.ts +1 -0
- package/dist/gear-planner/gear-position.d.ts +19 -0
- package/dist/gear-planner/index.d.ts +2 -0
- package/dist/gear-planner/item-workbench.d.ts +18 -0
- package/dist/gear-planner/item-workbench.styles.d.ts +1 -0
- package/dist/gear-planner/types.d.ts +24 -0
- package/dist/gear-planner.js +5 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +9 -7
- package/dist/react/dungeon-cast-browser.d.ts +8 -4
- package/dist/react/dungeon-cast-browser.js +11 -6
- package/dist/react/dungeon-map.d.ts +3 -2
- package/dist/react/dungeon-map.js +1 -3
- package/dist/react/gear-planner.d.ts +14 -0
- package/dist/react/gear-planner.js +23 -0
- package/dist/react/index.d.ts +2 -1
- package/dist/react/talent-calculator.d.ts +3 -2
- package/dist/react/talent-calculator.js +1 -3
- package/dist/react/tooltip.d.ts +0 -1
- package/dist/react/tooltip.js +1 -3
- package/dist/react.js +2 -1
- package/dist/styles/dungeon-poster-fonts.d.ts +2 -0
- package/dist/styles/dungeon-poster.styles.d.ts +7 -0
- package/dist/styles/field-manual-fonts.d.ts +0 -1
- package/dist/styles/field-manual.styles.d.ts +0 -1
- package/dist/talent-calculator/index.d.ts +0 -1
- package/dist/talent-calculator/render-model.d.ts +3 -3
- package/dist/talent-calculator/selection-state.d.ts +0 -1
- package/dist/talent-calculator/talent-calculator.d.ts +2 -2
- package/dist/talent-calculator/talent-calculator.styles.d.ts +0 -1
- package/dist/talent-calculator/types.d.ts +2 -3
- package/dist/talent-calculator.js +1 -3
- package/dist/tooltip/index.d.ts +0 -1
- package/dist/tooltip/positioning.d.ts +0 -1
- package/dist/tooltip/rich-text.d.ts +0 -1
- package/dist/tooltip/tooltip.d.ts +0 -1
- package/dist/tooltip/tooltip.styles.d.ts +0 -1
- package/dist/tooltip/types.d.ts +0 -1
- package/dist/tooltip.js +3 -5
- package/package.json +21 -15
- package/src/dungeon-cast-browser/README.md +10 -4
- package/src/dungeon-map/README.md +5 -6
- package/src/gear-planner/README.md +44 -0
- package/src/talent-calculator/README.md +5 -6
- package/src/tooltip/README.md +5 -6
- package/dist/chunks/data-source-XbksqUrw.js +0 -9
- package/dist/chunks/data-source-XbksqUrw.js.map +0 -1
- package/dist/chunks/decorate-B_ZTN9li.js.map +0 -1
- package/dist/chunks/dungeon-cast-browser-CSnu3gC5.js.map +0 -1
- package/dist/chunks/dungeon-map-CutIcCVP.js.map +0 -1
- package/dist/chunks/talent-calculator-Dxuc2dZ8.js.map +0 -1
- package/dist/chunks/tooltip-DoQVSnOn.js +0 -412
- package/dist/chunks/tooltip-DoQVSnOn.js.map +0 -1
- package/dist/data/cast-data-source.d.ts.map +0 -1
- package/dist/data/data-source.d.ts.map +0 -1
- package/dist/data/package-cast-data-source.d.ts.map +0 -1
- package/dist/data/package-data-source.d.ts.map +0 -1
- package/dist/data/package-talent-data-source.d.ts.map +0 -1
- package/dist/data/talent-data-source.d.ts.map +0 -1
- package/dist/dungeon-cast-browser/dungeon-cast-browser.d.ts.map +0 -1
- package/dist/dungeon-cast-browser/dungeon-cast-browser.styles.d.ts.map +0 -1
- package/dist/dungeon-cast-browser/index.d.ts.map +0 -1
- package/dist/dungeon-cast-browser/render-model.d.ts.map +0 -1
- package/dist/dungeon-cast-browser/types.d.ts.map +0 -1
- package/dist/dungeon-cast-browser.js.map +0 -1
- package/dist/dungeon-map/dungeon-map.d.ts.map +0 -1
- package/dist/dungeon-map/dungeon-map.styles.d.ts.map +0 -1
- package/dist/dungeon-map/index.d.ts.map +0 -1
- package/dist/dungeon-map/layout.d.ts.map +0 -1
- package/dist/dungeon-map/render-model.d.ts.map +0 -1
- package/dist/dungeon-map/types.d.ts.map +0 -1
- package/dist/dungeon-map/viewport-controller.d.ts.map +0 -1
- package/dist/dungeon-map.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/react/dungeon-cast-browser.d.ts.map +0 -1
- package/dist/react/dungeon-cast-browser.js.map +0 -1
- package/dist/react/dungeon-map.d.ts.map +0 -1
- package/dist/react/dungeon-map.js.map +0 -1
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/talent-calculator.d.ts.map +0 -1
- package/dist/react/talent-calculator.js.map +0 -1
- package/dist/react/tooltip.d.ts.map +0 -1
- package/dist/react/tooltip.js.map +0 -1
- package/dist/styles/field-manual-fonts.d.ts.map +0 -1
- package/dist/styles/field-manual.styles.d.ts.map +0 -1
- package/dist/talent-calculator/index.d.ts.map +0 -1
- package/dist/talent-calculator/render-model.d.ts.map +0 -1
- package/dist/talent-calculator/selection-state.d.ts.map +0 -1
- package/dist/talent-calculator/talent-calculator.d.ts.map +0 -1
- package/dist/talent-calculator/talent-calculator.styles.d.ts.map +0 -1
- package/dist/talent-calculator/types.d.ts.map +0 -1
- package/dist/talent-calculator.js.map +0 -1
- package/dist/tooltip/index.d.ts.map +0 -1
- package/dist/tooltip/positioning.d.ts.map +0 -1
- package/dist/tooltip/rich-text.d.ts.map +0 -1
- package/dist/tooltip/tooltip.d.ts.map +0 -1
- package/dist/tooltip/tooltip.styles.d.ts.map +0 -1
- package/dist/tooltip/types.d.ts.map +0 -1
- package/dist/tooltip.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"talent-calculator-Dxuc2dZ8.js","names":["#abortController","#flushRendererWarnings","#loadHeroTalents","#skipNextPropertyPointEvent","#emitPointsChange","#normalizeSelections","#reportOverBudget","#rendererFailures","#prepareTalentRenderContent","#emitSelectionChange","#renderHeader","#renderGrid","#renderStatus","#setTalentPoints","#renderTalent","#handleTalentActivation","#renderTalentContent","#pendingRendererWarnings","#renderDefaultTalentIcon","#handleImageError","#loadToken","#lastOverBudgetSignature","#reportWarnings","#setError"],"sources":["../../src/data/package-talent-data-source.ts","../../src/talent-calculator/render-model.ts","../../src/talent-calculator/selection-state.ts","../../src/talent-calculator/talent-calculator.styles.ts","../../src/talent-calculator/talent-calculator.ts"],"sourcesContent":["import { createPackageFellowshipDataClient } from \"@crockery/fellowship-data/client\";\n\nimport type { FellowshipTalentDataSource } from \"./talent-data-source.js\";\n\n/** @deprecated Create a package Fellowship data client instead. */\nexport interface PackageFellowshipTalentDataSourceOptions {\n readonly assetBaseUrl?: string;\n}\n\n/** @deprecated Use `createPackageFellowshipDataClient` from `@crockery/fellowship-data/client`. */\nexport const createPackageFellowshipTalentDataSource = (\n options: PackageFellowshipTalentDataSourceOptions = {},\n): FellowshipTalentDataSource => {\n const client = createPackageFellowshipDataClient(options);\n return {\n loadHeroTalents: (heroId, signal) => client.loadHeroTalents(heroId, signal),\n resolveAssetUrl: (asset) => client.resolveAssetUrl(asset),\n };\n};\n","import { findImage } from \"@crockery/fellowship-data\";\nimport type { Talent } from \"@crockery/fellowship-data\";\n\nimport type {\n FellowshipTalentDataSource,\n TalentCalculatorData,\n} from \"../data/talent-data-source.js\";\nimport type {\n TalentCalculatorRenderModel,\n TalentCalculatorTalentModel,\n TalentCalculatorWarning,\n} from \"./types.js\";\n\nconst romanNumerals = [\"I\", \"II\", \"III\", \"IV\", \"V\", \"VI\"] as const;\n\nexport const talentRowLabel = (row: number): string => romanNumerals[row - 1] ?? String(row);\n\nconst warning = (\n code: TalentCalculatorWarning[\"code\"],\n talent: Talent,\n message: string,\n): TalentCalculatorWarning => ({ code, message, talentId: talent.id });\n\nexport const createTalentCalculatorRenderModel = (\n data: TalentCalculatorData,\n source: FellowshipTalentDataSource,\n): TalentCalculatorRenderModel => {\n const warnings: TalentCalculatorWarning[] = [];\n const occupied = new Set<string>();\n const models: TalentCalculatorTalentModel[] = [];\n\n for (const talent of data.talents) {\n if (talent.row === null && talent.column === null) {\n continue;\n }\n if (\n !Number.isInteger(talent.row) ||\n !Number.isInteger(talent.column) ||\n talent.row === null ||\n talent.column === null ||\n talent.row < 1 ||\n talent.row > 6 ||\n talent.column < 1 ||\n talent.column > 3\n ) {\n warnings.push(\n warning(\n \"invalid-position\",\n talent,\n `Talent “${talent.id}” has an invalid row or column and was skipped.`,\n ),\n );\n continue;\n }\n\n const cell = `${talent.row}:${talent.column}`;\n if (occupied.has(cell)) {\n warnings.push(\n warning(\n \"duplicate-position\",\n talent,\n `Talent “${talent.id}” duplicates row ${talent.row}, column ${talent.column} and was skipped.`,\n ),\n );\n continue;\n }\n occupied.add(cell);\n\n const pointCost =\n Number.isInteger(talent.pointCost) && (talent.pointCost ?? 0) > 0 ? talent.pointCost : null;\n if (pointCost === null) {\n warnings.push(\n warning(\"invalid-cost\", talent, `Talent “${talent.id}” has an invalid point cost.`),\n );\n }\n if (!talent.name) {\n warnings.push(warning(\"missing-name\", talent, `Talent “${talent.id}” has no display name.`));\n }\n if (!talent.description) {\n warnings.push(\n warning(\"missing-description\", talent, `Talent “${talent.id}” has no description.`),\n );\n }\n\n const image =\n findImage(talent.images, { type: \"talent\", size: \"200\" }) ??\n findImage(talent.images, { type: \"talent\", size: \"full\" });\n let imageUrl: string | null = null;\n if (!image) {\n warnings.push(warning(\"missing-image\", talent, `Talent “${talent.id}” has no icon.`));\n } else {\n try {\n imageUrl = source.resolveAssetUrl(image);\n } catch (error: unknown) {\n warnings.push(\n warning(\n \"asset-url-error\",\n talent,\n `The icon URL for talent “${talent.id}” could not be resolved: ${String(error)}.`,\n ),\n );\n }\n }\n\n models.push({\n talent,\n row: talent.row,\n column: talent.column,\n pointCost,\n imageUrl,\n });\n }\n\n models.sort((a, b) => a.row - b.row || a.column - b.column);\n const talentById = new Map(models.map((model) => [model.talent.id, model]));\n const rows = [...new Set(models.map(({ row }) => row))].map((row) => ({\n row,\n label: talentRowLabel(row),\n talents: models.filter((talent) => talent.row === row),\n }));\n\n return { hero: data.hero, rows, talentById, warnings };\n};\n","import type { TalentCalculatorRenderModel, TalentCalculatorSelectionState } from \"./types.js\";\n\nexport const MAX_TALENT_POINTS = 14;\n\nexport const normalizeTalentPoints = (value: number): number =>\n Math.min(\n MAX_TALENT_POINTS,\n Math.max(0, Math.round(Number.isFinite(value) ? value : MAX_TALENT_POINTS)),\n );\n\nexport const normalizeSelectedTalentIds = (\n model: TalentCalculatorRenderModel,\n selectedTalentIds: readonly string[],\n): {\n readonly selectedTalentIds: readonly string[];\n readonly removedTalentIds: readonly string[];\n} => {\n const requested = new Set(selectedTalentIds);\n const selected = [...model.talentById.values()]\n .filter(({ talent }) => requested.has(talent.id))\n .map(({ talent }) => talent.id);\n const selectedSet = new Set(selected);\n const removed = [...new Set(selectedTalentIds)].filter((id) => !selectedSet.has(id));\n return { selectedTalentIds: selected, removedTalentIds: removed };\n};\n\nexport const createTalentCalculatorSelectionState = (\n model: TalentCalculatorRenderModel,\n selectedTalentIds: readonly string[],\n talentPoints: number,\n): TalentCalculatorSelectionState => {\n const selected = new Set(selectedTalentIds);\n const spentPoints = [...model.talentById.values()].reduce(\n (total, talent) =>\n selected.has(talent.talent.id) && talent.pointCost !== null\n ? total + talent.pointCost\n : total,\n 0,\n );\n const remainingPoints = talentPoints - spentPoints;\n return {\n selectedTalentIds: [...model.talentById.values()]\n .filter(({ talent }) => selected.has(talent.id))\n .map(({ talent }) => talent.id),\n spentPoints,\n remainingPoints,\n overBudget: remainingPoints < 0,\n };\n};\n\nexport const canSelectTalent = (\n pointCost: number | null,\n state: TalentCalculatorSelectionState,\n): boolean => pointCost !== null && !state.overBudget && pointCost <= state.remainingPoints;\n","import { css } from \"lit\";\n\nexport const talentCalculatorStyles = css`\n :host {\n --fellowship-talent-background: var(--_fellowship-field-canvas);\n --fellowship-talent-surface: var(--_fellowship-field-surface);\n --fellowship-talent-card-background: var(--_fellowship-field-surface);\n --fellowship-talent-card-selected-background: color-mix(\n in srgb,\n var(--_fellowship-field-surface-raised) 88%,\n var(--_fellowship-field-accent)\n );\n --fellowship-talent-foreground: var(--_fellowship-field-ink);\n --fellowship-talent-muted: var(--_fellowship-field-muted);\n --fellowship-talent-disabled: var(--_fellowship-field-tertiary);\n --fellowship-talent-border: var(--_fellowship-field-border);\n --fellowship-talent-selection: var(--_fellowship-field-accent);\n --fellowship-talent-focus: var(--_fellowship-field-focus);\n --fellowship-talent-points: var(--_fellowship-field-brass);\n --fellowship-talent-danger: var(--_fellowship-field-ember);\n --fellowship-talent-control-surface: var(--_fellowship-field-inset);\n --fellowship-talent-icon-size: 56px;\n --fellowship-talent-font-family: var(--_fellowship-field-font-family);\n --fellowship-talent-tactical-font-family: var(--_fellowship-field-mono-font-family);\n --fellowship-talent-radius: var(--_fellowship-field-radius);\n\n display: block;\n min-width: 0;\n container-type: inline-size;\n color: var(--fellowship-talent-foreground);\n font-family: var(--fellowship-talent-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 button {\n font: inherit;\n }\n\n .container {\n min-width: 0;\n padding: 16px;\n border-radius: calc(var(--fellowship-talent-radius) + 2px);\n background: var(--fellowship-talent-background);\n }\n\n .header {\n min-height: 72px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n padding: 8px 8px 24px;\n }\n\n .heading {\n margin: 0;\n color: var(--fellowship-talent-muted);\n font-size: 12px;\n font-weight: 600;\n line-height: 16px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n }\n\n .points-cluster,\n .point-controls {\n display: flex;\n align-items: center;\n }\n\n .points-cluster {\n gap: 16px;\n }\n\n .points {\n display: flex;\n align-items: baseline;\n gap: 4px;\n color: var(--fellowship-talent-foreground);\n font-family: var(--fellowship-talent-tactical-font-family);\n font-size: 32px;\n font-weight: 600;\n line-height: 40px;\n letter-spacing: -0.04em;\n font-variant-numeric: tabular-nums;\n }\n\n .points-spent {\n color: var(--fellowship-talent-points);\n }\n\n .points-separator,\n .points-total {\n color: var(--_fellowship-field-tertiary);\n }\n\n .points-separator {\n font-size: 18px;\n font-weight: 500;\n }\n\n .points[data-invalid=\"true\"],\n .points[data-invalid=\"true\"] .points-spent {\n color: var(--fellowship-talent-danger);\n }\n\n .point-controls {\n gap: 4px;\n padding: 2px;\n border-radius: calc(var(--fellowship-talent-radius) + 2px);\n background: var(--fellowship-talent-control-surface);\n box-shadow: inset 0 0 0 1px var(--fellowship-talent-border);\n }\n\n .point-control {\n width: 40px;\n height: 40px;\n display: inline-grid;\n place-items: center;\n padding: 0;\n border: 0;\n border-radius: 4px;\n background: transparent;\n color: var(--fellowship-talent-muted);\n font-family: var(--fellowship-talent-tactical-font-family);\n font-size: 18px;\n font-weight: 500;\n line-height: 1;\n cursor: pointer;\n transition:\n background-color 120ms cubic-bezier(0.23, 1, 0.32, 1),\n color 120ms cubic-bezier(0.23, 1, 0.32, 1),\n transform 120ms cubic-bezier(0.23, 1, 0.32, 1);\n }\n\n .point-control:active:not(:disabled) {\n transform: scale(0.97);\n }\n\n .point-control:focus-visible,\n .clear-all:focus-visible,\n .talent:focus-visible,\n .retry-button:focus-visible {\n outline: 2px solid var(--fellowship-talent-focus);\n outline-offset: 2px;\n }\n\n .point-control:disabled {\n color: var(--fellowship-talent-disabled);\n cursor: not-allowed;\n opacity: 0.48;\n }\n\n .clear-all {\n min-height: 44px;\n padding: 0 8px;\n border: 0;\n border-radius: 4px;\n background: transparent;\n color: var(--fellowship-talent-muted);\n font-size: 12px;\n font-weight: 600;\n line-height: 1;\n letter-spacing: 0.04em;\n white-space: nowrap;\n cursor: pointer;\n transition:\n background-color 120ms cubic-bezier(0.23, 1, 0.32, 1),\n color 120ms cubic-bezier(0.23, 1, 0.32, 1),\n transform 120ms cubic-bezier(0.23, 1, 0.32, 1);\n }\n\n .clear-all:active:not(:disabled) {\n transform: scale(0.97);\n }\n\n .clear-all:disabled {\n color: var(--fellowship-talent-disabled);\n cursor: not-allowed;\n opacity: 0.42;\n }\n\n .grid {\n display: grid;\n gap: 16px;\n padding: 8px 0 0;\n }\n\n .row {\n min-width: 0;\n min-inline-size: 0;\n width: 100%;\n display: grid;\n grid-template-columns: 48px minmax(0, 1fr);\n align-items: stretch;\n gap: 8px;\n margin: 0;\n padding: 0;\n border: 0;\n }\n\n .row-label {\n position: relative;\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding-top: 16px;\n color: var(--fellowship-talent-disabled);\n font-family: var(--fellowship-talent-tactical-font-family);\n font-size: 11px;\n font-weight: 600;\n line-height: 16px;\n text-align: center;\n letter-spacing: 0.08em;\n }\n\n .row-label::before {\n position: absolute;\n inset: 0 0 0 auto;\n width: 1px;\n background: var(--fellowship-talent-border);\n content: \"\";\n }\n\n .row-label::after {\n position: absolute;\n top: 23px;\n right: -3px;\n width: 7px;\n height: 1px;\n background: var(--fellowship-talent-selection);\n content: \"\";\n }\n\n .row-talents {\n min-width: 0;\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 8px;\n }\n\n fellowship-tooltip {\n display: block;\n min-width: 0;\n }\n\n .talent {\n position: relative;\n width: 100%;\n min-width: 0;\n min-height: 88px;\n display: grid;\n grid-template-columns: var(--fellowship-talent-icon-size) minmax(0, 1fr);\n align-items: center;\n gap: 16px;\n padding: 16px 36px 16px 16px;\n overflow: hidden;\n border: 1px solid transparent;\n border-radius: var(--fellowship-talent-radius);\n background: var(--fellowship-talent-card-background);\n color: var(--fellowship-talent-foreground);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fellowship-talent-border) 64%, transparent);\n text-align: left;\n cursor: pointer;\n transition:\n background-color 140ms cubic-bezier(0.23, 1, 0.32, 1),\n box-shadow 140ms cubic-bezier(0.23, 1, 0.32, 1),\n transform 120ms cubic-bezier(0.23, 1, 0.32, 1),\n opacity 140ms linear;\n }\n\n .talent::before {\n position: absolute;\n top: 16px;\n left: 0;\n width: 2px;\n height: 24px;\n background: transparent;\n content: \"\";\n }\n\n .talent:active:not([aria-disabled=\"true\"]) {\n transform: scale(0.99);\n }\n\n .talent[aria-pressed=\"true\"] {\n background: var(--fellowship-talent-card-selected-background);\n box-shadow: inset 0 0 0 1px\n color-mix(in srgb, var(--fellowship-talent-selection) 32%, transparent);\n }\n\n .talent[aria-pressed=\"true\"]::before {\n background: var(--fellowship-talent-selection);\n }\n\n .talent[aria-disabled=\"true\"] {\n color: var(--fellowship-talent-disabled);\n cursor: not-allowed;\n opacity: 0.46;\n }\n\n .talent-icon,\n .talent-icon-fallback {\n width: var(--fellowship-talent-icon-size);\n height: var(--fellowship-talent-icon-size);\n border-radius: calc(var(--fellowship-talent-radius) - 2px);\n box-shadow: inset 0 0 0 1px var(--fellowship-talent-border);\n }\n\n .talent-icon {\n display: block;\n object-fit: cover;\n }\n\n .talent-icon-fallback {\n display: grid;\n place-items: center;\n background: var(--fellowship-talent-control-surface);\n color: var(--fellowship-talent-muted);\n font-family: var(--fellowship-talent-tactical-font-family);\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.08em;\n }\n\n .talent[aria-pressed=\"true\"] .talent-icon,\n .talent[aria-pressed=\"true\"] .talent-icon-fallback {\n box-shadow:\n inset 0 0 0 1px var(--fellowship-talent-border),\n 0 0 0 1px color-mix(in srgb, var(--fellowship-talent-selection) 60%, transparent);\n }\n\n .talent-name {\n min-width: 0;\n overflow: hidden;\n color: currentColor;\n font-size: 16px;\n font-weight: 600;\n line-height: 24px;\n text-overflow: ellipsis;\n text-wrap: balance;\n white-space: nowrap;\n }\n\n .talent-cost {\n position: absolute;\n top: 12px;\n right: 12px;\n color: var(--fellowship-talent-muted);\n font-family: var(--fellowship-talent-tactical-font-family);\n font-size: 11px;\n font-weight: 600;\n line-height: 16px;\n font-variant-numeric: tabular-nums;\n }\n\n .talent[aria-pressed=\"true\"] .talent-cost {\n color: var(--fellowship-talent-focus);\n }\n\n .status {\n min-height: 256px;\n display: grid;\n place-items: center;\n padding: 32px;\n color: var(--fellowship-talent-muted);\n text-align: center;\n }\n\n .status-card {\n max-width: 480px;\n }\n\n .status-mark {\n display: flex;\n width: 40px;\n height: 24px;\n align-items: center;\n justify-content: space-between;\n margin: 0 auto 16px;\n color: var(--fellowship-talent-points);\n font-family: var(--fellowship-talent-tactical-font-family);\n font-size: 16px;\n }\n\n .status-mark::before,\n .status-mark::after {\n width: 8px;\n height: 1px;\n background: var(--fellowship-talent-border);\n content: \"\";\n }\n\n .status-title {\n margin: 0;\n color: var(--fellowship-talent-foreground);\n font-size: 18px;\n font-weight: 600;\n line-height: 24px;\n text-wrap: balance;\n }\n\n .status-copy {\n margin: 8px 0 0;\n font-size: 14px;\n line-height: 24px;\n text-wrap: pretty;\n }\n\n .retry-button {\n min-height: 44px;\n margin-top: 24px;\n padding: 0 16px;\n border: 1px solid var(--fellowship-talent-border);\n border-radius: 4px;\n background: var(--fellowship-talent-control-surface);\n color: var(--fellowship-talent-foreground);\n font-weight: 600;\n cursor: pointer;\n }\n\n .visually-hidden {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n @media (hover: hover) {\n .point-control:hover:not(:disabled) {\n background: var(--fellowship-talent-surface);\n color: var(--fellowship-talent-foreground);\n }\n\n .clear-all:hover:not(:disabled) {\n background: color-mix(in srgb, var(--fellowship-talent-danger) 8%, transparent);\n color: var(--fellowship-talent-danger);\n }\n\n .talent:hover:not([aria-disabled=\"true\"]) {\n background: var(--_fellowship-field-surface-raised);\n box-shadow: inset 0 0 0 1px var(--fellowship-talent-border);\n }\n }\n\n @container (max-width: 719px) {\n .container {\n padding: 16px 12px;\n }\n\n .header {\n min-height: 80px;\n grid-template-columns: 1fr;\n display: grid;\n gap: 8px;\n padding: 4px 4px 20px;\n }\n\n .heading {\n font-size: 11px;\n }\n\n .points-cluster {\n width: 100%;\n flex-wrap: wrap;\n justify-content: flex-start;\n gap: 8px;\n }\n\n .points {\n font-size: 28px;\n line-height: 36px;\n }\n\n .grid {\n gap: 24px;\n }\n\n .row {\n grid-template-columns: 1fr;\n gap: 8px;\n }\n\n .row-label {\n min-height: 24px;\n align-items: center;\n justify-content: flex-start;\n padding: 0 0 0 16px;\n text-align: left;\n }\n\n .row-label::before {\n inset: 12px auto auto 0;\n width: 8px;\n height: 1px;\n }\n\n .row-label::after {\n top: 9px;\n right: auto;\n left: 0;\n width: 1px;\n height: 7px;\n }\n\n .row-talents {\n grid-template-columns: 1fr;\n gap: 8px;\n }\n\n .talent {\n --fellowship-talent-icon-size: 52px;\n\n min-height: 80px;\n padding-block: 12px;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .point-control,\n .clear-all,\n .talent {\n transition:\n background-color 100ms linear,\n box-shadow 100ms linear,\n color 100ms linear,\n opacity 100ms linear;\n }\n\n .point-control:active:not(:disabled),\n .clear-all:active:not(:disabled),\n .talent:active:not([aria-disabled=\"true\"]) {\n transform: none;\n }\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 { assertSupportedFellowshipDataSchema, FellowshipDataError } from \"../data/data-source.js\";\nimport { createPackageFellowshipTalentDataSource } from \"../data/package-talent-data-source.js\";\nimport type { FellowshipTalentDataSource } from \"../data/talent-data-source.js\";\nimport { fieldManualStyles } from \"../styles/field-manual.styles.js\";\nimport { installFieldManualFonts } from \"../styles/field-manual-fonts.js\";\nimport { stripFellowshipRichText } from \"../tooltip/rich-text.js\";\nimport { defineFellowshipTooltip } from \"../tooltip/tooltip.js\";\nimport { createTalentCalculatorRenderModel } from \"./render-model.js\";\nimport {\n canSelectTalent,\n createTalentCalculatorSelectionState,\n MAX_TALENT_POINTS,\n normalizeSelectedTalentIds,\n normalizeTalentPoints,\n} from \"./selection-state.js\";\nimport { talentCalculatorStyles } from \"./talent-calculator.styles.js\";\nimport type {\n FellowshipTalentsErrorDetail,\n FellowshipTalentsReadyDetail,\n FellowshipTalentsWarningDetail,\n LitTalentRenderer,\n TalentCalculatorRenderModel,\n TalentCalculatorSelectionState,\n TalentCalculatorTalentModel,\n TalentCalculatorTalentRenderContext,\n TalentCalculatorWarning,\n TalentPointsChangeDetail,\n TalentPointsChangeSource,\n TalentSelectionChangeDetail,\n TalentSelectionChangeSource,\n} from \"./types.js\";\n\ntype ComponentStatus = \"empty\" | \"loading\" | \"ready\" | \"error\";\n\ninterface TalentPresentation {\n readonly context: TalentCalculatorTalentRenderContext;\n readonly name: string;\n readonly descriptionId: string;\n readonly affordability: string;\n readonly description: string;\n}\n\nconst eventOptions = { bubbles: true, composed: true } as const;\n\nconst stringArrayChanged = (\n value: readonly string[],\n previous: readonly string[] | undefined,\n): boolean =>\n value.length !== previous?.length || value.some((item, index) => item !== previous[index]);\n\nexport const talentSlotName = (talentId: string): string => `talent:${talentId}`;\n\nconst fallbackSelectionState = (talentPoints: number): TalentCalculatorSelectionState => ({\n selectedTalentIds: [],\n spentPoints: 0,\n remainingPoints: talentPoints,\n overBudget: false,\n});\n\nexport { MAX_TALENT_POINTS };\n\n/**\n * @fires fellowship-talents-ready - Fired after talent data is loaded and rendered.\n * @fires fellowship-talents-error - Fired when talent data cannot be loaded.\n * @fires fellowship-talents-warning - Fired for recoverable data and renderer issues.\n * @fires fellowship-talent-selection-change - Fired when selected talents change.\n * @fires fellowship-talent-points-change - Fired when the point budget changes.\n */\n@customElement(\"fellowship-talent-calculator\")\nexport class FellowshipTalentCalculatorElement extends LitElement {\n static override styles = [fieldManualStyles, talentCalculatorStyles];\n\n @property({ type: String, attribute: \"hero-id\" }) heroId = \"\";\n @property({ type: Number, attribute: \"talent-points\" }) talentPoints = MAX_TALENT_POINTS;\n @property({ attribute: false, hasChanged: stringArrayChanged })\n selectedTalentIds: readonly string[] = [];\n @property({ type: String, attribute: \"asset-base-url\" }) assetBaseUrl: string | null = null;\n @property({ attribute: false }) dataSource: FellowshipTalentDataSource | null = null;\n @property({ attribute: false }) renderTalent: LitTalentRenderer | null = null;\n @property({ type: Boolean, attribute: \"show-point-controls\" }) showPointControls = true;\n @property({ type: Boolean, attribute: \"show-tooltips\" }) showTooltips = true;\n\n @state() private _status: ComponentStatus = \"empty\";\n @state() private _error: FellowshipTalentsErrorDetail | null = null;\n @state() private _model: TalentCalculatorRenderModel | null = null;\n @state() private _failedImages = new Set<string>();\n @state() private _announcement = \"\";\n @state() private _renderSelectionState = fallbackSelectionState(MAX_TALENT_POINTS);\n @state() private _talentPresentation = new Map<string, TalentPresentation>();\n @state() private _talentRenderContent = new Map<string, unknown>();\n\n #abortController: AbortController | null = null;\n #loadToken = 0;\n #lastOverBudgetSignature: string | null = null;\n #skipNextPropertyPointEvent = false;\n #rendererFailures = new Set<string>();\n #pendingRendererWarnings: TalentCalculatorWarning[] = [];\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 this.#flushRendererWarnings();\n\n if (changed.has(\"heroId\") || changed.has(\"dataSource\") || changed.has(\"assetBaseUrl\")) {\n queueMicrotask(() => {\n if (this.isConnected) {\n void this.#loadHeroTalents();\n }\n });\n return;\n }\n\n if (changed.has(\"talentPoints\")) {\n const normalized = normalizeTalentPoints(this.talentPoints);\n if (normalized !== this.talentPoints) {\n this.#skipNextPropertyPointEvent = true;\n this.talentPoints = normalized;\n this.#emitPointsChange(\"normalization\");\n return;\n }\n if (this.#skipNextPropertyPointEvent) {\n this.#skipNextPropertyPointEvent = false;\n } else if (changed.get(\"talentPoints\") !== undefined) {\n this.#emitPointsChange(\"property\");\n }\n }\n\n if (changed.has(\"selectedTalentIds\") && this._model) {\n this.#normalizeSelections(true);\n }\n\n this.#reportOverBudget();\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n super.willUpdate(changed);\n\n const selectionChanged =\n changed.has(\"_model\") || changed.has(\"selectedTalentIds\") || changed.has(\"talentPoints\");\n if (selectionChanged) {\n this._renderSelectionState = this._model\n ? createTalentCalculatorSelectionState(\n this._model,\n this.selectedTalentIds,\n this.talentPoints,\n )\n : fallbackSelectionState(this.talentPoints);\n }\n\n if (changed.has(\"renderTalent\")) {\n this.#rendererFailures.clear();\n }\n if (selectionChanged || changed.has(\"renderTalent\")) {\n this.#prepareTalentRenderContent(this._renderSelectionState);\n }\n }\n\n get renderModel(): TalentCalculatorRenderModel | null {\n return this._model;\n }\n\n get selectionState(): TalentCalculatorSelectionState {\n return this._model\n ? createTalentCalculatorSelectionState(this._model, this.selectedTalentIds, this.talentPoints)\n : fallbackSelectionState(this.talentPoints);\n }\n\n reset(): void {\n if (this.selectedTalentIds.length === 0) {\n return;\n }\n this.selectedTalentIds = [];\n this.#emitSelectionChange(\"reset\");\n }\n\n selectTalent(talentId: string): boolean {\n const model = this._model?.talentById.get(talentId);\n const state = this.selectionState;\n if (\n !model ||\n state.selectedTalentIds.includes(talentId) ||\n !canSelectTalent(model.pointCost, state)\n ) {\n return false;\n }\n this.selectedTalentIds = [...state.selectedTalentIds, talentId];\n this.#emitSelectionChange(\"api\");\n return true;\n }\n\n deselectTalent(talentId: string): boolean {\n if (!this.selectionState.selectedTalentIds.includes(talentId)) {\n return false;\n }\n this.selectedTalentIds = this.selectionState.selectedTalentIds.filter((id) => id !== talentId);\n this.#emitSelectionChange(\"api\");\n return true;\n }\n\n retry(): void {\n void this.#loadHeroTalents();\n }\n\n override render(): TemplateResult {\n const state = this._renderSelectionState;\n const label = this._model?.hero.name ?? this._model?.hero.id ?? \"Fellowship hero\";\n return html`\n <section\n class=\"container\"\n part=\"container\"\n aria-label=${`${label} talent calculator`}\n aria-invalid=${String(state.overBudget)}\n >\n ${this.#renderHeader(state)}\n ${\n this._status === \"ready\" && this._model\n ? this.#renderGrid(this._model)\n : this.#renderStatus()\n }\n <p class=\"visually-hidden\" role=\"status\" aria-live=\"polite\">${this._announcement}</p>\n </section>\n `;\n }\n\n #renderHeader(state: TalentCalculatorSelectionState): TemplateResult {\n return html`\n <header class=\"header\" part=\"header\">\n <h2 class=\"heading\" part=\"heading\">Talent points</h2>\n <div class=\"points-cluster\">\n <div\n class=\"points\"\n part=\"points\"\n data-invalid=${String(state.overBudget)}\n aria-label=${`${state.spentPoints} of ${this.talentPoints} talent points spent`}\n aria-live=\"polite\"\n >\n <span class=\"points-spent\" part=\"points-spent\">${state.spentPoints}</span>\n <span class=\"points-separator\" aria-hidden=\"true\">/</span>\n <span class=\"points-total\" part=\"points-total\">${this.talentPoints}</span>\n </div>\n ${\n this.showPointControls\n ? html`\n <div\n class=\"point-controls\"\n part=\"point-controls\"\n role=\"group\"\n aria-label=\"Talent points\"\n >\n <button\n class=\"point-control\"\n part=\"point-control\"\n type=\"button\"\n aria-label=\"Decrease available talent points\"\n title=\"Decrease available talent points\"\n ?disabled=${this.talentPoints <= state.spentPoints}\n @click=${() => this.#setTalentPoints(this.talentPoints - 1, \"decrement\")}\n >\n −\n </button>\n <button\n class=\"point-control\"\n part=\"point-control\"\n type=\"button\"\n aria-label=\"Increase available talent points\"\n title=\"Increase available talent points\"\n ?disabled=${this.talentPoints >= MAX_TALENT_POINTS}\n @click=${() => this.#setTalentPoints(this.talentPoints + 1, \"increment\")}\n >\n +\n </button>\n </div>\n `\n : nothing\n }\n <button\n class=\"clear-all\"\n part=\"clear-all\"\n type=\"button\"\n ?disabled=${state.selectedTalentIds.length === 0}\n @click=${this.reset}\n >\n Clear All\n </button>\n </div>\n </header>\n `;\n }\n\n #renderGrid(model: TalentCalculatorRenderModel): TemplateResult {\n if (model.rows.length === 0) {\n return html`\n <div class=\"status\" part=\"empty-state\" role=\"status\">\n <div class=\"status-card\">\n <div class=\"status-mark\" aria-hidden=\"true\">◇</div>\n <p class=\"status-title\">No active talents</p>\n <p class=\"status-copy\">\n ${model.hero.name ?? model.hero.id} has no positioned talents.\n </p>\n </div>\n </div>\n `;\n }\n\n return html`\n <div class=\"grid\" part=\"grid\" role=\"group\" aria-label=\"Talent tiers\">\n ${repeat(\n model.rows,\n (row) => row.row,\n (row) => html`\n <fieldset class=\"row\" part=\"row\">\n <legend class=\"visually-hidden\">Talent tier ${row.label}</legend>\n <span class=\"row-label\" part=\"row-label\" aria-hidden=\"true\">${row.label}</span>\n <div class=\"row-talents\">\n ${repeat(\n row.talents,\n (talent) => talent.talent.id,\n (talent) => this.#renderTalent(talent),\n )}\n </div>\n </fieldset>\n `,\n )}\n </div>\n `;\n }\n\n #renderTalent(talent: TalentCalculatorTalentModel): TemplateResult | typeof nothing {\n const presentation = this._talentPresentation.get(talent.talent.id);\n if (!presentation) {\n return nothing;\n }\n const { context, name, descriptionId, affordability, description } = presentation;\n const { selected, disabled } = context;\n const card = html`\n <button\n class=\"talent\"\n part=\"talent\"\n type=\"button\"\n aria-label=${`${name}, ${affordability}`}\n aria-describedby=${descriptionId}\n aria-pressed=${String(selected)}\n aria-disabled=${String(disabled)}\n @click=${() => this.#handleTalentActivation(talent, selected, disabled)}\n >\n ${this.#renderTalentContent(context)}\n <span class=\"talent-cost\" part=\"talent-cost\">${talent.pointCost ?? \"–\"}</span>\n </button>\n `;\n\n return html`\n ${\n this.showTooltips && (talent.talent.description || disabled)\n ? html`\n <fellowship-tooltip\n .heading=${name}\n .text=${talent.talent.description ?? affordability}\n >\n ${card}\n </fellowship-tooltip>\n `\n : card\n }\n <span class=\"visually-hidden\" id=${descriptionId}>${description}</span>\n `;\n }\n\n #prepareTalentRenderContent(state: TalentCalculatorSelectionState): void {\n const renderer = this.renderTalent;\n const model = this._model;\n const content = new Map<string, unknown>();\n const presentations = new Map<string, TalentPresentation>();\n\n if (model) {\n for (const talent of model.talentById.values()) {\n const selected = state.selectedTalentIds.includes(talent.talent.id);\n const affordable = canSelectTalent(talent.pointCost, state);\n const disabled = !selected && !affordable;\n const name = talent.talent.name ?? talent.talent.id;\n const affordability =\n talent.pointCost === null\n ? \"This talent has no valid point cost.\"\n : disabled\n ? `Requires ${talent.pointCost} talent points; ${Math.max(0, state.remainingPoints)} remaining.`\n : `${talent.pointCost} talent ${talent.pointCost === 1 ? \"point\" : \"points\"}.`;\n const context: TalentCalculatorTalentRenderContext = {\n talent,\n hero: model.hero,\n selected,\n affordable,\n disabled,\n spentPoints: state.spentPoints,\n remainingPoints: state.remainingPoints,\n talentPoints: this.talentPoints,\n };\n presentations.set(talent.talent.id, {\n context,\n name,\n descriptionId: `talent-description-${talent.row}-${talent.column}`,\n affordability,\n description: [stripFellowshipRichText(talent.talent.description ?? \"\"), affordability]\n .filter(Boolean)\n .join(\" \"),\n });\n if (!renderer) {\n continue;\n }\n try {\n content.set(talent.talent.id, renderer(context));\n } catch (error: unknown) {\n if (!this.#rendererFailures.has(talent.talent.id)) {\n this.#rendererFailures.add(talent.talent.id);\n this.#pendingRendererWarnings.push({\n code: \"renderer-error\",\n message: `The talent renderer failed for “${talent.talent.id}”: ${String(error)}.`,\n talentId: talent.talent.id,\n });\n }\n }\n }\n }\n\n this._talentPresentation = presentations;\n this._talentRenderContent = content;\n }\n\n #renderTalentContent(context: TalentCalculatorTalentRenderContext): unknown {\n const talent = context.talent;\n const name = talent.talent.name ?? talent.talent.id;\n const content =\n this.renderTalent && this._talentRenderContent.has(talent.talent.id)\n ? this._talentRenderContent.get(talent.talent.id)\n : html`\n <slot name=${talentSlotName(talent.talent.id)}>\n <span class=\"talent-name\" part=\"talent-name\" title=${name}>${name}</span>\n </slot>\n `;\n\n return html`\n ${this.#renderDefaultTalentIcon(talent, name)} ${content}\n `;\n }\n\n #renderDefaultTalentIcon(talent: TalentCalculatorTalentModel, name: string): TemplateResult {\n if (talent.imageUrl && !this._failedImages.has(talent.talent.id)) {\n return html`\n <img\n class=\"talent-icon\"\n part=\"talent-icon\"\n src=${talent.imageUrl}\n alt=\"\"\n draggable=\"false\"\n @error=${() => this.#handleImageError(talent)}\n />\n `;\n }\n const rune = name\n .split(/\\s+/)\n .map((part) => part[0])\n .join(\"\")\n .slice(0, 2)\n .toUpperCase();\n return html`<span class=\"talent-icon-fallback\" part=\"talent-icon-fallback\">${rune}</span>`;\n }\n\n #renderStatus(): TemplateResult {\n const loading = this._status === \"loading\";\n const empty = this._status === \"empty\";\n const title = loading\n ? \"Reading talent codex\"\n : empty\n ? \"Choose a hero\"\n : \"Talents unavailable\";\n const copy = loading\n ? \"Loading the hero and their available talents.\"\n : empty\n ? \"Set the hero-id attribute or heroId property to build a talent loadout.\"\n : (this._error?.message ?? \"An unexpected talent data error occurred.\");\n return html`\n <div\n class=\"status\"\n part=${this._status === \"error\" ? \"error-state\" : \"status\"}\n role=${this._status === \"error\" ? \"alert\" : \"status\"}\n >\n <div class=\"status-card\">\n <div class=\"status-mark\" aria-hidden=\"true\">${loading ? \"…\" : \"✦\"}</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 async #loadHeroTalents(): Promise<void> {\n const heroId = this.heroId.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._failedImages = new Set();\n this.#rendererFailures.clear();\n this.#lastOverBudgetSignature = null;\n\n if (!heroId) {\n this._status = \"empty\";\n return;\n }\n\n this._status = \"loading\";\n const source =\n this.dataSource ??\n createPackageFellowshipTalentDataSource(\n this.assetBaseUrl ? { assetBaseUrl: this.assetBaseUrl } : {},\n );\n\n try {\n const data = await source.loadHeroTalents(heroId, this.#abortController.signal);\n if (token !== this.#loadToken || this.#abortController.signal.aborted) {\n return;\n }\n assertSupportedFellowshipDataSchema(data.manifest.schemaVersion);\n const model = createTalentCalculatorRenderModel(data, source);\n this._model = model;\n this._status = \"ready\";\n const normalized = this.#normalizeSelections(false);\n await this.updateComplete;\n const selection = this.selectionState;\n this.dispatchEvent(\n new CustomEvent<FellowshipTalentsReadyDetail>(\"fellowship-talents-ready\", {\n ...eventOptions,\n detail: { data, model, selection },\n }),\n );\n if (model.warnings.length > 0) {\n this.#reportWarnings(model.warnings);\n }\n if (normalized) {\n this.#emitSelectionChange(\"normalization\");\n }\n this.#reportOverBudget();\n } catch (error: unknown) {\n if (token !== this.#loadToken || this.#abortController.signal.aborted) {\n return;\n }\n this.#setError(error);\n }\n }\n\n #normalizeSelections(emit: boolean): boolean {\n if (!this._model) {\n return false;\n }\n const normalized = normalizeSelectedTalentIds(this._model, this.selectedTalentIds);\n const changed =\n normalized.selectedTalentIds.length !== this.selectedTalentIds.length ||\n normalized.selectedTalentIds.some((id, index) => id !== this.selectedTalentIds[index]);\n if (!changed) {\n return false;\n }\n this.selectedTalentIds = normalized.selectedTalentIds;\n if (normalized.removedTalentIds.length > 0) {\n this.#reportWarnings([\n {\n code: \"invalid-selection\",\n message: `Removed unavailable talent selections: ${normalized.removedTalentIds.join(\", \")}.`,\n talentId: null,\n },\n ]);\n }\n if (emit) {\n this.#emitSelectionChange(\"normalization\");\n }\n return true;\n }\n\n #handleTalentActivation(\n talent: TalentCalculatorTalentModel,\n selected: boolean,\n disabled: boolean,\n ): void {\n if (selected) {\n this.selectedTalentIds = this.selectionState.selectedTalentIds.filter(\n (id) => id !== talent.talent.id,\n );\n this.#emitSelectionChange(\"user\");\n return;\n }\n if (disabled) {\n this._announcement = this.selectionState.overBudget\n ? \"The current build exceeds the available talent points. Deselect a talent first.\"\n : `Not enough points for ${talent.talent.name ?? talent.talent.id}.`;\n return;\n }\n this.selectedTalentIds = [...this.selectionState.selectedTalentIds, talent.talent.id];\n this.#emitSelectionChange(\"user\");\n }\n\n #setTalentPoints(value: number, source: TalentPointsChangeSource): void {\n const normalized = normalizeTalentPoints(value);\n const state = this.selectionState;\n if (normalized === this.talentPoints || normalized < state.spentPoints) {\n return;\n }\n this.talentPoints = normalized;\n this.#skipNextPropertyPointEvent = true;\n this.#emitPointsChange(source);\n }\n\n #emitSelectionChange(source: TalentSelectionChangeSource): void {\n const state = this._model\n ? createTalentCalculatorSelectionState(this._model, this.selectedTalentIds, this.talentPoints)\n : fallbackSelectionState(this.talentPoints);\n this.dispatchEvent(\n new CustomEvent<TalentSelectionChangeDetail>(\"fellowship-talent-selection-change\", {\n ...eventOptions,\n detail: { ...state, source },\n }),\n );\n }\n\n #emitPointsChange(source: TalentPointsChangeSource): void {\n const state = this.selectionState;\n this.dispatchEvent(\n new CustomEvent<TalentPointsChangeDetail>(\"fellowship-talent-points-change\", {\n ...eventOptions,\n detail: { ...state, talentPoints: this.talentPoints, source },\n }),\n );\n }\n\n #reportOverBudget(): void {\n const state = this.selectionState;\n if (!state.overBudget) {\n this.#lastOverBudgetSignature = null;\n this.removeAttribute(\"aria-invalid\");\n return;\n }\n this.setAttribute(\"aria-invalid\", \"true\");\n const signature = `${this.talentPoints}:${state.selectedTalentIds.join(\",\")}`;\n if (signature === this.#lastOverBudgetSignature) {\n return;\n }\n this.#lastOverBudgetSignature = signature;\n this.#reportWarnings([\n {\n code: \"over-budget\",\n message: `The selected talents cost ${state.spentPoints} points, exceeding the ${this.talentPoints}-point budget.`,\n talentId: null,\n },\n ]);\n }\n\n #handleImageError(talent: TalentCalculatorTalentModel): void {\n if (this._failedImages.has(talent.talent.id)) {\n return;\n }\n this._failedImages = new Set(this._failedImages).add(talent.talent.id);\n this.#reportWarnings([\n {\n code: \"asset-image-error\",\n message: `The icon for talent “${talent.talent.id}” could not be loaded.`,\n talentId: talent.talent.id,\n },\n ]);\n }\n\n #reportWarnings(warnings: readonly TalentCalculatorWarning[]): void {\n this.dispatchEvent(\n new CustomEvent<FellowshipTalentsWarningDetail>(\"fellowship-talents-warning\", {\n ...eventOptions,\n detail: { warnings, model: this._model },\n }),\n );\n }\n\n #flushRendererWarnings(): void {\n if (this.#pendingRendererWarnings.length === 0) {\n return;\n }\n const warnings = this.#pendingRendererWarnings;\n this.#pendingRendererWarnings = [];\n this.#reportWarnings(warnings);\n }\n\n #setError(error: unknown): void {\n const detail: FellowshipTalentsErrorDetail =\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<FellowshipTalentsErrorDetail>(\"fellowship-talents-error\", {\n ...eventOptions,\n detail,\n }),\n );\n }\n}\n\nexport const defineFellowshipTalentCalculator = (): void => {\n defineFellowshipTooltip();\n if (!customElements.get(\"fellowship-talent-calculator\")) {\n customElements.define(\"fellowship-talent-calculator\", FellowshipTalentCalculatorElement);\n }\n};\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"fellowship-talent-calculator\": FellowshipTalentCalculatorElement;\n }\n}\n"],"mappings":"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"}
|