@crockery/fellowship-components 1.1.0 → 2.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.
@@ -4,10 +4,64 @@ 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/rarity-thumbnail.styles.ts
9
+ var S = a`
10
+ [data-rarity="Common"] {
11
+ --_fellowship-rarity-start: var(--fellowship-rarity-common-start, #545456);
12
+ --_fellowship-rarity-end: var(--fellowship-rarity-common-end, #909291);
13
+ --_fellowship-rarity-border: var(--fellowship-rarity-common-border, #b8bab9);
14
+ }
15
+
16
+ [data-rarity="Uncommon"] {
17
+ --_fellowship-rarity-start: var(--fellowship-rarity-uncommon-start, #235824);
18
+ --_fellowship-rarity-end: var(--fellowship-rarity-uncommon-end, #509650);
19
+ --_fellowship-rarity-border: var(--fellowship-rarity-uncommon-border, #77bd74);
20
+ }
21
+
22
+ [data-rarity="Rare"] {
23
+ --_fellowship-rarity-start: var(--fellowship-rarity-rare-start, #253358);
24
+ --_fellowship-rarity-end: var(--fellowship-rarity-rare-end, #475b80);
25
+ --_fellowship-rarity-border: var(--fellowship-rarity-rare-border, #718bb7);
26
+ }
27
+
28
+ [data-rarity="Epic"] {
29
+ --_fellowship-rarity-start: var(--fellowship-rarity-epic-start, #402858);
30
+ --_fellowship-rarity-end: var(--fellowship-rarity-epic-end, #6b4b8e);
31
+ --_fellowship-rarity-border: var(--fellowship-rarity-epic-border, #9b72c3);
32
+ }
33
+
34
+ [data-rarity="Champion"] {
35
+ --_fellowship-rarity-start: var(--fellowship-rarity-champion-start, #8a6222);
36
+ --_fellowship-rarity-end: var(--fellowship-rarity-champion-end, #c49d3f);
37
+ --_fellowship-rarity-border: var(--fellowship-rarity-champion-border, #e7c96e);
38
+ }
39
+
40
+ [data-rarity="Heroic"] {
41
+ --_fellowship-rarity-start: var(--fellowship-rarity-heroic-start, #7b3538);
42
+ --_fellowship-rarity-end: var(--fellowship-rarity-heroic-end, #c64d42);
43
+ --_fellowship-rarity-border: var(--fellowship-rarity-heroic-border, #e37b6f);
44
+ }
45
+
46
+ [data-rarity="Legendary"] {
47
+ --_fellowship-rarity-start: var(--fellowship-rarity-legendary-start, #8f4a2f);
48
+ --_fellowship-rarity-end: var(--fellowship-rarity-legendary-end, #d07c44);
49
+ --_fellowship-rarity-border: var(--fellowship-rarity-legendary-border, #efa36c);
50
+ }
51
+
52
+ [data-rarity] {
53
+ border-color: var(--_fellowship-rarity-border);
54
+ background: linear-gradient(
55
+ 145deg,
56
+ var(--_fellowship-rarity-start),
57
+ var(--_fellowship-rarity-end)
58
+ );
59
+ }
60
+ `;
61
+ //#endregion
8
62
  //#region src/gear-planner/gear-position.ts
9
63
  r();
