@crockery/fellowship-components 0.4.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +38 -22
  2. package/RELEASING.md +2 -2
  3. package/THIRD_PARTY_LICENSES.md +12 -0
  4. package/dist/assets/dm-mono-latin-400-normal-4GdczIuU.woff2 +0 -0
  5. package/dist/assets/dm-mono-latin-500-normal-DRMDZjhP.woff2 +0 -0
  6. package/dist/assets/syne-latin-700-normal-AF3Rs61n.woff2 +0 -0
  7. package/dist/assets/syne-latin-800-normal-EehdNYzx.woff2 +0 -0
  8. package/dist/chunks/decorate-DyMgX_Uh.js +9 -0
  9. package/dist/chunks/{dungeon-cast-browser-CSnu3gC5.js → dungeon-cast-browser-DdG8klca.js} +62 -58
  10. package/dist/chunks/{dungeon-map-CutIcCVP.js → dungeon-map-D0-DAjZ9.js} +130 -131
  11. package/dist/chunks/{decorate-B_ZTN9li.js → field-manual-fonts-DTFloVFe.js} +18 -23
  12. package/dist/chunks/gear-planner-uQOpm4QI.js +1701 -0
  13. package/dist/chunks/{talent-calculator-Dxuc2dZ8.js → talent-calculator-DbU6P1AP.js} +36 -35
  14. package/dist/chunks/tooltip-C3SSJw-t.js +482 -0
  15. package/dist/data/cast-data-source.d.ts +1 -1
  16. package/dist/data/data-source.d.ts +1 -1
  17. package/dist/data/package-cast-data-source.d.ts +1 -5
  18. package/dist/data/package-data-source.d.ts +1 -5
  19. package/dist/data/package-talent-data-source.d.ts +1 -5
  20. package/dist/data/talent-data-source.d.ts +1 -1
  21. package/dist/dungeon-cast-browser/dungeon-cast-browser.d.ts +2 -1
  22. package/dist/dungeon-cast-browser/render-model.d.ts +3 -2
  23. package/dist/dungeon-cast-browser/types.d.ts +2 -2
  24. package/dist/dungeon-cast-browser.js +1 -1
  25. package/dist/dungeon-map/dungeon-map.d.ts +2 -1
  26. package/dist/dungeon-map/render-model.d.ts +3 -3
  27. package/dist/dungeon-map/types.d.ts +2 -2
  28. package/dist/dungeon-map.js +1 -1
  29. package/dist/gear-planner/gear-planner.d.ts +36 -0
  30. package/dist/gear-planner/gear-planner.styles.d.ts +1 -0
  31. package/dist/gear-planner/gear-position.d.ts +20 -0
  32. package/dist/gear-planner/index.d.ts +2 -0
  33. package/dist/gear-planner/item-workbench.d.ts +18 -0
  34. package/dist/gear-planner/item-workbench.styles.d.ts +1 -0
  35. package/dist/gear-planner/types.d.ts +24 -0
  36. package/dist/gear-planner.js +5 -0
  37. package/dist/index.d.ts +6 -5
  38. package/dist/index.js +9 -7
  39. package/dist/react/dungeon-cast-browser.d.ts +8 -3
  40. package/dist/react/dungeon-cast-browser.js +11 -4
  41. package/dist/react/dungeon-map.d.ts +3 -1
  42. package/dist/react/dungeon-map.js +1 -1
  43. package/dist/react/gear-planner.d.ts +14 -0
  44. package/dist/react/gear-planner.js +23 -0
  45. package/dist/react/index.d.ts +2 -0
  46. package/dist/react/talent-calculator.d.ts +3 -1
  47. package/dist/react/talent-calculator.js +1 -1
  48. package/dist/react/tooltip.js +1 -1
  49. package/dist/react.js +2 -1
  50. package/dist/styles/dungeon-poster-fonts.d.ts +2 -0
  51. package/dist/styles/dungeon-poster.styles.d.ts +7 -0
  52. package/dist/talent-calculator/render-model.d.ts +3 -2
  53. package/dist/talent-calculator/talent-calculator.d.ts +2 -1
  54. package/dist/talent-calculator/types.d.ts +2 -2
  55. package/dist/talent-calculator.js +1 -1
  56. package/dist/tooltip.js +3 -3
  57. package/package.json +15 -4
  58. package/src/dungeon-cast-browser/README.md +10 -4
  59. package/src/dungeon-map/README.md +5 -6
  60. package/src/gear-planner/README.md +44 -0
  61. package/src/talent-calculator/README.md +5 -6
  62. package/src/tooltip/README.md +5 -6
  63. package/dist/chunks/data-source-XbksqUrw.js +0 -7
  64. package/dist/chunks/tooltip-DoQVSnOn.js +0 -410
