@crockery/fellowship-components 0.4.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +31 -17
  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-Bgt77Ovf.js +1696 -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 +19 -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 -2
  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,1696 @@
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 _, validateGearPlannerBuild as v } from "@crockery/fellowship-data/gear-planner";
8
+ //#region src/gear-planner/gear-position.ts
9
+ r();
10
+ var y = class extends i {
11
+ constructor(...e) {
12
+ super(...e), this.position = null, this.item = null, this.itemBuild = 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.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 })], y.prototype, "position", void 0), e([l({ attribute: !1 })], y.prototype, "item", void 0), e([l({ attribute: !1 })], y.prototype, "itemBuild", void 0), e([l({
184
+ type: Boolean,
185
+ reflect: !0
186
+ })], y.prototype, "selected", void 0), e([l({ type: String })], y.prototype, "imageUrl", void 0), e([l({ attribute: !1 })], y.prototype, "tooltipDetails", void 0), y = e([c("fellowship-gear-position")], y);
187
+ var b = () => {
188
+ customElements.get("fellowship-gear-position") || customElements.define("fellowship-gear-position", y);
189
+ }, x = 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
+ `, S = 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, x];
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 = g(this.data, this.build, this.positionId).filter((e) => {
448
+ let t = this._query.trim().toLocaleLowerCase();
449
+ return !t || (e.item.name ?? e.item.id).toLocaleLowerCase().includes(t);
450
+ });
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
+ ${a.length === 0 ? o`<p class="empty-search">No valid items match this search.</p>` : f(a, (e) => e.item.id, (e) => o`
471
+ <button
472
+ type="button"
473
+ class="item-choice"
474
+ aria-pressed=${String(e.item.id === n?.itemId)}
475
+ ?disabled=${e.validItemLevels.length === 0 || e.validRarities.length === 0}
476
+ @click=${() => this.#l(e.item.id)}
477
+ >
478
+ <span class="choice-mark" aria-hidden="true"></span>
479
+ <span class="choice-image">
480
+ ${this.#o(e.item.images, e.item.name ?? e.item.id)}
481
+ </span>
482
+ <span>${e.item.name ?? e.item.id}</span>
483
+ <small>${e.maxEquipped === 1 ? "Unique" : e.itemType}</small>
484
+ </button>
485
+ `)}
486
+ </div>
487
+ ${n && r && i ? this.#e(n, i) : o`<div class="empty configure-empty">Choose an item to configure it.</div>`}
488
+ `;
489
+ }
490
+ #e(e, t) {
491
+ let n = t.slots.filter((e) => e.rollGroupId === null);
492
+ return o`
493
+ <section class="configuration" aria-label="Item configuration">
494
+ ${this.#a(t)}
495
+ <div class="control-grid">
496
+ <label>
497
+ <span>Item level</span>
498
+ <select
499
+ .value=${String(e.itemLevel)}
500
+ @change=${(t) => this.#h({
501
+ ...e,
502
+ itemLevel: Number(t.currentTarget.value)
503
+ }, "item-level")}
504
+ >
505
+ ${f(t.item.validItemLevels, (e) => e, (e) => o`<option value=${e}>${e}</option>`)}
506
+ </select>
507
+ </label>
508
+ <label>
509
+ <span>Rarity</span>
510
+ <select
511
+ .value=${e.rarity}
512
+ @change=${(t) => this.#h({
513
+ ...e,
514
+ rarity: t.currentTarget.value
515
+ }, "rarity")}
516
+ >
517
+ ${f(t.item.validRarities, (e) => e, (e) => o`<option value=${e}>${e}</option>`)}
518
+ </select>
519
+ </label>
520
+ </div>
521
+
522
+ ${f(n, (e) => e.id, (n) => this.#t(e, n, t))}
523
+ ${f(t.rollGroups, (e) => e.id, (n, r) => {
524
+ let i = t.slots.filter((e) => n.slotIds.includes(e.id));
525
+ return this.#n(e, i, r + 1);
526
+ })}
527
+ ${t.socketTiers.length > 0 ? o`
528
+ <fieldset>
529
+ <legend>Socketed gems</legend>
530
+ ${f(t.socketTiers, (e, t) => t, (t, n) => this.#r(e, t, n))}
531
+ </fieldset>
532
+ ` : s}
533
+ ${t.traitTree ? o`
534
+ <fieldset>
535
+ <legend>Trait topology</legend>
536
+ <p class="hint">Weapon and necklace nodes follow the extracted parent links.</p>
537
+ <div class="trait-grid">
538
+ ${f(t.traitTree.nodes, (e) => e.id, (t) => {
539
+ let n = e.traits.find(({ nodeId: e }) => e === t.id);
540
+ return o`
541
+ <label class="trait-node" style=${`--row:${t.row + 1};--column:${t.column + 1}`}>
542
+ <span>Node ${t.row + 1}.${t.column + 1}</span>
543
+ <select
544
+ aria-label=${`Trait node ${t.row + 1}.${t.column + 1}`}
545
+ .value=${n?.traitId ?? ""}
546
+ @change=${(n) => this.#p(e, t.id, n.currentTarget.value)}
547
+ >
548
+ <option value="">Unassigned</option>
549
+ ${f(t.eligibleTraitIds, (e) => e, (e) => o`
550
+ <option value=${e}>
551
+ ${this.data?.traitById.get(e)?.name ?? e}
552
+ </option>
553
+ `)}
554
+ </select>
555
+ ${n ? o`
556
+ <input
557
+ type="number"
558
+ min="1"
559
+ max=${t.maxRank}
560
+ .value=${String(n.rank)}
561
+ aria-label="Trait rank"
562
+ @change=${(n) => this.#m(e, t.id, Number(n.currentTarget.value), t.maxRank)}
563
+ />
564
+ ` : s}
565
+ </label>
566
+ `;
567
+ })}
568
+ </div>
569
+ </fieldset>
570
+ ` : s}
571
+ </section>
572
+ `;
573
+ }
574
+ #t(e, t, n) {
575
+ if (t.kind === "gem-socket" || t.kind === "item-trait") return o``;
576
+ let r = this.#i(e, t);
577
+ return o`
578
+ <label class="configuration-control">
579
+ <span>${this.#s(t.kind)}</span>
580
+ <select
581
+ .value=${r}
582
+ @change=${(r) => this.#u(e, t, r.currentTarget.value, n)}
583
+ >
584
+ <option value="">${t.required ? "Choose a value" : "None"}</option>
585
+ ${f(t.kind === "blessing" ? n.blessings.map((e) => e.id) : t.allowedChoiceIds, (e) => e, (e) => o`
586
+ <option value=${e}>${this.#c(t.kind, e)}</option>
587
+ `)}
588
+ </select>
589
+ </label>
590
+ `;
591
+ }
592
+ #n(e, t, n) {
593
+ let r = t.find((t) => this.#i(e, t));
594
+ return o`
595
+ <label class="configuration-control">
596
+ <span>Rolled choice ${n}</span>
597
+ <select
598
+ .value=${r ? `${r.id}\u001f${this.#i(e, r)}` : ""}
599
+ @change=${(n) => {
600
+ let r = n.currentTarget.value;
601
+ this.#d(e, t, r);
602
+ }}
603
+ >
604
+ <option value="">Choose one roll</option>
605
+ ${t.flatMap((e) => e.allowedChoiceIds.map((t) => o`
606
+ <option value=${`${e.id}\u001f${t}`}>
607
+ ${this.#s(e.kind)} · ${this.#c(e.kind, t)}
608
+ </option>
609
+ `))}
610
+ </select>
611
+ </label>
612
+ `;
613
+ }
614
+ #r(e, t, n) {
615
+ 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) ?? [];
616
+ return o`
617
+ <label class="configuration-control">
618
+ <span>Socket ${n + 1} · ${t.replace("SocketTier", "Tier ")}</span>
619
+ <select
620
+ .value=${i}
621
+ @change=${(t) => this.#f(e, r, t.currentTarget.value)}
622
+ >
623
+ <option value="">Empty socket</option>
624
+ ${f(s, (e) => e.id, (e) => o`<option value=${e.id}>${e.name} · ${e.power} power</option>`)}
625
+ </select>
626
+ </label>
627
+ `;
628
+ }
629
+ #i(e, t) {
630
+ return t.kind === "blessing" ? e.blessings.find(({ slotId: e }) => e === t.id)?.blessingId ?? "" : e.rolledModifiers.find(({ slotId: e }) => e === t.id)?.choiceId ?? "";
631
+ }
632
+ #a(e) {
633
+ 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;
634
+ return r.length === 0 && !a && c.length === 0 ? o`` : o`
635
+ <aside class="fixed-details" aria-label="Fixed item mechanics">
636
+ <strong>Fixed item identity</strong>
637
+ ${r.length > 0 ? o`<span>Set · ${r.join(", ")}</span>` : s}
638
+ ${a ? o`<span>Relic · ${a.name ?? a.id}</span>` : s}
639
+ ${c.length > 0 ? o`<span>Granted ability · ${c.join(", ")}</span>` : s}
640
+ </aside>
641
+ `;
642
+ }
643
+ #o(e, t) {
644
+ let n = p(e, {
645
+ size: "200",
646
+ type: "hero-item"
647
+ });
648
+ if (!n || !this.resolveAssetUrl) return t.slice(0, 2);
649
+ let r;
650
+ try {
651
+ r = this.resolveAssetUrl(n);
652
+ } catch {
653
+ return t.slice(0, 2);
654
+ }
655
+ return o`
656
+ <img
657
+ src=${r}
658
+ alt=""
659
+ @error=${(e) => {
660
+ e.currentTarget.hidden = !0;
661
+ }}
662
+ />
663
+ `;
664
+ }
665
+ #s(e) {
666
+ return {
667
+ "rolled-stat": "Rolled stat",
668
+ "gem-power": "Gem power",
669
+ "item-trait": "Item trait",
670
+ blessing: "Blessing",
671
+ "gem-socket": "Gem socket"
672
+ }[e];
673
+ }
674
+ #c(e, t) {
675
+ 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");
676
+ }
677
+ #l(e) {
678
+ let t = this.data?.itemById.get(e), n = t?.validItemLevels.at(-1), r = t?.validRarities.at(-1);
679
+ !t || n === void 0 || r === void 0 || this.#h({
680
+ itemId: e,
681
+ itemLevel: n,
682
+ rarity: r,
683
+ rolledModifiers: [],
684
+ gems: [],
685
+ traits: [],
686
+ blessings: []
687
+ }, "item");
688
+ }
689
+ #u(e, t, n, r) {
690
+ if (t.kind === "blessing") {
691
+ let r = e.blessings.filter((e) => e.slotId !== t.id);
692
+ n && r.push({
693
+ slotId: t.id,
694
+ blessingId: n,
695
+ rank: 1
696
+ }), this.#h({
697
+ ...e,
698
+ blessings: r
699
+ }, "blessing");
700
+ return;
701
+ }
702
+ let i = e.rolledModifiers.filter((e) => e.slotId !== t.id);
703
+ n && t.kind !== "gem-socket" && i.push({
704
+ slotId: t.id,
705
+ kind: t.kind,
706
+ choiceId: n
707
+ }), this.#h({
708
+ ...e,
709
+ rolledModifiers: i
710
+ }, "modifier");
711
+ }
712
+ #d(e, t, n) {
713
+ 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);
714
+ c && s && (c.kind === "blessing" ? a.push({
715
+ slotId: c.id,
716
+ blessingId: s,
717
+ rank: 1
718
+ }) : c.kind !== "gem-socket" && i.push({
719
+ slotId: c.id,
720
+ kind: c.kind,
721
+ choiceId: s
722
+ })), this.#h({
723
+ ...e,
724
+ rolledModifiers: i,
725
+ blessings: a
726
+ }, c?.kind === "blessing" ? "blessing" : "modifier");
727
+ }
728
+ #f(e, t, n) {
729
+ let r = e.gems.filter((e) => e.socketId !== t);
730
+ n && r.push({
731
+ socketId: t,
732
+ gemId: n
733
+ }), this.#h({
734
+ ...e,
735
+ gems: r
736
+ }, "gem");
737
+ }
738
+ #p(e, t, n) {
739
+ let r = e.traits.filter((e) => e.nodeId !== t);
740
+ n && r.push({
741
+ nodeId: t,
742
+ traitId: n,
743
+ rank: 1
744
+ }), this.#h({
745
+ ...e,
746
+ traits: r
747
+ }, "trait");
748
+ }
749
+ #m(e, t, n, r) {
750
+ let i = e.traits.map((e) => e.nodeId === t ? {
751
+ ...e,
752
+ rank: Math.max(1, Math.min(r, Math.round(n)))
753
+ } : e);
754
+ this.#h({
755
+ ...e,
756
+ traits: i
757
+ }, "trait");
758
+ }
759
+ #h(e, t) {
760
+ this.dispatchEvent(new CustomEvent("fellowship-workbench-change", {
761
+ bubbles: !0,
762
+ composed: !0,
763
+ detail: {
764
+ item: e,
765
+ source: t
766
+ }
767
+ }));
768
+ }
769
+ #g;
770
+ };
771
+ e([l({ attribute: !1 })], S.prototype, "data", void 0), e([l({ attribute: !1 })], S.prototype, "build", void 0), e([l({ type: String })], S.prototype, "positionId", void 0), e([l({ attribute: !1 })], S.prototype, "resolveAssetUrl", void 0), e([d()], S.prototype, "_query", void 0), S = e([c("fellowship-item-workbench")], S);
772
+ var C = () => {
773
+ customElements.get("fellowship-item-workbench") || customElements.define("fellowship-item-workbench", S);
774
+ }, w = a`
775
+ :host {
776
+ display: block;
777
+ min-width: 0;
778
+ color: var(--_fellowship-poster-plum);
779
+ font: 400 12px/1.5 var(--_fellowship-poster-mono);
780
+ }
781
+
782
+ .container {
783
+ container-type: inline-size;
784
+ min-width: 0;
785
+ }
786
+
787
+ * {
788
+ box-sizing: border-box;
789
+ }
790
+
791
+ button,
792
+ input,
793
+ select {
794
+ font: inherit;
795
+ }
796
+
797
+ .planner {
798
+ min-width: 0;
799
+ padding: 16px;
800
+ border: var(--_fellowship-poster-seam) solid var(--_fellowship-poster-plum);
801
+ background: var(--_fellowship-poster-cream);
802
+ box-shadow: var(--_fellowship-poster-shadow);
803
+ }
804
+
805
+ .hero {
806
+ display: grid;
807
+ grid-template-columns: 72px minmax(0, max-content);
808
+ align-items: center;
809
+ justify-content: center;
810
+ gap: 12px;
811
+ min-width: 0;
812
+ margin: 0 auto 16px;
813
+ text-align: left;
814
+ }
815
+
816
+ .hero-image {
817
+ display: grid;
818
+ place-items: center;
819
+ width: 72px;
820
+ height: 72px;
821
+ overflow: hidden;
822
+ border: 3px solid var(--_fellowship-poster-plum);
823
+ background: var(--_fellowship-poster-violet);
824
+ color: var(--_fellowship-poster-cream);
825
+ font: 800 24px/1 var(--_fellowship-poster-display);
826
+ }
827
+
828
+ .hero-image img {
829
+ width: 100%;
830
+ height: 100%;
831
+ object-fit: cover;
832
+ }
833
+
834
+ .hero-label,
835
+ .section-label {
836
+ display: block;
837
+ margin-bottom: 4px;
838
+ font-size: 10px;
839
+ font-weight: 500;
840
+ letter-spacing: 0.1em;
841
+ text-transform: uppercase;
842
+ }
843
+
844
+ h1,
845
+ h2,
846
+ h3,
847
+ p {
848
+ margin: 0;
849
+ }
850
+
851
+ h1 {
852
+ max-width: 26ch;
853
+ overflow-wrap: anywhere;
854
+ font-family: var(--_fellowship-poster-display);
855
+ font-size: clamp(24px, 4cqi, 48px);
856
+ font-weight: 800;
857
+ line-height: 0.9;
858
+ text-transform: uppercase;
859
+ }
860
+
861
+ h2 {
862
+ margin-bottom: 12px;
863
+ font-family: var(--_fellowship-poster-display);
864
+ font-size: 18px;
865
+ font-weight: 800;
866
+ line-height: 1;
867
+ text-transform: uppercase;
868
+ }
869
+
870
+ h3 {
871
+ margin: 16px 0 6px;
872
+ padding-top: 10px;
873
+ border-top: 2px solid currentColor;
874
+ font-family: var(--_fellowship-poster-display);
875
+ font-size: 13px;
876
+ font-weight: 800;
877
+ letter-spacing: 0.04em;
878
+ text-transform: uppercase;
879
+ }
880
+
881
+ .tabs {
882
+ display: grid;
883
+ grid-template-columns: repeat(3, minmax(0, 1fr));
884
+ margin-bottom: 12px;
885
+ border: 2px solid var(--_fellowship-poster-plum);
886
+ }
887
+
888
+ .tab {
889
+ min-height: 44px;
890
+ border: 0;
891
+ border-right: 2px solid var(--_fellowship-poster-plum);
892
+ background: var(--_fellowship-poster-cream);
893
+ color: var(--_fellowship-poster-plum);
894
+ font-size: 10px;
895
+ font-weight: 500;
896
+ text-transform: uppercase;
897
+ cursor: pointer;
898
+ }
899
+
900
+ .tab:last-child {
901
+ border-right: 0;
902
+ }
903
+
904
+ .tab[aria-selected="true"] {
905
+ background: var(--_fellowship-poster-violet);
906
+ color: var(--_fellowship-poster-cream);
907
+ }
908
+
909
+ .tab:focus-visible,
910
+ .sheet-close:focus-visible,
911
+ .condition:focus-within {
912
+ outline: 4px solid var(--_fellowship-poster-focus);
913
+ outline-offset: 2px;
914
+ }
915
+
916
+ .desktop-grid {
917
+ display: block;
918
+ min-width: 0;
919
+ }
920
+
921
+ .panel {
922
+ min-width: 0;
923
+ padding: 14px;
924
+ border: 2px solid var(--_fellowship-poster-plum);
925
+ background: var(--_fellowship-poster-white);
926
+ }
927
+
928
+ .slot-grid {
929
+ display: grid;
930
+ }
931
+
932
+ .slot-rail {
933
+ display: grid;
934
+ grid-template-columns: repeat(2, minmax(0, 1fr));
935
+ gap: 8px;
936
+ }
937
+
938
+ .summary-list,
939
+ .breakdown {
940
+ display: grid;
941
+ gap: 6px;
942
+ padding: 0;
943
+ list-style: none;
944
+ }
945
+
946
+ .summary-row,
947
+ .total-row,
948
+ .unsupported-row {
949
+ display: flex;
950
+ align-items: start;
951
+ justify-content: space-between;
952
+ gap: 12px;
953
+ padding: 6px 0;
954
+ border-bottom: 1px solid color-mix(in oklch, var(--_fellowship-poster-plum) 28%, transparent);
955
+ }
956
+
957
+ .summary-row span:first-child,
958
+ .total-row span:first-child,
959
+ .unsupported-row span:first-child {
960
+ min-width: 0;
961
+ overflow-wrap: anywhere;
962
+ }
963
+
964
+ .summary-row strong,
965
+ .total-row strong {
966
+ flex: 0 0 auto;
967
+ font-weight: 500;
968
+ }
969
+
970
+ .active-set {
971
+ padding-inline: 4px;
972
+ background: var(--_fellowship-poster-yellow);
973
+ }
974
+
975
+ .unsupported-row {
976
+ color: color-mix(in oklch, var(--_fellowship-poster-plum) 72%, white);
977
+ }
978
+
979
+ .unsupported-code {
980
+ font-size: 9px;
981
+ text-transform: uppercase;
982
+ }
983
+
984
+ .total-row {
985
+ font-size: 13px;
986
+ }
987
+
988
+ .condition {
989
+ display: grid;
990
+ grid-template-columns: 44px minmax(0, 1fr);
991
+ align-items: center;
992
+ min-height: 44px;
993
+ cursor: pointer;
994
+ }
995
+
996
+ .condition input {
997
+ width: 20px;
998
+ height: 20px;
999
+ accent-color: var(--_fellowship-poster-violet);
1000
+ }
1001
+
1002
+ .workbench-panel {
1003
+ min-width: 0;
1004
+ padding: 16px;
1005
+ border: 3px solid var(--_fellowship-poster-plum);
1006
+ background: var(--_fellowship-poster-cream);
1007
+ }
1008
+
1009
+ .workbench-panel:focus {
1010
+ outline: 4px solid var(--_fellowship-poster-focus);
1011
+ outline-offset: -4px;
1012
+ }
1013
+
1014
+ .sheet-backdrop,
1015
+ .sheet-header {
1016
+ display: none;
1017
+ }
1018
+
1019
+ .loading,
1020
+ .empty,
1021
+ .error {
1022
+ display: grid;
1023
+ min-height: 320px;
1024
+ place-items: center;
1025
+ padding: 32px;
1026
+ border: 3px solid var(--_fellowship-poster-plum);
1027
+ background: var(--_fellowship-poster-cream);
1028
+ color: var(--_fellowship-poster-plum);
1029
+ text-align: center;
1030
+ }
1031
+
1032
+ .loading strong,
1033
+ .empty strong,
1034
+ .error strong {
1035
+ display: block;
1036
+ margin-bottom: 8px;
1037
+ font: 800 clamp(24px, 5cqi, 54px) / 0.9 var(--_fellowship-poster-display);
1038
+ text-transform: uppercase;
1039
+ }
1040
+
1041
+ .error {
1042
+ background: var(--_fellowship-poster-yellow);
1043
+ }
1044
+
1045
+ .sr-only {
1046
+ position: absolute;
1047
+ width: 1px;
1048
+ height: 1px;
1049
+ overflow: hidden;
1050
+ clip: rect(0, 0, 0, 0);
1051
+ white-space: nowrap;
1052
+ }
1053
+
1054
+ [hidden] {
1055
+ display: none !important;
1056
+ }
1057
+
1058
+ @container (min-width: 1200px) {
1059
+ .planner {
1060
+ padding: 20px;
1061
+ }
1062
+
1063
+ .hero {
1064
+ grid-template-columns: 92px minmax(0, max-content);
1065
+ margin-bottom: 20px;
1066
+ }
1067
+
1068
+ .hero-image {
1069
+ width: 92px;
1070
+ height: 92px;
1071
+ }
1072
+
1073
+ .tabs {
1074
+ display: none;
1075
+ }
1076
+
1077
+ .desktop-grid {
1078
+ display: grid;
1079
+ grid-template-columns:
1080
+ minmax(170px, 0.78fr)
1081
+ minmax(142px, 0.64fr)
1082
+ minmax(390px, 1.9fr)
1083
+ minmax(142px, 0.64fr)
1084
+ minmax(190px, 0.86fr);
1085
+ align-items: start;
1086
+ gap: 12px;
1087
+ }
1088
+
1089
+ .slot-grid {
1090
+ display: contents;
1091
+ }
1092
+
1093
+ .gear-workspace {
1094
+ display: contents;
1095
+ }
1096
+
1097
+ .slot-rail {
1098
+ display: grid;
1099
+ grid-template-columns: minmax(0, 1fr);
1100
+ gap: 8px;
1101
+ }
1102
+
1103
+ .left-summary {
1104
+ grid-column: 1;
1105
+ }
1106
+
1107
+ .left-rail {
1108
+ grid-column: 2;
1109
+ }
1110
+
1111
+ .workbench-panel {
1112
+ grid-column: 3;
1113
+ min-height: 650px;
1114
+ }
1115
+
1116
+ .right-rail {
1117
+ grid-column: 4;
1118
+ }
1119
+
1120
+ .right-summary {
1121
+ grid-column: 5;
1122
+ }
1123
+
1124
+ .left-summary,
1125
+ .left-rail,
1126
+ .workbench-panel,
1127
+ .right-rail,
1128
+ .right-summary {
1129
+ grid-row: 1;
1130
+ }
1131
+ }
1132
+
1133
+ @container (max-width: 1199px) {
1134
+ .panel[aria-hidden="true"],
1135
+ .gear-workspace[aria-hidden="true"] {
1136
+ display: none;
1137
+ }
1138
+
1139
+ .panel {
1140
+ margin-top: 8px;
1141
+ }
1142
+
1143
+ .workbench-panel {
1144
+ position: fixed;
1145
+ z-index: 1001;
1146
+ inset: 0;
1147
+ display: none;
1148
+ overflow: auto;
1149
+ padding: max(16px, env(safe-area-inset-top)) 16px
1150
+ max(24px, env(safe-area-inset-bottom));
1151
+ border: 0;
1152
+ background: var(--_fellowship-poster-cream);
1153
+ overscroll-behavior: contain;
1154
+ }
1155
+
1156
+ .workbench-panel[data-sheet-open="true"] {
1157
+ display: block;
1158
+ }
1159
+
1160
+ .sheet-header {
1161
+ position: sticky;
1162
+ z-index: 2;
1163
+ top: calc(-1 * max(16px, env(safe-area-inset-top)));
1164
+ display: flex;
1165
+ align-items: center;
1166
+ justify-content: space-between;
1167
+ gap: 12px;
1168
+ margin: calc(-1 * max(16px, env(safe-area-inset-top))) -16px 16px;
1169
+ padding: max(12px, env(safe-area-inset-top)) 16px 12px;
1170
+ border-bottom: 3px solid var(--_fellowship-poster-plum);
1171
+ background: var(--_fellowship-poster-yellow);
1172
+ }
1173
+
1174
+ .sheet-header strong {
1175
+ font: 800 16px/1 var(--_fellowship-poster-display);
1176
+ text-transform: uppercase;
1177
+ }
1178
+
1179
+ .sheet-close {
1180
+ min-width: 44px;
1181
+ min-height: 44px;
1182
+ border: 2px solid var(--_fellowship-poster-plum);
1183
+ background: var(--_fellowship-poster-plum);
1184
+ color: var(--_fellowship-poster-cream);
1185
+ cursor: pointer;
1186
+ }
1187
+ }
1188
+
1189
+ @container (max-width: 560px) {
1190
+ .planner {
1191
+ padding: 8px;
1192
+ border-width: 2px;
1193
+ box-shadow: 2px 2px 0 var(--_fellowship-poster-yellow);
1194
+ }
1195
+
1196
+ .hero {
1197
+ grid-template-columns: 52px minmax(0, 1fr);
1198
+ justify-content: start;
1199
+ margin-bottom: 10px;
1200
+ }
1201
+
1202
+ .hero-image {
1203
+ width: 52px;
1204
+ height: 52px;
1205
+ border-width: 2px;
1206
+ font-size: 18px;
1207
+ }
1208
+
1209
+ h1 {
1210
+ font-size: 24px;
1211
+ }
1212
+
1213
+ .slot-rail {
1214
+ grid-template-columns: 1fr;
1215
+ gap: 6px;
1216
+ }
1217
+
1218
+ .tab {
1219
+ padding-inline: 4px;
1220
+ font-size: 9px;
1221
+ }
1222
+ }
1223
+
1224
+ @media (prefers-reduced-motion: reduce) {
1225
+ *,
1226
+ *::before,
1227
+ *::after {
1228
+ scroll-behavior: auto !important;
1229
+ transition-duration: 0.01ms !important;
1230
+ animation-duration: 0.01ms !important;
1231
+ animation-iteration-count: 1 !important;
1232
+ }
1233
+ }
1234
+ `, T = [
1235
+ "head",
1236
+ "shoulders",
1237
+ "chest",
1238
+ "hands",
1239
+ "waist",
1240
+ "ring-1",
1241
+ "trinket-1"
1242
+ ], E = [
1243
+ "necklace",
1244
+ "back",
1245
+ "wrists",
1246
+ "feet",
1247
+ "ring-2",
1248
+ "trinket-2",
1249
+ "weapon"
1250
+ ], D = "button:not([disabled]), input:not([disabled]), select:not([disabled]), [href], [tabindex]:not([tabindex=\"-1\"])", O = (e) => {
1251
+ let t = [];
1252
+ for (let n of e.children) n instanceof HTMLElement && n.matches(D) && t.push(n), n.shadowRoot && t.push(...O(n.shadowRoot)), t.push(...O(n));
1253
+ return t;
1254
+ }, k = (e) => {
1255
+ let t = e.activeElement;
1256
+ for (; t?.shadowRoot?.activeElement;) t = t.shadowRoot.activeElement;
1257
+ return t;
1258
+ };
1259
+ b(), C();
1260
+ var A = class extends i {
1261
+ constructor(...e) {
1262
+ 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) => {
1263
+ 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(() => {
1264
+ this.#r = null, this.shadowRoot?.querySelector(".workbench-panel")?.focus({ preventScroll: !0 });
1265
+ }, 0));
1266
+ }, this.#m = (e) => {
1267
+ if (e.stopPropagation(), !this.data || !this.build || !this._selectedPositionId) return;
1268
+ let t = {
1269
+ ...this.build,
1270
+ positions: this.build.positions.map((t) => t.positionId === this._selectedPositionId ? {
1271
+ ...t,
1272
+ item: e.detail.item
1273
+ } : t)
1274
+ }, n = _(this.data, t), r = v(this.data, n.build);
1275
+ if (!r.valid || !r.build) {
1276
+ let e = r.issues.find(({ severity: e }) => e === "error");
1277
+ this.#_({
1278
+ code: e?.code ?? "invalid-change",
1279
+ message: e?.message ?? "That item choice is not valid.",
1280
+ recoverable: !0
1281
+ }), this._announcement = e?.message ?? "That item choice was rejected.";
1282
+ return;
1283
+ }
1284
+ 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);
1285
+ 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);
1286
+ }, this.#v = () => {
1287
+ this._sheetOpen = !1, this.updateComplete.then(() => {
1288
+ this.#n !== null && window.cancelAnimationFrame(this.#n), this.#n = window.requestAnimationFrame(() => {
1289
+ this.#n = null, this.#i?.focus({ preventScroll: !0 });
1290
+ });
1291
+ });
1292
+ }, this.#y = (e) => {
1293
+ e.key !== "Escape" || !this._compact || !this._sheetOpen || (e.preventDefault(), this.#v());
1294
+ }, this.#b = (e) => {
1295
+ if (!this._compact || !this._sheetOpen) return;
1296
+ if (e.key === "Escape") {
1297
+ e.preventDefault(), this.#v();
1298
+ return;
1299
+ }
1300
+ if (e.key !== "Tab" || !this._workbenchPanel) return;
1301
+ let t = O(this._workbenchPanel), n = t[0], r = t.at(-1), i = k(this.shadowRoot);
1302
+ if (!n || !r) {
1303
+ e.preventDefault();
1304
+ return;
1305
+ }
1306
+ e.shiftKey && (i === n || i === this._workbenchPanel) ? (e.preventDefault(), r.focus()) : !e.shiftKey && i === r && (e.preventDefault(), n.focus());
1307
+ }, this.#x = (e) => {
1308
+ if (!["ArrowLeft", "ArrowRight"].includes(e.key)) return;
1309
+ e.preventDefault();
1310
+ let t = [
1311
+ "traits",
1312
+ "gear",
1313
+ "bonuses"
1314
+ ], n = e.key === "ArrowRight" ? 1 : -1, r = t.indexOf(this._tab);
1315
+ this._tab = t[(r + n + t.length) % t.length] ?? "gear", this.updateComplete.then(() => {
1316
+ (this.shadowRoot?.querySelector(".tab[aria-selected=\"true\"]"))?.focus();
1317
+ });
1318
+ }, this.#C = (e) => {
1319
+ e.currentTarget.hidden = !0;
1320
+ };
1321
+ }
1322
+ static {
1323
+ this.styles = [n, w];
1324
+ }
1325
+ #e;
1326
+ #t;
1327
+ #n;
1328
+ #r;
1329
+ #i;
1330
+ #a;
1331
+ #o;
1332
+ connectedCallback() {
1333
+ super.connectedCallback(), t(this.ownerDocument), this.#e ??= new ResizeObserver(() => {
1334
+ let e = this.getBoundingClientRect().width < 1200;
1335
+ this.#t !== null && window.cancelAnimationFrame(this.#t), this.#t = window.requestAnimationFrame(() => {
1336
+ this.#t = null, e !== this._compact && (this._compact = e, e || (this._sheetOpen = !1));
1337
+ });
1338
+ }), this.#e.observe(this), this.ownerDocument.addEventListener("keydown", this.#y);
1339
+ }
1340
+ disconnectedCallback() {
1341
+ 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();
1342
+ }
1343
+ willUpdate(e) {
1344
+ 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));
1345
+ }
1346
+ updated(e) {
1347
+ if (super.updated(e), e.has("data") || e.has("build") || e.has("resolveAssetUrl")) {
1348
+ (e.has("data") || e.has("resolveAssetUrl")) && this.#o.clear();
1349
+ let t = !!(this.data && this.build && !this.resolveAssetUrl);
1350
+ t && !this.#a ? (this.#a = !0, this.#_({
1351
+ code: "asset-resolver-missing",
1352
+ message: "A resolveAssetUrl callback is required to render Fellowship images.",
1353
+ recoverable: !0
1354
+ })) : t || (this.#a = !1);
1355
+ }
1356
+ }
1357
+ render() {
1358
+ if (this.loading) return o`
1359
+ <div class="loading" role="status">
1360
+ <div><strong>Loading gear</strong><span>Preparing live-game choices…</span></div>
1361
+ </div>
1362
+ `;
1363
+ if (this.error) return o`
1364
+ <div class="error" role="alert">
1365
+ <div><strong>Planner unavailable</strong><span>${this.error.message}</span></div>
1366
+ </div>
1367
+ `;
1368
+ if (this.data && this.build && !this.resolveAssetUrl) return o`
1369
+ <div class="error" role="alert">
1370
+ <div>
1371
+ <strong>Planner unavailable</strong>
1372
+ <span>A resolveAssetUrl callback is required to render Fellowship images.</span>
1373
+ </div>
1374
+ </div>
1375
+ `;
1376
+ if (!this.data || !this.build) return o`
1377
+ <div class="empty">
1378
+ <div>
1379
+ <strong>Choose your hero</strong>
1380
+ <span>The planner starts only after an explicit hero selection.</span>
1381
+ </div>
1382
+ </div>
1383
+ `;
1384
+ let e = this.data.heroById.get(this.build.heroId);
1385
+ if (!e) return o`
1386
+ <div class="error" role="alert">
1387
+ <div><strong>Unknown hero</strong><span>This build cannot be displayed safely.</span></div>
1388
+ </div>
1389
+ `;
1390
+ let t = p(e.hero.images, {
1391
+ size: "200",
1392
+ type: "hero"
1393
+ }), n = t ? this.#S(t) : null;
1394
+ return o`
1395
+ <div class="container">
1396
+ <section class="planner" aria-label="Fellership Gear Planner">
1397
+ <header class="hero">
1398
+ <span class="hero-image">
1399
+ ${n ? o`<img src=${n} alt="" @error=${this.#C} />` : (e.hero.name ?? e.hero.id).slice(0, 2)}
1400
+ </span>
1401
+ <span>
1402
+ <span class="hero-label">Current hero</span>
1403
+ <h1>${e.hero.name ?? e.hero.id}</h1>
1404
+ </span>
1405
+ </header>
1406
+
1407
+ ${this.#s()}
1408
+ <div class="desktop-grid">
1409
+ <aside
1410
+ class="panel left-summary"
1411
+ aria-label="Traits and gems"
1412
+ aria-hidden=${String(this._compact && this._tab !== "traits")}
1413
+ >
1414
+ ${this.#d()}
1415
+ </aside>
1416
+ <div
1417
+ class="gear-workspace"
1418
+ aria-hidden=${String(this._compact && this._tab !== "gear")}
1419
+ >
1420
+ <div class="slot-grid">
1421
+ <div class="slot-rail left-rail">
1422
+ ${this.#c(this._compact ? this.data.positions.map(({ id: e }) => e) : T)}
1423
+ </div>
1424
+ ${this._compact ? s : o`
1425
+ <div class="slot-rail right-rail">
1426
+ ${this.#c(E)}
1427
+ </div>
1428
+ `}
1429
+ </div>
1430
+ </div>
1431
+ ${this.#l()}
1432
+ <aside
1433
+ class="panel right-summary"
1434
+ aria-label="Blessings, sets and results"
1435
+ aria-hidden=${String(this._compact && this._tab !== "bonuses")}
1436
+ >
1437
+ ${this.#f()}
1438
+ </aside>
1439
+ </div>
1440
+ <p class="sr-only" aria-live="polite">${this._announcement}</p>
1441
+ </section>
1442
+ </div>
1443
+ `;
1444
+ }
1445
+ #s() {
1446
+ return o`
1447
+ <div class="tabs" role="tablist" aria-label="Planner workspace">
1448
+ ${[
1449
+ ["traits", "Traits & gems"],
1450
+ ["gear", "Gear"],
1451
+ ["bonuses", "Bonuses"]
1452
+ ].map(([e, t]) => o`
1453
+ <button
1454
+ class="tab"
1455
+ type="button"
1456
+ role="tab"
1457
+ aria-selected=${String(this._tab === e)}
1458
+ tabindex=${this._tab === e ? "0" : "-1"}
1459
+ @click=${() => {
1460
+ this._tab = e;
1461
+ }}
1462
+ @keydown=${this.#x}
1463
+ >
1464
+ ${t}
1465
+ </button>
1466
+ `)}
1467
+ </div>
1468
+ `;
1469
+ }
1470
+ #c(e) {
1471
+ return !this.data || !this.build ? o`` : o`
1472
+ ${f(e, (e) => e, (e) => {
1473
+ let t = this.data?.positions.find(({ id: t }) => t === e) ?? null, n = this.build?.positions.find((t) => t.positionId === e) ?? null, r = n?.item ? this.data?.itemById.get(n.item.itemId) ?? null : null, i = r ? p(r.item.images, {
1474
+ size: "200",
1475
+ type: "hero-item"
1476
+ }) : null;
1477
+ return o`
1478
+ <fellowship-gear-position
1479
+ .position=${t}
1480
+ .item=${r}
1481
+ .itemBuild=${n?.item ?? null}
1482
+ .selected=${e === this._selectedPositionId}
1483
+ .imageUrl=${i ? this.#S(i) : null}
1484
+ .tooltipDetails=${this.#u(e, n?.item ?? null)}
1485
+ @fellowship-gear-position-select=${this.#p}
1486
+ ></fellowship-gear-position>
1487
+ `;
1488
+ })}
1489
+ `;
1490
+ }
1491
+ #l() {
1492
+ return o`
1493
+ <section
1494
+ class="workbench-panel"
1495
+ data-sheet-open=${String(this._sheetOpen)}
1496
+ role=${this._compact ? "dialog" : "region"}
1497
+ aria-modal=${this._compact ? "true" : s}
1498
+ aria-label="Item workbench"
1499
+ tabindex=${this._compact ? "-1" : s}
1500
+ @keydown=${this.#b}
1501
+ @fellowship-workbench-change=${this.#m}
1502
+ >
1503
+ <header class="sheet-header">
1504
+ <strong>Configure item</strong>
1505
+ <button class="sheet-close" type="button" @click=${this.#v}>Close</button>
1506
+ </header>
1507
+ <fellowship-item-workbench
1508
+ .data=${this.data}
1509
+ .build=${this.build}
1510
+ .positionId=${this._selectedPositionId}
1511
+ .resolveAssetUrl=${this.resolveAssetUrl}
1512
+ ></fellowship-item-workbench>
1513
+ </section>
1514
+ `;
1515
+ }
1516
+ #u(e, t) {
1517
+ if (!this.data || !this.build || !t) return [];
1518
+ let n = this.data.itemById.get(t.itemId);
1519
+ if (!n) return [];
1520
+ 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);
1521
+ return [
1522
+ r ? `Fixed stats · ${r}` : "",
1523
+ i ? `Rolled · ${i}` : "",
1524
+ a ? `Gems · ${a}` : "",
1525
+ o ? `Traits · ${o}` : "",
1526
+ s ? `Blessings · ${s}` : "",
1527
+ c ? `Set · ${c}` : "",
1528
+ u ? `Relic · ${u.name ?? u.id}` : "",
1529
+ n.item.linkedAbilityIds.length > 0 ? `Granted ability · ${n.item.linkedAbilityIds.join(", ")}` : "",
1530
+ d ? "Some mechanics are excluded from exact totals." : ""
1531
+ ].filter(Boolean);
1532
+ }
1533
+ #d() {
1534
+ if (!this.data || !this.build) return o``;
1535
+ let e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Map();
1536
+ for (let n of this.build.positions) {
1537
+ for (let t of n.item?.traits ?? []) e.set(t.traitId, (e.get(t.traitId) ?? 0) + t.rank);
1538
+ for (let e of n.item?.gems ?? []) {
1539
+ let n = this.data.socketGemById.get(e.gemId);
1540
+ if (!n) continue;
1541
+ let r = t.get(n.family) ?? {
1542
+ count: 0,
1543
+ power: 0
1544
+ };
1545
+ t.set(n.family, {
1546
+ count: r.count + 1,
1547
+ power: r.power + n.power
1548
+ });
1549
+ }
1550
+ }
1551
+ return o`
1552
+ <span class="section-label">Loadout summary</span>
1553
+ <h2>Traits & gems</h2>
1554
+ <h3>Active traits</h3>
1555
+ <ul class="summary-list">
1556
+ ${e.size === 0 ? o`<li class="summary-row"><span>No traits selected</span></li>` : [...e].map(([e, t]) => o`
1557
+ <li class="summary-row">
1558
+ <span>${this.data?.traitById.get(e)?.name ?? e}</span>
1559
+ <strong>R${t}</strong>
1560
+ </li>
1561
+ `)}
1562
+ </ul>
1563
+ <h3>Socket gems</h3>
1564
+ <ul class="summary-list">
1565
+ ${t.size === 0 ? o`<li class="summary-row"><span>No gems socketed</span></li>` : [...t].map(([e, t]) => o`
1566
+ <li class="summary-row">
1567
+ <span>${e} × ${t.count}</span>
1568
+ <strong>${t.power}</strong>
1569
+ </li>
1570
+ `)}
1571
+ </ul>
1572
+ `;
1573
+ }
1574
+ #f() {
1575
+ if (!this.data || !this.build || !this._calculation) return o``;
1576
+ let e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Map();
1577
+ for (let n of this.build.positions) {
1578
+ for (let t of n.item?.blessings ?? []) e.set(t.blessingId, (e.get(t.blessingId) ?? 0) + t.rank);
1579
+ if (!n.item) continue;
1580
+ let r = this.data.itemById.get(n.item.itemId);
1581
+ for (let e of r?.setBonusIdsByHero[this.build.heroId] ?? []) t.set(e, (t.get(e) ?? 0) + 1);
1582
+ }
1583
+ return o`
1584
+ <span class="section-label">Build output</span>
1585
+ <h2>Bonuses & totals</h2>
1586
+ <h3>Blessings</h3>
1587
+ <ul class="summary-list">
1588
+ ${e.size === 0 ? o`<li class="summary-row"><span>No blessings selected</span></li>` : [...e].map(([e, t]) => o`
1589
+ <li class="summary-row">
1590
+ <span>${this.data?.blessingById.get(e)?.name ?? e}</span>
1591
+ <strong>R${t}</strong>
1592
+ </li>
1593
+ `)}
1594
+ </ul>
1595
+ <h3>Set bonuses</h3>
1596
+ <ul class="summary-list">
1597
+ ${t.size === 0 ? o`<li class="summary-row"><span>No set pieces equipped</span></li>` : [...t].map(([e, t]) => {
1598
+ let n = this.data?.setBonuses.find(({ id: t }) => t === e);
1599
+ return o`
1600
+ <li class="summary-row">
1601
+ <span class=${n?.requiredSetCount !== null && t >= (n?.requiredSetCount ?? 0) ? "active-set" : ""}>${n?.name ?? e}</span>
1602
+ <strong>${t}/${n?.requiredSetCount ?? "?"}</strong>
1603
+ </li>
1604
+ `;
1605
+ })}
1606
+ </ul>
1607
+ <h3>Exact totals</h3>
1608
+ <ul class="breakdown">
1609
+ ${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`
1610
+ <li class="total-row"><span>${e}</span><strong>${t}</strong></li>
1611
+ `)}
1612
+ </ul>
1613
+ ${this._calculation.conditionalMechanics.length > 0 ? o`
1614
+ <h3>Conditional</h3>
1615
+ ${this._calculation.conditionalMechanics.map((e) => o`
1616
+ <label class="condition">
1617
+ <input
1618
+ type="checkbox"
1619
+ .checked=${e.applied}
1620
+ @change=${() => this.#h(e.id)}
1621
+ />
1622
+ <span>${e.sourceName} · Apply</span>
1623
+ </label>
1624
+ `)}
1625
+ ` : s}
1626
+ <h3>Unsupported</h3>
1627
+ <ul class="breakdown">
1628
+ ${this._calculation.unsupportedContributions.length === 0 ? o`<li class="unsupported-row"><span>None</span></li>` : this._calculation.unsupportedContributions.map((e) => o`
1629
+ <li class="unsupported-row">
1630
+ <span>${e.sourceName}</span>
1631
+ <span class="unsupported-code">${e.reasonCode.replaceAll("-", " ")}</span>
1632
+ </li>
1633
+ `)}
1634
+ </ul>
1635
+ `;
1636
+ }
1637
+ #p;
1638
+ #m;
1639
+ #h(e) {
1640
+ if (!this.build) return;
1641
+ let t = new Set(this.build.disabledConditionalContributionIds);
1642
+ t.has(e) ? t.delete(e) : t.add(e), this.#g({
1643
+ ...this.build,
1644
+ disabledConditionalContributionIds: [...t].sort()
1645
+ }, "conditional", null);
1646
+ }
1647
+ #g(e, t, n) {
1648
+ this.dispatchEvent(new CustomEvent("fellowship-gear-build-change", {
1649
+ bubbles: !0,
1650
+ composed: !0,
1651
+ detail: {
1652
+ build: e,
1653
+ source: t,
1654
+ positionId: n
1655
+ }
1656
+ }));
1657
+ }
1658
+ #_(e) {
1659
+ this.dispatchEvent(new CustomEvent("fellowship-gear-planner-error", {
1660
+ bubbles: !0,
1661
+ composed: !0,
1662
+ detail: {
1663
+ error: e,
1664
+ positionId: this._selectedPositionId
1665
+ }
1666
+ }));
1667
+ }
1668
+ #v;
1669
+ #y;
1670
+ #b;
1671
+ #x;
1672
+ #S(e) {
1673
+ if (!this.resolveAssetUrl) return null;
1674
+ try {
1675
+ return this.resolveAssetUrl(e);
1676
+ } catch (t) {
1677
+ return this.#o.has(e.path) || (this.#o.add(e.path), queueMicrotask(() => {
1678
+ this.isConnected && this.#_({
1679
+ code: "asset-url-error",
1680
+ message: `Could not resolve ${e.path}: ${String(t)}.`,
1681
+ recoverable: !0
1682
+ });
1683
+ })), null;
1684
+ }
1685
+ }
1686
+ #C;
1687
+ };
1688
+ e([l({ attribute: !1 })], A.prototype, "data", void 0), e([l({ attribute: !1 })], A.prototype, "build", void 0), e([l({
1689
+ type: Boolean,
1690
+ reflect: !0
1691
+ })], A.prototype, "loading", void 0), e([l({ attribute: !1 })], A.prototype, "error", void 0), e([l({ attribute: !1 })], A.prototype, "resolveAssetUrl", void 0), e([d()], A.prototype, "_selectedPositionId", void 0), e([d()], A.prototype, "_tab", void 0), e([d()], A.prototype, "_sheetOpen", void 0), e([d()], A.prototype, "_compact", void 0), e([d()], A.prototype, "_announcement", void 0), e([d()], A.prototype, "_calculation", void 0), e([u(".workbench-panel")], A.prototype, "_workbenchPanel", void 0), A = e([c("fellowship-gear-planner")], A);
1692
+ var j = () => {
1693
+ customElements.get("fellowship-gear-planner") || customElements.define("fellowship-gear-planner", A);
1694
+ };
1695
+ //#endregion
1696
+ export { j as n, A as t };