10
- var b = class extends i {
64
+ var C = class extends i {
11
65
  constructor(...e) {
12
66
  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
67
  this.dispatchEvent(new CustomEvent("fellowship-gear-position-select", {
@@ -23,7 +77,7 @@ var b = class extends i {
23
77
  };
24
78
  }
25
79
  static {
26
- this.styles = a`
80
+ this.styles = [a`
27
81
  * {
28
82
  box-sizing: border-box;
29
83
  }
@@ -140,7 +194,7 @@ var b = class extends i {
140
194
  transition: none;
141
195
  }
142
196
  }
143
- `;
197
+ `, S];
144
198
  }
145
199
  render() {
146
200
  if (!this.position) return o``;
@@ -151,7 +205,7 @@ var b = class extends i {
151
205
  aria-label=${`${this.position.label}: ${t}`}
152
206
  @click=${this.#e}
153
207
  >
154
- <span class="image">
208
+ <span class="image" data-rarity=${this.itemBuild?.rarity ?? s}>
155
209
  ${this.imageUrl ? o`<img src=${this.imageUrl} alt="" @error=${this.#t} />` : this.position.label.slice(0, 2)}
156
210
  </span>
157
211
  <span class="copy">
@@ -180,13 +234,13 @@ var b = class extends i {
180
234
  #e;
181
235
  #t;
182
236
  };
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({
237
+ e([l({ attribute: !1 })], C.prototype, "position", void 0), e([l({ attribute: !1 })], C.prototype, "item", void 0), e([l({ attribute: !1 })], C.prototype, "itemBuild", void 0), e([l({ type: String })], C.prototype, "itemName", void 0), e([l({
184
238
  type: Boolean,
185
239
  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`
240
+ })], C.prototype, "selected", void 0), e([l({ type: String })], C.prototype, "imageUrl", void 0), e([l({ attribute: !1 })], C.prototype, "tooltipDetails", void 0), C = e([c("fellowship-gear-position")], C);
241
+ var w = () => {
242
+ customElements.get("fellowship-gear-position") || customElements.define("fellowship-gear-position", C);
243
+ }, T = a`
190
244
  :host {
191
245
  display: block;
192
246
  min-width: 0;
@@ -366,6 +420,13 @@ var x = () => {
366
420
  display: block;
367
421
  }
368
422
 
423
+ .control-hint {
424
+ display: block;
425
+ margin-top: 3px;
426
+ font-size: 9px;
427
+ text-transform: uppercase;
428
+ }
429
+
369
430
  fieldset {
370
431
  min-width: 0;
371
432
  margin: 4px 0 0;
@@ -431,23 +492,31 @@ var x = () => {
431
492
  grid-row: auto;
432
493
  }
433
494
  }
434
- `, C = class extends i {
495
+ `, E = class extends i {
435
496
  constructor(...e) {
436
497
  super(...e), this.data = null, this.build = null, this.positionId = null, this.resolveAssetUrl = null, this._query = "", this.#g = (e) => {
437
498
  this._query = e.currentTarget.value;
438
499
  };
439
500
  }
440
501
  static {
441
- this.styles = [n, S];
502
+ this.styles = [
503
+ n,
504
+ T,
505
+ S
506
+ ];
442
507
  }
443
508
  render() {
444
509
  if (!this.data || !this.build || !this.positionId) return o`<div class="empty">Select a gear position to begin.</div>`;
445
510
  let e = this.data.positions.find(({ id: e }) => e === this.positionId), t = this.build.positions.find(({ positionId: e }) => e === this.positionId);
446
511
  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)) : [];
512
+ 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, a = this.data.heroById.get(this.build.heroId), s = this._query.trim().toLocaleLowerCase(), c = a ? _(this.data, this.build, this.positionId).map((e) => {
513
+ let t = e.validRarities[0] ?? "Common";
514
+ return {
515
+ item: e,
516
+ name: b(e, a),
517
+ rarity: t
518
+ };
519
+ }).filter(({ name: e }) => !s || e.toLocaleLowerCase().includes(s)) : [];
451
520
  return o`
452
521
  <header class="workbench-header">
453
522
  <div>
@@ -476,8 +545,8 @@ var x = () => {
476
545
  @click=${() => this.#l(e.item.item.id)}
477
546
  >
478
547
  <span class="choice-mark" aria-hidden="true"></span>
479
- <span class="choice-image">
480
- ${this.#o(e.item.item.images, e.name)}
548
+ <span class="choice-image" data-rarity=${e.rarity}>
549
+ ${this.#o(e.item, a, e.rarity, e.name)}
481
550
  </span>
482
551
  <span>${e.name}</span>
483
552
  <small>
@@ -490,7 +559,7 @@ var x = () => {
490
559
  `;
491
560
  }
492
561
  #e(e, t) {
493
- let n = t.slots.filter((e) => e.rollGroupId === null);
562
+ let n = t.slots.filter((e) => e.rollGroupId === null), r = h(e.itemLevel, e.rarity);
494
563
  return o`
495
564
  <section class="configuration" aria-label="Item configuration">
496
565
  ${this.#a(t)}
@@ -507,6 +576,20 @@ var x = () => {
507
576
  ${f(t.item.validItemLevels, (e) => e, (e) => o`<option value=${e}>${e}</option>`)}
508
577
  </select>
509
578
  </label>
579
+ <label>
580
+ <span>Applied tempers</span>
581
+ <input
582
+ type="number"
583
+ min="0"
584
+ max=${r}
585
+ .value=${String(e.appliedTempers)}
586
+ @change=${(t) => this.#h({
587
+ ...e,
588
+ appliedTempers: Math.max(0, Math.min(r, Math.round(Number(t.currentTarget.value))))
589
+ }, "tempers")}
590
+ />
591
+ <small class="control-hint">Maximum ${r}</small>
592
+ </label>
510
593
  <label>
511
594
  <span>Rarity</span>
512
595
  <select
@@ -642,21 +725,18 @@ var x = () => {
642
725
  </aside>
643
726
  `;
644
727
  }
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;
728
+ #o(e, t, n, r) {
729
+ let i = y(e, t, n, "200");
730
+ if (!i || !this.resolveAssetUrl) return r.slice(0, 2);
731
+ let a;
652
732
  try {
653
- r = this.resolveAssetUrl(n);
733
+ a = this.resolveAssetUrl(i);
654
734
  } catch {
655
- return t.slice(0, 2);
735
+ return r.slice(0, 2);
656
736
  }
657
737
  return o`
658
738
  <img
659
- src=${r}
739
+ src=${a}
660
740
  alt=""
661
741
  @error=${(e) => {
662
742
  e.currentTarget.hidden = !0;
@@ -682,6 +762,7 @@ var x = () => {
682
762
  itemId: e,
683
763
  itemLevel: n,
684
764
  rarity: r,
765
+ appliedTempers: 0,
685
766
  rolledModifiers: [],
686
767
  gems: [],
687
768
  traits: [],
@@ -770,10 +851,10 @@ var x = () => {
770
851
  }
771
852
  #g;
772
853
  };
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`
854
+ e([l({ attribute: !1 })], E.prototype, "data", void 0), e([l({ attribute: !1 })], E.prototype, "build", void 0), e([l({ type: String })], E.prototype, "positionId", void 0), e([l({ attribute: !1 })], E.prototype, "resolveAssetUrl", void 0), e([d()], E.prototype, "_query", void 0), E = e([c("fellowship-item-workbench")], E);
855
+ var D = () => {
856
+ customElements.get("fellowship-item-workbench") || customElements.define("fellowship-item-workbench", E);
857
+ }, O = a`
777
858
  :host {
778
859
  display: block;
779
860
  min-width: 0;
@@ -815,6 +896,34 @@ var w = () => {
815
896
  text-align: left;
816
897
  }
817
898
 
899
+ .talent-points > span {
900
+ display: block;
901
+ margin-bottom: 4px;
902
+ font-size: 10px;
903
+ font-weight: 500;
904
+ letter-spacing: 0.08em;
905
+ text-transform: uppercase;
906
+ }
907
+
908
+ .talent-points {
909
+ grid-column: 1 / -1;
910
+ }
911
+
912
+ .talent-points input {
913
+ width: 100%;
914
+ min-height: 44px;
915
+ padding-inline: 10px;
916
+ border: 2px solid var(--_fellowship-poster-plum);
917
+ border-radius: var(--_fellowship-poster-radius);
918
+ background: var(--_fellowship-poster-white);
919
+ color: var(--_fellowship-poster-plum);
920
+ }
921
+
922
+ .talent-points input:focus-visible {
923
+ outline: 4px solid var(--_fellowship-poster-focus);
924
+ outline-offset: 2px;
925
+ }
926
+
818
927
  .hero-image {
819
928
  display: grid;
820
929
  place-items: center;
@@ -1063,10 +1172,14 @@ var w = () => {
1063
1172
  }
1064
1173
 
1065
1174
  .hero {
1066
- grid-template-columns: 92px minmax(0, max-content);
1175
+ grid-template-columns: 92px minmax(0, max-content) minmax(150px, 180px);
1067
1176
  margin-bottom: 20px;
1068
1177
  }
1069
1178
 
1179
+ .talent-points {
1180
+ grid-column: auto;
1181
+ }
1182
+
1070
1183
  .hero-image {
1071
1184
  width: 92px;
1072
1185
  height: 92px;
@@ -1083,7 +1196,7 @@ var w = () => {
1083
1196
  minmax(142px, 0.64fr)
1084
1197
  minmax(390px, 1.9fr)
1085
1198
  minmax(142px, 0.64fr)
1086
- minmax(190px, 0.86fr);
1199
+ minmax(220px, 1fr);
1087
1200
  align-items: start;
1088
1201
  gap: 12px;
1089
1202
  }
@@ -1201,6 +1314,10 @@ var w = () => {
1201
1314
  margin-bottom: 10px;
1202
1315
  }
1203
1316
 
1317
+ .talent-points {
1318
+ grid-column: 1 / -1;
1319
+ }
1320
+
1204
1321
  .hero-image {
1205
1322
  width: 52px;
1206
1323
  height: 52px;
@@ -1233,39 +1350,30 @@ var w = () => {
1233
1350
  animation-iteration-count: 1 !important;
1234
1351
  }
1235
1352
  }
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) => {
1353
+ `, k = "button:not([disabled]), input:not([disabled]), select:not([disabled]), [href], [tabindex]:not([tabindex=\"-1\"])", A = (e) => {
1253
1354
  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));
1355
+ for (let n of e.children) n instanceof HTMLElement && n.matches(k) && t.push(n), n.shadowRoot && t.push(...A(n.shadowRoot)), t.push(...A(n));
1255
1356
  return t;
1256
- }, A = (e) => {
1357
+ }, j = (e) => {
1257
1358
  let t = e.activeElement;
1258
1359
  for (; t?.shadowRoot?.activeElement;) t = t.shadowRoot.activeElement;
1259
1360
  return t;
1260
1361
  };
1261
- x(), w();
1262
- var j = class extends i {
1362
+ w(), D();
1363
+ var M = class extends i {
1263
1364
  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) => {
1365
+ 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._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.#m = (e) => {
1366
+ if (!this.data || !this.build) return;
1367
+ let t = Math.max(0, Math.min(this.data.maxAllocatedTalentPoints, Math.round(Number(e.currentTarget.value))));
1368
+ this.#v({
1369
+ ...this.build,
1370
+ allocatedTalentPoints: t
1371
+ }, "talent-points", null);
1372
+ }, this.#h = (e) => {
1265
1373
  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
1374
  this.#r = null, this.shadowRoot?.querySelector(".workbench-panel")?.focus({ preventScroll: !0 });
1267
1375
  }, 0));
1268
- }, this.#m = (e) => {
1376
+ }, this.#g = (e) => {
1269
1377
  if (e.stopPropagation(), !this.data || !this.build || !this._selectedPositionId) return;
1270
1378
  let t = {
1271
1379
  ...this.build,
@@ -1273,10 +1381,10 @@ var j = class extends i {
1273
1381
  ...t,
1274
1382
  item: e.detail.item
1275
1383
  } : t)
1276
- }, n = _(this.data, t), r = y(this.data, n.build);
1384
+ }, n = v(this.data, t), r = x(this.data, n.build);
1277
1385
  if (!r.valid || !r.build) {
1278
1386
  let e = r.issues.find(({ severity: e }) => e === "error");
1279
- this.#_({
1387
+ this.#y({
1280
1388
  code: e?.code ?? "invalid-change",
1281
1389
  message: e?.message ?? "That item choice is not valid.",
1282
1390
  recoverable: !0
@@ -1284,29 +1392,29 @@ var j = class extends i {
1284
1392
  return;
1285
1393
  }
1286
1394
  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 = () => {
1395
+ this._announcement = i > 0 ? `${i} incompatible item choice${i === 1 ? " was" : "s were"} reset.` : "Build updated.", this.#v(r.build, e.detail.source, this._selectedPositionId);
1396
+ }, this.#b = () => {
1289
1397
  this._sheetOpen = !1, this.updateComplete.then(() => {
1290
1398
  this.#n !== null && window.cancelAnimationFrame(this.#n), this.#n = window.requestAnimationFrame(() => {
1291
1399
  this.#n = null, this.#i?.focus({ preventScroll: !0 });
1292
1400
  });
1293
1401
  });
