@omnigateway/pokemon 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,8 +56,8 @@ check:
56
56
  | Capability | Why |
57
57
  | --- | --- |
58
58
  | `storage` | Three tables on the plugin's own migration track, named `plugin_pokemon_<name>` by the host: the companion row per key, the Dex, and the grant ledger. |
59
- | `files` | The species index and cached sprites live in the plugin's scoped data directory, **not** in a table. That directory is excluded from database snapshots, exactly as `request_bodies/` is — the alternative would put tens of megabytes of artwork into every snapshot an operator downloads, and it re-fetches itself anyway. |
60
- | `net:outbound` | Species data and sprites are fetched at runtime. The manifest also declares the origins, `https://pokeapi.co` and `https://raw.githubusercontent.com`; the host hands the plugin a `fetch` bound to that allowlist and refuses anything else. |
59
+ | `files` | The species index and cached sprites — companions and item icons alike — live in the plugin's scoped data directory, **not** in a table. That directory is excluded from database snapshots, exactly as `request_bodies/` is — the alternative would put tens of megabytes of artwork into every snapshot an operator downloads, and it re-fetches itself anyway. |
60
+ | `net:outbound` | Species data, companion sprites and item icons are fetched at runtime. The manifest also declares the origins, `https://pokeapi.co` and `https://raw.githubusercontent.com`; the host hands the plugin a `fetch` bound to that allowlist and refuses anything else. |
61
61
  | `events:request` | Growth is credited from `RequestCompleted` — all four token classes, which are disjoint, so summing them double-counts nothing. |
62
62
  | `events:limit` | A key parked at a `5h` or `1w` ceiling earns a rare candy, rated by the window's own length. A `1m` ceiling pays nothing: a minute is not a span in which work happened. |
63
63
 
@@ -68,8 +68,9 @@ accidental overreach is impossible and that the plugin's intent is auditable
68
68
  from one readable file. It constrains honest code and not hostile code.
69
69
 
70
70
  The plugin degrades rather than failing when a capability is absent. With no
71
- `net`, an incubating egg holds its progress instead of losing it, and the sprite
72
- route answers `503`.
71
+ `net`, an incubating egg holds its progress instead of losing it, and both
72
+ sprite routes answer `503` — the panel then draws each item as an emoji, which
73
+ is the same thing it draws before the cache has filled.
73
74
 
74
75
  ## Nintendo and Game Freak intellectual property
75
76
 
@@ -106,6 +107,27 @@ is deliberate: a plugin developed inside the monorepo can reach packages an
106
107
  installed plugin cannot, and a build that only succeeds there proves nothing
107
108
  about one that has to run anywhere else.
108
109
 
110
+ `@omnigateway/dashboard-sdk` is an **external** of the UI build, alongside React
111
+ and friends — the console serves one copy of each through its import map.
112
+ (`@omnigateway/plugin-api` is not: it is a runtime dependency of the *server*
113
+ half and is bundled into it, and the panel never imports it.)
114
+
115
+ The SDK is the easiest external to forget, because it is the one package here
116
+ that is obviously ours, and forgetting it fails silently: the SDK holds the
117
+ console's LIVE switch in a React context, so a bundled copy is a second context,
118
+ and the panel stops refreshing without an error anywhere.
119
+ `test/package.test.ts` checks the built bundle still imports it and carries no
120
+ `createContext` of its own.
121
+
122
+ ## The console's LIVE switch
123
+
124
+ The panel polls, because growth arrives from requests it cannot hear about. It
125
+ polls on the console's cadence rather than its own: the chassis bar's LIVE
126
+ control pauses every screen at once, this panel included, and there is
127
+ deliberately no per-panel refresh setting. Paused means it stops refetching, not
128
+ that it stops working — an operator who pauses still sees the companion they
129
+ opened.
130
+
109
131
  ## Licence
110
132
 
111
133
  MIT. See [LICENSE](LICENSE). The licence covers this plugin's code and not the
package/omni-plugin.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "id": "pokemon",
3
3
  "name": "Pok\u00e9mon Companion",
4
- "version": "1.0.1",
4
+ "version": "1.1.0",
5
5
  "api": 1,
6
- "sdk": "^0.1.0",
6
+ "sdk": "^0.1.1",
7
7
  "server": "server/index.js",
8
8
  "ui": "ui/index.js",
9
9
  "nav": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnigateway/pokemon",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "A Pokémon companion for OmniGateway. Each gateway key raises one that hatches, evolves, and graduates into a Pokédex on the tokens that key spends.",
5
5
  "license": "MIT",
6
6
  "author": "Harismawan <mail@harismawan.com>",
package/server/index.js CHANGED
@@ -43,9 +43,27 @@ var RARE_CANDY_XP = 1e8;
43
43
  var ITEM_PRICES = {
44
44
  rareCandy: 500000000,
45
45
  mint: 1e8,
46
- shinyCharm: 3000000000
46
+ shinyCharm: 3000000000,
47
+ everstone: 1e9,
48
+ lure: 1e9,
49
+ sootheBell: 3000000000,
50
+ incense: 500000000,
51
+ repel: 500000000
47
52
  };
53
+ var SOOTHE_BONUS = 0.25;
48
54
  var ITEM_KINDS = Object.keys(ITEM_PRICES);
55
+ var ITEM_SPRITE_FILES = {
56
+ rareCandy: "rare-candy",
57
+ shinyCharm: "shiny-charm",
58
+ everstone: "everstone",
59
+ sootheBell: "soothe-bell",
60
+ repel: "repel",
61
+ incense: "luck-incense",
62
+ lure: "honey",
63
+ mint: "mental-herb",
64
+ egg: "lucky-egg"
65
+ };
66
+ var ITEM_SPRITE_NAMES = new Map(Object.entries(ITEM_SPRITE_FILES));
49
67
  var FRESH_EGG_BASE_PRICE = 1e9;