@@ -0,0 +1,1701 @@
1
+ import { t as e } from "./decorate-DyMgX_Uh.js";
2
+ import { a as t, i as n, n as r } from "./tooltip-C3SSJw-t.js";
3
+ import { LitElement as i, css as a, html as o, nothing as s } from "lit";
4
+ import { customElement as c, property as l, query as u, state as d } from "lit/decorators.js";
5
+ import { repeat as f } from "lit/directives/repeat.js";
6
+ import { findImage as p } from "@crockery/fellowship-data";
7
+ import { calculateGearPlannerBuild as m, getValidGearConfiguration as h, getValidGearItems as g, normalizeGearPlannerBuild as _, resolveGearPlannerItemName as v, validateGearPlannerBuild as y } from "@crockery/fellowship-data/gear-planner";
8
+ //#region src/gear-planner/gear-position.ts
9
+ r();
10
+ var b = class extends i {
11
+ constructor(...e) {
12
+ super(...e), this.position = null, this.item = null, this.itemBuild = null, this.itemName = null, this.selected = !1, this.imageUrl = null, this.tooltipDetails = [], this.#e = (e) => {
13
+ this.dispatchEvent(new CustomEvent("fellowship-gear-position-select", {
14
+ bubbles: !0,
15
+ composed: !0,
16
+ detail: {
17
+ positionId: this.position?.id,
18
+ trigger: e.currentTarget
19
+ }
20
+ }));
21
+ }, this.#t = (e) => {
22
+ e.currentTarget.hidden = !0;
23
+ };
24
+ }
25
+ static {
26
+ this.styles = a`
27
+ * {
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ :host {
32
+ display: block;
33
+ min-width: 0;
34
+ }
35
+
36
+ fellowship-tooltip {
37
+ display: block;
38
+ }
39
+
40
+ button {
41
+ position: relative;
42
+ display: grid;
43
+ grid-template-columns: 52px minmax(0, 1fr);
44
+ align-items: center;
45
+ width: 100%;
46
+ min-height: 68px;
47
+ padding: 6px;
48
+ border: 2px solid var(--_fellowship-poster-plum);
49
+ border-radius: var(--_fellowship-poster-radius);
50
+ background: var(--_fellowship-poster-cream);
51
+ color: var(--_fellowship-poster-plum);
52
+ font: 500 12px/1.3 var(--_fellowship-poster-mono);
53
+ text-align: left;
54
+ cursor: pointer;
55
+ transition:
56
+ transform var(--_fellowship-poster-motion) var(--_fellowship-poster-ease),
57
+ background var(--_fellowship-poster-motion) var(--_fellowship-poster-ease);
58
+ }
59
+
60
+ button:hover {
61
+ background: var(--_fellowship-poster-white);
62
+ transform: translate(-1px, -1px);
63
+ }
64
+
65
+ button[aria-pressed="true"] {
66
+ background: var(--_fellowship-poster-yellow);
67
+ box-shadow: 3px 3px 0 var(--_fellowship-poster-violet);
68
+ }
69
+
70
+ button:focus-visible {
71
+ outline: 4px solid var(--_fellowship-poster-focus);
72
+ outline-offset: 2px;
73
+ }
74
+
75
+ .image,
76
+ img {
77
+ width: 44px;
78
+ height: 44px;
79
+ }
80
+
81
+ .image {
82
+ display: grid;
83
+ place-items: center;
84
+ overflow: hidden;
85
+ border: 2px solid currentColor;
86
+ background: var(--_fellowship-poster-plum);
87
+ color: var(--_fellowship-poster-cream);
88
+ font: 800 14px/1 var(--_fellowship-poster-display);
89
+ }
90
+
91
+ img {
92
+ display: block;
93
+ object-fit: cover;
94
+ }
95
+
96
+ .copy {
97
+ min-width: 0;
98
+ padding-inline: 4px;
99
+ }
100
+
101
+ .slot {
102
+ display: block;
103
+ margin-bottom: 3px;
104
+ font-size: 10px;
105
+ letter-spacing: 0.08em;
106
+ text-transform: uppercase;
107
+ }
108
+
109
+ .name {
110
+ display: block;
111
+ overflow-wrap: anywhere;
112
+ font-family: var(--_fellowship-poster-display);
113
+ font-size: 13px;
114
+ font-weight: 800;
115
+ line-height: 1.1;
116
+ }
117
+
118
+ .meta {
119
+ display: block;
120
+ margin-top: 4px;
121
+ font-size: 10px;
122
+ }
123
+
124
+ .tooltip-content {
125
+ display: grid;
126
+ gap: 6px;
127
+ max-width: 310px;
128
+ color: var(--fellowship-tooltip-ink);
129
+ font: 400 12px/1.45 var(--_fellowship-poster-mono);
130
+ }
131
+
132
+ .tooltip-content strong {
133
+ color: var(--_fellowship-poster-yellow);
134
+ font-family: var(--_fellowship-poster-display);
135
+ font-size: 15px;
136
+ }
137
+
138
+ @media (prefers-reduced-motion: reduce) {
139
+ button {
140
+ transition: none;
141
+ }
142
+ }
143
+ `;
144
+ }
145
+ render() {
146
+ if (!this.position) return o``;
147
+ let e = this.itemName ?? this.item?.item.name ?? this.item?.item.id ?? "Choose item", t = this.itemBuild ? `${e}. Level ${this.itemBuild.itemLevel}, ${this.itemBuild.rarity}.` : `${this.position.label}. Empty.`, n = o`
148
+ <button
149
+ type="button"
150
+ aria-pressed=${String(this.selected)}
151
+ aria-label=${`${this.position.label}: ${t}`}
152
+ @click=${this.#e}
153
+ >
154
+ <span class="image">
155
+ ${this.imageUrl ? o`<img src=${this.imageUrl} alt="" @error=${this.#t} />` : this.position.label.slice(0, 2)}
156
+ </span>
157
+ <span class="copy">
158
+ <span class="slot">${this.position.label}</span>
159
+ <span class="name">${e}</span>
160
+ ${this.itemBuild ? o`<span class="meta">LV ${this.itemBuild.itemLevel} · ${this.itemBuild.rarity}</span>` : s}
161
+ </span>
162
+ </button>
163
+ `;
164
+ return !this.item || !this.itemBuild ? n : o`
165
+ <fellowship-tooltip .text=${t} placement="auto">
166
+ ${n}
167
+ <span slot="content" class="tooltip-content">
168
+ <strong>${e}</strong>
169
+ <span>Level ${this.itemBuild.itemLevel} · ${this.itemBuild.rarity}</span>
170
+ <span>
171
+ ${this.itemBuild.rolledModifiers.length} rolls · ${this.itemBuild.gems.length} gems ·
172
+ ${this.itemBuild.traits.length} traits · ${this.itemBuild.blessings.length} blessings
173
+ </span>
174
+ ${this.tooltipDetails.map((e) => o`<span>${e}</span>`)}
175
+ ${this.item.item.description ? o`<span>${this.item.item.description}</span>` : s}
176
+ </span>
177
+ </fellowship-tooltip>
178
+ `;
179
+ }
180
+ #e;
181
+ #t;
182
+ };
183
+ e([l({ attribute: !1 })], b.prototype, "position", void 0), e([l({ attribute: !1 })], b.prototype, "item", void 0), e([l({ attribute: !1 })], b.prototype, "itemBuild", void 0), e([l({ type: String })], b.prototype, "itemName", void 0), e([l({
184
+ type: Boolean,
185
+ reflect: !0
186
+ })], b.prototype, "selected", void 0), e([l({ type: String })], b.prototype, "imageUrl", void 0), e([l({ attribute: !1 })], b.prototype, "tooltipDetails", void 0), b = e([c("fellowship-gear-position")], b);
187
+ var x = () => {
188
+ customElements.get("fellowship-gear-position") || customElements.define("fellowship-gear-position", b);
189
+ }, S = a`
190
+ :host {
191
+ display: block;
192
+ min-width: 0;
193
+ color: var(--_fellowship-poster-plum);
194
+ font: 400 12px/1.5 var(--_fellowship-poster-mono);
195
+ }
196
+
197
+ * {
198
+ box-sizing: border-box;
199
+ }
200
+
201
+ .workbench-header {
202
+ display: flex;
203
+ align-items: end;
204
+ justify-content: space-between;
205
+ gap: 16px;
206
+ padding-bottom: 12px;
207
+ border-bottom: 3px solid var(--_fellowship-poster-plum);
208
+ }
209
+
210
+ .eyebrow,
211
+ label > span {
212
+ display: block;
213
+ margin-bottom: 4px;
214
+ font-size: 10px;
215
+ font-weight: 500;
216
+ letter-spacing: 0.09em;
217
+ text-transform: uppercase;
218
+ }
219
+
220
+ h2 {
221
+ margin: 0;
222
+ font-family: var(--_fellowship-poster-display);
223
+ font-size: clamp(24px, 3cqi, 38px);
224
+ font-weight: 800;
225
+ line-height: 0.94;
226
+ text-transform: uppercase;
227
+ }
228
+
229
+ .current-rarity {
230
+ padding: 4px 8px;
231
+ background: var(--_fellowship-poster-violet);
232
+ color: var(--_fellowship-poster-cream);
233
+ font-weight: 500;
234
+ text-transform: uppercase;
235
+ }
236
+
237
+ .search {
238
+ display: block;
239
+ margin-block: 16px 8px;
240
+ }
241
+
242
+ input,
243
+ select {
244
+ width: 100%;
245
+ min-height: 44px;
246
+ border: 2px solid var(--_fellowship-poster-plum);
247
+ border-radius: var(--_fellowship-poster-radius);
248
+ background: var(--_fellowship-poster-white);
249
+ color: var(--_fellowship-poster-plum);
250
+ font: 500 12px/1.4 var(--_fellowship-poster-mono);
251
+ }
252
+
253
+ input {
254
+ padding-inline: 12px;
255
+ }
256
+
257
+ select {
258
+ padding: 8px 32px 8px 10px;
259
+ }
260
+
261
+ input:focus-visible,
262
+ select:focus-visible,
263
+ button:focus-visible {
264
+ outline: 4px solid var(--_fellowship-poster-focus);
265
+ outline-offset: 2px;
266
+ }
267
+
268
+ .item-list {
269
+ display: grid;
270
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
271
+ gap: 6px;
272
+ max-height: 196px;
273
+ overflow: auto;
274
+ padding: 4px 4px 8px 0;
275
+ }
276
+
277
+ .item-choice {
278
+ display: grid;
279
+ grid-template-columns: 8px 38px minmax(0, 1fr);
280
+ gap: 3px 8px;
281
+ align-content: center;
282
+ min-height: 52px;
283
+ padding: 8px;
284
+ border: 2px solid var(--_fellowship-poster-plum);
285
+ border-radius: 0;
286
+ background: var(--_fellowship-poster-cream);
287
+ color: var(--_fellowship-poster-plum);
288
+ font: 700 12px/1.2 var(--_fellowship-poster-display);
289
+ text-align: left;
290
+ cursor: pointer;
291
+ }
292
+
293
+ .item-choice:hover,
294
+ .item-choice[aria-pressed="true"] {
295
+ background: var(--_fellowship-poster-yellow);
296
+ }
297
+
298
+ .item-choice:disabled {
299
+ cursor: not-allowed;
300
+ opacity: 0.48;
301
+ }
302
+
303
+ .choice-mark {
304
+ grid-row: span 2;
305
+ width: 8px;
306
+ height: 100%;
307
+ background: var(--_fellowship-poster-violet);
308
+ }
309
+
310
+ .choice-image,
311
+ .choice-image img {
312
+ width: 36px;
313
+ height: 36px;
314
+ }
315
+
316
+ .choice-image {
317
+ grid-row: span 2;
318
+ display: grid;
319
+ place-items: center;
320
+ overflow: hidden;
321
+ border: 2px solid currentColor;
322
+ background: var(--_fellowship-poster-plum);
323
+ color: var(--_fellowship-poster-cream);
324
+ }
325
+
326
+ .choice-image img {
327
+ object-fit: cover;
328
+ }
329
+
330
+ .item-choice small {
331
+ grid-column: 3;
332
+ font: 400 9px/1.2 var(--_fellowship-poster-mono);
333
+ text-transform: uppercase;
334
+ }
335
+
336
+ .configuration {
337
+ display: grid;
338
+ gap: 12px;
339
+ margin-top: 16px;
340
+ padding-top: 16px;
341
+ border-top: 3px solid var(--_fellowship-poster-plum);
342
+ }
343
+
344
+ .fixed-details {
345
+ display: grid;
346
+ gap: 3px;
347
+ padding: 10px;
348
+ border-left: 6px solid var(--_fellowship-poster-violet);
349
+ background: var(--_fellowship-poster-yellow);
350
+ overflow-wrap: anywhere;
351
+ }
352
+
353
+ .fixed-details strong {
354
+ font-family: var(--_fellowship-poster-display);
355
+ font-weight: 800;
356
+ text-transform: uppercase;
357
+ }
358
+
359
+ .control-grid {
360
+ display: grid;
361
+ grid-template-columns: repeat(2, minmax(0, 1fr));
362
+ gap: 8px;
363
+ }
364
+
365
+ .configuration-control {
366
+ display: block;
367
+ }
368
+
369
+ fieldset {
370
+ min-width: 0;
371
+ margin: 4px 0 0;
372
+ padding: 12px;
373
+ border: 2px solid var(--_fellowship-poster-plum);
374
+ }
375
+
376
+ legend {
377
+ padding-inline: 6px;
378
+ font-family: var(--_fellowship-poster-display);
379
+ font-weight: 800;
380
+ text-transform: uppercase;
381
+ }
382
+
383
+ .hint {
384
+ margin: 0 0 8px;
385
+ font-size: 10px;
386
+ }
387
+
388
+ .trait-grid {
389
+ display: grid;
390
+ grid-template-columns: repeat(4, minmax(0, 1fr));
391
+ gap: 8px;
392
+ }
393
+
394
+ .trait-node {
395
+ grid-column: var(--column);
396
+ grid-row: var(--row);
397
+ min-width: 0;
398
+ padding: 6px;
399
+ border-left: 4px solid var(--_fellowship-poster-violet);
400
+ background: color-mix(
401
+ in oklch,
402
+ var(--_fellowship-poster-yellow) 24%,
403
+ var(--_fellowship-poster-cream)
404
+ );
405
+ }
406
+
407
+ .trait-node input {
408
+ margin-top: 6px;
409
+ }
410
+
411
+ .empty,
412
+ .empty-search {
413
+ margin: 0;
414
+ padding: 24px 16px;
415
+ border: 2px dashed var(--_fellowship-poster-plum);
416
+ text-align: center;
417
+ }
418
+
419
+ .configure-empty {
420
+ margin-top: 16px;
421
+ }
422
+
423
+ @media (max-width: 560px) {
424
+ .control-grid,
425
+ .trait-grid {
426
+ grid-template-columns: 1fr;
427
+ }
428
+
429
+ .trait-node {
430
+ grid-column: 1;
431
+ grid-row: auto;
432
+ }
433
+ }
434
+ `, C = class extends i {
435
+ constructor(...e) {
436
+ super(...e), this.data = null, this.build = null, this.positionId = null, this.resolveAssetUrl = null, this._query = "", this.#g = (e) => {
437
+ this._query = e.currentTarget.value;
438
+ };
439
+ }
440
+ static {
441
+ this.styles = [n, S];
442
+ }
443
+ render() {
444
+ if (!this.data || !this.build || !this.positionId) return o`<div class="empty">Select a gear position to begin.</div>`;
445
+ let e = this.data.positions.find(({ id: e }) => e === this.positionId), t = this.build.positions.find(({ positionId: e }) => e === this.positionId);
446
+ if (!e) return o`<div class="empty">This gear position is unavailable.</div>`;
447
+ let n = t?.item ?? null, r = n ? this.data.itemById.get(n.itemId) : null, i = n && r ? h(this.data, this.build, this.positionId, n.itemId) : null, a = this.data.heroById.get(this.build.heroId), s = this._query.trim().toLocaleLowerCase(), c = a ? g(this.data, this.build, this.positionId).map((e) => ({
448
+ item: e,
449
+ name: v(e, a)
450
+ })).filter(({ name: e }) => !s || e.toLocaleLowerCase().includes(s)) : [];
451
+ return o`
452
+ <header class="workbench-header">
453
+ <div>
454
+ <span class="eyebrow">Active position</span>
455
+ <h2>${e.label}</h2>
456
+ </div>
457
+ ${r ? o`<span class="current-rarity">${n?.rarity}</span>` : o`<span class="current-rarity">Empty</span>`}
458
+ </header>
459
+
460
+ <label class="search">
461
+ <span>Search valid ${e.itemType.toLocaleLowerCase()} items</span>
462
+ <input
463
+ type="search"
464
+ .value=${this._query}
465
+ placeholder="Type an item name"
466
+ @input=${this.#g}
467
+ />
468
+ </label>
469
+ <div class="item-list" aria-label=${`Valid items for ${e.label}`}>
470
+ ${c.length === 0 ? o`<p class="empty-search">No valid items match this search.</p>` : f(c, (e) => e.item.item.id, (e) => o`
471
+ <button
472
+ type="button"
473
+ class="item-choice"
474
+ aria-pressed=${String(e.item.item.id === n?.itemId)}
475
+ ?disabled=${e.item.validItemLevels.length === 0 || e.item.validRarities.length === 0}
476
+ @click=${() => this.#l(e.item.item.id)}
477
+ >
478
+ <span class="choice-mark" aria-hidden="true"></span>
479
+ <span class="choice-image">
480
+ ${this.#o(e.item.item.images, e.name)}
481
+ </span>
482
+ <span>${e.name}</span>
483
+ <small>
484
+ ${e.item.maxEquipped === 1 ? "Unique" : e.item.itemType}
485
+ </small>
486
+ </button>
487
+ `)}
488
+ </div>
489
+ ${n && r && i ? this.#e(n, i) : o`<div class="empty configure-empty">Choose an item to configure it.</div>`}
490
+ `;
491
+ }
492
+ #e(e, t) {
493
+ let n = t.slots.filter((e) => e.rollGroupId === null);
494
+ return o`
495
+ <section class="configuration" aria-label="Item configuration">
496
+ ${this.#a(t)}
497
+ <div class="control-grid">
498
+ <label>
499
+ <span>Item level</span>
500
+ <select
501
+ .value=${String(e.itemLevel)}
502
+ @change=${(t) => this.#h({
503
+ ...e,
504
+ itemLevel: Number(t.currentTarget.value)
505
+ }, "item-level")}
506
+ >
507
+ ${f(t.item.validItemLevels, (e) => e, (e) => o`<option value=${e}>${e}</option>`)}
508
+ </select>
509
+ </label>
510
+ <label>
511
+ <span>Rarity</span>
512
+ <select
513
+ .value=${e.rarity}
514
+ @change=${(t) => this.#h({
515
+ ...e,
516
+ rarity: t.currentTarget.value
517
+ }, "rarity")}
518
+ >
519
+ ${f(t.item.validRarities, (e) => e, (e) => o`<option value=${e}>${e}</option>`)}
520
+ </select>
521
+ </label>
522
+ </div>
523
+
524
+ ${f(n, (e) => e.id, (n) => this.#t(e, n, t))}
525
+ ${f(t.rollGroups, (e) => e.id, (n, r) => {
526
+ let i = t.slots.filter((e) => n.slotIds.includes(e.id));
527
+ return this.#n(e, i, r + 1);
528
+ })}
529
+ ${t.socketTiers.length > 0 ? o`
530
+ <fieldset>
531
+ <legend>Socketed gems</legend>
532
+ ${f(t.socketTiers, (e, t) => t, (t, n) => this.#r(e, t, n))}
533
+ </fieldset>
534
+ ` : s}
535
+ ${t.traitTree ? o`
536
+ <fieldset>
537
+ <legend>Trait topology</legend>
538
+ <p class="hint">Weapon and necklace nodes follow the extracted parent links.</p>
539
+ <div class="trait-grid">
540
+ ${f(t.traitTree.nodes, (e) => e.id, (t) => {
541
+ let n = e.traits.find(({ nodeId: e }) => e === t.id);
542
+ return o`
543
+ <label class="trait-node" style=${`--row:${t.row + 1};--column:${t.column + 1}`}>
544
+ <span>Node ${t.row + 1}.${t.column + 1}</span>
545
+ <select
546
+ aria-label=${`Trait node ${t.row + 1}.${t.column + 1}`}
547
+ .value=${n?.traitId ?? ""}
548
+ @change=${(n) => this.#p(e, t.id, n.currentTarget.value)}
549
+ >
550
+ <option value="">Unassigned</option>
551
+ ${f(t.eligibleTraitIds, (e) => e, (e) => o`
552
+ <option value=${e}>
553
+ ${this.data?.traitById.get(e)?.name ?? e}
554
+ </option>
555
+ `)}
556
+ </select>
557
+ ${n ? o`
558
+ <input
559
+ type="number"
560
+ min="1"
561
+ max=${t.maxRank}
562
+ .value=${String(n.rank)}
563
+ aria-label="Trait rank"
564
+ @change=${(n) => this.#m(e, t.id, Number(n.currentTarget.value), t.maxRank)}
565
+ />
566
+ ` : s}
567
+ </label>
568
+ `;
569
+ })}
570
+ </div>
571
+ </fieldset>
572
+ ` : s}
573
+ </section>
574
+ `;
575
+ }
576
+ #t(e, t, n) {
577
+ if (t.kind === "gem-socket" || t.kind === "item-trait") return o``;
578
+ let r = this.#i(e, t);
579
+ return o`
580
+ <label class="configuration-control">
581
+ <span>${this.#s(t.kind)}</span>
582
+ <select
583
+ .value=${r}
584
+ @change=${(r) => this.#u(e, t, r.currentTarget.value, n)}
585
+ >
586
+ <option value="">${t.required ? "Choose a value" : "None"}</option>
587
+ ${f(t.kind === "blessing" ? n.blessings.map((e) => e.id) : t.allowedChoiceIds, (e) => e, (e) => o`
588
+ <option value=${e}>${this.#c(t.kind, e)}</option>
589
+ `)}
590
+ </select>
591
+ </label>
592
+ `;
593
+ }
594
+ #n(e, t, n) {
595
+ let r = t.find((t) => this.#i(e, t));
596
+ return o`
597
+ <label class="configuration-control">
598
+ <span>Rolled choice ${n}</span>
599
+ <select
600
+ .value=${r ? `${r.id}\u001f${this.#i(e, r)}` : ""}
601
+ @change=${(n) => {
602
+ let r = n.currentTarget.value;
603
+ this.#d(e, t, r);
604
+ }}
605
+ >
606
+ <option value="">Choose one roll</option>
607
+ ${t.flatMap((e) => e.allowedChoiceIds.map((t) => o`
608
+ <option value=${`${e.id}\u001f${t}`}>
609
+ ${this.#s(e.kind)} · ${this.#c(e.kind, t)}
610
+ </option>
611
+ `))}
612
+ </select>
613
+ </label>
614
+ `;
615
+ }
616
+ #r(e, t, n) {
617
+ let r = `socket:${n}`, i = e.gems.find((e) => e.socketId === r)?.gemId ?? "", a = Number(t.replace(/\D/g, "")), s = this.data?.socketGems.filter((e) => e.tier <= a) ?? [];
618
+ return o`
619
+ <label class="configuration-control">
620
+ <span>Socket ${n + 1} · ${t.replace("SocketTier", "Tier ")}</span>
621
+ <select
622
+ .value=${i}
623
+ @change=${(t) => this.#f(e, r, t.currentTarget.value)}
624
+ >
625
+ <option value="">Empty socket</option>
626
+ ${f(s, (e) => e.id, (e) => o`<option value=${e.id}>${e.name} · ${e.power} power</option>`)}
627
+ </select>
628
+ </label>
629
+ `;
630
+ }
631
+ #i(e, t) {
632
+ return t.kind === "blessing" ? e.blessings.find(({ slotId: e }) => e === t.id)?.blessingId ?? "" : e.rolledModifiers.find(({ slotId: e }) => e === t.id)?.choiceId ?? "";
633
+ }
634
+ #a(e) {
635
+ let t = e.item, n = this.build?.heroId ?? "", r = (t.setBonusIdsByHero[n] ?? []).map((e) => this.data?.setBonuses.find(({ id: t }) => t === e)?.name ?? e), i = t.relicIdsByHero[n], a = this.data?.relics.find(({ id: e }) => e === i), c = t.item.linkedAbilityIds;
636
+ return r.length === 0 && !a && c.length === 0 ? o`` : o`
637
+ <aside class="fixed-details" aria-label="Fixed item mechanics">
638
+ <strong>Fixed item identity</strong>
639
+ ${r.length > 0 ? o`<span>Set · ${r.join(", ")}</span>` : s}
640
+ ${a ? o`<span>Relic · ${a.name ?? a.id}</span>` : s}
641
+ ${c.length > 0 ? o`<span>Granted ability · ${c.join(", ")}</span>` : s}
642
+ </aside>
643
+ `;
644
+ }
645
+ #o(e, t) {
646
+ let n = p(e, {
647
+ size: "200",
648
+ type: "hero-item"
649
+ });
650
+ if (!n || !this.resolveAssetUrl) return t.slice(0, 2);
651
+ let r;
652
+ try {
653
+ r = this.resolveAssetUrl(n);
654
+ } catch {
655
+ return t.slice(0, 2);
656
+ }
657
+ return o`
658
+ <img
659
+ src=${r}
660
+ alt=""
661
+ @error=${(e) => {
662
+ e.currentTarget.hidden = !0;
663
+ }}
664
+ />
665
+ `;
666
+ }
667
+ #s(e) {
668
+ return {
669
+ "rolled-stat": "Rolled stat",
670
+ "gem-power": "Gem power",
671
+ "item-trait": "Item trait",
672
+ blessing: "Blessing",
673
+ "gem-socket": "Gem socket"
674
+ }[e];
675
+ }
676
+ #c(e, t) {
677
+ return e === "blessing" ? this.data?.blessingById.get(t)?.name ?? t : e === "item-trait" ? this.data?.traitById.get(t)?.name ?? t : t.replaceAll(/([a-z])([A-Z])/g, "$1 $2");
678
+ }
679
+ #l(e) {
680
+ let t = this.data?.itemById.get(e), n = t?.validItemLevels.at(-1), r = t?.validRarities.at(-1);
681
+ !t || n === void 0 || r === void 0 || this.#h({
682
+ itemId: e,
683
+ itemLevel: n,
684
+ rarity: r,
685
+ rolledModifiers: [],
686
+ gems: [],
687
+ traits: [],
688
+ blessings: []
689
+ }, "item");
690
+ }
691
+ #u(e, t, n, r) {
692
+ if (t.kind === "blessing") {
693
+ let r = e.blessings.filter((e) => e.slotId !== t.id);
694
+ n && r.push({
695
+ slotId: t.id,
696
+ blessingId: n,
697
+ rank: 1
698
+ }), this.#h({
699
+ ...e,
700
+ blessings: r
701
+ }, "blessing");
702
+ return;
703
+ }
704
+ let i = e.rolledModifiers.filter((e) => e.slotId !== t.id);
705
+ n && t.kind !== "gem-socket" && i.push({
706
+ slotId: t.id,
707
+ kind: t.kind,
708
+ choiceId: n
709
+ }), this.#h({
710
+ ...e,
711
+ rolledModifiers: i
712
+ }, "modifier");
713
+ }
714
+ #d(e, t, n) {
715
+ let r = new Set(t.map((e) => e.id)), i = e.rolledModifiers.filter((e) => !r.has(e.slotId)), a = e.blessings.filter((e) => !r.has(e.slotId)), [o, s] = n.split(""), c = t.find((e) => e.id === o);
716
+ c && s && (c.kind === "blessing" ? a.push({
717
+ slotId: c.id,
718
+ blessingId: s,
719
+ rank: 1
720
+ }) : c.kind !== "gem-socket" && i.push({
721
+ slotId: c.id,
722
+ kind: c.kind,
723
+ choiceId: s
724
+ })), this.#h({
725
+ ...e,
726
+ rolledModifiers: i,
727
+ blessings: a
728
+ }, c?.kind === "blessing" ? "blessing" : "modifier");
729
+ }
730
+ #f(e, t, n) {
731
+ let r = e.gems.filter((e) => e.socketId !== t);
732
+ n && r.push({
733
+ socketId: t,
734
+ gemId: n
735
+ }), this.#h({
736
+ ...e,
737
+ gems: r
738
+ }, "gem");
739
+ }
740
+ #p(e, t, n) {
741
+ let r = e.traits.filter((e) => e.nodeId !== t);
742
+ n && r.push({
743
+ nodeId: t,
744
+ traitId: n,
745
+ rank: 1
746
+ }), this.#h({
747
+ ...e,
748
+ traits: r
749
+ }, "trait");
750
+ }
751
+ #m(e, t, n, r) {
752
+ let i = e.traits.map((e) => e.nodeId === t ? {
753
+ ...e,
754
+ rank: Math.max(1, Math.min(r, Math.round(n)))
755
+ } : e);
756
+ this.#h({
757
+ ...e,
758
+ traits: i
759
+ }, "trait");
760
+ }
761
+ #h(e, t) {
762
+ this.dispatchEvent(new CustomEvent("fellowship-workbench-change", {
763
+ bubbles: !0,
764
+ composed: !0,
765
+ detail: {
766
+ item: e,
767
+ source: t
768
+ }
769
+ }));
770
+ }
771
+ #g;
772
+ };
773
+ e([l({ attribute: !1 })], C.prototype, "data", void 0), e([l({ attribute: !1 })], C.prototype, "build", void 0), e([l({ type: String })], C.prototype, "positionId", void 0), e([l({ attribute: !1 })], C.prototype, "resolveAssetUrl", void 0), e([d()], C.prototype, "_query", void 0), C = e([c("fellowship-item-workbench")], C);
774
+ var w = () => {
775
+ customElements.get("fellowship-item-workbench") || customElements.define("fellowship-item-workbench", C);
776
+ }, T = a`
777
+ :host {
778
+ display: block;
779
+ min-width: 0;
780
+ color: var(--_fellowship-poster-plum);
781
+ font: 400 12px/1.5 var(--_fellowship-poster-mono);
782
+ }
783
+
784
+ .container {
785
+ container-type: inline-size;
786
+ min-width: 0;
787
+ }
788
+
789
+ * {
790
+ box-sizing: border-box;
791
+ }
792
+
793
+ button,
794
+ input,
795
+ select {
796
+ font: inherit;
797
+ }
798
+
799
+ .planner {
800
+ min-width: 0;
801
+ padding: 16px;
802
+ border: var(--_fellowship-poster-seam) solid var(--_fellowship-poster-plum);
803
+ background: var(--_fellowship-poster-cream);
804
+ box-shadow: var(--_fellowship-poster-shadow);
805
+ }
806
+
807
+ .hero {
808
+ display: grid;
809
+ grid-template-columns: 72px minmax(0, max-content);
810
+ align-items: center;
811
+ justify-content: center;
812
+ gap: 12px;
813
+ min-width: 0;
814
+ margin: 0 auto 16px;
815
+ text-align: left;
816
+ }
817
+
818
+ .hero-image {
819
+ display: grid;
820
+ place-items: center;
821
+ width: 72px;
822
+ height: 72px;
823
+ overflow: hidden;
824
+ border: 3px solid var(--_fellowship-poster-plum);
825
+ background: var(--_fellowship-poster-violet);
826
+ color: var(--_fellowship-poster-cream);
827
+ font: 800 24px/1 var(--_fellowship-poster-display);
828
+ }
829
+
830
+ .hero-image img {
831
+ width: 100%;
832
+ height: 100%;
833
+ object-fit: cover;
834
+ }
835
+
836
+ .hero-label,
837
+ .section-label {
838
+ display: block;
839
+ margin-bottom: 4px;
840
+ font-size: 10px;
841
+ font-weight: 500;
842
+ letter-spacing: 0.1em;
843
+ text-transform: uppercase;
844
+ }
845
+
846
+ h1,
847
+ h2,
848
+ h3,
849
+ p {
850
+ margin: 0;
851
+ }
852
+
853
+ h1 {
854
+ max-width: 26ch;
855
+ overflow-wrap: anywhere;
856
+ font-family: var(--_fellowship-poster-display);
857
+ font-size: clamp(24px, 4cqi, 48px);
858
+ font-weight: 800;
859
+ line-height: 0.9;
860
+ text-transform: uppercase;
861
+ }
862
+
863
+ h2 {
864
+ margin-bottom: 12px;
865
+ font-family: var(--_fellowship-poster-display);
866
+ font-size: 18px;
867
+ font-weight: 800;
868
+ line-height: 1;
869
+ text-transform: uppercase;
870
+ }
871
+
872
+ h3 {
873
+ margin: 16px 0 6px;
874
+ padding-top: 10px;
875
+ border-top: 2px solid currentColor;
876
+ font-family: var(--_fellowship-poster-display);
877
+ font-size: 13px;
878
+ font-weight: 800;
879
+ letter-spacing: 0.04em;
880
+ text-transform: uppercase;
881
+ }
882
+
883
+ .tabs {
884
+ display: grid;
885
+ grid-template-columns: repeat(3, minmax(0, 1fr));
886
+ margin-bottom: 12px;
887
+ border: 2px solid var(--_fellowship-poster-plum);
888
+ }
889
+
890
+ .tab {
891
+ min-height: 44px;
892
+ border: 0;
893
+ border-right: 2px solid var(--_fellowship-poster-plum);
894
+ background: var(--_fellowship-poster-cream);
895
+ color: var(--_fellowship-poster-plum);
896
+ font-size: 10px;
897
+ font-weight: 500;
898
+ text-transform: uppercase;
899
+ cursor: pointer;
900
+ }
901
+
902
+ .tab:last-child {
903
+ border-right: 0;
904
+ }
905
+
906
+ .tab[aria-selected="true"] {
907
+ background: var(--_fellowship-poster-violet);
908
+ color: var(--_fellowship-poster-cream);
909
+ }
910
+
911
+ .tab:focus-visible,
912
+ .sheet-close:focus-visible,
913
+ .condition:focus-within {
914
+ outline: 4px solid var(--_fellowship-poster-focus);
915
+ outline-offset: 2px;
916
+ }
917
+
918
+ .desktop-grid {
919
+ display: block;
920
+ min-width: 0;
921
+ }
922
+
923
+ .panel {
924
+ min-width: 0;
925
+ padding: 14px;
926
+ border: 2px solid var(--_fellowship-poster-plum);
927
+ background: var(--_fellowship-poster-white);
928
+ }
929
+
930
+ .slot-grid {
931
+ display: grid;
932
+ }
933
+
934
+ .slot-rail {
935
+ display: grid;
936
+ grid-template-columns: repeat(2, minmax(0, 1fr));
937
+ gap: 8px;
938
+ }
939
+
940
+ .summary-list,
941
+ .breakdown {
942
+ display: grid;
943
+ gap: 6px;
944
+ padding: 0;
945
+ list-style: none;
946
+ }
947
+
948
+ .summary-row,
949
+ .total-row,
950
+ .unsupported-row {
951
+ display: flex;
952
+ align-items: start;
953
+ justify-content: space-between;
954
+ gap: 12px;
955
+ padding: 6px 0;
956
+ border-bottom: 1px solid color-mix(in oklch, var(--_fellowship-poster-plum) 28%, transparent);
957
+ }
958
+
959
+ .summary-row span:first-child,
960
+ .total-row span:first-child,
961
+ .unsupported-row span:first-child {
962
+ min-width: 0;
963
+ overflow-wrap: anywhere;
964
+ }
965
+
966
+ .summary-row strong,
967
+ .total-row strong {
968
+ flex: 0 0 auto;
969
+ font-weight: 500;
970
+ }
971
+
972
+ .active-set {
973
+ padding-inline: 4px;
974
+ background: var(--_fellowship-poster-yellow);
975
+ }
976
+
977
+ .unsupported-row {
978
+ color: color-mix(in oklch, var(--_fellowship-poster-plum) 72%, white);
979
+ }
980
+
981
+ .unsupported-code {
982
+ font-size: 9px;
983
+ text-transform: uppercase;
984
+ }
985
+
986
+ .total-row {
987
+ font-size: 13px;
988
+ }
989
+
990
+ .condition {
991
+ display: grid;
992
+ grid-template-columns: 44px minmax(0, 1fr);
993
+ align-items: center;
994
+ min-height: 44px;
995
+ cursor: pointer;
996
+ }
997
+
998
+ .condition input {
999
+ width: 20px;
1000
+ height: 20px;
1001
+ accent-color: var(--_fellowship-poster-violet);
1002
+ }
1003
+
1004
+ .workbench-panel {
1005
+ min-width: 0;
1006
+ padding: 16px;
1007
+ border: 3px solid var(--_fellowship-poster-plum);
1008
+ background: var(--_fellowship-poster-cream);
1009
+ }
1010
+
1011
+ .workbench-panel:focus {
1012
+ outline: 4px solid var(--_fellowship-poster-focus);
1013
+ outline-offset: -4px;
1014
+ }
1015
+
1016
+ .sheet-backdrop,
1017
+ .sheet-header {
1018
+ display: none;
1019
+ }
1020
+
1021
+ .loading,
1022
+ .empty,
1023
+ .error {
1024
+ display: grid;
1025
+ min-height: 320px;
1026
+ place-items: center;
1027
+ padding: 32px;
1028
+ border: 3px solid var(--_fellowship-poster-plum);
1029
+ background: var(--_fellowship-poster-cream);
1030
+ color: var(--_fellowship-poster-plum);
1031
+ text-align: center;
1032
+ }
1033
+
1034
+ .loading strong,
1035
+ .empty strong,
1036
+ .error strong {
1037
+ display: block;
1038
+ margin-bottom: 8px;
1039
+ font: 800 clamp(24px, 5cqi, 54px) / 0.9 var(--_fellowship-poster-display);
1040
+ text-transform: uppercase;
1041
+ }
1042
+
1043
+ .error {
1044
+ background: var(--_fellowship-poster-yellow);
1045
+ }
1046
+
1047
+ .sr-only {
1048
+ position: absolute;
1049
+ width: 1px;
1050
+ height: 1px;
1051
+ overflow: hidden;
1052
+ clip: rect(0, 0, 0, 0);
1053
+ white-space: nowrap;
1054
+ }
1055
+
1056
+ [hidden] {
1057
+ display: none !important;
1058
+ }
1059
+
1060
+ @container (min-width: 1200px) {
1061
+ .planner {
1062
+ padding: 20px;
1063
+ }
1064
+
1065
+ .hero {
1066
+ grid-template-columns: 92px minmax(0, max-content);
1067
+ margin-bottom: 20px;
1068
+ }
1069
+
1070
+ .hero-image {
1071
+ width: 92px;
1072
+ height: 92px;
1073
+ }
1074
+
1075
+ .tabs {
1076
+ display: none;
1077
+ }
1078
+
1079
+ .desktop-grid {
1080
+ display: grid;
1081
+ grid-template-columns:
1082
+ minmax(170px, 0.78fr)
1083
+ minmax(142px, 0.64fr)
1084
+ minmax(390px, 1.9fr)
1085
+ minmax(142px, 0.64fr)
1086
+ minmax(190px, 0.86fr);
1087
+ align-items: start;
1088
+ gap: 12px;
1089
+ }
1090
+
1091
+ .slot-grid {
1092
+ display: contents;
1093
+ }
1094
+
1095
+ .gear-workspace {
1096
+ display: contents;
1097
+ }
1098
+
1099
+ .slot-rail {
1100
+ display: grid;
1101
+ grid-template-columns: minmax(0, 1fr);
1102
+ gap: 8px;
1103
+ }
1104
+
1105
+ .left-summary {
1106
+ grid-column: 1;
1107
+ }
1108
+
1109
+ .left-rail {
1110
+ grid-column: 2;
1111
+ }
1112
+
1113
+ .workbench-panel {
1114
+ grid-column: 3;
1115
+ min-height: 650px;
1116
+ }
1117
+
1118
+ .right-rail {
1119
+ grid-column: 4;
1120
+ }
1121
+
1122
+ .right-summary {
1123
+ grid-column: 5;
1124
+ }
1125
+
1126
+ .left-summary,
1127
+ .left-rail,
1128
+ .workbench-panel,
1129
+ .right-rail,
1130
+ .right-summary {
1131
+ grid-row: 1;
1132
+ }
1133
+ }
1134
+
1135
+ @container (max-width: 1199px) {
1136
+ .panel[aria-hidden="true"],
1137
+ .gear-workspace[aria-hidden="true"] {
1138
+ display: none;
1139
+ }
1140
+
1141
+ .panel {
1142
+ margin-top: 8px;
1143
+ }
1144
+
1145
+ .workbench-panel {
1146
+ position: fixed;
1147
+ z-index: 1001;
1148
+ inset: 0;
1149
+ display: none;
1150
+ overflow: auto;
1151
+ padding: max(16px, env(safe-area-inset-top)) 16px
1152
+ max(24px, env(safe-area-inset-bottom));
1153
+ border: 0;
1154
+ background: var(--_fellowship-poster-cream);
1155
+ overscroll-behavior: contain;
1156
+ }
1157
+
1158
+ .workbench-panel[data-sheet-open="true"] {
1159
+ display: block;
1160
+ }
1161
+
1162
+ .sheet-header {
1163
+ position: sticky;
1164
+ z-index: 2;
1165
+ top: calc(-1 * max(16px, env(safe-area-inset-top)));
1166
+ display: flex;
1167
+ align-items: center;
1168
+ justify-content: space-between;
1169
+ gap: 12px;
1170
+ margin: calc(-1 * max(16px, env(safe-area-inset-top))) -16px 16px;
1171
+ padding: max(12px, env(safe-area-inset-top)) 16px 12px;
1172
+ border-bottom: 3px solid var(--_fellowship-poster-plum);
1173
+ background: var(--_fellowship-poster-yellow);
1174
+ }
1175
+
1176
+ .sheet-header strong {
1177
+ font: 800 16px/1 var(--_fellowship-poster-display);
1178
+ text-transform: uppercase;
1179
+ }
1180
+
1181
+ .sheet-close {
1182
+ min-width: 44px;
1183
+ min-height: 44px;
1184
+ border: 2px solid var(--_fellowship-poster-plum);
1185
+ background: var(--_fellowship-poster-plum);
1186
+ color: var(--_fellowship-poster-cream);
1187
+ cursor: pointer;
1188
+ }
1189
+ }
1190
+
1191
+ @container (max-width: 560px) {
1192
+ .planner {
1193
+ padding: 8px;
1194
+ border-width: 2px;
1195
+ box-shadow: 2px 2px 0 var(--_fellowship-poster-yellow);
1196
+ }
1197
+
1198
+ .hero {
1199
+ grid-template-columns: 52px minmax(0, 1fr);
1200
+ justify-content: start;
1201
+ margin-bottom: 10px;
1202
+ }
1203
+
1204
+ .hero-image {
1205
+ width: 52px;
1206
+ height: 52px;
1207
+ border-width: 2px;
1208
+ font-size: 18px;
1209
+ }
1210
+
1211
+ h1 {
1212
+ font-size: 24px;
1213
+ }
1214
+
1215
+ .slot-rail {
1216
+ grid-template-columns: 1fr;
1217
+ gap: 6px;
1218
+ }
1219
+
1220
+ .tab {
1221
+ padding-inline: 4px;
1222
+ font-size: 9px;
1223
+ }
1224
+ }
1225
+
1226
+ @media (prefers-reduced-motion: reduce) {
1227
+ *,
1228
+ *::before,
1229
+ *::after {
1230
+ scroll-behavior: auto !important;
1231
+ transition-duration: 0.01ms !important;
1232
+ animation-duration: 0.01ms !important;
1233
+ animation-iteration-count: 1 !important;
1234
+ }
1235
+ }
1236
+ `, E = [
1237
+ "head",
1238
+ "shoulders",
1239
+ "chest",
1240
+ "hands",
1241
+ "waist",
1242
+ "ring-1",
1243
+ "trinket-1"
1244
+ ], D = [
1245
+ "necklace",
1246
+ "back",
1247
+ "wrists",
1248
+ "feet",
1249
+ "ring-2",
1250
+ "trinket-2",
1251
+ "weapon"
1252
+ ], O = "button:not([disabled]), input:not([disabled]), select:not([disabled]), [href], [tabindex]:not([tabindex=\"-1\"])", k = (e) => {
1253
+ let t = [];
1254
+ for (let n of e.children) n instanceof HTMLElement && n.matches(O) && t.push(n), n.shadowRoot && t.push(...k(n.shadowRoot)), t.push(...k(n));
1255
+ return t;
1256
+ }, A = (e) => {
1257
+ let t = e.activeElement;
1258
+ for (; t?.shadowRoot?.activeElement;) t = t.shadowRoot.activeElement;
1259
+ return t;
1260
+ };
1261
+ x(), w();
1262
+ var j = class extends i {
1263
+ constructor(...e) {
1264
+ super(...e), this.data = null, this.build = null, this.loading = !1, this.error = null, this.resolveAssetUrl = null, this._selectedPositionId = null, this._tab = "gear", this._sheetOpen = !1, this._compact = !1, this._announcement = "", this._calculation = null, this.#e = null, this.#t = null, this.#n = null, this.#r = null, this.#i = null, this.#a = !1, this.#o = /* @__PURE__ */ new Set(), this.#p = (e) => {
1265
+ e.stopPropagation(), this._selectedPositionId = e.detail.positionId, this.#i = e.detail.trigger, this._compact && (this._sheetOpen = !0, this.#r !== null && window.clearTimeout(this.#r), this.#r = window.setTimeout(() => {
1266
+ this.#r = null, this.shadowRoot?.querySelector(".workbench-panel")?.focus({ preventScroll: !0 });
1267
+ }, 0));
1268
+ }, this.#m = (e) => {
1269
+ if (e.stopPropagation(), !this.data || !this.build || !this._selectedPositionId) return;
1270
+ let t = {
1271
+ ...this.build,
1272
+ positions: this.build.positions.map((t) => t.positionId === this._selectedPositionId ? {
1273
+ ...t,
1274
+ item: e.detail.item
1275
+ } : t)
1276
+ }, n = _(this.data, t), r = y(this.data, n.build);
1277
+ if (!r.valid || !r.build) {
1278
+ let e = r.issues.find(({ severity: e }) => e === "error");
1279
+ this.#_({
1280
+ code: e?.code ?? "invalid-change",
1281
+ message: e?.message ?? "That item choice is not valid.",
1282
+ recoverable: !0
1283
+ }), this._announcement = e?.message ?? "That item choice was rejected.";
1284
+ return;
1285
+ }
1286
+ let i = e.detail.item.rolledModifiers.length + e.detail.item.gems.length + e.detail.item.traits.length + e.detail.item.blessings.length - (r.build.positions.find(({ positionId: e }) => e === this._selectedPositionId)?.item?.rolledModifiers.length ?? 0) - (r.build.positions.find(({ positionId: e }) => e === this._selectedPositionId)?.item?.gems.length ?? 0) - (r.build.positions.find(({ positionId: e }) => e === this._selectedPositionId)?.item?.traits.length ?? 0) - (r.build.positions.find(({ positionId: e }) => e === this._selectedPositionId)?.item?.blessings.length ?? 0);
1287
+ this._announcement = i > 0 ? `${i} incompatible item choice${i === 1 ? " was" : "s were"} reset.` : "Build updated.", this.#g(r.build, e.detail.source, this._selectedPositionId);
1288
+ }, this.#v = () => {
1289
+ this._sheetOpen = !1, this.updateComplete.then(() => {
1290
+ this.#n !== null && window.cancelAnimationFrame(this.#n), this.#n = window.requestAnimationFrame(() => {
1291
+ this.#n = null, this.#i?.focus({ preventScroll: !0 });
1292
+ });
1293
+ });
1294
+ }, this.#y = (e) => {
1295
+ e.key !== "Escape" || !this._compact || !this._sheetOpen || (e.preventDefault(), this.#v());
1296
+ }, this.#b = (e) => {
1297
+ if (!this._compact || !this._sheetOpen) return;
1298
+ if (e.key === "Escape") {
1299
+ e.preventDefault(), this.#v();
1300
+ return;
1301
+ }
1302
+ if (e.key !== "Tab" || !this._workbenchPanel) return;
1303
+ let t = k(this._workbenchPanel), n = t[0], r = t.at(-1), i = A(this.shadowRoot);
1304
+ if (!n || !r) {
1305
+ e.preventDefault();
1306
+ return;
1307
+ }
1308
+ e.shiftKey && (i === n || i === this._workbenchPanel) ? (e.preventDefault(), r.focus()) : !e.shiftKey && i === r && (e.preventDefault(), n.focus());
1309
+ }, this.#x = (e) => {
1310
+ if (!["ArrowLeft", "ArrowRight"].includes(e.key)) return;
1311
+ e.preventDefault();
1312
+ let t = [
1313
+ "traits",
1314
+ "gear",
1315
+ "bonuses"
1316
+ ], n = e.key === "ArrowRight" ? 1 : -1, r = t.indexOf(this._tab);
1317
+ this._tab = t[(r + n + t.length) % t.length] ?? "gear", this.updateComplete.then(() => {
1318
+ (this.shadowRoot?.querySelector(".tab[aria-selected=\"true\"]"))?.focus();
1319
+ });
1320
+ }, this.#C = (e) => {
1321
+ e.currentTarget.hidden = !0;
1322
+ };
1323
+ }
1324
+ static {
1325
+ this.styles = [n, T];
1326
+ }
1327
+ #e;
1328
+ #t;
1329
+ #n;
1330
+ #r;
1331
+ #i;
1332
+ #a;
1333
+ #o;
1334
+ connectedCallback() {
1335
+ super.connectedCallback(), t(this.ownerDocument), this.#e ??= new ResizeObserver(() => {
1336
+ let e = this.getBoundingClientRect().width < 1200;
1337
+ this.#t !== null && window.cancelAnimationFrame(this.#t), this.#t = window.requestAnimationFrame(() => {
1338
+ this.#t = null, e !== this._compact && (this._compact = e, e || (this._sheetOpen = !1));
1339
+ });
1340
+ }), this.#e.observe(this), this.ownerDocument.addEventListener("keydown", this.#y);
1341
+ }
1342
+ disconnectedCallback() {
1343
+ this.#e?.disconnect(), this.ownerDocument.removeEventListener("keydown", this.#y), this.#t !== null && (window.cancelAnimationFrame(this.#t), this.#t = null), this.#n !== null && (window.cancelAnimationFrame(this.#n), this.#n = null), this.#r !== null && (window.clearTimeout(this.#r), this.#r = null), super.disconnectedCallback();
1344
+ }
1345
+ willUpdate(e) {
1346
+ super.willUpdate(e), (e.has("data") || e.has("build")) && (this._calculation = this.data && this.build ? m(this.data, this.build) : null, this.data && this._selectedPositionId && !this.data.positions.some(({ id: e }) => e === this._selectedPositionId) && (this._selectedPositionId = null));
1347
+ }
1348
+ updated(e) {
1349
+ if (super.updated(e), e.has("data") || e.has("build") || e.has("resolveAssetUrl")) {
1350
+ (e.has("data") || e.has("resolveAssetUrl")) && this.#o.clear();
1351
+ let t = !!(this.data && this.build && !this.resolveAssetUrl);
1352
+ t && !this.#a ? (this.#a = !0, this.#_({
1353
+ code: "asset-resolver-missing",
1354
+ message: "A resolveAssetUrl callback is required to render Fellowship images.",
1355
+ recoverable: !0
1356
+ })) : t || (this.#a = !1);
1357
+ }
1358
+ }
1359
+ render() {
1360
+ if (this.loading) return o`
1361
+ <div class="loading" role="status">
1362
+ <div><strong>Loading gear</strong><span>Preparing live-game choices…</span></div>
1363
+ </div>
1364
+ `;
1365
+ if (this.error) return o`
1366
+ <div class="error" role="alert">
1367
+ <div><strong>Planner unavailable</strong><span>${this.error.message}</span></div>
1368
+ </div>
1369
+ `;
1370
+ if (this.data && this.build && !this.resolveAssetUrl) return o`
1371
+ <div class="error" role="alert">
1372
+ <div>
1373
+ <strong>Planner unavailable</strong>
1374
+ <span>A resolveAssetUrl callback is required to render Fellowship images.</span>
1375
+ </div>
1376
+ </div>
1377
+ `;
1378
+ if (!this.data || !this.build) return o`
1379
+ <div class="empty">
1380
+ <div>
1381
+ <strong>Choose your hero</strong>
1382
+ <span>The planner starts only after an explicit hero selection.</span>
1383
+ </div>
1384
+ </div>
1385
+ `;
1386
+ let e = this.data.heroById.get(this.build.heroId);
1387
+ if (!e) return o`
1388
+ <div class="error" role="alert">
1389
+ <div><strong>Unknown hero</strong><span>This build cannot be displayed safely.</span></div>
1390
+ </div>
1391
+ `;
1392
+ let t = p(e.hero.images, {
1393
+ size: "200",
1394
+ type: "hero"
1395
+ }), n = t ? this.#S(t) : null;
1396
+ return o`
1397
+ <div class="container">
1398
+ <section class="planner" aria-label="Fellership Gear Planner">
1399
+ <header class="hero">
1400
+ <span class="hero-image">
1401
+ ${n ? o`<img src=${n} alt="" @error=${this.#C} />` : (e.hero.name ?? e.hero.id).slice(0, 2)}
1402
+ </span>
1403
+ <span>
1404
+ <span class="hero-label">Current hero</span>
1405
+ <h1>${e.hero.name ?? e.hero.id}</h1>
1406
+ </span>
1407
+ </header>
1408
+
1409
+ ${this.#s()}
1410
+ <div class="desktop-grid">
1411
+ <aside
1412
+ class="panel left-summary"
1413
+ aria-label="Traits and gems"
1414
+ aria-hidden=${String(this._compact && this._tab !== "traits")}
1415
+ >
1416
+ ${this.#d()}
1417
+ </aside>
1418
+ <div
1419
+ class="gear-workspace"
1420
+ aria-hidden=${String(this._compact && this._tab !== "gear")}
1421
+ >
1422
+ <div class="slot-grid">
1423
+ <div class="slot-rail left-rail">
1424
+ ${this.#c(this._compact ? this.data.positions.map(({ id: e }) => e) : E)}
1425
+ </div>
1426
+ ${this._compact ? s : o`
1427
+ <div class="slot-rail right-rail">
1428
+ ${this.#c(D)}
1429
+ </div>
1430
+ `}
1431
+ </div>
1432
+ </div>
1433
+ ${this.#l()}
1434
+ <aside
1435
+ class="panel right-summary"
1436
+ aria-label="Blessings, sets and results"
1437
+ aria-hidden=${String(this._compact && this._tab !== "bonuses")}
1438
+ >
1439
+ ${this.#f()}
1440
+ </aside>
1441
+ </div>
1442
+ <p class="sr-only" aria-live="polite">${this._announcement}</p>
1443
+ </section>
1444
+ </div>
1445
+ `;
1446
+ }
1447
+ #s() {
1448
+ return o`
1449
+ <div class="tabs" role="tablist" aria-label="Planner workspace">
1450
+ ${[
1451
+ ["traits", "Traits & gems"],
1452
+ ["gear", "Gear"],
1453
+ ["bonuses", "Bonuses"]
1454
+ ].map(([e, t]) => o`
1455
+ <button
1456
+ class="tab"
1457
+ type="button"
1458
+ role="tab"
1459
+ aria-selected=${String(this._tab === e)}
1460
+ tabindex=${this._tab === e ? "0" : "-1"}
1461
+ @click=${() => {
1462
+ this._tab = e;
1463
+ }}
1464
+ @keydown=${this.#x}
1465
+ >
1466
+ ${t}
1467
+ </button>
1468
+ `)}
1469
+ </div>
1470
+ `;
1471
+ }
1472
+ #c(e) {
1473
+ if (!this.data || !this.build) return o``;
1474
+ let t = this.data.heroById.get(this.build.heroId) ?? null;
1475
+ return o`
1476
+ ${f(e, (e) => e, (e) => {
1477
+ let n = this.data?.positions.find(({ id: t }) => t === e) ?? null, r = this.build?.positions.find((t) => t.positionId === e) ?? null, i = r?.item ? this.data?.itemById.get(r.item.itemId) ?? null : null, a = i ? p(i.item.images, {
1478
+ size: "200",
1479
+ type: "hero-item"
1480
+ }) : null;
1481
+ return o`
1482
+ <fellowship-gear-position
1483
+ .position=${n}
1484
+ .item=${i}
1485
+ .itemBuild=${r?.item ?? null}
1486
+ .itemName=${i && t ? v(i, t) : null}
1487
+ .selected=${e === this._selectedPositionId}
1488
+ .imageUrl=${a ? this.#S(a) : null}
1489
+ .tooltipDetails=${this.#u(e, r?.item ?? null)}
1490
+ @fellowship-gear-position-select=${this.#p}
1491
+ ></fellowship-gear-position>
1492
+ `;
1493
+ })}
1494
+ `;
1495
+ }
1496
+ #l() {
1497
+ return o`
1498
+ <section
1499
+ class="workbench-panel"
1500
+ data-sheet-open=${String(this._sheetOpen)}
1501
+ role=${this._compact ? "dialog" : "region"}
1502
+ aria-modal=${this._compact ? "true" : s}
1503
+ aria-label="Item workbench"
1504
+ tabindex=${this._compact ? "-1" : s}
1505
+ @keydown=${this.#b}
1506
+ @fellowship-workbench-change=${this.#m}
1507
+ >
1508
+ <header class="sheet-header">
1509
+ <strong>Configure item</strong>
1510
+ <button class="sheet-close" type="button" @click=${this.#v}>Close</button>
1511
+ </header>
1512
+ <fellowship-item-workbench
1513
+ .data=${this.data}
1514
+ .build=${this.build}
1515
+ .positionId=${this._selectedPositionId}
1516
+ .resolveAssetUrl=${this.resolveAssetUrl}
1517
+ ></fellowship-item-workbench>
1518
+ </section>
1519
+ `;
1520
+ }
1521
+ #u(e, t) {
1522
+ if (!this.data || !this.build || !t) return [];
1523
+ let n = this.data.itemById.get(t.itemId);
1524
+ if (!n) return [];
1525
+ let r = this._calculation?.fixedItemStats.filter((t) => t.positionId === e).map((e) => `${e.stat}: ${e.value}`).join(", "), i = t.rolledModifiers.map((e) => e.choiceId).join(", "), a = t.gems.map((e) => this.data?.socketGemById.get(e.gemId)?.name ?? e.gemId).join(", "), o = t.traits.map((e) => this.data?.traitById.get(e.traitId)?.name ?? e.traitId).join(", "), s = t.blessings.map((e) => this.data?.blessingById.get(e.blessingId)?.name ?? e.blessingId).join(", "), c = (n.setBonusIdsByHero[this.build.heroId] ?? []).map((e) => this.data?.setBonuses.find(({ id: t }) => t === e)?.name ?? e).join(", "), l = n.relicIdsByHero[this.build.heroId], u = this.data.relics.find(({ id: e }) => e === l), d = this._calculation?.unsupportedContributions.some((t) => t.positionId === e);
1526
+ return [
1527
+ r ? `Fixed stats · ${r}` : "",
1528
+ i ? `Rolled · ${i}` : "",
1529
+ a ? `Gems · ${a}` : "",
1530
+ o ? `Traits · ${o}` : "",
1531
+ s ? `Blessings · ${s}` : "",
1532
+ c ? `Set · ${c}` : "",
1533
+ u ? `Relic · ${u.name ?? u.id}` : "",
1534
+ n.item.linkedAbilityIds.length > 0 ? `Granted ability · ${n.item.linkedAbilityIds.join(", ")}` : "",
1535
+ d ? "Some mechanics are excluded from exact totals." : ""
1536
+ ].filter(Boolean);
1537
+ }
1538
+ #d() {
1539
+ if (!this.data || !this.build) return o``;
1540
+ let e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Map();
1541
+ for (let n of this.build.positions) {
1542
+ for (let t of n.item?.traits ?? []) e.set(t.traitId, (e.get(t.traitId) ?? 0) + t.rank);
1543
+ for (let e of n.item?.gems ?? []) {
1544
+ let n = this.data.socketGemById.get(e.gemId);
1545
+ if (!n) continue;
1546
+ let r = t.get(n.family) ?? {
1547
+ count: 0,
1548
+ power: 0
1549
+ };
1550
+ t.set(n.family, {
1551
+ count: r.count + 1,
1552
+ power: r.power + n.power
1553
+ });
1554
+ }
1555
+ }
1556
+ return o`
1557
+ <span class="section-label">Loadout summary</span>
1558
+ <h2>Traits & gems</h2>
1559
+ <h3>Active traits</h3>
1560
+ <ul class="summary-list">
1561
+ ${e.size === 0 ? o`<li class="summary-row"><span>No traits selected</span></li>` : [...e].map(([e, t]) => o`
1562
+ <li class="summary-row">
1563
+ <span>${this.data?.traitById.get(e)?.name ?? e}</span>
1564
+ <strong>R${t}</strong>
1565
+ </li>
1566
+ `)}
1567
+ </ul>
1568
+ <h3>Socket gems</h3>
1569
+ <ul class="summary-list">
1570
+ ${t.size === 0 ? o`<li class="summary-row"><span>No gems socketed</span></li>` : [...t].map(([e, t]) => o`
1571
+ <li class="summary-row">
1572
+ <span>${e} × ${t.count}</span>
1573
+ <strong>${t.power}</strong>
1574
+ </li>
1575
+ `)}
1576
+ </ul>
1577
+ `;
1578
+ }
1579
+ #f() {
1580
+ if (!this.data || !this.build || !this._calculation) return o``;
1581
+ let e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Map();
1582
+ for (let n of this.build.positions) {
1583
+ for (let t of n.item?.blessings ?? []) e.set(t.blessingId, (e.get(t.blessingId) ?? 0) + t.rank);
1584
+ if (!n.item) continue;
1585
+ let r = this.data.itemById.get(n.item.itemId);
1586
+ for (let e of r?.setBonusIdsByHero[this.build.heroId] ?? []) t.set(e, (t.get(e) ?? 0) + 1);
1587
+ }
1588
+ return o`
1589
+ <span class="section-label">Build output</span>
1590
+ <h2>Bonuses & totals</h2>
1591
+ <h3>Blessings</h3>
1592
+ <ul class="summary-list">
1593
+ ${e.size === 0 ? o`<li class="summary-row"><span>No blessings selected</span></li>` : [...e].map(([e, t]) => o`
1594
+ <li class="summary-row">
1595
+ <span>${this.data?.blessingById.get(e)?.name ?? e}</span>
1596
+ <strong>R${t}</strong>
1597
+ </li>
1598
+ `)}
1599
+ </ul>
1600
+ <h3>Set bonuses</h3>
1601
+ <ul class="summary-list">
1602
+ ${t.size === 0 ? o`<li class="summary-row"><span>No set pieces equipped</span></li>` : [...t].map(([e, t]) => {
1603
+ let n = this.data?.setBonuses.find(({ id: t }) => t === e);
1604
+ return o`
1605
+ <li class="summary-row">
1606
+ <span class=${n?.requiredSetCount !== null && t >= (n?.requiredSetCount ?? 0) ? "active-set" : ""}>${n?.name ?? e}</span>
1607
+ <strong>${t}/${n?.requiredSetCount ?? "?"}</strong>
1608
+ </li>
1609
+ `;
1610
+ })}
1611
+ </ul>
1612
+ <h3>Exact totals</h3>
1613
+ <ul class="breakdown">
1614
+ ${Object.keys(this._calculation.finalTotals).length === 0 ? o`<li class="total-row"><span>No exact totals yet</span></li>` : Object.entries(this._calculation.finalTotals).map(([e, t]) => o`
1615
+ <li class="total-row"><span>${e}</span><strong>${t}</strong></li>
1616
+ `)}
1617
+ </ul>
1618
+ ${this._calculation.conditionalMechanics.length > 0 ? o`
1619
+ <h3>Conditional</h3>
1620
+ ${this._calculation.conditionalMechanics.map((e) => o`
1621
+ <label class="condition">
1622
+ <input
1623
+ type="checkbox"
1624
+ .checked=${e.applied}
1625
+ @change=${() => this.#h(e.id)}
1626
+ />
1627
+ <span>${e.sourceName} · Apply</span>
1628
+ </label>
1629
+ `)}
1630
+ ` : s}
1631
+ <h3>Unsupported</h3>
1632
+ <ul class="breakdown">
1633
+ ${this._calculation.unsupportedContributions.length === 0 ? o`<li class="unsupported-row"><span>None</span></li>` : this._calculation.unsupportedContributions.map((e) => o`
1634
+ <li class="unsupported-row">
1635
+ <span>${e.sourceName}</span>
1636
+ <span class="unsupported-code">${e.reasonCode.replaceAll("-", " ")}</span>
1637
+ </li>
1638
+ `)}
1639
+ </ul>
1640
+ `;
1641
+ }
1642
+ #p;
1643
+ #m;
1644
+ #h(e) {
1645
+ if (!this.build) return;
1646
+ let t = new Set(this.build.disabledConditionalContributionIds);
1647
+ t.has(e) ? t.delete(e) : t.add(e), this.#g({
1648
+ ...this.build,
1649
+ disabledConditionalContributionIds: [...t].sort()
1650
+ }, "conditional", null);
1651
+ }
1652
+ #g(e, t, n) {
1653
+ this.dispatchEvent(new CustomEvent("fellowship-gear-build-change", {
1654
+ bubbles: !0,
1655
+ composed: !0,
1656
+ detail: {
1657
+ build: e,
1658
+ source: t,
1659
+ positionId: n
1660
+ }
1661
+ }));
1662
+ }
1663
+ #_(e) {
1664
+ this.dispatchEvent(new CustomEvent("fellowship-gear-planner-error", {
1665
+ bubbles: !0,
1666
+ composed: !0,
1667
+ detail: {
1668
+ error: e,
1669
+ positionId: this._selectedPositionId
1670
+ }
1671
+ }));
1672
+ }
1673
+ #v;
1674
+ #y;
1675
+ #b;
1676
+ #x;
1677
+ #S(e) {
1678
+ if (!this.resolveAssetUrl) return null;
1679
+ try {
1680
+ return this.resolveAssetUrl(e);
1681
+ } catch (t) {
1682
+ return this.#o.has(e.path) || (this.#o.add(e.path), queueMicrotask(() => {
1683
+ this.isConnected && this.#_({
1684
+ code: "asset-url-error",
1685
+ message: `Could not resolve ${e.path}: ${String(t)}.`,
1686
+ recoverable: !0
1687
+ });
1688
+ })), null;
1689
+ }
1690
+ }
1691
+ #C;
1692
+ };
1693
+ e([l({ attribute: !1 })], j.prototype, "data", void 0), e([l({ attribute: !1 })], j.prototype, "build", void 0), e([l({
1694
+ type: Boolean,
1695
+ reflect: !0
1696
+ })], j.prototype, "loading", void 0), e([l({ attribute: !1 })], j.prototype, "error", void 0), e([l({ attribute: !1 })], j.prototype, "resolveAssetUrl", void 0), e([d()], j.prototype, "_selectedPositionId", void 0), e([d()], j.prototype, "_tab", void 0), e([d()], j.prototype, "_sheetOpen", void 0), e([d()], j.prototype, "_compact", void 0), e([d()], j.prototype, "_announcement", void 0), e([d()], j.prototype, "_calculation", void 0), e([u(".workbench-panel")], j.prototype, "_workbenchPanel", void 0), j = e([c("fellowship-gear-planner")], j);
1697
+ var M = () => {
1698
+ customElements.get("fellowship-gear-planner") || customElements.define("fellowship-gear-planner", j);
1699
+ };
1700
+ //#endregion
1701
+ export { M as n, j as t };