1294
- }, this.#y = (e) => {
1295
- e.key !== "Escape" || !this._compact || !this._sheetOpen || (e.preventDefault(), this.#v());
1296
- }, this.#b = (e) => {
1402
+ }, this.#x = (e) => {
1403
+ e.key !== "Escape" || !this._compact || !this._sheetOpen || (e.preventDefault(), this.#b());
1404
+ }, this.#S = (e) => {
1297
1405
  if (!this._compact || !this._sheetOpen) return;
1298
1406
  if (e.key === "Escape") {
1299
- e.preventDefault(), this.#v();
1407
+ e.preventDefault(), this.#b();
1300
1408
  return;
1301
1409
  }
1302
1410
  if (e.key !== "Tab" || !this._workbenchPanel) return;
1303
- let t = k(this._workbenchPanel), n = t[0], r = t.at(-1), i = A(this.shadowRoot);
1411
+ let t = A(this._workbenchPanel), n = t[0], r = t.at(-1), i = j(this.shadowRoot);
1304
1412
  if (!n || !r) {
1305
1413
  e.preventDefault();
1306
1414
  return;
1307
1415
  }
1308
1416
  e.shiftKey && (i === n || i === this._workbenchPanel) ? (e.preventDefault(), r.focus()) : !e.shiftKey && i === r && (e.preventDefault(), n.focus());