50
68
  function freshEggPrice(tier) {
51
69
  if (tier === null)
@@ -99,12 +117,14 @@ function decideGrant(input) {
99
117
  var POKEAPI_ORIGIN = "https://pokeapi.co";
100
118
  var SPRITE_ORIGIN = "https://raw.githubusercontent.com";
101
119
  var SPRITE_DIR = "/PokeAPI/sprites/master/sprites/pokemon/versions/generation-v/black-white/animated";
120
+ var ITEM_SPRITE_DIR = "/PokeAPI/sprites/master/sprites/items";
102
121
  var MAX_EVOLUTION_CHAIN_ID = 2000;
103
122
  var INDEX_FETCH_CONCURRENCY = 8;
104
123
  var INDEX_PATH = "species/index.json";
105
124
  var speciesPath = (id) => `species/${id}.json`;
106
125
  var chainPath = (chainId) => `species/chain-${chainId}.json`;
107
126
  var spritePath = (id, shiny) => shiny ? `sprites/shiny/${id}.gif` : `sprites/${id}.gif`;
127
+ var itemSpritePath = (name) => `sprites/items/${name}.png`;
108
128
  function isFetchableSpeciesId(id) {
109
129
  return Number.isInteger(id) && hasAnimatedSprite(id);
110
130
  }
@@ -348,6 +368,10 @@ async function loadDetail(deps, id, chains) {
348
368
  function speciesDetail(deps, id) {
349
369
  return loadDetail(deps, id, new Map);
350
370
  }
371
+ function speciesDetails(deps, ids) {
372
+ const chains = new Map;
373
+ return Promise.all(ids.map((id) => loadDetail(deps, id, chains)));
374
+ }
351
375
  async function cachedSpeciesName(deps, id) {
352
376
  if (!isFetchableSpeciesId(id))
353
377
  return null;
@@ -427,6 +451,22 @@ async function spriteBytes(deps, id, shiny) {
427
451
  await writeCache(deps, path, bytes);
428
452
  return bytes;
429
453
  }
454
+ async function itemSpriteBytes(deps, item) {
455
+ const name = ITEM_SPRITE_NAMES.get(item);
456
+ if (name === undefined)
457
+ return null;
458
+ const path = itemSpritePath(name);
459
+ try {
460
+ const cached = await deps.files.read(path);
461
+ if (cached !== null && cached.length > 0)
462
+ return cached;
463
+ } catch {}
464
+ const bytes = await fetchBytes(deps, `${SPRITE_ORIGIN}${ITEM_SPRITE_DIR}/${name}.png`);
465
+ if (bytes === null)
466
+ return null;
467
+ await writeCache(deps, path, bytes);
468
+ return bytes;
469
+ }
430
470
 
431
471
  // src/roll.ts
432
472
  var NATURES = [
@@ -467,23 +507,33 @@ function mulberry32(seed) {
467
507
  };
468
508
  }
469
509
  var COLLECTED_WEIGHT = 0.25;
510
+ var FORM_WEIGHT = 0.6;
470
511
  function roll(input) {
471
512
  const random = mulberry32(input.seed);
472
- const eligible = input.candidates.filter((candidate) => {
513
+ const eligibleWith = (withLure) => input.candidates.filter((candidate) => {
473
514
  if (!hasAnimatedSprite(candidate.id))
474
515
  return false;
475
516
  if (candidate.id === DITTO_SPECIES_ID)
476
517
  return false;
518
+ if (input.excludeFinal != null && candidate.finalId === input.excludeFinal)
519
+ return false;
520
+ if (withLure && input.collectedFinals.has(candidate.finalId))
521
+ return false;
477
522
  if (input.guarantee === null)
478
523
  return true;
479
524
  const rarity2 = rarityFromCaptureRate(candidate.captureRate, false, false);
480
525
  return sortRank(rarity2) >= sortRank(input.guarantee);
481
526
  });
527
+ const wanted = input.onlyUncollected === true;
528
+ const lured = wanted ? eligibleWith(true) : [];
529
+ const usedLure = wanted && lured.length > 0;
530
+ const eligible = usedLure ? lured : eligibleWith(false);
482
531
  if (eligible.length === 0)
483
532
  return null;
484
533
  const weights = eligible.map((candidate) => {
485
534
  const base = Math.max(1, candidate.captureRate);
486
- return input.collectedFinals.has(candidate.finalId) ? base * COLLECTED_WEIGHT : base;
535
+ const collected = input.collectedFinals.has(candidate.finalId) ? base * COLLECTED_WEIGHT : base;
536
+ return input.preferLongLines === true ? collected * (1 + FORM_WEIGHT * (Math.max(1, candidate.forms) - 1)) : collected;
487
537
  });
488
538
  const total = weights.reduce((a, b) => a + b, 0);
489
539
  let target = random() * total;
@@ -502,10 +552,16 @@ function roll(input) {
502
552
  const rarity = rarityFromCaptureRate(chosen.captureRate, false, false);
503
553
  const dittoEligible = rarity === "common" && chosen.forms >= 2;
504
554
  const ditto = dittoEligible && random() < 1 / ODDS.dittoDisguise;
505
- return { speciesId: chosen.id, isShiny, nature, ditto };
555
+ return { speciesId: chosen.id, isShiny, nature, ditto, usedLure };
506
556
  }
507
557
 
508
558
  // src/state.ts
559
+ function emptyInventory() {
560
+ const inventory = {};
561
+ for (const kind of ITEM_KINDS)
562
+ inventory[kind] = 0;
563
+ return inventory;
564
+ }
509
565
  function freshState() {
510
566
  return {
511
567
  consumedTotal: 0,
@@ -513,7 +569,11 @@ function freshState() {
513
569
  eggUsage: 0,
514
570
  eggTier: null,
515
571
  pendingHatch: null,
516
- inventory: { rareCandy: 0, mint: 0, shinyCharm: 0 }
572
+ pendingReveal: null,
573
+ lure: false,
574
+ incense: false,
575
+ repel: null,
576
+ inventory: emptyInventory()
517
577
  };
518
578
  }
519
579
  function isRecord(value) {
@@ -534,7 +594,7 @@ function parseState(raw) {
534
594
  }
535
595
  if (!isRecord(parsed))
536
596
  return null;
537
- const inventory = { rareCandy: 0, mint: 0, shinyCharm: 0 };
597
+ const inventory = emptyInventory();
538
598
  const storedInventory = parsed.inventory;
539
599
  if (isRecord(storedInventory)) {
540
600
  for (const kind of ITEM_KINDS) {
@@ -562,7 +622,10 @@ function parseState(raw) {
562
622
  isShiny: storedActive.isShiny === true,
563
623
  nature: nature ?? "hardy",
564
624
  dittoDisguise: typeof storedActive.dittoDisguise === "number" ? storedActive.dittoDisguise : null,
565
- dittoRevealed: storedActive.dittoRevealed === true
625
+ dittoRevealed: storedActive.dittoRevealed === true,
626
+ everstone: storedActive.everstone === true,
627
+ soothe: storedActive.soothe === true,
628
+ soothedRaw: Math.max(0, asInt(storedActive.soothedRaw, 0))
566
629
  };
567
630
  }
568
631
  const storedPending = parsed.pendingHatch;
@@ -582,13 +645,28 @@ function parseState(raw) {
582
645
  };
583
646
  }
584
647
  }
648
+ const storedReveal = parsed.pendingReveal;
649
+ let pendingReveal = null;
650
+ if (isRecord(storedReveal)) {
651
+ const rarity = asRarity(storedReveal.rarity);
652
+ const path = Array.isArray(storedReveal.path) ? storedReveal.path.filter((id) => typeof id === "number" && id > 0) : [];
653
+ if (rarity !== null && path.length > 0)
654
+ pendingReveal = { path, rarity };
655
+ }
656
+ const storedConsumed = parsed.consumedTotal;
657
+ if (typeof storedConsumed !== "number" || !Number.isFinite(storedConsumed))
658
+ return null;
585
659
  const eggTier = asRarity(parsed.eggTier);
586
660
  return {
587
- consumedTotal: Math.max(0, asInt(parsed.consumedTotal, 0)),
661
+ consumedTotal: Math.max(0, Math.trunc(storedConsumed)),
588
662
  active,
589
663
  eggUsage: Math.max(0, asInt(parsed.eggUsage, 0)),
590
664
  eggTier: eggTier === null || eggTier === "legendary" ? null : eggTier,
591
665
  pendingHatch,
666
+ pendingReveal,
667
+ lure: parsed.lure === true,
668
+ incense: parsed.incense === true,
669
+ repel: typeof parsed.repel === "number" && Number.isInteger(parsed.repel) && parsed.repel > 0 ? parsed.repel : null,
592
670
  inventory
593
671
  };
594
672
  }
@@ -606,7 +684,19 @@ function advance(state, tokensTotal) {
606
684
  const events = [];
607
685
  let next = { ...state, consumedTotal: Math.trunc(tokensTotal) };
608
686
  if (gained > 0) {
609
- next = next.active === null ? { ...next, eggUsage: next.eggUsage + gained } : { ...next, active: { ...next.active, usedAtStage: next.active.usedAtStage + gained } };
687
+ const active = next.active;
688
+ if (active === null) {
689
+ next = { ...next, eggUsage: next.eggUsage + gained };
690
+ } else if (!active.soothe) {
691
+ next = { ...next, active: { ...active, usedAtStage: active.usedAtStage + gained } };
692
+ } else {
693
+ const raw = active.soothedRaw + gained;
694
+ const owed = Math.floor(raw * SOOTHE_BONUS) - Math.floor(active.soothedRaw * SOOTHE_BONUS);
695
+ next = {
696
+ ...next,
697
+ active: { ...active, soothedRaw: raw, usedAtStage: active.usedAtStage + gained + owed }
698
+ };
699
+ }
610
700
  }
611
701
  for (let step = 0;step < MAX_TRANSITIONS_PER_ADVANCE; step++) {
612
702
  if (next.active === null) {
@@ -624,7 +714,10 @@ function advance(state, tokensTotal) {
624
714
  isShiny: hatch.isShiny,
625
715
  nature: hatch.nature,
626
716
  dittoDisguise: hatch.ditto ? hatch.speciesId : null,
627
- dittoRevealed: false
717
+ dittoRevealed: false,
718
+ everstone: false,
719
+ soothe: false,
720
+ soothedRaw: 0
628
721
  };
629
722
  events.push({
630
723
  kind: "hatched",
@@ -636,10 +729,36 @@ function advance(state, tokensTotal) {
636
729
  continue;
637
730
  }
638
731
  const mon = next.active;
732
+ if (mon.everstone)
733
+ break;
639
734
  const needed = phaseThreshold(mon.rarity, mon.plannedPath.length, mon.stageIndex);
640
735
  if (mon.usedAtStage < needed)
641
736
  break;
642
737
  const excess = mon.usedAtStage - needed;
738
+ if (mon.dittoDisguise !== null && !mon.dittoRevealed) {
739
+ if (next.pendingReveal === null)
740
+ break;
741
+ const reveal = next.pendingReveal;
742
+ events.push({
743
+ kind: "revealed",
744
+ disguisedAs: mon.plannedPath[mon.stageIndex] ?? mon.baseId,
745
+ speciesId: reveal.path[0]
746
+ });
747
+ next = {
748
+ ...next,
749
+ active: {
750
+ ...mon,
751
+ baseId: reveal.path[0],
752
+ plannedPath: reveal.path,
753
+ stageIndex: 0,
754
+ usedAtStage: excess,
755
+ rarity: reveal.rarity,
756
+ dittoRevealed: true
757
+ },
758
+ pendingReveal: null
759
+ };
760
+ continue;
761
+ }
643
762
  if (mon.stageIndex < mon.plannedPath.length - 1) {
644
763
  events.push({
645
764
  kind: "evolved",
@@ -832,10 +951,16 @@ function consume(storage, apiKeyId, item, applyToState, now) {
832
951
  return { ok: false, reason: "unreadable" };
833
952
  if ((row.state.inventory[item] ?? 0) <= 0)
834
953
  return { ok: false, reason: "none-held" };
835
- const nextState = applyToState({
836
- ...row.state,
837
- inventory: { ...row.state.inventory, [item]: (row.state.inventory[item] ?? 0) - 1 }
838
- });
954
+ const outcome = applyToState(row.state);
955
+ if ("refused" in outcome)
956
+ return { ok: false, reason: outcome.refused };
957
+ const nextState = {
958
+ ...outcome.applied,
959
+ inventory: {
960
+ ...outcome.applied.inventory,
961
+ [item]: (outcome.applied.inventory[item] ?? 0) - 1
962
+ }
963
+ };
839
964
  storage.run("UPDATE {{companion}} SET state = ?, updated_at = ? WHERE api_key_id = ?", [
840
965
  serialiseState(nextState),
841
966
  now,
@@ -853,7 +978,10 @@ function purchase(storage, apiKeyId, entry, applyToState, now) {
853
978
  return { ok: false, reason: "unreadable" };
854
979
  if (wallet(row) < price)
855
980
  return { ok: false, reason: "insufficient" };
856
- const nextState = applyToState(row.state);
981
+ const outcome = applyToState(row.state);
982
+ if ("refused" in outcome)
983
+ return { ok: false, reason: outcome.refused };
984
+ const nextState = outcome.applied;
857
985
  storage.run("UPDATE {{companion}} SET state = ?, tokens_spent = tokens_spent + ?, updated_at = ? WHERE api_key_id = ?", [serialiseState(nextState), price, now, apiKeyId]);
858
986
  return {
859
987
  ok: true,
@@ -864,6 +992,9 @@ function purchase(storage, apiKeyId, entry, applyToState, now) {
864
992
 
865
993
  // src/server.ts
866
994
  var MAX_MULTIPLIER = 1000;
995
+ var WARM_PER_POLL = 8;
996
+ var WARM_BACKOFF_MS = 60000;
997
+ var WARM_BACKOFF_MAX_MS = 60 * 60000;
867
998
  function multiplierFrom(config) {
868
999
  const raw = config.multiplier;
869
1000
  if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0)
@@ -890,7 +1021,7 @@ var server_default = definePlugin({
890
1021
  const existing = inFlight.get(apiKeyId);
891
1022
  if (existing !== undefined)
892
1023
  return existing;
893
- const started = prefetchHatch(apiKeyId, state).finally(() => inFlight.delete(apiKeyId));
1024
+ const started = prefetchHatch(apiKeyId, state).then(() => prefetchReveal(apiKeyId, state)).finally(() => inFlight.delete(apiKeyId));
894
1025
  inFlight.set(apiKeyId, started);
895
1026
  return started;
896
1027
  };
@@ -906,6 +1037,41 @@ var server_default = definePlugin({
906
1037
  names.set(speciesId, found);
907
1038
  return found;
908
1039
  };
1040
+ const warming = new Set;
1041
+ const cold = new Map;
1042
+ const warmNames = (ids) => {
1043
+ if (net === undefined || files === undefined)
1044
+ return;
1045
+ const now = ctx.now();
1046
+ const batch = [];
1047
+ for (const id of ids) {
1048
+ if (batch.length >= WARM_PER_POLL)
1049
+ break;
1050
+ if (names.has(id) || warming.has(id))
1051
+ continue;
1052
+ const chilled = cold.get(id);
1053
+ if (chilled !== undefined && now < chilled.until)
1054
+ continue;
1055
+ batch.push(id);
1056
+ warming.add(id);
1057
+ }
1058
+ if (batch.length === 0)
1059
+ return;
1060
+ speciesDetails({ net, files }, batch).then((details) => {
1061
+ batch.forEach((id, index) => {
1062
+ if (details[index]?.names.en !== undefined) {
1063
+ cold.delete(id);
1064
+ return;
1065
+ }
1066
+ const failures = (cold.get(id)?.failures ?? 0) + 1;
1067
+ const wait = Math.min(WARM_BACKOFF_MS * 2 ** (failures - 1), WARM_BACKOFF_MAX_MS);
1068
+ cold.set(id, { until: now + wait, failures });
1069
+ });
1070
+ }).catch(() => {}).finally(() => {
1071
+ for (const id of batch)
1072
+ warming.delete(id);
1073
+ });
1074
+ };
909
1075
  const settleAndRecord = (apiKeyId) => {
910
1076
  const result = settle(storage, apiKeyId, ctx.now());
911
1077
  if (result === null)
@@ -925,6 +1091,37 @@ var server_default = definePlugin({
925
1091
  ctx.logger.info("companion graduated", { event: "companion.graduated", count: 1 });
926
1092
  }
927
1093
  };
1094
+ const prefetchReveal = async (apiKeyId, state) => {
1095
+ const mon = state.active;
1096
+ if (mon === null || mon.dittoDisguise === null || mon.dittoRevealed)
1097
+ return;
1098
+ if (state.pendingReveal !== null)
1099
+ return;
1100
+ if (net === undefined || files === undefined)
1101
+ return;
1102
+ const detail = await speciesDetail({ net, files }, DITTO_SPECIES_ID);
1103
+ if (detail === null)
1104
+ return;
1105
+ const current = readCompanion(storage, apiKeyId);
1106
+ if (current?.state == null)
1107
+ return;
1108
+ const latest = current.state.active;
1109
+ if (latest === null || latest.dittoDisguise === null || latest.dittoRevealed)
1110
+ return;
1111
+ if (current.state.pendingReveal !== null)
1112
+ return;
1113
+ storage.run("UPDATE {{companion}} SET state = ?, updated_at = ? WHERE api_key_id = ?", [
1114
+ JSON.stringify({
1115
+ ...current.state,
1116
+ pendingReveal: {
1117
+ path: detail.chain,
1118
+ rarity: rarityFromCaptureRate(detail.captureRate, detail.isLegendary, detail.isMythical)
1119
+ }
1120
+ }),
1121
+ ctx.now(),
1122
+ apiKeyId
1123
+ ]);
1124
+ };
928
1125
  const prefetchHatch = async (apiKeyId, state) => {
929
1126
  if (state.active !== null || state.pendingHatch !== null)
930
1127
  return;
@@ -939,7 +1136,10 @@ var server_default = definePlugin({
939
1136
  seed: hashSeed(`${apiKeyId}:${state.consumedTotal}`),
940
1137
  guarantee: state.eggTier,
941
1138
  hasShinyCharm: hasShinyCharm(state),
942
- collectedFinals: collected
1139
+ collectedFinals: collected,
1140
+ onlyUncollected: state.lure,
1141
+ preferLongLines: state.incense,
1142
+ excludeFinal: state.repel
943
1143
  });
944
1144
  if (rolled === null)
945
1145
  return;
@@ -951,6 +1151,10 @@ var server_default = definePlugin({
951
1151
  const current = readCompanion(storage, apiKeyId);
952
1152
  if (current?.state == null || current.state.pendingHatch !== null)
953
1153
  return;
1154
+ if (current.state.active !== null)
1155
+ return;
1156
+ if (paidRollInputs(current.state) !== paidRollInputs(state))
1157
+ return;
954
1158
  storage.run("UPDATE {{companion}} SET state = ?, updated_at = ? WHERE api_key_id = ?", [
955
1159
  JSON.stringify({
956
1160
  ...current.state,
@@ -961,7 +1165,10 @@ var server_default = definePlugin({
961
1165
  isShiny: rolled.isShiny,
962
1166
  nature: rolled.nature,
963
1167
  ditto: rolled.ditto
964
- }
1168
+ },
1169
+ lure: state.lure && !rolled.usedLure,
1170
+ incense: false,
1171
+ repel: null
965
1172
  }),
966
1173
  ctx.now(),
967
1174
  apiKeyId
@@ -1046,14 +1253,21 @@ var server_default = definePlugin({
1046
1253
  prefetchOnce(apiKeyId, row.state).catch(() => {});
1047
1254
  const active = row.state?.active ?? null;
1048
1255
  const dex = readDex(storage, apiKeyId);
1256
+ const stageId = active === null ? null : active.plannedPath[active.stageIndex] ?? null;
1257
+ const stageName = await nameOf(stageId);
1258
+ const named = await Promise.all(dex.map(async (entry) => ({ ...entry, name: await nameOf(entry.finalId) })));
1259
+ warmNames([
1260
+ ...stageId !== null && stageName === null ? [stageId] : [],
1261
+ ...named.filter((entry) => entry.name === null).map((entry) => entry.finalId)
1262
+ ]);
1049
1263
  return {
1050
1264
  json: {
1051
1265
  state: row.state,
1052
1266
  tokensTotal: row.tokensTotal,
1053
1267
  wallet: wallet(row),
1054
1268
  lastCreditAt: row.lastCreditAt,
1055
- name: await nameOf(active === null ? null : active.plannedPath[active.stageIndex] ?? null),
1056
- dex: await Promise.all(dex.map(async (entry) => ({ ...entry, name: await nameOf(entry.finalId) }))),
1269
+ name: stageName,
1270
+ dex: named,
1057
1271
  shop: shopCatalogue(),
1058
1272
  nextThreshold: active === null ? EGG_HATCH_THRESHOLD : phaseThreshold(active.rarity, active.plannedPath.length, active.stageIndex),
1059
1273
  progress: active === null ? row.state?.eggUsage ?? 0 : active.usedAtStage
@@ -1083,6 +1297,27 @@ var server_default = definePlugin({
1083
1297
  };
1084
1298
  }
1085
1299
  },
1300
+ {
1301
+ method: "GET",
1302
+ path: "/item-sprite/:item",
1303
+ handler: async (request) => {
1304
+ const item = request.params.item ?? "";
1305
+ if (!ITEM_SPRITE_NAMES.has(item)) {
1306
+ return { status: 404, json: { error: "no item icon" } };
1307
+ }
1308
+ if (net === undefined || files === undefined) {
1309
+ return { status: 503, json: { error: "sprites need the net and files capabilities" } };
1310
+ }
1311
+ const bytes = await itemSpriteBytes({ net, files }, item);
1312
+ if (bytes === null)
1313
+ return { status: 404, json: { error: "no item icon" } };
1314
+ return {
1315
+ bytes,
1316
+ contentType: "image/png",
1317
+ cacheControl: "public, max-age=31536000, immutable"
1318
+ };
1319
+ }
1320
+ },
1086
1321
  {
1087
1322
  method: "POST",
1088
1323
  path: "/keys/:id/use",
@@ -1098,6 +1333,30 @@ var server_default = definePlugin({
1098
1333
  return { json: { ok: true } };
1099
1334
  }
1100
1335
  },
1336
+ {
1337
+ method: "POST",
1338
+ path: "/keys/:id/unpin",
1339
+ handler: (request) => {
1340
+ const apiKeyId = request.params.id ?? "";
1341
+ const row = readCompanion(storage, apiKeyId);
1342
+ if (row === null)
1343
+ return { status: 404, json: { error: "no companion for that key" } };
1344
+ if (row.state === null)
1345
+ return { status: 409, json: { error: "unreadable" } };
1346
+ const active = row.state.active;
1347
+ if (active === null)
1348
+ return { status: 409, json: { error: "no-companion" } };
1349
+ if (!active.everstone)
1350
+ return { status: 409, json: { error: "nothing-new" } };
1351
+ storage.run("UPDATE {{companion}} SET state = ?, updated_at = ? WHERE api_key_id = ?", [
1352
+ JSON.stringify({ ...row.state, active: { ...active, everstone: false } }),
1353
+ ctx.now(),
1354
+ apiKeyId
1355
+ ]);
1356
+ settleAndRecord(apiKeyId);
1357
+ return { json: { ok: true } };
1358
+ }
1359
+ },
1101
1360
  {
1102
1361
  method: "POST",
1103
1362
  path: "/keys/:id/purchase",
@@ -1116,6 +1375,15 @@ var server_default = definePlugin({
1116
1375
  return { routes };
1117
1376
  }
1118
1377
  });
1378
+ function paidRollInputs(state) {
1379
+ return JSON.stringify([
1380
+ state.eggTier,
1381
+ hasShinyCharm(state),
1382
+ state.lure,
1383
+ state.incense,
1384
+ state.repel
1385
+ ]);
1386
+ }
1119
1387
  function hashSeed(input) {
1120
1388
  let hash = 2166136261;
1121
1389
  for (let i = 0;i < input.length; i++) {
@@ -1136,7 +1404,7 @@ function shopCatalogue() {
1136
1404
  price: freshEggPrice("uncommon")
1137
1405
  },
1138
1406
  { entry: { kind: "egg", tier: "rare" }, price: freshEggPrice("rare") }
1139
- ];
1407
+ ].sort((a, b) => a.price - b.price);
1140
1408
  }
1141
1409
  function parseShopEntry(body) {
1142
1410
  if (typeof body !== "object" || body === null)
@@ -1158,32 +1426,86 @@ function parseShopEntry(body) {
1158
1426
  }
1159
1427
  function applyPurchase(state, entry) {
1160
1428
  if (entry.kind === "egg") {
1161
- return { ...state, active: null, eggUsage: 0, eggTier: entry.tier, pendingHatch: null };
1429
+ if (state.active === null)
1430
+ return { refused: "no-companion" };
1431
+ return {
1432
+ applied: { ...state, active: null, eggUsage: 0, eggTier: entry.tier, pendingHatch: null }
1433
+ };
1434
+ }
1435
+ if (!HELD_ITEMS.includes(entry.item) && (state.inventory[entry.item] ?? 0) > 0) {
1436
+ return { refused: "already-owned" };
1162
1437
  }
1163
1438
  return {
1164
- ...state,
1165
- inventory: { ...state.inventory, [entry.item]: (state.inventory[entry.item] ?? 0) + 1 }
1439
+ applied: {
1440
+ ...state,
1441
+ inventory: { ...state.inventory, [entry.item]: (state.inventory[entry.item] ?? 0) + 1 }
1442
+ }
1166
1443
  };
1167
1444
  }
1445
+ var HELD_ITEMS = [
1446
+ "rareCandy",
1447
+ "mint",
1448
+ "everstone",
1449
+ "lure",
1450
+ "sootheBell",
1451
+ "incense",
1452
+ "repel"
1453
+ ];
1168
1454
  function parseHeldItem(body) {
1169
1455
  if (typeof body !== "object" || body === null)
1170
1456
  return null;
1171
1457
  const item = body.item;
1172
- return item === "rareCandy" || item === "mint" ? item : null;
1458
+ return HELD_ITEMS.includes(item) ? item : null;
1173
1459
  }
1174
1460
  function useItem(state, item) {
1461
+ if (item === "everstone" || item === "sootheBell" || item === "repel") {
1462
+ const active = state.active;
1463
+ if (active === null)
1464
+ return { refused: "no-companion" };
1465
+ if (item === "everstone") {
1466
+ if (active.everstone)
1467
+ return { refused: "nothing-new" };
1468
+ return { applied: { ...state, active: { ...active, everstone: true } } };
1469
+ }
1470
+ if (item === "sootheBell") {
1471
+ if (active.soothe)
1472
+ return { refused: "nothing-new" };
1473
+ return { applied: { ...state, active: { ...active, soothe: true } } };
1474
+ }
1475
+ const finalId = active.plannedPath[active.plannedPath.length - 1];
1476
+ if (finalId === undefined)
1477
+ return { refused: "no-companion" };
1478
+ if (finalId === DITTO_SPECIES_ID)
1479
+ return { refused: "nothing-new" };
1480
+ if (state.repel !== null)
1481
+ return { refused: "already-armed" };
1482
+ return { applied: { ...state, repel: finalId } };
1483
+ }
1484
+ if (item === "lure" || item === "incense") {
1485
+ if (item === "lure") {
1486
+ if (state.lure)
1487
+ return { refused: "nothing-new" };
1488
+ return { applied: { ...state, lure: true } };
1489
+ }
1490
+ if (state.incense)
1491
+ return { refused: "nothing-new" };
1492
+ return { applied: { ...state, incense: true } };
1493
+ }
1175
1494
  if (item === "mint") {
1176
1495
  if (state.active === null)
1177
- return state;
1496
+ return { refused: "no-companion" };
1178
1497
  const index = NATURES.indexOf(state.active.nature);
1179
1498
  const nature = NATURES[(index + 1) % NATURES.length];
1180
- return { ...state, active: { ...state.active, nature } };
1499
+ return { applied: { ...state, active: { ...state.active, nature } } };
1181
1500
  }
1182
- return state.active === null ? { ...state, eggUsage: state.eggUsage + RARE_CANDY_XP } : {
1183
- ...state,
1184
- active: { ...state.active, usedAtStage: state.active.usedAtStage + RARE_CANDY_XP }
1501
+ return {
1502
+ applied: state.active === null ? { ...state, eggUsage: state.eggUsage + RARE_CANDY_XP } : {
1503
+ ...state,
1504
+ active: { ...state.active, usedAtStage: state.active.usedAtStage + RARE_CANDY_XP }
1505
+ }
1185
1506
  };
1186
1507
  }
1187
1508
  export {
1509
+ HELD_ITEMS,
1188
1510
  server_default as default
1189
1511
  };
package/ui/index.js CHANGED
@@ -1,10 +1,10 @@
1
- function V(e){return e}import{useMutation as we,useQuery as ve,useQueryClient as De}from"@tanstack/react-query";import{useState as Pe}from"react";function w(e,r,i){return`/api/plugins/${e}/sprite/${r}${i?"?shiny=1":""}`}function c(e){if(e>=1e9)return`${(e/1e9).toFixed(2)}B`;if(e>=1e6)return`${(e/1e6).toFixed(1)}M`;return e.toLocaleString()}function v(e,r){return e??`#${r}`}function A(e,r,i){return`${e??`Species ${r}`}${i?", shiny":""}`}function R(e){return e.replace(/([A-Z])/g," $1").toLowerCase()}function F(e){if(e.kind==="item")return R(e.item);return e.tier===null?"fresh egg":`fresh egg (${e.tier}+)`}var te=["rareCandy","mint"],ne=[null,"common","uncommon","rare","legendary"],re=60000,ee=60*re;function T(e,r,i){if(!e)return"egg";if(r===null)return"sleep";let o=i-r;if(o<5*re)return"working";if(o<ee)return"idle";if(o<8*ee)return"tired";return"sleep"}import a from"styled-components";var l={xs:"4px",sm:"8px",md:"12px",lg:"20px",xl:"32px"},H="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",I=a.section`
1
+ import{definePluginUI as It,useLive as $t}from"@omnigateway/dashboard-sdk";import{useMutation as fe,useQuery as at,useQueryClient as wt}from"@tanstack/react-query";import{useState as st}from"react";var dt={rareCandy:{blurb:"Injects 100.0M growth. Priced at five times what it grants.",emoji:"\uD83C\uDF6C",consumable:!0},mint:{blurb:"Rerolls this companion's nature. Cosmetic, and cheap enough to reroll again.",emoji:"\uD83C\uDF3F",consumable:!0},shinyCharm:{blurb:"Kept, never spent. Raises every future hatch from 1 in 64 to 1 in 48.",emoji:"✨",consumable:!1},everstone:{blurb:"Pins this companion: it will not evolve, reveal, or graduate.",emoji:"\uD83E\uDEA8",consumable:!0},lure:{blurb:"Next egg prefers a species the Dex has not collected. A preference, never a veto.",emoji:"\uD83C\uDF6F",consumable:!0},sootheBell:{blurb:"This companion grows 25% faster. Bound to it, and never repays its price.",emoji:"\uD83D\uDD14",consumable:!0},incense:{blurb:"Next egg leans toward a longer evolution line. Buys events, not value.",emoji:"\uD83D\uDD6F️",consumable:!0},repel:{blurb:"Next egg will not hatch one named line.",emoji:"\uD83D\uDEAB",consumable:!0}};function L(e){return ye.get(e)??{blurb:"",emoji:"❔",consumable:!0}}var ye=new Map(Object.entries(dt)),Dt=[...ye].filter(([,e])=>e.consumable).map(([e])=>e),ve="\uD83E\uDD5A";function ke(e){return e===null?"Sends this companion off and starts again.":`Sends this companion off for an egg guaranteed to hatch ${e} or better.`}function R(e,t,o){return`/api/plugins/${e}/sprite/${t}${o?"?shiny=1":""}`}function Ie(e,t){return`/api/plugins/${e}/item-sprite/${t}`}function h(e){if(e>=1e9)return`${(e/1e9).toFixed(2)}B`;if(e>=1e6)return`${(e/1e6).toFixed(1)}M`;return e.toLocaleString()}function T(e,t){return e??`#${t}`}function E(e,t,o){return`${e??`Species ${t}`}${o?", shiny":""}`}function N(e){return e.replace(/([A-Z])/g," $1").toLowerCase()}function $e(e){if(e.kind==="item")return N(e.item);return e.tier===null?"fresh egg":`fresh egg (${e.tier}+)`}var we=[null,"common","uncommon","rare","legendary"],Ce=60000,Se=60*Ce;function M(e,t,o){if(!e)return"egg";if(t===null)return"sleep";let i=o-t;if(i<5*Ce)return"working";if(i<Se)return"idle";if(i<8*Se)return"tired";return"sleep"}import{useState as ut}from"react";import r from"styled-components";var l={xs:"4px",sm:"8px",md:"12px",lg:"20px",xl:"32px"},B="192px",de="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",O=r.section`
2
2
  background: var(--panel);
3
3
  border: 1px solid var(--rule);
4
4
  border-radius: 8px;
5
5
  padding: ${l.lg};
6
6
  color: var(--ink);
7
- `,P=a.h3`
7
+ `,ue=r.h3`
8
8
  display: flex;
9
9
  align-items: center;
10
10
  gap: ${l.md};
@@ -21,20 +21,83 @@ function V(e){return e}import{useMutation as we,useQuery as ve,useQueryClient as
21
21
  height: 1px;
22
22
  background: var(--rule);
23
23
  }
24
- `,y=a.div`
24
+ `,Re=r(ue)`
25
+ &::after {
26
+ content: none;
27
+ }
28
+ `,Te=r.span`
29
+ flex: 1;
30
+ height: 1px;
31
+ background: var(--rule);
32
+ `,De=r.button`
33
+ display: flex;
34
+ align-items: center;
35
+ gap: ${l.sm};
36
+ flex: 1;
37
+ padding: 0;
38
+ background: none;
39
+ border: 0;
40
+ color: inherit;
41
+ font: inherit;
42
+ letter-spacing: inherit;
43
+ text-transform: inherit;
44
+ text-align: left;
45
+ cursor: pointer;
46
+
47
+ &:hover {
48
+ color: var(--ink);
49
+ }
50
+ &:focus-visible {
51
+ outline: 2px solid var(--accent);
52
+ outline-offset: 4px;
53
+ border-radius: 2px;
54
+ }
55
+ `,Ee=r.span`
56
+ display: inline-block;
57
+ font-size: 9px;
58
+ line-height: 1;
59
+ transform: rotate(${(e)=>e.$open?"90deg":"0deg"});
60
+ transition: transform 120ms ease-out;
61
+
62
+ @media (prefers-reduced-motion: reduce) {
63
+ transition: none;
64
+ }
65
+ `,Pe=r.span`
66
+ color: var(--ink-faint);
67
+ letter-spacing: 0.08em;
68
+ white-space: nowrap;
69
+ `,I=r.div`
25
70
  display: flex;
26
71
  gap: ${l.md};
27
72
  align-items: center;
28
73
  flex-wrap: wrap;
29
- `,p=a.span`
74
+ `,Ae=r(I)`
75
+ h2 {
76
+ margin: 0;
77
+ }
78
+ `,x=r.span`
79
+ color: var(--ink-dim);
80
+ `,_=r(x)`
81
+ display: block;
82
+ margin-top: ${l.sm};
83
+
84
+ /* Two facts about one meter are closer to each other than either is to the
85
+ meter, because that is what they are: "stage 1 of 2" and the tokens under
86
+ it are one reading, not two. Spaced evenly they read as a list of unrelated
87
+ numbers that happen to be stacked. */
88
+ & + & {
89
+ margin-top: ${l.xs};
90
+ }
91
+ `,Le=r.p`
30
92
  color: var(--ink-dim);
31
- `,G=a.p`
93
+ margin: ${l.xs} 0 ${l.lg};
94
+ `,ce=r.p`
32
95
  color: var(--warn);
33
96
  background: var(--warn-wash);
34
97
  border-radius: 6px;
35
98
  padding: ${l.md};
36
99
  margin: ${l.md} 0 0;
37
- `,b=a.span`
100
+ `,b=r.span`
38
101
  display: inline-flex;
39
102
  align-items: center;
40
103
  gap: ${l.xs};
@@ -46,30 +109,31 @@ function V(e){return e}import{useMutation as we,useQuery as ve,useQueryClient as
46
109
  text-transform: uppercase;
47
110
  color: var(--ink-dim);
48
111
  white-space: nowrap;
49
- `,ie=a(b)`
112
+ `,Ne=r(I)`
113
+ margin: ${l.xs} 0 ${l.sm};
114
+ `,V=r(b)`
50
115
  border-color: var(--rule-strong);
51
116
  color: var(--ink);
52
117
  font-weight: 600;
53
- `,C=a.img`
54
- width: 96px;
55
- height: 96px;
118
+ `,z=r.img`
119
+ width: ${B};
120
+ height: ${B};
56
121
  image-rendering: pixelated;
57
- background: var(--panel-sunk);
58
- border: 1px solid var(--rule);
59
- border-radius: 6px;
60
- `,D=a.div`
61
- width: 96px;
62
- height: 96px;
122
+ `,q=r.div`
123
+ box-sizing: border-box;
124
+ width: ${B};
125
+ height: ${B};
63
126
  border-radius: 50% 50% 45% 45%;
64
127
  background: var(--panel-sunk);
65
128
  border: 2px solid var(--rule-strong);
66
- `,oe=a.div`
67
- width: 96px;
68
- height: 96px;
129
+ `,Be=r.div`
130
+ box-sizing: border-box;
131
+ width: ${B};
132
+ height: ${B};
69
133
  border-radius: 6px;
70
134
  background: var(--warn-wash);
71
135
  border: 2px dashed var(--warn);
72
- `,x=a.button`
136
+ `,k=r.button`
73
137
  background: var(--panel-raised);
74
138
  border: 1px solid var(--rule);
75
139
  border-radius: 6px;
@@ -94,22 +158,22 @@ function V(e){return e}import{useMutation as we,useQuery as ve,useQueryClient as
94
158
  color: var(--ink-faint);
95
159
  cursor: not-allowed;
96
160
  }
97
- `,B=a.span`
98
- font-family: ${H};
161
+ `,K=r.span`
162
+ font-family: ${de};
99
163
  font-variant-numeric: tabular-nums;
100
- `,ae=a.div`
164
+ `,Ke=r.div`
101
165
  display: flex;
102
166
  gap: 3px;
103
167
  min-width: 240px;
104
168
  max-width: 340px;
105
- `,se=a.div`
169
+ `,Fe=r.div`
106
170
  flex: 1;
107
171
  height: 10px;
108
172
  background: var(--panel-sunk);
109
173
  border: 1px solid var(--rule);
110
174
  border-radius: 3px;
111
175
  overflow: hidden;
112
- `,q=a.div`
176
+ `,me=r.div`
113
177
  height: 100%;
114
178
  background: var(--accent);
115
179
  width: ${(e)=>Math.min(100,Math.max(0,e.$pct))}%;
@@ -118,48 +182,94 @@ function V(e){return e}import{useMutation as we,useQuery as ve,useQueryClient as
118
182
  @media (prefers-reduced-motion: reduce) {
119
183
  transition: none;
120
184
  }
121
- `,le=a.dl`
185
+ `,Me=r.dl`
122
186
  display: flex;
123
187
  flex-wrap: wrap;
124
188
  gap: ${l.xl};
125
189
  margin: ${l.lg} 0 0;
126
190
  padding-top: ${l.lg};
127
191
  border-top: 1px solid var(--rule);
128
- `,N=a.div`
192
+ `,J=r.div`
129
193
  display: flex;
130
194
  flex-direction: column;
131
195
  gap: 2px;
132
- `,L=a.dt`
196
+ `,Q=r.dt`
133
197
  font-size: 11px;
134
198
  letter-spacing: 0.08em;
135
199
  text-transform: uppercase;
136
200
  color: var(--ink-faint);
137
- `,_=a.dd`
201
+ `,Y=r.dd`
138
202
  margin: 0;
139
- font-family: ${H};
203
+ font-family: ${de};
140
204
  font-variant-numeric: tabular-nums;
141
205
  font-size: 18px;
142
206
  color: var(--ink);
143
- `,pe=a.div`
207
+ `,Oe=r.div`
144
208
  display: grid;
145
209
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
146
210
  gap: ${l.md};
147
- `,ue=a.figure`
211
+ `,_e=r.button`
212
+ margin: 0;
213
+ display: flex;
214
+ flex-direction: column;
215
+ align-items: center;
216
+ gap: 2px;
217
+ padding: ${l.xs};
218
+ background: ${(e)=>e.$open?"var(--accent-wash)":"none"};
219
+ border: 1px solid ${(e)=>e.$open?"var(--accent)":"transparent"};
220
+ border-radius: 8px;
221
+ color: inherit;
222
+ font: inherit;
223
+ cursor: pointer;
224
+
225
+ &:hover {
226
+ border-color: ${(e)=>e.$open?"var(--accent)":"var(--rule)"};
227
+ }
228
+ &:focus-visible {
229
+ outline: 2px solid var(--accent);
230
+ outline-offset: 2px;
231
+ }
232
+ `,Ge=r.div`
233
+ grid-column: 1 / -1;
234
+ display: flex;
235
+ gap: ${l.lg};
236
+ padding: ${l.md};
237
+ background: var(--panel-sunk);
238
+ border: 1px solid var(--rule);
239
+ border-radius: 8px;
240
+ `,He=r.div`
241
+ display: flex;
242
+ flex-direction: column;
243
+ align-items: flex-start;
244
+ gap: ${l.sm};
245
+ min-width: 0;
246
+ `,Ue=r.div`
247
+ display: flex;
248
+ align-items: center;
249
+ gap: ${l.sm};
250
+ flex-wrap: wrap;
251
+ `,je=r.figure`
148
252
  margin: 0;
149
253
  display: flex;
150
254
  flex-direction: column;
151
255
  align-items: center;
152
256
  gap: 2px;
153
- `,z=a.figcaption`
257
+
258
+ img {
259
+ width: 48px;
260
+ height: 48px;
261
+ image-rendering: pixelated;
262
+ }
263
+ `,Z=r.figcaption`
154
264
  color: var(--ink-dim);
155
265
  font-size: 11px;
156
266
  text-align: center;
157
267
  overflow-wrap: anywhere;
158
- `,de=a.div`
268
+ `,Ve=r.div`
159
269
  display: grid;
160
- grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
270
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
161
271
  gap: ${l.md};
162
- `,ce=a.button`
272
+ `,ze=r.button`
163
273
  display: flex;
164
274
  flex-direction: column;
165
275
  align-items: center;
@@ -180,16 +290,52 @@ function V(e){return e}import{useMutation as we,useQuery as ve,useQueryClient as
180
290
  outline: 2px solid var(--accent);
181
291
  outline-offset: 2px;
182
292
  }
183
- `,K=a.span`
184
- font-family: ${H};
293
+ `,W=r.span`
294
+ font-family: ${de};
185
295
  font-size: 12px;
186
296
  color: var(--ink-dim);
187
297
  overflow-wrap: anywhere;
188
- `,ge=a.div`
298
+ `,X=r.div`
299
+ display: grid;
300
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
301
+ gap: ${l.md};
302
+ `,ee=r.div`
189
303
  display: flex;
190
- align-items: center;
304
+ flex-direction: column;
191
305
  gap: ${l.sm};
192
- padding: ${l.sm} ${l.md};
306
+ padding: ${l.md};
193
307
  background: var(--panel-sunk);
194
- border-radius: 6px;
195
- `;import{jsx as U,jsxs as me}from"react/jsx-runtime";function fe({inventory:e,onUse:r,pending:i}){let o=Object.entries(e).filter(([,t])=>t>0);if(o.length===0)return U(p,{children:"Nothing in the bag."});return U(y,{children:o.map(([t,n])=>me(ge,{children:[U("span",{children:`${R(t)} · ${n}`}),te.includes(t)?me(x,{disabled:i,onClick:()=>r(t),type:"button",children:["Use ",R(t)]}):U(p,{children:"held"})]},t))})}import{useState as $e}from"react";import{jsx as S,jsxs as J,Fragment as Ie}from"react/jsx-runtime";function he({entries:e,pluginId:r}){let[i,o]=$e(null);if(e.length===0)return S(p,{children:"Nothing graduated yet."});let t=i===null?e:e.filter((n)=>n.rarity===i);return J(Ie,{children:[S(y,{children:ne.map((n)=>S(x,{"aria-pressed":i===n,onClick:()=>o(n),type:"button",children:n??"all"},n??"all"))}),t.length===0?J(p,{children:["No ",i," graduates yet."]}):S(pe,{children:t.map((n)=>J(ue,{children:[S("img",{alt:`${n.rarity}${n.isShiny?" shiny":""} ${n.name??`species ${n.finalId}`}`,src:w(r,n.finalId,n.isShiny),style:{width:"64px",height:"64px",imageRendering:"pixelated"}}),S(z,{children:v(n.name,n.finalId)}),n.nature===null?null:S(z,{children:n.nature})]},n.id))})]})}import{jsx as Q,jsxs as Ee}from"react/jsx-runtime";function W({stages:e,stageIndex:r,progress:i,threshold:o,label:t,valueText:n}){let f=i/Math.max(1,o)*100;return Q(ae,{"aria-label":t,"aria-valuemax":o,"aria-valuemin":0,"aria-valuenow":i,"aria-valuetext":n,role:"progressbar",children:Array.from({length:Math.max(1,e)},(s,k)=>Ee(se,{children:[k<r?Q(q,{$pct:100}):null,k===r?Q(q,{$pct:f}):null]},k))})}function ye(e,r,i,o){return`stage ${r+1} of ${e}, ${c(i)} of ${c(o)} tokens`}import{jsx as g,jsxs as m,Fragment as M}from"react/jsx-runtime";function xe({view:e,activity:r,pluginId:i}){let o=e.state;if(o===null)return null;let{active:t}=o,n=t===null?null:t.plannedPath[t.stageIndex]??null;return m(M,{children:[m(y,{children:[n===null?g(D,{"aria-label":"An egg, not yet hatched",role:"img"}):g(C,{alt:A(e.name,n,t?.isShiny===!0),src:w(i,n,t?.isShiny===!0)}),m("div",{children:[g("h3",{children:n===null?"Egg":v(e.name,n)}),m(y,{children:[t===null?o.eggTier===null?null:m(b,{children:[o.eggTier,"+ guaranteed"]}):m(M,{children:[g(b,{children:t.rarity}),t.isShiny?m(ie,{children:[g("span",{"aria-hidden":"true",children:"✦"}),"shiny"]}):null,g(b,{children:t.nature}),t.dittoDisguise===null?null:g(b,{children:"?"})]}),g(b,{"aria-label":`Activity: ${r}`,role:"status",children:r})]}),t===null?m(M,{children:[g(W,{label:"Incubation",progress:e.progress,stageIndex:0,stages:1,threshold:e.nextThreshold,valueText:`${c(e.progress)} of ${c(e.nextThreshold)} tokens incubated`}),m(p,{children:[c(e.progress)," / ",c(e.nextThreshold)," tokens incubated"]})]}):m(M,{children:[g(W,{label:"Growth to the next evolution",progress:e.progress,stageIndex:t.stageIndex,stages:t.plannedPath.length,threshold:e.nextThreshold,valueText:ye(t.plannedPath.length,t.stageIndex,e.progress,e.nextThreshold)}),m(p,{children:["Stage ",t.stageIndex+1," of ",t.plannedPath.length]}),m(p,{children:[c(e.progress)," / ",c(e.nextThreshold)," to the next stage"]})]})]})]}),m(le,{children:[m(N,{children:[g(L,{children:"Earned"}),g(_,{children:c(e.tokensTotal)})]}),m(N,{children:[g(L,{children:"To spend"}),g(_,{children:c(e.wallet)})]}),m(N,{children:[g(L,{children:"Graduated"}),g(_,{children:e.dex.length.toLocaleString()})]})]})]})}import{useState as Ae}from"react";import{jsx as u,jsxs as O}from"react/jsx-runtime";function be({keys:e,onPick:r,pluginId:i,rosterFailed:o}){let[t,n]=Ae("");return O(I,{children:[u("h2",{children:"Companion"}),u("p",{children:u(p,{children:"Each API key raises its own Pokémon on the tokens it spends. Pick a key."})}),e.length>0?u(de,{children:e.map((f)=>u(Re,{entry:f,onPick:r,pluginId:i},f.apiKeyId))}):u(p,{children:o?"The list of keys could not be loaded. Enter a key id below to reach a companion directly.":"No key has spent a token yet. A companion appears the first time a key serves a request."}),u(P,{children:"Or by key id"}),u("form",{onSubmit:(f)=>{f.preventDefault();let s=t.trim();if(s!=="")r(s)},children:O(y,{children:[u("input",{"aria-label":"API key id",onChange:(f)=>n(f.target.value),placeholder:"key id",value:t}),u(x,{type:"submit",children:"Show"})]})})]})}function Re({entry:e,onPick:r,pluginId:i}){let o=T(e.speciesId!==null,e.lastCreditAt,Date.now());return O(ce,{onClick:()=>r(e.apiKeyId),type:"button",children:[e.unreadable?u(oe,{"aria-label":"This key's save could not be read",role:"img"}):e.speciesId===null?u(D,{"aria-label":"An egg, not yet hatched",role:"img"}):u(C,{alt:A(e.name,e.speciesId,e.isShiny),src:w(i,e.speciesId,e.isShiny)}),u("strong",{children:e.unreadable?"Save unreadable":e.speciesId===null?"Egg":v(e.name,e.speciesId)}),e.rarity===null?null:u(b,{children:e.rarity}),u(K,{children:e.apiKeyId}),O(p,{children:[u(B,{children:c(e.tokensTotal)}),e.unreadable?null:` · ${o}`]})]})}import{jsx as ke,jsxs as Ce}from"react/jsx-runtime";function Se({offers:e,wallet:r,onBuy:i,pending:o}){return ke(y,{children:e.map((t)=>Ce(x,{disabled:r<t.price||o,onClick:()=>i(t.entry),type:"button",children:[F(t.entry)," · ",ke(B,{children:c(t.price)})]},`${t.entry.kind}:${F(t.entry)}`))})}import{jsx as d,jsxs as Y,Fragment as _e}from"react/jsx-runtime";var Be=15000;function Ne({pluginId:e,api:r}){let[i,o]=Pe({at:"start"}),t=De(),n=ve({queryKey:["roster"],queryFn:()=>r.get("keys")}),f=n.data?.keys??[];if(i.at==="start"&&!n.isPending){let h=f.length===1?f[0]:void 0;o(h===void 0?{at:"roster"}:{at:"key",apiKeyId:h.apiKeyId})}let s=i.at==="key"?i.apiKeyId:null,k=ve({queryKey:["companion",s],queryFn:()=>r.get(`keys/${s}`),enabled:s!==null,refetchInterval:Be}),[Te,E]=Pe(null),Z=()=>{t.invalidateQueries({queryKey:["companion",s]}),t.invalidateQueries({queryKey:["roster"]})},X=we({mutationFn:(h)=>r.post(`keys/${s}/purchase`,h),onMutate:()=>E(null),onError:(h)=>E(h instanceof Error?h.message:"the purchase was refused"),onSuccess:Z}),j=we({mutationFn:(h)=>r.post(`keys/${s}/use`,{item:h}),onMutate:()=>E(null),onError:(h)=>E(h instanceof Error?h.message:"the item could not be used"),onSuccess:Z});if(n.isPending)return d(I,{children:"Loading…"});if(s===null)return d(be,{keys:f,onPick:(h)=>o({at:"key",apiKeyId:h}),pluginId:e,rosterFailed:n.isError});return Y(I,{children:[Y(y,{children:[d("h2",{children:"Companion"}),d(K,{children:s}),d(x,{onClick:()=>o({at:"roster"}),type:"button",children:"All keys"})]}),d(Le,{buy:X.mutate,buying:X.isPending,keyId:s,onUse:j.mutate,pluginId:e,query:k,refusal:Te,using:j.isPending})]})}function Le({query:e,pluginId:r,buy:i,onUse:o,buying:t,using:n,refusal:f}){if(e.isPending)return d(p,{children:"Loading…"});if(e.isError||e.data===void 0)return d(p,{children:"No companion for that key yet."});let s=e.data;if(s.state===null)return d(G,{children:"This key's save could not be read. It has been left untouched rather than replaced — nothing has been lost, but it needs looking at."});let k=T(s.state.active!==null,s.lastCreditAt,Date.now());return Y(_e,{children:[d(xe,{activity:k,pluginId:r,view:s}),d(P,{children:"Shop"}),d(Se,{offers:s.shop,onBuy:i,pending:t,wallet:s.wallet}),d(P,{children:"Bag"}),d(fe,{inventory:s.state.inventory,onUse:o,pending:n}),f===null?null:d(G,{role:"alert",children:f}),d(P,{children:"Pokédex"}),d(he,{entries:s.dex,pluginId:r})]})}var Ut=V({mount:Ne});export{T as activityOf,Ut as default};
308
+ border: 1px solid var(--rule);
309
+ border-radius: 8px;
310
+ `,te=r.div`
311
+ display: flex;
312
+ align-items: center;
313
+ gap: ${l.sm};
314
+ `,ne=r.span`
315
+ flex: 1;
316
+ overflow-wrap: anywhere;
317
+ `,oe=r.p`
318
+ margin: 0;
319
+ color: var(--ink-dim);
320
+ font-size: 12px;
321
+ line-height: 1.45;
322
+ `,re=r.div`
323
+ display: flex;
324
+ align-items: center;
325
+ justify-content: flex-end;
326
+ gap: ${l.sm};
327
+ margin-top: auto;
328
+ `,qe=r.span`
329
+ display: inline-flex;
330
+ align-items: center;
331
+ justify-content: center;
332
+ flex: none;
333
+ width: 32px;
334
+ height: 32px;
335
+ font-size: 20px;
336
+ line-height: 1;
337
+ `,Je=r.img`
338
+ width: 32px;
339
+ height: 32px;
340
+ image-rendering: pixelated;
341
+ `;import{jsx as Qe}from"react/jsx-runtime";function ie({item:e,emoji:t,pluginId:o}){let[i,d]=ut(!1);return Qe(qe,{"aria-hidden":"true",children:i?t:Qe(Je,{alt:"",onError:()=>d(!0),src:Ie(o,e)})})}import{jsx as P,jsxs as ae}from"react/jsx-runtime";function Ye({inventory:e,onUse:t,pending:o,pluginId:i}){let d=Object.entries(e).filter(([,p])=>p>0);if(d.length===0)return P(x,{children:"Nothing in the bag."});return P(X,{children:d.map(([p,n])=>{let a=L(p);return ae(ee,{children:[ae(te,{children:[P(ie,{emoji:a.emoji,item:p,pluginId:i}),P(ne,{children:N(p)}),ae(K,{children:["×",n]})]}),P(oe,{children:a.blurb}),P(re,{children:a.consumable?ae(k,{disabled:o,onClick:()=>t(p),type:"button",children:["Use ",N(p)]}):P(x,{children:"held"})})]},p)})})}import{Fragment as ct,useState as Ze}from"react";import{jsx as y,jsxs as w,Fragment as gt}from"react/jsx-runtime";function We({entries:e,pluginId:t}){let[o,i]=Ze(null),[d,p]=Ze(null);if(e.length===0)return y(x,{children:"Nothing graduated yet."});let n=o===null?e:e.filter((a)=>a.rarity===o);return w(gt,{children:[y(I,{children:we.map((a)=>y(k,{"aria-pressed":o===a,onClick:()=>{i(a),p(null)},type:"button",children:a??"all"},a??"all"))}),n.length===0?w(x,{children:["No ",o," graduates yet."]}):y(Oe,{children:n.map((a)=>{let s=d===a.id,S=`dex-detail-${a.id}`;return w(ct,{children:[w(_e,{$open:s,"aria-controls":S,"aria-expanded":s,onClick:()=>p(s?null:a.id),type:"button",children:[y("img",{alt:`${a.rarity}${a.isShiny?" shiny":""} ${a.name??`species ${a.finalId}`}`,src:R(t,a.finalId,a.isShiny),style:{width:"64px",height:"64px",imageRendering:"pixelated"}}),y(Z,{children:T(a.name,a.finalId)}),a.nature===null?null:y(Z,{children:a.nature})]}),s?y(mt,{entry:a,id:S,pluginId:t}):null]},a.id)})})]})}function mt({entry:e,id:t,pluginId:o}){return w(Ge,{id:t,children:[y("img",{alt:E(e.name,e.finalId,e.isShiny),src:R(o,e.finalId,e.isShiny),style:{width:"96px",height:"96px",imageRendering:"pixelated"}}),w(He,{children:[y("strong",{children:T(e.name,e.finalId)}),w(I,{children:[y(b,{children:e.rarity}),e.isShiny?y(V,{children:"✦ shiny"}):null,e.nature===null?null:y(b,{children:e.nature})]}),y(Ue,{children:e.chainOrder.map((i,d)=>w(je,{children:[y("img",{alt:E(null,i,!1),src:R(o,i,!1)}),y(Z,{children:i===e.finalId?T(e.name,i):`#${i}`})]},`${i}-${d}`))}),w(x,{children:["caught ",new Date(e.caughtAt).toLocaleDateString()]})]})]})}import{jsx as ge,jsxs as ht}from"react/jsx-runtime";function he({stages:e,stageIndex:t,progress:o,threshold:i,label:d,valueText:p}){let n=o/Math.max(1,i)*100;return ge(Ke,{"aria-label":d,"aria-valuemax":i,"aria-valuemin":0,"aria-valuenow":o,"aria-valuetext":p,role:"progressbar",children:Array.from({length:Math.max(1,e)},(a,s)=>ht(Fe,{children:[s<t?ge(me,{$pct:100}):null,s===t?ge(me,{$pct:n}):null]},s))})}function Xe(e,t,o,i){return`stage ${t+1} of ${e}, ${h(o)} of ${h(i)} tokens`}import{jsx as c,jsxs as m,Fragment as G}from"react/jsx-runtime";function et({view:e,activity:t,pluginId:o,onRelease:i,releasing:d}){let p=e.state;if(p===null)return null;let{active:n}=p,a=n===null?null:n.plannedPath[n.stageIndex]??null;return m(G,{children:[m(I,{children:[a===null?c(q,{"aria-label":"An egg, not yet hatched",role:"img"}):c(z,{alt:E(e.name,a,n?.isShiny===!0),src:R(o,a,n?.isShiny===!0)}),m("div",{children:[c("h3",{children:a===null?"Egg":T(e.name,a)}),m(Ne,{children:[n===null?p.eggTier===null?null:m(b,{children:[p.eggTier,"+ guaranteed"]}):m(G,{children:[c(b,{children:n.rarity}),n.isShiny?m(V,{children:[c("span",{"aria-hidden":"true",children:"✦"}),"shiny"]}):null,c(b,{children:n.nature}),n.dittoDisguise===null||n.dittoRevealed?null:c(b,{children:"?"}),n.everstone?c(b,{children:"everstone"}):null,n.soothe?c(b,{children:"soothe bell"}):null]}),c(b,{"aria-label":`Activity: ${t}`,role:"status",children:t})]}),n===null?m(G,{children:[c(he,{label:"Incubation",progress:e.progress,stageIndex:0,stages:1,threshold:e.nextThreshold,valueText:`${h(e.progress)} of ${h(e.nextThreshold)} tokens incubated`}),m(_,{children:[h(e.progress)," / ",h(e.nextThreshold)," tokens incubated"]})]}):m(G,{children:[c(he,{label:"Growth to the next evolution",progress:e.progress,stageIndex:n.stageIndex,stages:n.plannedPath.length,threshold:e.nextThreshold,valueText:Xe(n.plannedPath.length,n.stageIndex,e.progress,e.nextThreshold)}),m(_,{children:["Stage ",n.stageIndex+1," of ",n.plannedPath.length]}),n.everstone?m(G,{children:[m(_,{children:["Held at this stage · ",h(e.progress)," banked"]}),c(k,{disabled:d,onClick:i,type:"button",children:"Release"})]}):m(_,{children:[h(e.progress)," / ",h(e.nextThreshold)," to the next stage"]})]})]})]}),m(Me,{children:[m(J,{children:[c(Q,{children:"Earned"}),c(Y,{children:h(e.tokensTotal)})]}),m(J,{children:[c(Q,{children:"To spend"}),c(Y,{children:h(e.wallet)})]}),m(J,{children:[c(Q,{children:"Graduated"}),c(Y,{children:e.dex.length.toLocaleString()})]})]})]})}import{useState as ft}from"react";import{jsx as v,jsxs as se}from"react/jsx-runtime";function tt({keys:e,onPick:t,pluginId:o,rosterFailed:i}){let[d,p]=ft("");return se(O,{children:[v("h2",{children:"Companion"}),v(Le,{children:"Each API key raises its own Pokémon on the tokens it spends. Pick a key."}),e.length>0?v(Ve,{children:e.map((n)=>v(xt,{entry:n,onPick:t,pluginId:o},n.apiKeyId))}):v(x,{children:i?"The list of keys could not be loaded. Enter a key id below to reach a companion directly.":"No key has spent a token yet. A companion appears the first time a key serves a request."}),v(ue,{children:"Or by key id"}),v("form",{onSubmit:(n)=>{n.preventDefault();let a=d.trim();if(a!=="")t(a)},children:se(I,{children:[v("input",{"aria-label":"API key id",onChange:(n)=>p(n.target.value),placeholder:"key id",value:d}),v(k,{type:"submit",children:"Show"})]})})]})}function xt({entry:e,onPick:t,pluginId:o}){let i=M(e.speciesId!==null,e.lastCreditAt,Date.now());return se(ze,{onClick:()=>t(e.apiKeyId),type:"button",children:[e.unreadable?v(Be,{"aria-label":"This key's save could not be read",role:"img"}):e.speciesId===null?v(q,{"aria-label":"An egg, not yet hatched",role:"img"}):v(z,{alt:E(e.name,e.speciesId,e.isShiny),src:R(o,e.speciesId,e.isShiny)}),v("strong",{children:e.unreadable?"Save unreadable":e.speciesId===null?"Egg":T(e.name,e.speciesId)}),e.rarity===null?null:v(b,{children:e.rarity}),v(W,{children:e.apiKeyId}),se(x,{children:[v(K,{children:h(e.tokensTotal)}),e.unreadable?null:` · ${i}`]})]})}import{useId as bt,useState as yt}from"react";import{jsx as H,jsxs as nt,Fragment as kt}from"react/jsx-runtime";var F={shop:!0,bag:!0,dex:!0},ot=(e)=>`plugin:${e}:sections`;function vt(e){try{let t=globalThis.localStorage?.getItem(ot(e));if(t===null||t===void 0)return F;let o=JSON.parse(t);if(typeof o!=="object"||o===null)return F;let i=o;return{shop:typeof i.shop==="boolean"?i.shop:F.shop,bag:typeof i.bag==="boolean"?i.bag:F.bag,dex:typeof i.dex==="boolean"?i.dex:F.dex}}catch{return F}}function rt(e){let[t,o]=yt(()=>vt(e));return{open:t,toggle:(d)=>{o((p)=>{let n={...p,[d]:!p[d]};try{globalThis.localStorage?.setItem(ot(e),JSON.stringify(n))}catch{}return n})}}}function le({title:e,count:t,open:o,onToggle:i,children:d}){let p=bt();return nt(kt,{children:[H(Re,{children:nt(De,{"aria-controls":p,"aria-expanded":o,onClick:i,type:"button",children:[H(Ee,{$open:o,children:"▶"}),e,H(Te,{}),H(Pe,{children:t})]})}),o?H("div",{id:p,children:d}):null]})}import{jsx as A,jsxs as pe}from"react/jsx-runtime";function it({offers:e,wallet:t,onBuy:o,pending:i,inventory:d,hasCompanion:p,pluginId:n}){return A(X,{children:e.map((a)=>{let{entry:s,price:S}=a,C=St(s,d),u=s.kind==="egg"&&!p,D=s.kind==="item"?N(s.item):"fresh egg";return pe(ee,{children:[pe(te,{children:[A(ie,{emoji:s.kind==="item"?L(s.item).emoji:ve,item:s.kind==="item"?s.item:"egg",pluginId:n}),A(ne,{children:D}),C?A(x,{children:"owned"}):A(K,{children:h(S)})]}),s.kind==="egg"&&s.tier!==null?pe(b,{children:[s.tier,"+"]}):null,A(oe,{children:s.kind==="item"?L(s.item).blurb:ke(s.tier)}),A(re,{children:pe(k,{disabled:C||u||t<S||i,onClick:()=>o(s),type:"button",children:["Buy ",$e(s)]})})]},s.kind==="item"?`item:${s.item}`:`egg:${s.tier??""}`)})})}function St(e,t){if(e.kind!=="item")return!1;return!L(e.item).consumable&&(t[e.item]??0)>0}import{jsx as f,jsxs as xe,Fragment as Tt}from"react/jsx-runtime";var lt=1e4;function Ct({pluginId:e,api:t}){let[o,i]=st({at:"start"}),d=wt(),{cadence:p}=$t(),n=at({queryKey:["roster"],queryFn:()=>t.get("keys"),refetchInterval:p(lt)}),a=n.data?.keys??[];if(o.at==="start"&&!n.isPending){let g=a.length===1?a[0]:void 0;i(g===void 0?{at:"roster"}:{at:"key",apiKeyId:g.apiKeyId})}let s=o.at==="key"?o.apiKeyId:null,S=at({queryKey:["companion",s],queryFn:()=>t.get(`keys/${s}`),enabled:s!==null,refetchInterval:p(lt)}),[C,u]=st(null),D=()=>{d.invalidateQueries({queryKey:["companion",s]}),d.invalidateQueries({queryKey:["roster"]})},U=fe({mutationFn:(g)=>t.post(`keys/${s}/purchase`,g),onMutate:()=>u(null),onError:(g)=>u(g instanceof Error?g.message:"the purchase was refused"),onSuccess:D}),j=fe({mutationFn:(g)=>t.post(`keys/${s}/use`,{item:g}),onMutate:()=>u(null),onError:(g)=>u(g instanceof Error?g.message:"the item could not be used"),onSuccess:D}),be=fe({mutationFn:()=>t.post(`keys/${s}/unpin`),onMutate:()=>u(null),onError:(g)=>u(g instanceof Error?g.message:"the companion could not be released"),onSuccess:D});if(n.isPending)return f(O,{children:"Loading…"});if(s===null)return f(tt,{keys:a,onPick:(g)=>i({at:"key",apiKeyId:g}),pluginId:e,rosterFailed:n.isError});return xe(O,{children:[xe(Ae,{children:[f("h2",{children:"Companion"}),f(W,{children:s}),f(k,{onClick:()=>i({at:"roster"}),type:"button",children:"All keys"})]}),f(Rt,{buy:U.mutate,buying:U.isPending,keyId:s,onRelease:()=>be.mutate(),onUse:j.mutate,pluginId:e,query:S,refusal:C,releasing:be.isPending,using:j.isPending})]})}function Rt({query:e,pluginId:t,buy:o,onUse:i,onRelease:d,buying:p,using:n,releasing:a,refusal:s}){let{open:S,toggle:C}=rt(t);if(e.isPending)return f(x,{children:"Loading…"});if(e.isError||e.data===void 0)return f(x,{children:"No companion for that key yet."});let u=e.data;if(u.state===null)return f(ce,{children:"This key's save could not be read. It has been left untouched rather than replaced — nothing has been lost, but it needs looking at."});let D=M(u.state.active!==null,u.lastCreditAt,Date.now()),U=Object.values(u.state.inventory).filter((j)=>j>0).length;return xe(Tt,{children:[f(et,{activity:D,onRelease:d,pluginId:t,releasing:a,view:u}),f(le,{count:pt(u.shop.length,"offer"),onToggle:()=>C("shop"),open:S.shop,title:"Shop",children:f(it,{hasCompanion:u.state.active!==null,inventory:u.state.inventory,offers:u.shop,onBuy:o,pending:p,pluginId:t,wallet:u.wallet})}),f(le,{count:`${U} held`,onToggle:()=>C("bag"),open:S.bag,title:"Bag",children:f(Ye,{inventory:u.state.inventory,onUse:i,pending:n,pluginId:t})}),s===null?null:f(ce,{role:"alert",children:s}),f(le,{count:pt(u.dex.length,"graduate"),onToggle:()=>C("dex"),open:S.dex,title:"Pokédex",children:f(We,{entries:u.dex,pluginId:t})})]})}function pt(e,t){return`${e} ${t}${e===1?"":"s"}`}var An=It({mount:Ct});export{M as activityOf,An as default};