@crockery/fellowship-components 1.1.0 → 2.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.
@@ -4,10 +4,813 @@ import { LitElement as i, css as a, html as o, nothing as s } from "lit";
4
4
  import { customElement as c, property as l, query as u, state as d } from "lit/decorators.js";
5
5
  import { repeat as f } from "lit/directives/repeat.js";
6
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";
7
+ import { calculateGearPlannerBuild as m, getMaxGearPlannerAppliedTempers as h, getValidGearConfiguration as g, getValidGearItems as _, normalizeGearPlannerBuild as v, resolveGearPlannerItemImage as y, resolveGearPlannerItemName as b, validateGearPlannerBuild as x } from "@crockery/fellowship-data/gear-planner";
8
+ //#region src/gear-planner/equipment-selector.styles.ts
9
+ var S = a`
10
+ :host {
11
+ position: fixed;
12
+ z-index: 1002;
13
+ inset: auto;
14
+ left: var(--_selector-x, 12px);
15
+ top: var(--_selector-y, 12px);
16
+ display: grid;
17
+ width: min(400px, calc(100vw - 24px));
18
+ max-height: min(680px, calc(100dvh - 24px));
19
+ margin: 0;
20
+ padding: 0;
21
+ overflow: hidden;
22
+ border: 3px solid var(--_fellowship-poster-plum);
23
+ border-radius: var(--_fellowship-poster-radius);
24
+ background: var(--_fellowship-poster-cream);
25
+ color: var(--_fellowship-poster-plum);
26
+ box-shadow: 6px 6px 0 var(--_fellowship-poster-yellow);
27
+ font: 400 12px/1.45 var(--_fellowship-poster-mono);
28
+ overscroll-behavior: contain;
29
+ }
30
+
31
+ :host(:focus-visible) {
32
+ outline: 4px solid var(--_fellowship-poster-focus);
33
+ outline-offset: -7px;
34
+ }
35
+
36
+ * {
37
+ box-sizing: border-box;
38
+ }
39
+
40
+ button,
41
+ input {
42
+ font: inherit;
43
+ }
44
+
45
+ .selector {
46
+ display: grid;
47
+ grid-template-rows: auto auto minmax(0, 1fr);
48
+ min-height: 0;
49
+ max-height: inherit;
50
+ }
51
+
52
+ .actions {
53
+ position: relative;
54
+ z-index: 2;
55
+ display: grid;
56
+ grid-template-columns: minmax(0, 1fr) minmax(0, 0.7fr);
57
+ gap: 8px;
58
+ padding: 10px;
59
+ border-bottom: 3px solid var(--_fellowship-poster-plum);
60
+ background: var(--_fellowship-poster-cream);
61
+ }
62
+
63
+ .action,
64
+ .chip,
65
+ .candidate {
66
+ border: 2px solid var(--_fellowship-poster-plum);
67
+ border-radius: var(--_fellowship-poster-radius);
68
+ color: var(--_fellowship-poster-plum);
69
+ cursor: pointer;
70
+ transition:
71
+ background var(--_fellowship-poster-motion) var(--_fellowship-poster-ease),
72
+ color var(--_fellowship-poster-motion) var(--_fellowship-poster-ease),
73
+ box-shadow var(--_fellowship-poster-motion) var(--_fellowship-poster-ease),
74
+ transform var(--_fellowship-poster-motion) var(--_fellowship-poster-ease);
75
+ }
76
+
77
+ .action {
78
+ min-width: 0;
79
+ min-height: 44px;
80
+ padding: 8px 10px;
81
+ background: var(--_fellowship-poster-white);
82
+ font-weight: 500;
83
+ text-transform: uppercase;
84
+ }
85
+
86
+ .action-primary {
87
+ background: var(--_fellowship-poster-yellow);
88
+ font-family: var(--_fellowship-poster-display);
89
+ font-weight: 800;
90
+ }
91
+
92
+ .action-expand {
93
+ display: none;
94
+ background: var(--_fellowship-poster-violet);
95
+ color: var(--_fellowship-poster-cream);
96
+ }
97
+
98
+ .action:hover:not(:disabled),
99
+ .chip:hover,
100
+ .candidate:hover {
101
+ box-shadow: 3px 3px 0 var(--_fellowship-poster-violet);
102
+ transform: translate(-1px, -1px);
103
+ }
104
+
105
+ .action:active:not(:disabled),
106
+ .chip:active,
107
+ .candidate:active {
108
+ box-shadow: none;
109
+ transform: translate(0, 0);
110
+ }
111
+
112
+ .action:disabled {
113
+ cursor: not-allowed;
114
+ opacity: 0.42;
115
+ }
116
+
117
+ .search-and-filters {
118
+ position: relative;
119
+ z-index: 1;
120
+ display: grid;
121
+ gap: 8px;
122
+ padding: 10px;
123
+ border-bottom: 2px solid var(--_fellowship-poster-plum);
124
+ background: var(--_fellowship-poster-white);
125
+ }
126
+
127
+ .search {
128
+ display: block;
129
+ }
130
+
131
+ .search input {
132
+ width: 100%;
133
+ min-height: 44px;
134
+ padding: 9px 12px;
135
+ border: 2px solid var(--_fellowship-poster-plum);
136
+ border-radius: var(--_fellowship-poster-radius);
137
+ background: var(--_fellowship-poster-cream);
138
+ color: var(--_fellowship-poster-plum);
139
+ font-weight: 500;
140
+ }
141
+
142
+ .chips {
143
+ display: flex;
144
+ gap: 6px;
145
+ min-width: 0;
146
+ margin: 0;
147
+ padding: 0 2px 3px 0;
148
+ overflow-x: auto;
149
+ border: 0;
150
+ scrollbar-width: thin;
151
+ }
152
+
153
+ .chip {
154
+ flex: 0 0 auto;
155
+ min-height: 44px;
156
+ padding: 7px 12px;
157
+ background: var(--_fellowship-poster-cream);
158
+ font-size: 10px;
159
+ font-weight: 500;
160
+ letter-spacing: 0.04em;
161
+ text-transform: uppercase;
162
+ }
163
+
164
+ .chip[aria-pressed="true"] {
165
+ background: var(--_fellowship-poster-violet);
166
+ color: var(--_fellowship-poster-cream);
167
+ }
168
+
169
+ .candidate-list {
170
+ display: grid;
171
+ align-content: start;
172
+ min-height: 0;
173
+ overflow: auto;
174
+ padding: 6px;
175
+ background: var(--_fellowship-poster-cream);
176
+ scrollbar-width: thin;
177
+ }
178
+
179
+ .candidate {
180
+ display: grid;
181
+ grid-template-columns: 50px minmax(0, 1fr) auto;
182
+ gap: 3px 10px;
183
+ align-items: center;
184
+ width: 100%;
185
+ min-width: 0;
186
+ min-height: 72px;
187
+ padding: 8px;
188
+ border-width: 0 0 2px;
189
+ background: var(--_fellowship-poster-cream);
190
+ text-align: left;
191
+ }
192
+
193
+ .candidate:last-child {
194
+ border-bottom: 0;
195
+ }
196
+
197
+ .candidate[aria-pressed="true"] {
198
+ background: color-mix(
199
+ in oklch,
200
+ var(--_fellowship-poster-yellow) 52%,
201
+ var(--_fellowship-poster-cream)
202
+ );
203
+ }
204
+
205
+ .choice-image,
206
+ .choice-image img {
207
+ width: 48px;
208
+ height: 48px;
209
+ }
210
+
211
+ .choice-image {
212
+ position: relative;
213
+ grid-row: 1 / span 2;
214
+ display: grid;
215
+ place-items: center;
216
+ overflow: hidden;
217
+ border: 2px solid var(--_fellowship-rarity-border, var(--_fellowship-poster-plum));
218
+ background: var(--_fellowship-rarity-start, var(--_fellowship-poster-violet));
219
+ color: var(--_fellowship-poster-cream);
220
+ font: 800 13px/1 var(--_fellowship-poster-display);
221
+ text-transform: uppercase;
222
+ }
223
+
224
+ .choice-image img {
225
+ position: absolute;
226
+ inset: 0;
227
+ object-fit: cover;
228
+ }
229
+
230
+ .candidate-copy {
231
+ min-width: 0;
232
+ }
233
+
234
+ .candidate-name {
235
+ display: block;
236
+ overflow: hidden;
237
+ font: 800 14px/1.05 var(--_fellowship-poster-display);
238
+ text-overflow: ellipsis;
239
+ text-transform: uppercase;
240
+ white-space: nowrap;
241
+ }
242
+
243
+ .candidate-tags {
244
+ display: flex;
245
+ flex-wrap: wrap;
246
+ gap: 4px;
247
+ margin-top: 5px;
248
+ }
249
+
250
+ .tag {
251
+ padding: 2px 5px;
252
+ background: var(--_fellowship-poster-plum);
253
+ color: var(--_fellowship-poster-cream);
254
+ font-size: 8px;
255
+ font-weight: 500;
256
+ letter-spacing: 0.06em;
257
+ text-transform: uppercase;
258
+ }
259
+
260
+ .candidate-delta {
261
+ align-self: start;
262
+ min-width: max-content;
263
+ padding: 4px 6px;
264
+ background: var(--_fellowship-poster-yellow);
265
+ font-size: 10px;
266
+ font-weight: 500;
267
+ }
268
+
269
+ .candidate[aria-pressed="true"] .candidate-delta {
270
+ background: var(--_fellowship-poster-violet);
271
+ color: var(--_fellowship-poster-cream);
272
+ }
273
+
274
+ .candidate-stats {
275
+ grid-column: 2 / 4;
276
+ display: flex;
277
+ flex-wrap: wrap;
278
+ gap: 3px 8px;
279
+ min-width: 0;
280
+ color: color-mix(
281
+ in oklch,
282
+ var(--_fellowship-poster-plum) 72%,
283
+ var(--_fellowship-poster-cream)
284
+ );
285
+ font-size: 9px;
286
+ }
287
+
288
+ .partial {
289
+ color: var(--_fellowship-poster-violet);
290
+ font-weight: 500;
291
+ text-transform: uppercase;
292
+ }
293
+
294
+ .no-results {
295
+ display: grid;
296
+ min-height: 180px;
297
+ place-items: center;
298
+ padding: 24px;
299
+ border: 2px dashed var(--_fellowship-poster-plum);
300
+ text-align: center;
301
+ }
302
+
303
+ .no-results strong {
304
+ display: block;
305
+ margin-bottom: 6px;
306
+ font: 800 20px/1 var(--_fellowship-poster-display);
307
+ text-transform: uppercase;
308
+ }
309
+
310
+ .action:focus-visible,
311
+ .chip:focus-visible,
312
+ .candidate:focus-visible,
313
+ .search input:focus-visible {
314
+ position: relative;
315
+ z-index: 3;
316
+ outline: 4px solid var(--_fellowship-poster-focus);
317
+ outline-offset: 2px;
318
+ }
319
+
320
+ .sr-only {
321
+ position: absolute;
322
+ width: 1px;
323
+ height: 1px;
324
+ overflow: hidden;
325
+ clip: rect(0, 0, 0, 0);
326
+ white-space: nowrap;
327
+ }
328
+
329
+ [hidden] {
330
+ display: none !important;
331
+ }
332
+
333
+ :host([compact]) {
334
+ inset: auto 0 0;
335
+ width: 100%;
336
+ max-width: none;
337
+ height: 68dvh;
338
+ max-height: calc(100dvh - env(safe-area-inset-top));
339
+ border-width: 3px 0 0;
340
+ box-shadow: 0 -6px 0 var(--_fellowship-poster-yellow);
341
+ transition: height var(--_fellowship-poster-motion) var(--_fellowship-poster-ease);
342
+ }
343
+
344
+ :host([compact][expanded]) {
345
+ height: 92dvh;
346
+ }
347
+
348
+ :host([compact]) .actions {
349
+ grid-template-columns: minmax(0, 1fr) minmax(0, 0.7fr) auto;
350
+ padding-top: 12px;
351
+ }
352
+
353
+ :host([compact]) .action-expand {
354
+ display: block;
355
+ min-width: 72px;
356
+ }
357
+
358
+ :host([compact]) .candidate-list {
359
+ padding-bottom: max(12px, env(safe-area-inset-bottom));
360
+ }
361
+
362
+ @media (max-width: 430px) {
363
+ .candidate {
364
+ grid-template-columns: 46px minmax(0, 1fr) auto;
365
+ gap-inline: 8px;
366
+ min-height: 68px;
367
+ padding-inline: 6px;
368
+ }
369
+
370
+ .choice-image,
371
+ .choice-image img {
372
+ width: 44px;
373
+ height: 44px;
374
+ }
375
+
376
+ .candidate-name {
377
+ font-size: 13px;
378
+ }
379
+
380
+ .candidate-delta {
381
+ font-size: 9px;
382
+ }
383
+
384
+ :host([compact]) .actions {
385
+ gap: 6px;
386
+ padding-inline: 8px;
387
+ }
388
+
389
+ .action {
390
+ padding-inline: 7px;
391
+ font-size: 10px;
392
+ }
393
+
394
+ :host([compact]) .action-expand {
395
+ min-width: 64px;
396
+ }
397
+ }
398
+
399
+ @media (prefers-reduced-motion: reduce) {
400
+ :host,
401
+ .action,
402
+ .chip,
403
+ .candidate {
404
+ transition-duration: 0.01ms !important;
405
+ }
406
+
407
+ .action:hover:not(:disabled),
408
+ .chip:hover,
409
+ .candidate:hover {
410
+ transform: none;
411
+ }
412
+ }
413
+ `, C = a`
414
+ [data-rarity="Common"] {
415
+ --_fellowship-rarity-start: var(--fellowship-rarity-common-start, #545456);
416
+ --_fellowship-rarity-end: var(--fellowship-rarity-common-end, #909291);
417
+ --_fellowship-rarity-border: var(--fellowship-rarity-common-border, #b8bab9);
418
+ }
419
+
420
+ [data-rarity="Uncommon"] {
421
+ --_fellowship-rarity-start: var(--fellowship-rarity-uncommon-start, #235824);
422
+ --_fellowship-rarity-end: var(--fellowship-rarity-uncommon-end, #509650);
423
+ --_fellowship-rarity-border: var(--fellowship-rarity-uncommon-border, #77bd74);
424
+ }
425
+
426
+ [data-rarity="Rare"] {
427
+ --_fellowship-rarity-start: var(--fellowship-rarity-rare-start, #253358);
428
+ --_fellowship-rarity-end: var(--fellowship-rarity-rare-end, #475b80);
429
+ --_fellowship-rarity-border: var(--fellowship-rarity-rare-border, #718bb7);
430
+ }
431
+
432
+ [data-rarity="Epic"] {
433
+ --_fellowship-rarity-start: var(--fellowship-rarity-epic-start, #402858);
434
+ --_fellowship-rarity-end: var(--fellowship-rarity-epic-end, #6b4b8e);
435
+ --_fellowship-rarity-border: var(--fellowship-rarity-epic-border, #9b72c3);
436
+ }
437
+
438
+ [data-rarity="Champion"] {
439
+ --_fellowship-rarity-start: var(--fellowship-rarity-champion-start, #8a6222);
440
+ --_fellowship-rarity-end: var(--fellowship-rarity-champion-end, #c49d3f);
441
+ --_fellowship-rarity-border: var(--fellowship-rarity-champion-border, #e7c96e);
442
+ }
443
+
444
+ [data-rarity="Heroic"] {
445
+ --_fellowship-rarity-start: var(--fellowship-rarity-heroic-start, #7b3538);
446
+ --_fellowship-rarity-end: var(--fellowship-rarity-heroic-end, #c64d42);
447
+ --_fellowship-rarity-border: var(--fellowship-rarity-heroic-border, #e37b6f);
448
+ }
449
+
450
+ [data-rarity="Legendary"] {
451
+ --_fellowship-rarity-start: var(--fellowship-rarity-legendary-start, #8f4a2f);
452
+ --_fellowship-rarity-end: var(--fellowship-rarity-legendary-end, #d07c44);
453
+ --_fellowship-rarity-border: var(--fellowship-rarity-legendary-border, #efa36c);
454
+ }
455
+
456
+ [data-rarity] {
457
+ border-color: var(--_fellowship-rarity-border);
458
+ background: linear-gradient(
459
+ 145deg,
460
+ var(--_fellowship-rarity-start),
461
+ var(--_fellowship-rarity-end)
462
+ );
463
+ }
464
+ `, w = {
465
+ bubbles: !0,
466
+ composed: !0
467
+ }, T = [
468
+ {
469
+ id: "critical-strike",
470
+ stat: "CritChance",
471
+ label: "Crit"
472
+ },
473
+ {
474
+ id: "expertise",
475
+ stat: "Expertise",
476
+ label: "Expertise"
477
+ },
478
+ {
479
+ id: "haste",
480
+ stat: "Haste",
481
+ label: "Haste"
482
+ },
483
+ {
484
+ id: "spirit",
485
+ stat: "Spirit",
486
+ label: "Spirit"
487
+ },
488
+ {
489
+ id: "armor",
490
+ stat: "Armor",
491
+ label: "Armor"
492
+ }
493
+ ], E = (e) => {
494
+ let t = e.validItemLevels.at(-1), n = e.validRarities.at(-1);
495
+ return t === void 0 || n === void 0 ? null : {
496
+ itemId: e.item.id,
497
+ itemLevel: t,
498
+ rarity: n,
499
+ appliedTempers: 0,
500
+ rolledModifiers: [],
501
+ gems: [],
502
+ traits: [],
503
+ blessings: []
504
+ };
505
+ }, D = (e, t, n) => ({
506
+ ...e,
507
+ positions: e.positions.map((e) => e.positionId === t ? {
508
+ ...e,
509
+ item: n
510
+ } : e)
511
+ }), O = (e) => new Map(e.heroSheet.sections.flatMap((e) => e.rows.map((e) => [e.id, e]))), k = (e, t = 0) => {
512
+ let n = Number(e.toFixed(t));
513
+ return `${n > 0 ? "+" : ""}${new Intl.NumberFormat("en-US", {
514
+ minimumFractionDigits: t,
515
+ maximumFractionDigits: t
516
+ }).format(n)}`;
517
+ }, A = (e, t) => e.displayKind === "percentage" ? `${e.label} ${k(t * 100, e.decimals)}%` : `${e.label} ${k(t, e.decimals)}`, j = (e, t, n, r) => {
518
+ let i = [];
519
+ (e.setBonusIdsByHero[n.hero.id] ?? []).length > 0 && i.push({
520
+ id: "set",
521
+ label: "Set"
522
+ }), e.maxEquipped === 1 && i.push({
523
+ id: "unique",
524
+ label: "Unique"
525
+ });
526
+ let a = new Set(t.fixedItemStats.filter((e) => e.positionId === r && e.stat).map((e) => e.stat));
527
+ for (let e of T) a.has(e.stat) && i.push({
528
+ id: e.id,
529
+ label: e.label
530
+ });
531
+ return e.item.linkedAbilityIds.length > 0 && i.push({
532
+ id: "effect",
533
+ label: "Effect"
534
+ }), e.relicIdsByHero[n.hero.id] && i.push({
535
+ id: "relic",
536
+ label: "Relic"
537
+ }), i;
538
+ }, M = (e, t) => {
539
+ let n = new Map(e.map((e) => [e.id, e]));
540
+ return [
541
+ n.get("set"),
542
+ n.get("unique"),
543
+ ...T.map((e) => n.get(e.id)),
544
+ n.get("effect"),
545
+ n.get("relic")
546
+ ].filter((e) => !!e).map((e) => e.label).concat(t).slice(0, 2);
547
+ }, N = (e, t, n) => {
548
+ let r = e.heroById.get(t.heroId), i = e.positions.find((e) => e.id === n);
549
+ if (!r || !i) return [];
550
+ let a = t.positions.find((e) => e.positionId === n)?.item ?? null, o = m(e, t), s = O(o), c = s.get("power-score")?.value ?? 0, l = o.unsupportedContributions.some((e) => e.positionId === n), u = [
551
+ r.primaryStat?.toLocaleLowerCase(),
552
+ "critical-strike",
553
+ "expertise",
554
+ "haste",
555
+ "spirit",
556
+ "armor"
557
+ ].filter((e) => !!e);
558
+ return _(e, t, n).flatMap((d) => {
559
+ let f = d.item.id === a?.itemId, p = f ? a : E(d);
560
+ if (!p) return [];
561
+ let h = f ? t : D(t, n, p), g = f ? o : m(e, h), _ = O(g), v = j(d, g, r, n), y = u.flatMap((e) => {
562
+ let t = _.get(e), n = s.get(e);
563
+ if (!t) return [];
564
+ let r = t.value - (n?.value ?? 0);
565
+ return Math.abs(r) > 1e-4 ? [A(t, r)] : [];
566
+ });
567
+ return [{
568
+ item: d,
569
+ itemBuild: p,
570
+ name: b(d, r),
571
+ rarity: p.rarity,
572
+ features: v,
573
+ tags: M(v, i.itemType),
574
+ powerDelta: (_.get("power-score")?.value ?? 0) - c,
575
+ statDeltas: y.slice(0, 2),
576
+ partial: l || g.unsupportedContributions.some((e) => e.positionId === n),
577
+ equipped: f
578
+ }];
579
+ });
580
+ }, P = (e) => {
581
+ let t = /* @__PURE__ */ new Map();
582
+ for (let n of e) for (let e of n.features) {
583
+ let n = t.get(e.id) ?? {
584
+ label: e.label,
585
+ count: 0
586
+ };
587
+ t.set(e.id, {
588
+ ...n,
589
+ count: n.count + 1
590
+ });
591
+ }
592
+ let n = [{
593
+ id: "all",
594
+ label: "All"
595
+ }];
596
+ for (let e of ["set", "unique"]) {
597
+ let r = t.get(e);
598
+ r && n.push({
599
+ id: e,
600
+ label: r.label
601
+ });
602
+ }
603
+ let r = [
604
+ ...T.map((e) => e.id),
605
+ "effect",
606
+ "relic"
607
+ ];
608
+ return n.push(...r.flatMap((n) => {
609
+ let r = t.get(n);
610
+ return r && r.count < e.length ? [{
611
+ id: n,
612
+ label: r.label
613
+ }] : [];
614
+ }).slice(0, 2)), n;
615
+ }, F = class extends i {
616
+ constructor(...e) {
617
+ super(...e), this.data = null, this.build = null, this.positionId = null, this.resolveAssetUrl = null, this.compact = !1, this.expanded = !1, this._query = "", this._filterId = "all", this._candidates = [], this._filters = [{
618
+ id: "all",
619
+ label: "All"
620
+ }], this._visibleCandidates = [], this.#n = () => {
621
+ this.dispatchEvent(new CustomEvent("fellowship-equipment-selector-configure", w));
622
+ }, this.#r = () => {
623
+ this.dispatchEvent(new CustomEvent("fellowship-equipment-selector-unequip", w));
624
+ }, this.#i = () => {
625
+ this.expanded = !this.expanded;
626
+ }, this.#a = (e) => {
627
+ this._query = e.currentTarget.value;
628
+ }, this.#o = (e) => {
629
+ if (e.key === "Escape") {
630
+ e.preventDefault(), e.stopPropagation(), this.dispatchEvent(new CustomEvent("fellowship-equipment-selector-dismiss", w));
631
+ return;
632
+ }
633
+ if (e.key !== "Tab") return;
634
+ let t = [...this.shadowRoot.querySelectorAll("button:not([disabled]), input:not([disabled]), [href], [tabindex]:not([tabindex=\"-1\"])")].filter((e) => e.getClientRects().length > 0), n = t[0], r = t.at(-1);
635
+ if (!n || !r) {
636
+ e.preventDefault();
637
+ return;
638
+ }
639
+ e.shiftKey && this.shadowRoot.activeElement === n ? (e.preventDefault(), r.focus()) : !e.shiftKey && this.shadowRoot.activeElement === r && (e.preventDefault(), n.focus());
640
+ };
641
+ }
642
+ static {
643
+ this.styles = [
644
+ n,
645
+ C,
646
+ S
647
+ ];
648
+ }
649
+ willUpdate(e) {
650
+ super.willUpdate(e);
651
+ let t = e.has("data") || e.has("build") || e.has("positionId");
652
+ if (t && (this._candidates = this.data && this.build && this.positionId ? N(this.data, this.build, this.positionId) : [], this._filters = P(this._candidates), e.has("positionId") ? (this._query = "", this._filterId = "all", this.expanded = !1) : this._filters.some((e) => e.id === this._filterId) || (this._filterId = "all")), t || e.has("_query") || e.has("_filterId")) {
653
+ let e = this._query.trim().toLocaleLowerCase();
654
+ this._visibleCandidates = this._candidates.filter((t) => (!e || t.name.toLocaleLowerCase().includes(e)) && (this._filterId === "all" || t.features.some((e) => e.id === this._filterId)));
655
+ }
656
+ }
657
+ focusInitial() {
658
+ if (this.compact) {
659
+ this.focus({ preventScroll: !0 });
660
+ return;
661
+ }
662
+ this._searchInput?.focus({ preventScroll: !0 });
663
+ }
664
+ render() {
665
+ let e = this.data?.positions.find(({ id: e }) => e === this.positionId) ?? null, t = this.build?.positions.find(({ positionId: e }) => e === this.positionId)?.item ?? null;
666
+ return o`
667
+ <section class="selector" @keydown=${this.#o}>
668
+ <div class="actions">
669
+ <button
670
+ class="action action-primary"
671
+ type="button"
672
+ ?disabled=${!t}
673
+ @click=${this.#n}
674
+ >
675
+ Configure current
676
+ </button>
677
+ <button
678
+ class="action"
679
+ type="button"
680
+ ?disabled=${!t}
681
+ @click=${this.#r}
682
+ >
683
+ Unequip
684
+ </button>
685
+ <button
686
+ class="action action-expand"
687
+ type="button"
688
+ aria-expanded=${String(this.expanded)}
689
+ aria-hidden=${String(!this.compact)}
690
+ ?disabled=${!this.compact}
691
+ @click=${this.#i}
692
+ >
693
+ ${this.expanded ? "Collapse" : "Expand"}
694
+ </button>
695
+ </div>
696
+
697
+ <div class="search-and-filters">
698
+ <label class="search">
699
+ <span class="sr-only">Search valid ${e?.label ?? "equipment"} items</span>
700
+ <input
701
+ type="search"
702
+ .value=${this._query}
703
+ placeholder=${`Search ${e?.label?.toLocaleLowerCase() ?? "equipment"}`}
704
+ @input=${this.#a}
705
+ />
706
+ </label>
707
+ <fieldset class="chips">
708
+ <legend class="sr-only">Quick filters</legend>
709
+ ${f(this._filters, (e) => e.id, (e) => o`
710
+ <button
711
+ class="chip"
712
+ type="button"
713
+ aria-pressed=${String(this._filterId === e.id)}
714
+ @click=${() => {
715
+ this._filterId = e.id;
716
+ }}
717
+ >
718
+ ${e.label}
719
+ </button>
720
+ `)}
721
+ </fieldset>
722
+ </div>
723
+
724
+ <section
725
+ class="candidate-list"
726
+ aria-label=${`Alphabetical equipment choices for ${e?.label ?? "this slot"}`}
727
+ >
728
+ ${this._visibleCandidates.length === 0 ? o`
729
+ <div class="no-results" role="status">
730
+ <div>
731
+ <strong>No matching equipment</strong>
732
+ <span>Clear search or filters to see valid items.</span>
733
+ </div>
734
+ </div>
735
+ ` : f(this._visibleCandidates, (e) => e.item.item.id, (e) => this.#e(e))}
736
+ </section>
737
+ </section>
738
+ `;
739
+ }
740
+ #e(e) {
741
+ let t = this.data && this.build ? this.data.heroById.get(this.build.heroId) : null, n = this.data && t ? y(e.item, t, e.rarity, "200") : null, r = null;
742
+ if (n && this.resolveAssetUrl) try {
743
+ r = this.resolveAssetUrl(n);
744
+ } catch {
745
+ r = null;
746
+ }
747
+ return o`
748
+ <button
749
+ class="candidate"
750
+ type="button"
751
+ aria-pressed=${String(e.equipped)}
752
+ aria-label=${`${e.name}. ${e.equipped ? "Equipped" : e.powerDelta === 0 ? "No power change" : `${k(e.powerDelta)} power`}`}
753
+ @click=${() => this.#t(e)}
754
+ >
755
+ <span class="choice-image" data-rarity=${e.rarity} aria-hidden="true">
756
+ <span>${e.name.slice(0, 2)}</span>
757
+ ${r ? o`
758
+ <img
759
+ src=${r}
760
+ alt=""
761
+ @error=${(e) => {
762
+ e.currentTarget.hidden = !0;
763
+ }}
764
+ />
765
+ ` : s}
766
+ </span>
767
+ <span class="candidate-copy">
768
+ <span class="candidate-name">${e.name}</span>
769
+ <span class="candidate-tags">
770
+ ${f(e.tags, (e) => e, (e) => o`<span class="tag">${e}</span>`)}
771
+ </span>
772
+ </span>
773
+ <span class="candidate-delta">
774
+ ${e.equipped ? "Equipped" : e.powerDelta === 0 ? "0 Power" : `${k(e.powerDelta)} Power`}
775
+ </span>
776
+ <span class="candidate-stats">
777
+ <span>
778
+ ${e.statDeltas.length > 0 ? e.statDeltas.join(" · ") : "No key-stat change"}
779
+ </span>
780
+ ${e.partial ? o`<span class="partial">Partial estimate</span>` : s}
781
+ </span>
782
+ </button>
783
+ `;
784
+ }
785
+ #t(e) {
786
+ this.dispatchEvent(new CustomEvent("fellowship-equipment-selector-select", {
787
+ ...w,
788
+ detail: {
789
+ item: e.itemBuild,
790
+ name: e.name
791
+ }
792
+ }));
793
+ }
794
+ #n;
795
+ #r;
796
+ #i;
797
+ #a;
798
+ #o;
799
+ };
800
+ e([l({ attribute: !1 })], F.prototype, "data", void 0), e([l({ attribute: !1 })], F.prototype, "build", void 0), e([l({ type: String })], F.prototype, "positionId", void 0), e([l({ attribute: !1 })], F.prototype, "resolveAssetUrl", void 0), e([l({
801
+ type: Boolean,
802
+ reflect: !0
803
+ })], F.prototype, "compact", void 0), e([l({
804
+ type: Boolean,
805
+ reflect: !0
806
+ })], F.prototype, "expanded", void 0), e([d()], F.prototype, "_query", void 0), e([d()], F.prototype, "_filterId", void 0), e([d()], F.prototype, "_candidates", void 0), e([d()], F.prototype, "_filters", void 0), e([d()], F.prototype, "_visibleCandidates", void 0), e([u("input[type='search']")], F.prototype, "_searchInput", void 0), F = e([c("fellowship-equipment-selector")], F);
807
+ var I = () => {
808
+ customElements.get("fellowship-equipment-selector") || customElements.define("fellowship-equipment-selector", F);
809
+ };
810
+ //#endregion
8
811
  //#region src/gear-planner/gear-position.ts