1309
- }, this.#x = (e) => {
1417
+ }, this.#C = (e) => {
1310
1418
  if (!["ArrowLeft", "ArrowRight"].includes(e.key)) return;
1311
1419
  e.preventDefault();
1312
1420
  let t = [
@@ -1317,12 +1425,12 @@ var j = class extends i {
1317
1425
  this._tab = t[(r + n + t.length) % t.length] ?? "gear", this.updateComplete.then(() => {
1318
1426
  (this.shadowRoot?.querySelector(".tab[aria-selected=\"true\"]"))?.focus();
1319
1427
  });
1320
- }, this.#C = (e) => {
1428
+ }, this.#T = (e) => {
1321
1429
  e.currentTarget.hidden = !0;
1322
1430
  };
1323
1431
  }
1324
1432
  static {
1325
- this.styles = [n, T];
1433
+ this.styles = [n, O];
1326
1434
  }
1327
1435
  #e;
1328
1436
  #t;
@@ -1337,19 +1445,23 @@ var j = class extends i {
1337
1445
  this.#t !== null && window.cancelAnimationFrame(this.#t), this.#t = window.requestAnimationFrame(() => {
1338
1446
  this.#t = null, e !== this._compact && (this._compact = e, e || (this._sheetOpen = !1));
1339
1447
  });
1340
- }), this.#e.observe(this), this.ownerDocument.addEventListener("keydown", this.#y);
1448
+ }), this.#e.observe(this), this.ownerDocument.addEventListener("keydown", this.#x);
1341
1449
  }
1342
1450
  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();
1451
+ this.#e?.disconnect(), this.ownerDocument.removeEventListener("keydown", this.#x), this.#t !== null && (window.cancelAnimationFrame(this.#t), this.#t = null), this.#n !== null && (window.cancelAnimationFrame(this.#n), this.#n = null), this.#r !== null && (window.clearTimeout(this.#r), this.#r = null), super.disconnectedCallback();
1344
1452
  }
1345
1453
  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));
1454
+ if (super.willUpdate(e), e.has("data") || e.has("build")) {
1455
+ 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);
1456
+ let e = [...this.data?.positions ?? []].sort((e, t) => e.order - t.order);
1457
+ 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);
1458
+ }
1347
1459
  }
1348
1460
  updated(e) {
1349
1461
  if (super.updated(e), e.has("data") || e.has("build") || e.has("resolveAssetUrl")) {
1350
1462
  (e.has("data") || e.has("resolveAssetUrl")) && this.#o.clear();
1351
1463
  let t = !!(this.data && this.build && !this.resolveAssetUrl);
1352
- t && !this.#a ? (this.#a = !0, this.#_({
1464
+ t && !this.#a ? (this.#a = !0, this.#y({
1353
1465
  code: "asset-resolver-missing",
1354
1466
  message: "A resolveAssetUrl callback is required to render Fellowship images.",
1355
1467
  recoverable: !0
@@ -1392,18 +1504,28 @@ var j = class extends i {
1392
1504
  let t = p(e.hero.images, {
1393
1505
  size: "200",
1394
1506
  type: "hero"
1395
- }), n = t ? this.#S(t) : null;
1507
+ }), n = t ? this.#w(t) : null;
1396
1508
  return o`
1397
1509
  <div class="container">
1398
1510
  <section class="planner" aria-label="Fellership Gear Planner">
1399
1511
  <header class="hero">
1400
1512
  <span class="hero-image">
1401
- ${n ? o`<img src=${n} alt="" @error=${this.#C} />` : (e.hero.name ?? e.hero.id).slice(0, 2)}
1513
+ ${n ? o`<img src=${n} alt="" @error=${this.#T} />` : (e.hero.name ?? e.hero.id).slice(0, 2)}
1402
1514
  </span>
1403
1515
  <span>
1404
1516
  <span class="hero-label">Current hero</span>
1405
1517
  <h1>${e.hero.name ?? e.hero.id}</h1>
1406
1518
  </span>
1519
+ <label class="talent-points">
1520
+ <span>Allocated talent points</span>
1521
+ <input
1522
+ type="number"
1523
+ min="0"
1524
+ max=${this.data.maxAllocatedTalentPoints}
1525
+ .value=${String(this.build.allocatedTalentPoints)}
1526
+ @change=${this.#m}
1527
+ />
1528
+ </label>
1407
1529
  </header>
1408
1530
 
1409
1531
  ${this.#s()}
@@ -1421,11 +1543,11 @@ var j = class extends i {
1421
1543
  >
1422
1544
  <div class="slot-grid">
1423
1545
  <div class="slot-rail left-rail">
1424
- ${this.#c(this._compact ? this.data.positions.map(({ id: e }) => e) : E)}
1546
+ ${this.#c(this._compact ? this._mobilePositionIds : this._leftPositionIds)}
1425
1547
  </div>
1426
1548
  ${this._compact ? s : o`
1427
1549
  <div class="slot-rail right-rail">
1428
- ${this.#c(D)}
1550
+ ${this.#c(this._rightPositionIds)}
1429
1551
  </div>
1430
1552
  `}
1431
1553
  </div>
@@ -1447,11 +1569,11 @@ var j = class extends i {
1447
1569
  #s() {
1448
1570
  return o`
1449
1571
  <div class="tabs" role="tablist" aria-label="Planner workspace">
1450
- ${[
1572
+ ${f([
1451
1573
  ["traits", "Traits & gems"],
1452
1574
  ["gear", "Gear"],
1453
1575
  ["bonuses", "Bonuses"]
1454
- ].map(([e, t]) => o`
1576
+ ], ([e]) => e, ([e, t]) => o`
1455
1577
  <button
1456
1578
  class="tab"
1457
1579
  type="button"
@@ -1461,7 +1583,7 @@ var j = class extends i {
1461
1583
  @click=${() => {
1462
1584
  this._tab = e;
1463
1585
  }}
1464
- @keydown=${this.#x}
1586
+ @keydown=${this.#C}
1465
1587
  >
1466
1588
  ${t}
1467
1589
  </button>
@@ -1474,20 +1596,17 @@ var j = class extends i {
1474
1596
  let t = this.data.heroById.get(this.build.heroId) ?? null;
1475
1597
  return o`
1476
1598
  ${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;
1599
+ 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
1600
  return o`
1482
1601
  <fellowship-gear-position
1483
1602
  .position=${n}
1484
1603
  .item=${i}
1485
1604
  .itemBuild=${r?.item ?? null}
1486
- .itemName=${i && t ? v(i, t) : null}
1605
+ .itemName=${i && t ? b(i, t) : null}
1487
1606
  .selected=${e === this._selectedPositionId}
1488
- .imageUrl=${a ? this.#S(a) : null}
1607
+ .imageUrl=${a ? this.#w(a) : null}
1489
1608
  .tooltipDetails=${this.#u(e, r?.item ?? null)}
1490
- @fellowship-gear-position-select=${this.#p}
1609
+ @fellowship-gear-position-select=${this.#h}
1491
1610
  ></fellowship-gear-position>
1492
1611
  `;
1493
1612
  })}
@@ -1502,12 +1621,12 @@ var j = class extends i {
1502
1621
  aria-modal=${this._compact ? "true" : s}
1503
1622
  aria-label="Item workbench"
1504
1623
  tabindex=${this._compact ? "-1" : s}
1505
- @keydown=${this.#b}
1506
- @fellowship-workbench-change=${this.#m}
1624
+ @keydown=${this.#S}
1625
+ @fellowship-workbench-change=${this.#g}
1507
1626
  >
1508
1627
  <header class="sheet-header">
1509
1628
  <strong>Configure item</strong>
1510
- <button class="sheet-close" type="button" @click=${this.#v}>Close</button>
1629
+ <button class="sheet-close" type="button" @click=${this.#b}>Close</button>
1511
1630
  </header>
1512
1631
  <fellowship-item-workbench
1513
1632
  .data=${this.data}
@@ -1558,7 +1677,7 @@ var j = class extends i {
1558
1677
  <h2>Traits & gems</h2>
1559
1678
  <h3>Active traits</h3>
1560
1679
  <ul class="summary-list">
1561
- ${e.size === 0 ? o`<li class="summary-row"><span>No traits selected</span></li>` : [...e].map(([e, t]) => o`
1680
+ ${e.size === 0 ? o`<li class="summary-row"><span>No traits selected</span></li>` : f([...e], ([e]) => e, ([e, t]) => o`
1562
1681
  <li class="summary-row">
1563
1682
  <span>${this.data?.traitById.get(e)?.name ?? e}</span>
1564
1683
  <strong>R${t}</strong>
@@ -1567,7 +1686,7 @@ var j = class extends i {
1567
1686
  </ul>
1568
1687
  <h3>Socket gems</h3>
1569
1688
  <ul class="summary-list">
1570
- ${t.size === 0 ? o`<li class="summary-row"><span>No gems socketed</span></li>` : [...t].map(([e, t]) => o`
1689
+ ${t.size === 0 ? o`<li class="summary-row"><span>No gems socketed</span></li>` : f([...t], ([e]) => e, ([e, t]) => o`
1571
1690
  <li class="summary-row">
1572
1691
  <span>${e} × ${t.count}</span>
1573
1692
  <strong>${t.power}</strong>
@@ -1587,10 +1706,10 @@ var j = class extends i {
1587
1706
  }
1588
1707
  return o`
1589
1708
  <span class="section-label">Build output</span>
1590
- <h2>Bonuses & totals</h2>
1709
+ <h2>Bonuses & stats</h2>
1591
1710
  <h3>Blessings</h3>
1592
1711
  <ul class="summary-list">
1593
- ${e.size === 0 ? o`<li class="summary-row"><span>No blessings selected</span></li>` : [...e].map(([e, t]) => o`
1712
+ ${e.size === 0 ? o`<li class="summary-row"><span>No blessings selected</span></li>` : f([...e], ([e]) => e, ([e, t]) => o`
1594
1713
  <li class="summary-row">
1595
1714
  <span>${this.data?.blessingById.get(e)?.name ?? e}</span>
1596
1715
  <strong>R${t}</strong>
@@ -1599,7 +1718,7 @@ var j = class extends i {
1599
1718
  </ul>
1600
1719
  <h3>Set bonuses</h3>
1601
1720
  <ul class="summary-list">
1602
- ${t.size === 0 ? o`<li class="summary-row"><span>No set pieces equipped</span></li>` : [...t].map(([e, t]) => {
1721
+ ${t.size === 0 ? o`<li class="summary-row"><span>No set pieces equipped</span></li>` : f([...t], ([e]) => e, ([e, t]) => {
1603
1722
  let n = this.data?.setBonuses.find(({ id: t }) => t === e);
1604
1723
  return o`
1605
1724
  <li class="summary-row">
@@ -1609,20 +1728,29 @@ var j = class extends i {
1609
1728
  `;
1610
1729
  })}
1611
1730
  </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>
1731
+ <div class="hero-sheet" aria-label="Hero stats">
1732
+ ${f(this._calculation.heroSheet.sections, (e) => e.id, (e) => o`
1733
+ <section class="hero-sheet-section">
1734
+ <h3>${e.label}</h3>
1735
+ <ul class="breakdown">
1736
+ ${f(e.rows, (e) => e.id, (e) => o`
1737
+ <li class="total-row">
1738
+ <span>${e.label}</span>
1739
+ <strong>${this.#p(e)}</strong>
1740
+ </li>
1741
+ `)}
1742
+ </ul>
1743
+ </section>
1744
+ `)}
1745
+ </div>
1618
1746
  ${this._calculation.conditionalMechanics.length > 0 ? o`
1619
1747
  <h3>Conditional</h3>
1620
- ${this._calculation.conditionalMechanics.map((e) => o`
1748
+ ${f(this._calculation.conditionalMechanics, (e) => e.id, (e) => o`
1621
1749
  <label class="condition">
1622
1750
  <input
1623
1751
  type="checkbox"
1624
1752
  .checked=${e.applied}
1625
- @change=${() => this.#h(e.id)}
1753
+ @change=${() => this.#_(e.id)}
1626
1754
  />
1627
1755
  <span>${e.sourceName} · Apply</span>
1628
1756
  </label>
@@ -1630,7 +1758,7 @@ var j = class extends i {
1630
1758
  ` : s}
1631
1759
  <h3>Unsupported</h3>
1632
1760
  <ul class="breakdown">
1633
- ${this._calculation.unsupportedContributions.length === 0 ? o`<li class="unsupported-row"><span>None</span></li>` : this._calculation.unsupportedContributions.map((e) => o`
1761
+ ${this._calculation.unsupportedContributions.length === 0 ? o`<li class="unsupported-row"><span>None</span></li>` : f(this._calculation.unsupportedContributions, (e) => e.id, (e) => o`
1634
1762
  <li class="unsupported-row">
1635
1763
  <span>${e.sourceName}</span>
1636
1764
  <span class="unsupported-code">${e.reasonCode.replaceAll("-", " ")}</span>
@@ -1639,17 +1767,25 @@ var j = class extends i {
1639
1767
  </ul>
1640
1768
  `;
1641
1769
  }
1642
- #p;
1770
+ #p(e) {
1771
+ return new Intl.NumberFormat("en-US", {
1772
+ style: e.displayKind === "percentage" ? "percent" : "decimal",
1773
+ minimumFractionDigits: e.decimals,
1774
+ maximumFractionDigits: e.decimals
1775
+ }).format(e.value);
1776
+ }
1643
1777
  #m;
1644
- #h(e) {
1778
+ #h;
1779
+ #g;
1780
+ #_(e) {
1645
1781
  if (!this.build) return;
1646
1782
  let t = new Set(this.build.disabledConditionalContributionIds);
1647
- t.has(e) ? t.delete(e) : t.add(e), this.#g({
1783
+ t.has(e) ? t.delete(e) : t.add(e), this.#v({
1648
1784
  ...this.build,
1649
1785
  disabledConditionalContributionIds: [...t].sort()
1650
1786
  }, "conditional", null);
1651
1787
  }
1652
- #g(e, t, n) {
1788
+ #v(e, t, n) {
1653
1789
  this.dispatchEvent(new CustomEvent("fellowship-gear-build-change", {
1654
1790
  bubbles: !0,
1655
1791
  composed: !0,
@@ -1660,7 +1796,7 @@ var j = class extends i {
1660
1796
  }
1661
1797
  }));
1662
1798
  }
1663
- #_(e) {
1799
+ #y(e) {
1664
1800
  this.dispatchEvent(new CustomEvent("fellowship-gear-planner-error", {
1665
1801
  bubbles: !0,
1666
1802
  composed: !0,
@@ -1670,17 +1806,17 @@ var j = class extends i {
1670
1806
  }
1671
1807
  }));
1672
1808
  }
1673
- #v;
1674
- #y;
1675
1809
  #b;
1676
1810
  #x;
1677
- #S(e) {
1811
+ #S;
1812
+ #C;
1813
+ #w(e) {
1678
1814
  if (!this.resolveAssetUrl) return null;
1679
1815
  try {
1680
1816
  return this.resolveAssetUrl(e);
1681
1817
  } catch (t) {
1682
1818
  return this.#o.has(e.path) || (this.#o.add(e.path), queueMicrotask(() => {
1683
- this.isConnected && this.#_({
1819
+ this.isConnected && this.#y({
1684
1820
  code: "asset-url-error",
1685
1821
  message: `Could not resolve ${e.path}: ${String(t)}.`,
1686
1822
  recoverable: !0
@@ -1688,14 +1824,14 @@ var j = class extends i {
1688
1824
  })), null;
1689
1825
  }
1690
1826
  }
1691
- #C;
1827
+ #T;
1692
1828
  };
1693
- e([l({ attribute: !1 })], j.prototype, "data", void 0), e([l({ attribute: !1 })], j.prototype, "build", void 0), e([l({
1829
+ e([l({ attribute: !1 })], M.prototype, "data", void 0), e([l({ attribute: !1 })], M.prototype, "build", void 0), e([l({
1694
1830
  type: Boolean,
1695
1831
  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);
1832
+ })], M.prototype, "loading", void 0), e([l({ attribute: !1 })], M.prototype, "error", void 0), e([l({ attribute: !1 })], M.prototype, "resolveAssetUrl", void 0), e([d()], M.prototype, "_selectedPositionId", void 0), e([d()], M.prototype, "_tab", void 0), e([d()], M.prototype, "_sheetOpen", void 0), e([d()], M.prototype, "_compact", void 0), e([d()], M.prototype, "_announcement", void 0), e([d()], M.prototype, "_calculation", void 0), e([u(".workbench-panel")], M.prototype, "_workbenchPanel", void 0), M = e([c("fellowship-gear-planner")], M);
1833
+ var N = () => {
1834
+ customElements.get("fellowship-gear-planner") || customElements.define("fellowship-gear-planner", M);
1699
1835
  };
1700
1836
  //#endregion
1701
- export { M as n, j as t };
1837
+ export { N as n, M as t };
@@ -21,6 +21,9 @@ export declare class FellowshipGearPlannerElement extends LitElement {
21
21
  private _compact;
22
22
  private _announcement;
23
23
  private _calculation;
24
+ private _leftPositionIds;
25
+ private _rightPositionIds;
26
+ private _mobilePositionIds;
24
27
  private _workbenchPanel?;
25
28
  connectedCallback(): void;
26
29
  disconnectedCallback(): void;
@@ -2,7 +2,7 @@ import type { GearPlannerItem, GearPlannerItemBuild, GearPlannerPosition } from
2
2
  import { LitElement, type TemplateResult } from "lit";
3
3
  export declare class FellowshipGearPositionElement extends LitElement {
4
4
  #private;
5
- static styles: import("lit").CSSResult;
5
+ static styles: import("lit").CSSResult[];
6
6
  position: GearPlannerPosition | null;
7
7
  item: GearPlannerItem | null;
8
8
  itemBuild: GearPlannerItemBuild | null;
@@ -0,0 +1 @@
1
+ export declare const rarityThumbnailStyles: import("lit").CSSResult;
@@ -4,7 +4,7 @@ export interface GearPlannerPresentationError {
4
4
  readonly message: string;
5
5
  readonly recoverable?: boolean;
6
6
  }
7
- export type GearPlannerChangeSource = "item" | "item-level" | "rarity" | "modifier" | "gem" | "trait" | "blessing" | "conditional";
7
+ export type GearPlannerChangeSource = "item" | "item-level" | "rarity" | "tempers" | "talent-points" | "modifier" | "gem" | "trait" | "blessing" | "conditional";
8
8
  export interface GearPlannerBuildChangeDetail {
9
9
  readonly build: GearPlannerBuild;
10
10
  readonly source: GearPlannerChangeSource;
@@ -1,4 +1,4 @@
1
- import { n as e, t } from "./chunks/gear-planner-uQOpm4QI.js";
1
+ import { n as e, t } from "./chunks/gear-planner-C-ZTX8S2.js";
2
2
  //#region src/gear-planner/index.ts
3
3
  e();
4
4
  //#endregion
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import "./dungeon-cast-browser.js";
7
7
  import { n as C, o as w, r as T, s as E, t as D } from "./chunks/tooltip-C3SSJw-t.js";
8
8
  import "./talent-calculator.js";
9
9
  import "./tooltip.js";
10
- import { n as O, t as k } from "./chunks/gear-planner-uQOpm4QI.js";
10
+ import { n as O, t as k } from "./chunks/gear-planner-C-ZTX8S2.js";
11
11
  import "./gear-planner.js";
12
12
  import { createPackageFellowshipDataClient as A } from "@crockery/fellowship-data/client";
13
13
  export { e as FellowshipDataError, d as FellowshipDungeonCastBrowserElement, o as FellowshipDungeonMapElement, k as FellowshipGearPlannerElement, x as FellowshipTalentCalculatorElement, D as FellowshipTooltipElement, h as MAX_TALENT_POINTS, a as ViewportController, T as calculateTooltipPosition, f as canSelectTalent, u as castIdentityKey, c as createDungeonCastBrowserModel, n as createDungeonMapRenderModel, s as createPackageFellowshipCastDataSource, A as createPackageFellowshipDataClient, i as createPackageFellowshipDataSource, m as createPackageFellowshipTalentDataSource, g as createTalentCalculatorRenderModel, v as createTalentCalculatorSelectionState, l as defineFellowshipDungeonCastBrowser, r as defineFellowshipDungeonMap, O as defineFellowshipGearPlanner, _ as defineFellowshipTalentCalculator, C as defineFellowshipTooltip, t as humanizeMapLabel, b as normalizeSelectedTalentIds, p as normalizeTalentPoints, w as resolveFellowshipRichText, E as stripFellowshipRichText, S as talentRowLabel, y as talentSlotName };
@@ -1,4 +1,4 @@
1
- import { n as e, t } from "../chunks/gear-planner-uQOpm4QI.js";
1
+ import { n as e, t } from "../chunks/gear-planner-C-ZTX8S2.js";
2
2
  import { createComponent as n } from "@lit/react";
3
3
  import r, { forwardRef as i } from "react";
4
4
  import { jsx as a } from "react/jsx-runtime";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crockery/fellowship-components",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "Lit web components for rendering Fellowship game data.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -112,7 +112,7 @@
112
112
  "dependencies": {
113
113
  "@lit/react": "^1.0.8",
114
114
  "lit": "^3.3.3",
115
- "@crockery/fellowship-data": "^3.1.0"
115
+ "@crockery/fellowship-data": "^4.0.0"
116
116
  },
117
117
  "peerDependencies": {
118
118
  "react": ">=18 <20",
@@ -37,7 +37,7 @@ At 1200px and above, the component renders summary and slot rails around a
37
37
  persistent workbench. Below 1200px it uses Traits & gems, Gear, and Bonuses tabs,
38
38
  with configuration in a full-height focus-contained editor. Item choices are
39
39
  filtered by the Data package, all changes are immediate, and unsupported
40
- mechanics remain visibly excluded from exact totals.
40
+ mechanics remain visibly separated from the generated in-game hero sheet.
41
41
 
42
42
  The planner uses the public `--fellowship-poster-*` Dungeon Poster tokens and
43
43
  bundled Syne/DM Mono faces. It maintains 44px controls, visible focus, reduced