9
812
  r();
10
- var b = class extends i {
813
+ var L = class extends i {
11
814
  constructor(...e) {
12
815
  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
816
  this.dispatchEvent(new CustomEvent("fellowship-gear-position-select", {
@@ -23,7 +826,7 @@ var b = class extends i {
23
826
  };
24
827
  }
25
828
  static {
26
- this.styles = a`
829
+ this.styles = [a`
27
830
  * {
28
831
  box-sizing: border-box;
29
832
  }
@@ -140,7 +943,7 @@ var b = class extends i {
140
943
  transition: none;
141
944
  }
142
945
  }
143
- `;
946
+ `, C];
144
947
  }
145
948
  render() {
146
949
  if (!this.position) return o``;
@@ -151,7 +954,7 @@ var b = class extends i {
151
954
  aria-label=${`${this.position.label}: ${t}`}
152
955
  @click=${this.#e}
153
956
  >
154
- <span class="image">
957
+ <span class="image" data-rarity=${this.itemBuild?.rarity ?? s}>
155
958
  ${this.imageUrl ? o`<img src=${this.imageUrl} alt="" @error=${this.#t} />` : this.position.label.slice(0, 2)}
156
959
  </span>
157
960
  <span class="copy">
@@ -180,13 +983,13 @@ var b = class extends i {
180
983
  #e;
181
984
  #t;
182
985
  };
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({
986
+ e([l({ attribute: !1 })], L.prototype, "position", void 0), e([l({ attribute: !1 })], L.prototype, "item", void 0), e([l({ attribute: !1 })], L.prototype, "itemBuild", void 0), e([l({ type: String })], L.prototype, "itemName", void 0), e([l({
184
987
  type: Boolean,
185
988
  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`
989
+ })], L.prototype, "selected", void 0), e([l({ type: String })], L.prototype, "imageUrl", void 0), e([l({ attribute: !1 })], L.prototype, "tooltipDetails", void 0), L = e([c("fellowship-gear-position")], L);
990
+ var R = () => {
991
+ customElements.get("fellowship-gear-position") || customElements.define("fellowship-gear-position", L);
992
+ }, z = a`
190
993
  :host {
191
994
  display: block;
192
995
  min-width: 0;
@@ -234,11 +1037,6 @@ var x = () => {
234
1037
  text-transform: uppercase;
235
1038
  }
236
1039
 
237
- .search {
238
- display: block;
239
- margin-block: 16px 8px;
240
- }
241
-
242
1040
  input,
243
1041
  select {
244
1042
  width: 100%;
@@ -265,74 +1063,6 @@ var x = () => {
265
1063
  outline-offset: 2px;
266
1064
  }
267
1065
 
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
1066
  .configuration {
337
1067
  display: grid;
338
1068
  gap: 12px;
@@ -366,6 +1096,13 @@ var x = () => {
366
1096
  display: block;
367
1097
  }
368
1098
 
1099
+ .control-hint {
1100
+ display: block;
1101
+ margin-top: 3px;
1102
+ font-size: 9px;
1103
+ text-transform: uppercase;
1104
+ }
1105
+
369
1106
  fieldset {
370
1107
  min-width: 0;
371
1108
  margin: 4px 0 0;
@@ -408,8 +1145,7 @@ var x = () => {
408
1145
  margin-top: 6px;
409
1146
  }
410
1147
 
411
- .empty,
412
- .empty-search {
1148
+ .empty {
413
1149
  margin: 0;
414
1150
  padding: 24px 16px;
415
1151
  border: 2px dashed var(--_fellowship-poster-plum);
@@ -431,23 +1167,18 @@ var x = () => {
431
1167
  grid-row: auto;
432
1168
  }
433
1169
  }
434
- `, C = class extends i {
1170
+ `, B = class extends i {
435
1171
  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
- };
1172
+ super(...e), this.data = null, this.build = null, this.positionId = null;
439
1173
  }
440
1174
  static {
441
- this.styles = [n, S];
1175
+ this.styles = [n, z];
442
1176
  }
443
1177
  render() {
444
1178
  if (!this.data || !this.build || !this.positionId) return o`<div class="empty">Select a gear position to begin.</div>`;
445
1179
  let e = this.data.positions.find(({ id: e }) => e === this.positionId), t = this.build.positions.find(({ positionId: e }) => e === this.positionId);
446
1180
  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)) : [];
1181
+ let n = t?.item ?? null, r = n ? this.data.itemById.get(n.itemId) : null, i = n && r ? g(this.data, this.build, this.positionId, n.itemId) : null;
451
1182
  return o`
452
1183
  <header class="workbench-header">
453
1184
  <div>
@@ -456,41 +1187,15 @@ var x = () => {
456
1187
  </div>
457
1188
  ${r ? o`<span class="current-rarity">${n?.rarity}</span>` : o`<span class="current-rarity">Empty</span>`}
458
1189
  </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>`}
1190
+ ${n && r && i ? this.#e(n, i) : o`
1191
+ <div class="empty configure-empty">
1192
+ Choose an item from this slot’s equipment selector to configure it.
1193
+ </div>
1194
+ `}
490
1195
  `;
491
1196
  }
492
1197
  #e(e, t) {
493
- let n = t.slots.filter((e) => e.rollGroupId === null);
1198
+ let n = t.slots.filter((e) => e.rollGroupId === null), r = h(e.itemLevel, e.rarity);
494
1199
  return o`
495
1200
  <section class="configuration" aria-label="Item configuration">
496
1201
  ${this.#a(t)}
@@ -499,7 +1204,7 @@ var x = () => {
499
1204
  <span>Item level</span>
500
1205
  <select
501
1206
  .value=${String(e.itemLevel)}
502
- @change=${(t) => this.#h({
1207
+ @change=${(t) => this.#p({
503
1208
  ...e,
504
1209
  itemLevel: Number(t.currentTarget.value)
505
1210
  }, "item-level")}
@@ -507,11 +1212,25 @@ var x = () => {
507
1212
  ${f(t.item.validItemLevels, (e) => e, (e) => o`<option value=${e}>${e}</option>`)}
508
1213
  </select>
509
1214
  </label>
1215
+ <label>
1216
+ <span>Applied tempers</span>
1217
+ <input
1218
+ type="number"
1219
+ min="0"
1220
+ max=${r}
1221
+ .value=${String(e.appliedTempers)}
1222
+ @change=${(t) => this.#p({
1223
+ ...e,
1224
+ appliedTempers: Math.max(0, Math.min(r, Math.round(Number(t.currentTarget.value))))
1225
+ }, "tempers")}
1226
+ />
1227
+ <small class="control-hint">Maximum ${r}</small>
1228
+ </label>
510
1229
  <label>
511
1230
  <span>Rarity</span>
512
1231
  <select
513
1232
  .value=${e.rarity}
514
- @change=${(t) => this.#h({
1233
+ @change=${(t) => this.#p({
515
1234
  ...e,
516
1235
  rarity: t.currentTarget.value
517
1236
  }, "rarity")}
@@ -545,7 +1264,7 @@ var x = () => {
545
1264
  <select
546
1265
  aria-label=${`Trait node ${t.row + 1}.${t.column + 1}`}
547
1266
  .value=${n?.traitId ?? ""}
548
- @change=${(n) => this.#p(e, t.id, n.currentTarget.value)}
1267
+ @change=${(n) => this.#d(e, t.id, n.currentTarget.value)}
549
1268
  >
550
1269
  <option value="">Unassigned</option>
551
1270
  ${f(t.eligibleTraitIds, (e) => e, (e) => o`
@@ -561,7 +1280,7 @@ var x = () => {
561
1280
  max=${t.maxRank}
562
1281
  .value=${String(n.rank)}
563
1282
  aria-label="Trait rank"
564
- @change=${(n) => this.#m(e, t.id, Number(n.currentTarget.value), t.maxRank)}
1283
+ @change=${(n) => this.#f(e, t.id, Number(n.currentTarget.value), t.maxRank)}
565
1284
  />
566
1285
  ` : s}
567
1286
  </label>
@@ -578,14 +1297,14 @@ var x = () => {
578
1297
  let r = this.#i(e, t);
579
1298
  return o`
580
1299
  <label class="configuration-control">
581
- <span>${this.#s(t.kind)}</span>
1300
+ <span>${this.#o(t.kind)}</span>
582
1301
  <select
583
1302
  .value=${r}
584
- @change=${(r) => this.#u(e, t, r.currentTarget.value, n)}
1303
+ @change=${(r) => this.#c(e, t, r.currentTarget.value, n)}
585
1304
  >
586
1305
  <option value="">${t.required ? "Choose a value" : "None"}</option>
587
1306
  ${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>
1307
+ <option value=${e}>${this.#s(t.kind, e)}</option>
589
1308
  `)}
590
1309
  </select>
591
1310
  </label>
@@ -600,13 +1319,13 @@ var x = () => {
600
1319
  .value=${r ? `${r.id}\u001f${this.#i(e, r)}` : ""}
601
1320
  @change=${(n) => {
602
1321
  let r = n.currentTarget.value;
603
- this.#d(e, t, r);
1322
+ this.#l(e, t, r);
604
1323
  }}
605
1324
  >
606
1325
  <option value="">Choose one roll</option>
607
1326
  ${t.flatMap((e) => e.allowedChoiceIds.map((t) => o`
608
1327
  <option value=${`${e.id}\u001f${t}`}>
609
- ${this.#s(e.kind)} · ${this.#c(e.kind, t)}
1328
+ ${this.#o(e.kind)} · ${this.#s(e.kind, t)}
610
1329
  </option>
611
1330
  `))}
612
1331
  </select>
@@ -620,7 +1339,7 @@ var x = () => {
620
1339
  <span>Socket ${n + 1} · ${t.replace("SocketTier", "Tier ")}</span>
621
1340
  <select
622
1341
  .value=${i}
623
- @change=${(t) => this.#f(e, r, t.currentTarget.value)}
1342
+ @change=${(t) => this.#u(e, r, t.currentTarget.value)}
624
1343
  >
625
1344
  <option value="">Empty socket</option>
626
1345
  ${f(s, (e) => e.id, (e) => o`<option value=${e.id}>${e.name} · ${e.power} power</option>`)}
@@ -642,29 +1361,7 @@ var x = () => {
642
1361
  </aside>
643
1362
  `;
644
1363
  }
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) {
1364
+ #o(e) {
668
1365
  return {
669
1366
  "rolled-stat": "Rolled stat",
670
1367
  "gem-power": "Gem power",
@@ -673,29 +1370,17 @@ var x = () => {
673
1370
  "gem-socket": "Gem socket"
674
1371
  }[e];
675
1372
  }
676
- #c(e, t) {
1373
+ #s(e, t) {
677
1374
  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
1375
  }
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) {
1376
+ #c(e, t, n, r) {
692
1377
  if (t.kind === "blessing") {
693
1378
  let r = e.blessings.filter((e) => e.slotId !== t.id);
694
1379
  n && r.push({
695
1380
  slotId: t.id,
696
1381
  blessingId: n,
697
1382
  rank: 1
698
- }), this.#h({
1383
+ }), this.#p({
699
1384
  ...e,
700
1385
  blessings: r
701
1386
  }, "blessing");
@@ -706,12 +1391,12 @@ var x = () => {
706
1391
  slotId: t.id,
707
1392
  kind: t.kind,
708
1393
  choiceId: n
709
- }), this.#h({
1394
+ }), this.#p({
710
1395
  ...e,
711
1396
  rolledModifiers: i
712
1397
  }, "modifier");
713
1398
  }
714
- #d(e, t, n) {
1399
+ #l(e, t, n) {
715
1400
  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
1401
  c && s && (c.kind === "blessing" ? a.push({
717
1402
  slotId: c.id,
@@ -721,44 +1406,44 @@ var x = () => {
721
1406
  slotId: c.id,
722
1407
  kind: c.kind,
723
1408
  choiceId: s
724
- })), this.#h({
1409
+ })), this.#p({
725
1410
  ...e,
726
1411
  rolledModifiers: i,
727
1412
  blessings: a
728
1413
  }, c?.kind === "blessing" ? "blessing" : "modifier");
729
1414
  }
730
- #f(e, t, n) {
1415
+ #u(e, t, n) {
731
1416
  let r = e.gems.filter((e) => e.socketId !== t);
732
1417
  n && r.push({
733
1418
  socketId: t,
734
1419
  gemId: n
735
- }), this.#h({
1420
+ }), this.#p({
736
1421
  ...e,
737
1422
  gems: r
738
1423
  }, "gem");
739
1424
  }
740
- #p(e, t, n) {
1425
+ #d(e, t, n) {
741
1426
  let r = e.traits.filter((e) => e.nodeId !== t);
742
1427
  n && r.push({
743
1428
  nodeId: t,
744
1429
  traitId: n,
745
1430
  rank: 1
746
- }), this.#h({
1431
+ }), this.#p({
747
1432
  ...e,
748
1433
  traits: r
749
1434
  }, "trait");
750
1435
  }
751
- #m(e, t, n, r) {
1436
+ #f(e, t, n, r) {
752
1437
  let i = e.traits.map((e) => e.nodeId === t ? {
753
1438
  ...e,
754
1439
  rank: Math.max(1, Math.min(r, Math.round(n)))
755
1440
  } : e);
756
- this.#h({
1441
+ this.#p({
757
1442
  ...e,
758
1443
  traits: i
759
1444
  }, "trait");
760
1445
  }
761
- #h(e, t) {
1446
+ #p(e, t) {
762
1447
  this.dispatchEvent(new CustomEvent("fellowship-workbench-change", {
763
1448
  bubbles: !0,
764
1449
  composed: !0,
@@ -768,12 +1453,11 @@ var x = () => {
768
1453
  }
769
1454
  }));
770
1455
  }
771
- #g;
772
1456
  };
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`
1457
+ e([l({ attribute: !1 })], B.prototype, "data", void 0), e([l({ attribute: !1 })], B.prototype, "build", void 0), e([l({ type: String })], B.prototype, "positionId", void 0), B = e([c("fellowship-item-workbench")], B);
1458
+ var V = () => {
1459
+ customElements.get("fellowship-item-workbench") || customElements.define("fellowship-item-workbench", B);
1460
+ }, H = a`
777
1461
  :host {
778
1462
  display: block;
779
1463
  min-width: 0;
@@ -815,6 +1499,34 @@ var w = () => {
815
1499
  text-align: left;
816
1500
  }
817
1501
 
1502
+ .talent-points > span {
1503
+ display: block;
1504
+ margin-bottom: 4px;
1505
+ font-size: 10px;
1506
+ font-weight: 500;
1507
+ letter-spacing: 0.08em;
1508
+ text-transform: uppercase;
1509
+ }
1510
+
1511
+ .talent-points {
1512
+ grid-column: 1 / -1;
1513
+ }
1514
+
1515
+ .talent-points input {
1516
+ width: 100%;
1517
+ min-height: 44px;
1518
+ padding-inline: 10px;
1519
+ border: 2px solid var(--_fellowship-poster-plum);
1520
+ border-radius: var(--_fellowship-poster-radius);
1521
+ background: var(--_fellowship-poster-white);
1522
+ color: var(--_fellowship-poster-plum);
1523
+ }
1524
+
1525
+ .talent-points input:focus-visible {
1526
+ outline: 4px solid var(--_fellowship-poster-focus);
1527
+ outline-offset: 2px;
1528
+ }
1529
+
818
1530
  .hero-image {
819
1531
  display: grid;
820
1532
  place-items: center;
@@ -1013,6 +1725,27 @@ var w = () => {
1013
1725
  outline-offset: -4px;
1014
1726
  }
1015
1727
 
1728
+ .selector-backdrop {
1729
+ position: fixed;
1730
+ z-index: 1000;
1731
+ inset: 0;
1732
+ width: 100%;
1733
+ height: 100%;
1734
+ padding: 0;
1735
+ border: 0;
1736
+ background: color-mix(
1737
+ in oklch,
1738
+ var(--_fellowship-poster-plum) 18%,
1739
+ transparent
1740
+ );
1741
+ cursor: default;
1742
+ pointer-events: none;
1743
+ }
1744
+
1745
+ .selector-backdrop[data-compact="true"] {
1746
+ pointer-events: auto;
1747
+ }
1748
+
1016
1749
  .sheet-backdrop,
1017
1750
  .sheet-header {
1018
1751
  display: none;
@@ -1063,10 +1796,14 @@ var w = () => {
1063
1796
  }
1064
1797
 
1065
1798
  .hero {
1066
- grid-template-columns: 92px minmax(0, max-content);
1799
+ grid-template-columns: 92px minmax(0, max-content) minmax(150px, 180px);
1067
1800
  margin-bottom: 20px;
1068
1801
  }
1069
1802
 
1803
+ .talent-points {
1804
+ grid-column: auto;
1805
+ }
1806
+
1070
1807
  .hero-image {
1071
1808
  width: 92px;
1072
1809
  height: 92px;
@@ -1083,7 +1820,7 @@ var w = () => {
1083
1820
  minmax(142px, 0.64fr)
1084
1821
  minmax(390px, 1.9fr)
1085
1822
  minmax(142px, 0.64fr)
1086
- minmax(190px, 0.86fr);
1823
+ minmax(220px, 1fr);
1087
1824
  align-items: start;
1088
1825
  gap: 12px;
1089
1826
  }
@@ -1201,6 +1938,10 @@ var w = () => {
1201
1938
  margin-bottom: 10px;
1202
1939
  }
1203
1940
 
1941
+ .talent-points {
1942
+ grid-column: 1 / -1;
1943
+ }
1944
+
1204
1945
  .hero-image {
1205
1946
  width: 52px;
1206
1947
  height: 52px;
@@ -1233,80 +1974,89 @@ var w = () => {
1233
1974
  animation-iteration-count: 1 !important;
1234
1975
  }
1235
1976
  }
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) => {
1977
+ `, U = "button:not([disabled]), input:not([disabled]), select:not([disabled]), [href], [tabindex]:not([tabindex=\"-1\"])", W = (e) => {
1253
1978
  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));
1979
+ for (let n of e.children) n instanceof HTMLElement && n.matches(U) && t.push(n), n.shadowRoot && t.push(...W(n.shadowRoot)), t.push(...W(n));
1255
1980
  return t;
1256
- }, A = (e) => {
1981
+ }, G = (e) => {
1257
1982
  let t = e.activeElement;
1258
1983
  for (; t?.shadowRoot?.activeElement;) t = t.shadowRoot.activeElement;
1259
1984
  return t;
1260
1985
  };
1261
- x(), w();
1262
- var j = class extends i {
1986
+ I(), R(), V();
1987
+ var K = class extends i {
1263
1988
  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 = {
1989
+ super(...e), this.data = null, this.build = null, this.loading = !1, this.error = null, this.resolveAssetUrl = null, this._selectedPositionId = null, this._selectorOpen = !1, this._selectorPosition = {
1990
+ x: 12,
1991
+ y: 12,
1992
+ placement: "right"
1993
+ }, this._tab = "gear", this._sheetOpen = !1, this._compact = !1, this._announcement = "", this._calculation = null, this._leftPositionIds = [], this._rightPositionIds = [], this._mobilePositionIds = [], this.#e = null, this.#t = null, this.#n = null, this.#r = null, this.#i = null, this.#a = !1, this.#o = /* @__PURE__ */ new Set(), this.#h = (e) => {
1994
+ if (!this.data || !this.build) return;
1995
+ let t = Math.max(0, Math.min(this.data.maxAllocatedTalentPoints, Math.round(Number(e.currentTarget.value))));
1996
+ this.#C({
1271
1997
  ...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 });
1998
+ allocatedTalentPoints: t
1999
+ }, "talent-points", null);
2000
+ }, this.#g = (e) => {
2001
+ e.stopPropagation(), this.#r !== null && (window.cancelAnimationFrame(this.#r), this.#r = null), this._selectedPositionId = e.detail.positionId, this.#i = e.detail.trigger, this._sheetOpen = !1, this._selectorOpen = !0, this._announcement = `${e.detail.positionId} equipment selector opened.`, this.updateComplete.then(() => this.#T(!0));
2002
+ }, this.#_ = (e) => {
2003
+ e.stopPropagation(), this.#b(e.detail.item, e.detail.source);
2004
+ }, this.#v = (e) => {
2005
+ e.stopPropagation(), this.#b(e.detail.item, "item", `${e.detail.name} equipped.`) && this.#k(!0);
2006
+ }, this.#y = (e) => {
2007
+ e.stopPropagation(), this.#b(null, "item", "Item unequipped.") && this.#k(!0);
2008
+ }, this.#x = (e) => {
2009
+ e.stopPropagation(), !(!this.build || !this._selectedPositionId) && this.build.positions.find(({ positionId: e }) => e === this._selectedPositionId)?.item && (this.#k(!1), this._compact && (this._sheetOpen = !0), this._announcement = "Item configuration focused.", this.updateComplete.then(() => {
2010
+ this.#r !== null && window.cancelAnimationFrame(this.#r), this.#r = window.requestAnimationFrame(() => {
2011
+ this.#r = null, this._workbenchPanel?.focus({ preventScroll: !0 });
1292
2012
  });
2013
+ }));
2014
+ }, this.#E = () => {
2015
+ let e = this._equipmentSelector, t = this.#i, n = this.ownerDocument.defaultView;
2016
+ if (!this._selectorOpen || this._compact || !e || !t || !n) return;
2017
+ let r = t.getBoundingClientRect(), i = e.getBoundingClientRect(), a = n.innerWidth - 12 - r.right - 8, o = r.left - 12 - 8, s = this._leftPositionIds.includes(this._selectedPositionId), c = s ? "right" : "left", l = s ? a : o, u = s ? o : a;
2018
+ i.width > l && u > l && (c = s ? "left" : "right");
2019
+ let d = c === "right" ? r.right + 8 : r.left - 8 - i.width, f = Math.max(12, n.innerWidth - 12 - i.width), p = Math.max(12, n.innerHeight - 12 - i.height), m = Math.round(Math.min(Math.max(d, 12), f)), h = Math.round(Math.min(Math.max(r.top, 12), p));
2020
+ m === this._selectorPosition.x && h === this._selectorPosition.y && c === this._selectorPosition.placement || (this._selectorPosition = {
2021
+ x: m,
2022
+ y: h,
2023
+ placement: c
1293
2024
  });
1294
- }, this.#y = (e) => {
1295
- e.key !== "Escape" || !this._compact || !this._sheetOpen || (e.preventDefault(), this.#v());
1296
- }, this.#b = (e) => {
2025
+ }, this.#M = () => {
2026
+ this._sheetOpen = !1, this.#A();
2027
+ }, this.#N = (e) => {
2028
+ e.key === "Escape" && (this._selectorOpen ? (e.preventDefault(), this.#k(!0)) : this._compact && this._sheetOpen && (e.preventDefault(), this.#M()));
2029
+ }, this.#P = (e) => {
2030
+ if (!this._selectorOpen) return;
2031
+ let t = e.composedPath();
2032
+ t.includes(this._equipmentSelector) || t.includes(this.#i) || t.some((e) => e instanceof HTMLElement && e.tagName === "FELLOWSHIP-GEAR-POSITION") || this.#k(!0);
2033
+ }, this.#F = (e) => {
2034
+ if (!this._selectorOpen || !this._equipmentSelector) return;
2035
+ if (e.key === "Escape") {
2036
+ e.preventDefault(), e.stopPropagation(), this.#k(!0);
2037
+ return;
2038
+ }
2039
+ if (e.key !== "Tab") return;
2040
+ let t = W(this._equipmentSelector), n = t[0], r = t.at(-1), i = G(this.ownerDocument);
2041
+ if (!n || !r) {
2042
+ e.preventDefault();
2043
+ return;
2044
+ }
2045
+ e.shiftKey && (i === n || i === this._equipmentSelector) ? (e.preventDefault(), r.focus()) : !e.shiftKey && i === r && (e.preventDefault(), n.focus());
2046
+ }, this.#I = (e) => {
1297
2047
  if (!this._compact || !this._sheetOpen) return;
1298
2048
  if (e.key === "Escape") {
1299
- e.preventDefault(), this.#v();
2049
+ e.preventDefault(), this.#M();
1300
2050
  return;
1301
2051
  }
1302
2052
  if (e.key !== "Tab" || !this._workbenchPanel) return;
1303
- let t = k(this._workbenchPanel), n = t[0], r = t.at(-1), i = A(this.shadowRoot);
2053
+ let t = W(this._workbenchPanel), n = t[0], r = t.at(-1), i = G(this.shadowRoot);
1304
2054
  if (!n || !r) {
1305
2055
  e.preventDefault();
1306
2056
  return;
1307
2057
  }
1308
2058
  e.shiftKey && (i === n || i === this._workbenchPanel) ? (e.preventDefault(), r.focus()) : !e.shiftKey && i === r && (e.preventDefault(), n.focus());
1309
- }, this.#x = (e) => {
2059
+ }, this.#L = (e) => {
1310
2060
  if (!["ArrowLeft", "ArrowRight"].includes(e.key)) return;
1311
2061
  e.preventDefault();
1312
2062
  let t = [
@@ -1317,12 +2067,12 @@ var j = class extends i {
1317
2067
  this._tab = t[(r + n + t.length) % t.length] ?? "gear", this.updateComplete.then(() => {
1318
2068
  (this.shadowRoot?.querySelector(".tab[aria-selected=\"true\"]"))?.focus();
1319
2069
  });
1320
- }, this.#C = (e) => {
2070
+ }, this.#z = (e) => {
1321
2071
  e.currentTarget.hidden = !0;
1322
2072
  };
1323
2073
  }
1324
2074
  static {
1325
- this.styles = [n, T];
2075
+ this.styles = [n, H];
1326
2076
  }
1327
2077
  #e;
1328
2078
  #t;
@@ -1334,22 +2084,26 @@ var j = class extends i {
1334
2084
  connectedCallback() {
1335
2085
  super.connectedCallback(), t(this.ownerDocument), this.#e ??= new ResizeObserver(() => {
1336
2086
  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));
2087
+ this.#n !== null && window.cancelAnimationFrame(this.#n), this.#n = window.requestAnimationFrame(() => {
2088
+ this.#n = null, e !== this._compact && (this._compact = e, e || (this._sheetOpen = !1), this._selectorOpen && this.updateComplete.then(() => this.#T(!1)));
1339
2089
  });
1340
- }), this.#e.observe(this), this.ownerDocument.addEventListener("keydown", this.#y);
2090
+ }), this.#t ??= new ResizeObserver(() => this.#E()), this.#e.observe(this), this.ownerDocument.addEventListener("keydown", this.#N), this.ownerDocument.addEventListener("pointerdown", this.#P);
1341
2091
  }
1342
2092
  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();
2093
+ this.#e?.disconnect(), this.#O(), this.ownerDocument.removeEventListener("keydown", this.#N), this.ownerDocument.removeEventListener("pointerdown", this.#P), this.#n !== null && (window.cancelAnimationFrame(this.#n), this.#n = null), this.#r !== null && (window.cancelAnimationFrame(this.#r), this.#r = null), super.disconnectedCallback();
1344
2094
  }
1345
2095
  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));
2096
+ if (super.willUpdate(e), e.has("data") || e.has("build")) {
2097
+ 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, this._selectorOpen = !1, this.#O());
2098
+ let e = [...this.data?.positions ?? []].sort((e, t) => e.order - t.order);
2099
+ this._leftPositionIds = e.slice(0, 7).map(({ id: e }) => e), this._rightPositionIds = e.slice(7).map(({ id: e }) => e), this._mobilePositionIds = e.map(({ id: e }) => e);
2100
+ }
1347
2101
  }
1348
2102
  updated(e) {
1349
2103
  if (super.updated(e), e.has("data") || e.has("build") || e.has("resolveAssetUrl")) {
1350
2104
  (e.has("data") || e.has("resolveAssetUrl")) && this.#o.clear();
1351
2105
  let t = !!(this.data && this.build && !this.resolveAssetUrl);
1352
- t && !this.#a ? (this.#a = !0, this.#_({
2106
+ t && !this.#a ? (this.#a = !0, this.#w({
1353
2107
  code: "asset-resolver-missing",
1354
2108
  message: "A resolveAssetUrl callback is required to render Fellowship images.",
1355
2109
  recoverable: !0
@@ -1392,18 +2146,28 @@ var j = class extends i {
1392
2146
  let t = p(e.hero.images, {
1393
2147
  size: "200",
1394
2148
  type: "hero"
1395
- }), n = t ? this.#S(t) : null;
2149
+ }), n = t ? this.#R(t) : null;
1396
2150
  return o`
1397
2151
  <div class="container">
1398
2152
  <section class="planner" aria-label="Fellership Gear Planner">
1399
2153
  <header class="hero">
1400
2154
  <span class="hero-image">
1401
- ${n ? o`<img src=${n} alt="" @error=${this.#C} />` : (e.hero.name ?? e.hero.id).slice(0, 2)}
2155
+ ${n ? o`<img src=${n} alt="" @error=${this.#z} />` : (e.hero.name ?? e.hero.id).slice(0, 2)}
1402
2156
  </span>
1403
2157
  <span>
1404
2158
  <span class="hero-label">Current hero</span>
1405
2159
  <h1>${e.hero.name ?? e.hero.id}</h1>
1406
2160
  </span>
2161
+ <label class="talent-points">
2162
+ <span>Allocated talent points</span>
2163
+ <input
2164
+ type="number"
2165
+ min="0"
2166
+ max=${this.data.maxAllocatedTalentPoints}
2167
+ .value=${String(this.build.allocatedTalentPoints)}
2168
+ @change=${this.#h}
2169
+ />
2170
+ </label>
1407
2171
  </header>
1408
2172
 
1409
2173
  ${this.#s()}
@@ -1413,7 +2177,7 @@ var j = class extends i {
1413
2177
  aria-label="Traits and gems"
1414
2178
  aria-hidden=${String(this._compact && this._tab !== "traits")}
1415
2179
  >
1416
- ${this.#d()}
2180
+ ${this.#f()}
1417
2181
  </aside>
1418
2182
  <div
1419
2183
  class="gear-workspace"
@@ -1421,11 +2185,11 @@ var j = class extends i {
1421
2185
  >
1422
2186
  <div class="slot-grid">
1423
2187
  <div class="slot-rail left-rail">
1424
- ${this.#c(this._compact ? this.data.positions.map(({ id: e }) => e) : E)}
2188
+ ${this.#c(this._compact ? this._mobilePositionIds : this._leftPositionIds)}
1425
2189
  </div>
1426
2190
  ${this._compact ? s : o`
1427
2191
  <div class="slot-rail right-rail">
1428
- ${this.#c(D)}
2192
+ ${this.#c(this._rightPositionIds)}
1429
2193
  </div>
1430
2194
  `}
1431
2195
  </div>
@@ -1436,22 +2200,23 @@ var j = class extends i {
1436
2200
  aria-label="Blessings, sets and results"
1437
2201
  aria-hidden=${String(this._compact && this._tab !== "bonuses")}
1438
2202
  >
1439
- ${this.#f()}
2203
+ ${this.#p()}
1440
2204
  </aside>
1441
2205
  </div>
1442
2206
  <p class="sr-only" aria-live="polite">${this._announcement}</p>
1443
2207
  </section>
2208
+ ${this.#u()}
1444
2209
  </div>
1445
2210
  `;
1446
2211
  }
1447
2212
  #s() {
1448
2213
  return o`
1449
2214
  <div class="tabs" role="tablist" aria-label="Planner workspace">
1450
- ${[
2215
+ ${f([
1451
2216
  ["traits", "Traits & gems"],
1452
2217
  ["gear", "Gear"],
1453
2218
  ["bonuses", "Bonuses"]
1454
- ].map(([e, t]) => o`
2219
+ ], ([e]) => e, ([e, t]) => o`
1455
2220
  <button
1456
2221
  class="tab"
1457
2222
  type="button"
@@ -1461,7 +2226,7 @@ var j = class extends i {
1461
2226
  @click=${() => {
1462
2227
  this._tab = e;
1463
2228
  }}
1464
- @keydown=${this.#x}
2229
+ @keydown=${this.#L}
1465
2230
  >
1466
2231
  ${t}
1467
2232
  </button>
@@ -1474,20 +2239,17 @@ var j = class extends i {
1474
2239
  let t = this.data.heroById.get(this.build.heroId) ?? null;
1475
2240
  return o`
1476
2241
  ${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;
2242
+ 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 && t && r?.item ? y(i, t, r.item.rarity, "200") : null;
1481
2243
  return o`
1482
2244
  <fellowship-gear-position
1483
2245
  .position=${n}
1484
2246
  .item=${i}
1485
2247
  .itemBuild=${r?.item ?? null}
1486
- .itemName=${i && t ? v(i, t) : null}
2248
+ .itemName=${i && t ? b(i, t) : null}
1487
2249
  .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}
2250
+ .imageUrl=${a ? this.#R(a) : null}
2251
+ .tooltipDetails=${this.#d(e, r?.item ?? null)}
2252
+ @fellowship-gear-position-select=${this.#g}
1491
2253
  ></fellowship-gear-position>
1492
2254
  `;
1493
2255
  })}
@@ -1501,24 +2263,56 @@ var j = class extends i {
1501
2263
  role=${this._compact ? "dialog" : "region"}
1502
2264
  aria-modal=${this._compact ? "true" : s}
1503
2265
  aria-label="Item workbench"
1504
- tabindex=${this._compact ? "-1" : s}
1505
- @keydown=${this.#b}
1506
- @fellowship-workbench-change=${this.#m}
2266
+ tabindex="-1"
2267
+ @keydown=${this.#I}
2268
+ @fellowship-workbench-change=${this.#_}
1507
2269
  >
1508
2270
  <header class="sheet-header">
1509
2271
  <strong>Configure item</strong>
1510
- <button class="sheet-close" type="button" @click=${this.#v}>Close</button>
2272
+ <button class="sheet-close" type="button" @click=${this.#M}>Close</button>
1511
2273
  </header>
1512
2274
  <fellowship-item-workbench
1513
2275
  .data=${this.data}
1514
2276
  .build=${this.build}
1515
2277
  .positionId=${this._selectedPositionId}
1516
- .resolveAssetUrl=${this.resolveAssetUrl}
1517
2278
  ></fellowship-item-workbench>
1518
2279
  </section>
1519
2280
  `;
1520
2281
  }
1521
- #u(e, t) {
2282
+ #u() {
2283
+ if (!this._selectorOpen || !this.data || !this.build || !this._selectedPositionId || !this.resolveAssetUrl) return s;
2284
+ let e = this.data.positions.find(({ id: e }) => e === this._selectedPositionId), t = `--_selector-x:${this._selectorPosition.x}px;--_selector-y:${this._selectorPosition.y}px`;
2285
+ return o`
2286
+ <button
2287
+ class="selector-backdrop"
2288
+ type="button"
2289
+ tabindex="-1"
2290
+ aria-label="Dismiss equipment selector"
2291
+ data-compact=${String(this._compact)}
2292
+ @click=${() => this.#k(!0)}
2293
+ ></button>
2294
+ <fellowship-equipment-selector
2295
+ popover="manual"
2296
+ role="dialog"
2297
+ aria-modal="true"
2298
+ aria-label=${`Choose equipment for ${e?.label ?? "this slot"}`}
2299
+ tabindex="-1"
2300
+ data-placement=${this._selectorPosition.placement}
2301
+ style=${t}
2302
+ .data=${this.data}
2303
+ .build=${this.build}
2304
+ .positionId=${this._selectedPositionId}
2305
+ .resolveAssetUrl=${this.resolveAssetUrl}
2306
+ .compact=${this._compact}
2307
+ @keydown=${this.#F}
2308
+ @fellowship-equipment-selector-select=${this.#v}
2309
+ @fellowship-equipment-selector-configure=${this.#x}
2310
+ @fellowship-equipment-selector-unequip=${this.#y}
2311
+ @fellowship-equipment-selector-dismiss=${() => this.#k(!0)}
2312
+ ></fellowship-equipment-selector>
2313
+ `;
2314
+ }
2315
+ #d(e, t) {
1522
2316
  if (!this.data || !this.build || !t) return [];
1523
2317
  let n = this.data.itemById.get(t.itemId);
1524
2318
  if (!n) return [];
@@ -1535,7 +2329,7 @@ var j = class extends i {
1535
2329
  d ? "Some mechanics are excluded from exact totals." : ""
1536
2330
  ].filter(Boolean);
1537
2331
  }
1538
- #d() {
2332
+ #f() {
1539
2333
  if (!this.data || !this.build) return o``;
1540
2334
  let e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Map();
1541
2335
  for (let n of this.build.positions) {
@@ -1558,7 +2352,7 @@ var j = class extends i {
1558
2352
  <h2>Traits & gems</h2>
1559
2353
  <h3>Active traits</h3>
1560
2354
  <ul class="summary-list">
1561
- ${e.size === 0 ? o`<li class="summary-row"><span>No traits selected</span></li>` : [...e].map(([e, t]) => o`
2355
+ ${e.size === 0 ? o`<li class="summary-row"><span>No traits selected</span></li>` : f([...e], ([e]) => e, ([e, t]) => o`
1562
2356
  <li class="summary-row">
1563
2357
  <span>${this.data?.traitById.get(e)?.name ?? e}</span>
1564
2358
  <strong>R${t}</strong>
@@ -1567,7 +2361,7 @@ var j = class extends i {
1567
2361
  </ul>
1568
2362
  <h3>Socket gems</h3>
1569
2363
  <ul class="summary-list">
1570
- ${t.size === 0 ? o`<li class="summary-row"><span>No gems socketed</span></li>` : [...t].map(([e, t]) => o`
2364
+ ${t.size === 0 ? o`<li class="summary-row"><span>No gems socketed</span></li>` : f([...t], ([e]) => e, ([e, t]) => o`
1571
2365
  <li class="summary-row">
1572
2366
  <span>${e} × ${t.count}</span>
1573
2367
  <strong>${t.power}</strong>
@@ -1576,7 +2370,7 @@ var j = class extends i {
1576
2370
  </ul>
1577
2371
  `;
1578
2372
  }
1579
- #f() {
2373
+ #p() {
1580
2374
  if (!this.data || !this.build || !this._calculation) return o``;
1581
2375
  let e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Map();
1582
2376
  for (let n of this.build.positions) {
@@ -1587,10 +2381,10 @@ var j = class extends i {
1587
2381
  }
1588
2382
  return o`
1589
2383
  <span class="section-label">Build output</span>
1590
- <h2>Bonuses & totals</h2>
2384
+ <h2>Bonuses & stats</h2>
1591
2385
  <h3>Blessings</h3>
1592
2386
  <ul class="summary-list">
1593
- ${e.size === 0 ? o`<li class="summary-row"><span>No blessings selected</span></li>` : [...e].map(([e, t]) => o`
2387
+ ${e.size === 0 ? o`<li class="summary-row"><span>No blessings selected</span></li>` : f([...e], ([e]) => e, ([e, t]) => o`
1594
2388
  <li class="summary-row">
1595
2389
  <span>${this.data?.blessingById.get(e)?.name ?? e}</span>
1596
2390
  <strong>R${t}</strong>
@@ -1599,7 +2393,7 @@ var j = class extends i {
1599
2393
  </ul>
1600
2394
  <h3>Set bonuses</h3>
1601
2395
  <ul class="summary-list">
1602
- ${t.size === 0 ? o`<li class="summary-row"><span>No set pieces equipped</span></li>` : [...t].map(([e, t]) => {
2396
+ ${t.size === 0 ? o`<li class="summary-row"><span>No set pieces equipped</span></li>` : f([...t], ([e]) => e, ([e, t]) => {
1603
2397
  let n = this.data?.setBonuses.find(({ id: t }) => t === e);
1604
2398
  return o`
1605
2399
  <li class="summary-row">
@@ -1609,20 +2403,29 @@ var j = class extends i {
1609
2403
  `;
1610
2404
  })}
1611
2405
  </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>
2406
+ <div class="hero-sheet" aria-label="Hero stats">
2407
+ ${f(this._calculation.heroSheet.sections, (e) => e.id, (e) => o`
2408
+ <section class="hero-sheet-section">
2409
+ <h3>${e.label}</h3>
2410
+ <ul class="breakdown">
2411
+ ${f(e.rows, (e) => e.id, (e) => o`
2412
+ <li class="total-row">
2413
+ <span>${e.label}</span>
2414
+ <strong>${this.#m(e)}</strong>
2415
+ </li>
2416
+ `)}
2417
+ </ul>
2418
+ </section>
2419
+ `)}
2420
+ </div>
1618
2421
  ${this._calculation.conditionalMechanics.length > 0 ? o`
1619
2422
  <h3>Conditional</h3>
1620
- ${this._calculation.conditionalMechanics.map((e) => o`
2423
+ ${f(this._calculation.conditionalMechanics, (e) => e.id, (e) => o`
1621
2424
  <label class="condition">
1622
2425
  <input
1623
2426
  type="checkbox"
1624
2427
  .checked=${e.applied}
1625
- @change=${() => this.#h(e.id)}
2428
+ @change=${() => this.#S(e.id)}
1626
2429
  />
1627
2430
  <span>${e.sourceName} · Apply</span>
1628
2431
  </label>
@@ -1630,7 +2433,7 @@ var j = class extends i {
1630
2433
  ` : s}
1631
2434
  <h3>Unsupported</h3>
1632
2435
  <ul class="breakdown">
1633
- ${this._calculation.unsupportedContributions.length === 0 ? o`<li class="unsupported-row"><span>None</span></li>` : this._calculation.unsupportedContributions.map((e) => o`
2436
+ ${this._calculation.unsupportedContributions.length === 0 ? o`<li class="unsupported-row"><span>None</span></li>` : f(this._calculation.unsupportedContributions, (e) => e.id, (e) => o`
1634
2437
  <li class="unsupported-row">
1635
2438
  <span>${e.sourceName}</span>
1636
2439
  <span class="unsupported-code">${e.reasonCode.replaceAll("-", " ")}</span>
@@ -1639,17 +2442,48 @@ var j = class extends i {
1639
2442
  </ul>
1640
2443
  `;
1641
2444
  }
1642
- #p;
1643
- #m;
1644
- #h(e) {
2445
+ #m(e) {
2446
+ return new Intl.NumberFormat("en-US", {
2447
+ style: e.displayKind === "percentage" ? "percent" : "decimal",
2448
+ minimumFractionDigits: e.decimals,
2449
+ maximumFractionDigits: e.decimals
2450
+ }).format(e.value);
2451
+ }
2452
+ #h;
2453
+ #g;
2454
+ #_;
2455
+ #v;
2456
+ #y;
2457
+ #b(e, t, n = "Build updated.") {
2458
+ if (!this.data || !this.build || !this._selectedPositionId) return !1;
2459
+ let r = {
2460
+ ...this.build,
2461
+ positions: this.build.positions.map((t) => t.positionId === this._selectedPositionId ? {
2462
+ ...t,
2463
+ item: e
2464
+ } : t)
2465
+ }, i = v(this.data, r), a = x(this.data, i.build);
2466
+ if (!a.valid || !a.build) {
2467
+ let e = a.issues.find(({ severity: e }) => e === "error");
2468
+ return this.#w({
2469
+ code: e?.code ?? "invalid-change",
2470
+ message: e?.message ?? "That item choice is not valid.",
2471
+ recoverable: !0
2472
+ }), this._announcement = e?.message ?? "That item choice was rejected.", !1;
2473
+ }
2474
+ let o = (e) => e ? e.rolledModifiers.length + e.gems.length + e.traits.length + e.blessings.length : 0, s = a.build.positions.find(({ positionId: e }) => e === this._selectedPositionId)?.item ?? null, c = o(e) - o(s);
2475
+ return this._announcement = c > 0 ? `${c} incompatible item choice${c === 1 ? " was" : "s were"} reset.` : n, this.#C(a.build, t, this._selectedPositionId), !0;
2476
+ }
2477
+ #x;
2478
+ #S(e) {
1645
2479
  if (!this.build) return;
1646
2480
  let t = new Set(this.build.disabledConditionalContributionIds);
1647
- t.has(e) ? t.delete(e) : t.add(e), this.#g({
2481
+ t.has(e) ? t.delete(e) : t.add(e), this.#C({
1648
2482
  ...this.build,
1649
2483
  disabledConditionalContributionIds: [...t].sort()
1650
2484
  }, "conditional", null);
1651
2485
  }
1652
- #g(e, t, n) {
2486
+ #C(e, t, n) {
1653
2487
  this.dispatchEvent(new CustomEvent("fellowship-gear-build-change", {
1654
2488
  bubbles: !0,
1655
2489
  composed: !0,
@@ -1660,7 +2494,7 @@ var j = class extends i {
1660
2494
  }
1661
2495
  }));
1662
2496
  }
1663
- #_(e) {
2497
+ #w(e) {
1664
2498
  this.dispatchEvent(new CustomEvent("fellowship-gear-planner-error", {
1665
2499
  bubbles: !0,
1666
2500
  composed: !0,
@@ -1670,17 +2504,49 @@ var j = class extends i {
1670
2504
  }
1671
2505
  }));
1672
2506
  }
1673
- #v;
1674
- #y;
1675
- #b;
1676
- #x;
1677
- #S(e) {
2507
+ async #T(e) {
2508
+ let t = this._equipmentSelector;
2509
+ !this._selectorOpen || !t || (await t.updateComplete, "showPopover" in t && !t.matches(":popover-open") && t.showPopover(), this.#D(), this.#E(), e && t.focusInitial());
2510
+ }
2511
+ #E;
2512
+ #D() {
2513
+ let e = this.ownerDocument.defaultView;
2514
+ e && (this.#O(), e.addEventListener("resize", this.#E), e.addEventListener("scroll", this.#E, !0), this.#i && this.#t?.observe(this.#i), this._equipmentSelector && this.#t?.observe(this._equipmentSelector));
2515
+ }
2516
+ #O() {
2517
+ let e = this.ownerDocument.defaultView;
2518
+ e?.removeEventListener("resize", this.#E), e?.removeEventListener("scroll", this.#E, !0), this.#t?.disconnect();
2519
+ }
2520
+ #k(e) {
2521
+ if (!this._selectorOpen) return;
2522
+ let t = this._equipmentSelector;
2523
+ t && "hidePopover" in t && t.matches(":popover-open") && t.hidePopover(), this.#O(), this._selectorOpen = !1, e && this.#A();
2524
+ }
2525
+ #A() {
2526
+ this.updateComplete.then(() => {
2527
+ this.#r !== null && window.cancelAnimationFrame(this.#r), this.#r = window.requestAnimationFrame(() => {
2528
+ this.#r = null, this.#j()?.focus({ preventScroll: !0 });
2529
+ });
2530
+ });
2531
+ }
2532
+ #j() {
2533
+ if (this.#i instanceof HTMLButtonElement && this.#i.isConnected) return this.#i;
2534
+ let e = [...this.shadowRoot?.querySelectorAll("fellowship-gear-position") ?? []].find(({ position: e }) => e?.id === this._selectedPositionId)?.shadowRoot?.querySelector("button") ?? null;
2535
+ return this.#i = e, e;
2536
+ }
2537
+ #M;
2538
+ #N;
2539
+ #P;
2540
+ #F;
2541
+ #I;
2542
+ #L;
2543
+ #R(e) {
1678
2544
  if (!this.resolveAssetUrl) return null;
1679
2545
  try {
1680
2546
  return this.resolveAssetUrl(e);
1681
2547
  } catch (t) {
1682
2548
  return this.#o.has(e.path) || (this.#o.add(e.path), queueMicrotask(() => {
1683
- this.isConnected && this.#_({
2549
+ this.isConnected && this.#w({
1684
2550
  code: "asset-url-error",
1685
2551
  message: `Could not resolve ${e.path}: ${String(t)}.`,
1686
2552
  recoverable: !0
@@ -1688,14 +2554,14 @@ var j = class extends i {
1688
2554
  })), null;
1689
2555
  }
1690
2556
  }
1691
- #C;
2557
+ #z;
1692
2558
  };
1693
- e([l({ attribute: !1 })], j.prototype, "data", void 0), e([l({ attribute: !1 })], j.prototype, "build", void 0), e([l({
2559
+ e([l({ attribute: !1 })], K.prototype, "data", void 0), e([l({ attribute: !1 })], K.prototype, "build", void 0), e([l({
1694
2560
  type: Boolean,
1695
2561
  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);
2562
+ })], K.prototype, "loading", void 0), e([l({ attribute: !1 })], K.prototype, "error", void 0), e([l({ attribute: !1 })], K.prototype, "resolveAssetUrl", void 0), e([d()], K.prototype, "_selectedPositionId", void 0), e([d()], K.prototype, "_selectorOpen", void 0), e([d()], K.prototype, "_selectorPosition", void 0), e([d()], K.prototype, "_tab", void 0), e([d()], K.prototype, "_sheetOpen", void 0), e([d()], K.prototype, "_compact", void 0), e([d()], K.prototype, "_announcement", void 0), e([d()], K.prototype, "_calculation", void 0), e([u(".workbench-panel")], K.prototype, "_workbenchPanel", void 0), e([u("fellowship-equipment-selector")], K.prototype, "_equipmentSelector", void 0), K = e([c("fellowship-gear-planner")], K);
2563
+ var q = () => {
2564
+ customElements.get("fellowship-gear-planner") || customElements.define("fellowship-gear-planner", K);
1699
2565
  };
1700
2566
  //#endregion
1701
- export { M as n, j as t };
2567
+ export { q as n, K as t };