@hadoku/prefs-client 1.1.12 → 1.1.14

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/dist/avatar.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as AvatarCosmetics, C as CosmeticsView, L as LoadoutUpdate } from './cosmetics-contract-Cqim3RuG.js';
2
- export { a as CosmeticSlot, b as Loadout, P as Placement } from './cosmetics-contract-Cqim3RuG.js';
1
+ import { A as AvatarCosmetics, C as CosmeticsView, L as LoadoutUpdate } from './cosmetics-contract-CfWEWT9I.js';
2
+ export { a as CosmeticSlot, b as Loadout, P as Placement } from './cosmetics-contract-CfWEWT9I.js';
3
3
 
4
4
  /**
5
5
  * Avatars — the wire contract, shared by prefs-api and the client.
@@ -7,7 +7,11 @@
7
7
  *
8
8
  * Rules the catalogue keeps (operator, 2026-11-01; design:
9
9
  * docs/planning/avatar-cosmetics-2026-11-02.md):
10
- * - Earned by achievement or gifted by the operator, never bought; no levels.
10
+ * - Almost everything is free to everyone. Three items are earned, and kept for
11
+ * good once earned: Twinkle (bronze or better in a Major), Decode (silver or
12
+ * better) and the Crown (winning one). Never bought; no levels.
13
+ * - At most six items show per slot. The rest are archived (`hidden`): kept
14
+ * in the catalogue, shown to nobody but admins, and selectable by nobody.
11
15
  * - One item per slot.
12
16
  * - Name effects, gradients and message reveals derive every colour from the
13
17
  * person's character colour. Overlays may bring their own (the crown is gold).
@@ -35,8 +39,13 @@ interface CosmeticItem {
35
39
  id: string;
36
40
  slot: CosmeticSlot;
37
41
  label: string;
38
- /** How it is earned, as the inventory shows it to someone who has not. */
39
- earn: string;
42
+ /**
43
+ * How it is earned, as the inventory shows it to someone who has not.
44
+ * Absent: free, owned by everyone.
45
+ */
46
+ earn?: string;
47
+ /** Archived: shown to nobody but admins, and selectable by nobody. */
48
+ hidden?: true;
40
49
  art?: OverlayArt;
41
50
  }
42
51
  /**
@@ -47,6 +56,13 @@ interface CosmeticItem {
47
56
  */
48
57
  declare const COSMETICS: readonly CosmeticItem[];
49
58
  declare const COSMETICS_BY_ID: ReadonlyMap<string, CosmeticItem>;
59
+ /** Earned items: the only ones a grant can give. */
60
+ declare const EARNED_COSMETICS: readonly CosmeticItem[];
61
+ /**
62
+ * What a person can wear: every visible free item, and the earned items they
63
+ * were granted. A grant of an archived item gives nothing.
64
+ */
65
+ declare function ownedFrom(granted: Iterable<string>): Set<string>;
50
66
  /**
51
67
  * A person's adjustment of an overlay image, relative to its default spot:
52
68
  * x/y in % of the avatar's size, r in degrees, s in % of the default size.
@@ -118,7 +134,7 @@ interface AvatarCosmetics {
118
134
  }
119
135
  /** In AvatarSettings: what the owner has earned and what they wear. */
120
136
  interface CosmeticsView {
121
- /** Item ids, in catalogue order. */
137
+ /** What the person can wear: free items and the earned ones granted, in catalogue order. */
122
138
  owned: string[];
123
139
  loadout: Loadout;
124
140
  }
@@ -155,4 +171,4 @@ interface CosmeticGrantList {
155
171
  }[];
156
172
  }
157
173
 
158
- export { type AvatarCosmetics as A, type Box as B, type CosmeticsView as C, type LoadoutUpdate as L, type OverlayArt as O, type Placement as P, type CosmeticSlot as a, type Loadout as b, COSMETICS as c, COSMETICS_BY_ID as d, COSMETIC_ID_PATTERN as e, COSMETIC_SLOTS as f, type CosmeticGrantList as g, type CosmeticGrantRequest as h, type CosmeticItem as i, PLACEMENT_DEFAULT as j, PLACEMENT_LIMITS as k, PLACEMENT_ZONE as l, cleanLoadout as m, emptyLoadout as n, fitPlacement as o, overlayBounds as p, wornCosmetics as w };
174
+ export { type AvatarCosmetics as A, type Box as B, type CosmeticsView as C, EARNED_COSMETICS as E, type LoadoutUpdate as L, type OverlayArt as O, type Placement as P, type CosmeticSlot as a, type Loadout as b, COSMETICS as c, COSMETICS_BY_ID as d, COSMETIC_ID_PATTERN as e, COSMETIC_SLOTS as f, type CosmeticGrantList as g, type CosmeticGrantRequest as h, type CosmeticItem as i, PLACEMENT_DEFAULT as j, PLACEMENT_LIMITS as k, PLACEMENT_ZONE as l, cleanLoadout as m, emptyLoadout as n, fitPlacement as o, overlayBounds as p, ownedFrom as q, wornCosmetics as w };
@@ -1,5 +1,5 @@
1
- import { A as AvatarCosmetics, P as Placement } from './cosmetics-contract-Cqim3RuG.js';
2
- export { B as Box, c as COSMETICS, d as COSMETICS_BY_ID, e as COSMETIC_ID_PATTERN, f as COSMETIC_SLOTS, g as CosmeticGrantList, h as CosmeticGrantRequest, i as CosmeticItem, a as CosmeticSlot, C as CosmeticsView, b as Loadout, L as LoadoutUpdate, O as OverlayArt, j as PLACEMENT_DEFAULT, k as PLACEMENT_LIMITS, l as PLACEMENT_ZONE, m as cleanLoadout, n as emptyLoadout, o as fitPlacement, p as overlayBounds, w as wornCosmetics } from './cosmetics-contract-Cqim3RuG.js';
1
+ import { A as AvatarCosmetics, P as Placement } from './cosmetics-contract-CfWEWT9I.js';
2
+ export { B as Box, c as COSMETICS, d as COSMETICS_BY_ID, e as COSMETIC_ID_PATTERN, f as COSMETIC_SLOTS, g as CosmeticGrantList, h as CosmeticGrantRequest, i as CosmeticItem, a as CosmeticSlot, C as CosmeticsView, E as EARNED_COSMETICS, b as Loadout, L as LoadoutUpdate, O as OverlayArt, j as PLACEMENT_DEFAULT, k as PLACEMENT_LIMITS, l as PLACEMENT_ZONE, m as cleanLoadout, n as emptyLoadout, o as fitPlacement, p as overlayBounds, q as ownedFrom, w as wornCosmetics } from './cosmetics-contract-CfWEWT9I.js';
3
3
 
4
4
  /**
5
5
  * Avatar cosmetics — the renderer every app uses, so a name, an avatar and a
package/dist/cosmetics.js CHANGED
@@ -1,79 +1,101 @@
1
1
  // src/cosmetics-contract.ts
2
2
  var COSMETIC_SLOTS = ["effect", "gradient", "font", "overlay", "message"];
3
3
  var COSMETIC_ID_PATTERN = /^(effect|gradient|font|overlay|message)\.[a-z0-9-]{1,32}$/;
4
- var item = (slot, name, label, earn, art) => ({ id: `${slot}.${name}`, slot, label, earn, ...art ? { art } : {} });
4
+ var item = (slot, name, label, opts = {}) => ({ id: `${slot}.${name}`, slot, label, ...opts });
5
+ var archived = { hidden: true };
5
6
  var COSMETICS = [
6
- item("effect", "glint", "Glint", "Win a cardtable match"),
7
- item("effect", "aura", "Aura", "Attend 10 watchparties"),
8
- item("effect", "hop", "Hop", "Win 25 bomberman rounds"),
9
- item("effect", "outline", "Outline", "Finish 3 tournaments"),
10
- item("effect", "twinkle", "Twinkle", "Top four in a Major"),
11
- item("gradient", "gild", "Gilded", "Your first win in any game"),
12
- item("gradient", "gloss", "Gloss", "Win 10 games"),
13
- item("gradient", "split", "Split", "Finish 5 tournaments"),
14
- item("gradient", "brushed", "Brushed", "Play 100 games"),
15
- item("gradient", "lumen", "Lumen", "Host 5 watchparties"),
16
- item("font", "caps", "Small caps", "Finish a tournament"),
17
- item("font", "wide", "Wide", "Finish 5 tournaments"),
18
- item("font", "script", "Script", "Attend 25 watchparties"),
19
- item("font", "surf", "Surf", "Attend a summer watchparty"),
20
- item("font", "gothic", "Gothic", "Win an arcane-set game"),
21
- item("font", "runic", "Runic", "Win a fantasy-set game"),
22
- item("font", "corsair", "Corsair", "Win 3 matches in one night"),
23
- item("font", "metal", "Metal", "Win a Major final"),
24
- item("font", "comic", "Comic", "Win from behind"),
25
- item("font", "circuit", "Circuit", "Play 25 cyber-set games"),
26
- item("font", "hover", "Hover", "Win a race"),
27
- item("font", "retro", "Retro", "Play for a year"),
28
- item("font", "arcade", "Arcade", "Play 50 bomberman games"),
29
- item("font", "pixel", "Pixel", "Win 10 bomberman rounds"),
30
- item("overlay", "crown", "Crown", "Win a Major", {
31
- file: "crown.webp",
32
- top: -40,
33
- width: 70,
34
- tilt: -8,
35
- pixel: false,
36
- visible: [0.027, 0.137, 0.973, 0.863]
7
+ item("effect", "glint", "Glint"),
8
+ item("effect", "aura", "Aura"),
9
+ item("effect", "hop", "Hop"),
10
+ item("effect", "outline", "Outline"),
11
+ item("effect", "twinkle", "Twinkle", { earn: "Bronze or better in a Major" }),
12
+ item("gradient", "gild", "Gilded"),
13
+ item("gradient", "gloss", "Gloss"),
14
+ item("gradient", "split", "Split"),
15
+ item("gradient", "brushed", "Brushed"),
16
+ item("gradient", "lumen", "Lumen"),
17
+ item("font", "script", "Script"),
18
+ item("font", "surf", "Surf"),
19
+ item("font", "gothic", "Gothic"),
20
+ item("font", "runic", "Runic"),
21
+ item("font", "arcade", "Arcade"),
22
+ item("font", "caps", "Small caps", archived),
23
+ item("font", "wide", "Wide", archived),
24
+ item("font", "corsair", "Corsair", archived),
25
+ item("font", "metal", "Metal", archived),
26
+ item("font", "comic", "Comic", archived),
27
+ item("font", "circuit", "Circuit", archived),
28
+ item("font", "hover", "Hover", archived),
29
+ item("font", "retro", "Retro", archived),
30
+ item("font", "pixel", "Pixel", archived),
31
+ item("overlay", "crown", "Crown", {
32
+ earn: "Win a Major",
33
+ art: {
34
+ file: "crown.webp",
35
+ top: -40,
36
+ width: 70,
37
+ tilt: -8,
38
+ pixel: false,
39
+ visible: [0.027, 0.137, 0.973, 0.863]
40
+ }
37
41
  }),
38
- item("overlay", "halo", "Halo", "Help a new player (gift)", {
39
- file: "halo.webp",
40
- top: -34,
41
- width: 76,
42
- tilt: 0,
43
- pixel: true,
44
- visible: [0.023, 0.316, 0.969, 0.684]
42
+ item("overlay", "halo", "Halo", {
43
+ art: {
44
+ file: "halo.webp",
45
+ top: -34,
46
+ width: 76,
47
+ tilt: 0,
48
+ pixel: true,
49
+ visible: [0.033, 0.333, 0.967, 0.667]
50
+ }
45
51
  }),
46
- item("overlay", "horns", "Horns", "Win 10 games as the villain", {
47
- file: "horns.webp",
48
- top: -26,
49
- width: 100,
50
- tilt: 0,
51
- pixel: true,
52
- visible: [0.023, 0.129, 0.969, 0.871]
52
+ item("overlay", "flowers", "Flower crown", {
53
+ art: {
54
+ file: "flowers.webp",
55
+ top: -22,
56
+ width: 96,
57
+ tilt: 0,
58
+ pixel: false,
59
+ visible: [0.027, 0.258, 0.969, 0.746]
60
+ }
53
61
  }),
54
- item("overlay", "flowers", "Flower crown", "The anniversary of joining", {
55
- file: "flowers.webp",
56
- top: -22,
57
- width: 96,
58
- tilt: 0,
59
- pixel: false,
60
- visible: [0.027, 0.258, 0.969, 0.746]
62
+ item("overlay", "petals", "Petals"),
63
+ item("overlay", "horns", "Horns", {
64
+ ...archived,
65
+ art: {
66
+ file: "horns.webp",
67
+ top: -26,
68
+ width: 100,
69
+ tilt: 0,
70
+ pixel: true,
71
+ visible: [0.033, 0.133, 0.967, 0.867]
72
+ }
61
73
  }),
62
- item("overlay", "runes", "Runes", "Finish a Major"),
63
- item("overlay", "comet", "Comet", "A 10-game win streak"),
64
- item("overlay", "shine", "Shine", "Win a cardtable match"),
65
- item("overlay", "glow", "Glow", "Attend 25 watchparties"),
66
- item("overlay", "petals", "Petals", "The anniversary of joining"),
67
- item("message", "type", "Typewriter", "Send 1000 chat messages"),
68
- item("message", "decode", "Decode", "Win 25 cyber-set games"),
69
- item("message", "cascade", "Cascade", "Win 3 games in a row"),
70
- item("message", "words", "Word by word", "Send 100 chat messages"),
71
- item("message", "bubble", "Bubble", "Host 3 watchparties"),
72
- item("message", "drift", "Drift", "Attend 20 watchparties")
74
+ item("overlay", "runes", "Runes", archived),
75
+ item("overlay", "comet", "Comet", archived),
76
+ item("overlay", "shine", "Shine", archived),
77
+ item("overlay", "glow", "Glow", archived),
78
+ item("message", "type", "Typewriter"),
79
+ item("message", "decode", "Decode", { earn: "Silver or better in a Major" }),
80
+ item("message", "cascade", "Cascade"),
81
+ item("message", "words", "Word by word"),
82
+ item("message", "bubble", "Bubble", archived),
83
+ item("message", "drift", "Drift")
73
84
  ];
74
85
  var COSMETICS_BY_ID = new Map(
75
86
  COSMETICS.map((i) => [i.id, i])
76
87
  );
88
+ var EARNED_COSMETICS = COSMETICS.filter(
89
+ (i) => i.earn !== void 0 && !i.hidden
90
+ );
91
+ function ownedFrom(granted) {
92
+ const out = new Set(COSMETICS.filter((i) => !i.hidden && i.earn === void 0).map((i) => i.id));
93
+ for (const id of granted) {
94
+ const it = COSMETICS_BY_ID.get(id);
95
+ if (it && !it.hidden) out.add(id);
96
+ }
97
+ return out;
98
+ }
77
99
  var PLACEMENT_DEFAULT = { x: 0, y: 0, r: 0, s: 100, flip: false, back: false };
78
100
  var PLACEMENT_LIMITS = {
79
101
  x: [-60, 60],
@@ -162,13 +184,15 @@ function cleanLoadout(raw, owned) {
162
184
  const src = raw && typeof raw === "object" ? raw : {};
163
185
  for (const slot of COSMETIC_SLOTS) {
164
186
  const id = src.equipped?.[slot];
165
- if (typeof id === "string" && owned.has(id) && COSMETICS_BY_ID.get(id)?.slot === slot) {
166
- out.equipped[slot] = id;
187
+ const it = typeof id === "string" ? COSMETICS_BY_ID.get(id) : void 0;
188
+ if (it && !it.hidden && owned.has(it.id) && it.slot === slot) {
189
+ out.equipped[slot] = it.id;
167
190
  }
168
191
  }
169
192
  for (const [id, p] of Object.entries(src.placement ?? {})) {
170
- const art = COSMETICS_BY_ID.get(id)?.art;
171
- if (art && owned.has(id) && p && typeof p === "object") {
193
+ const it = COSMETICS_BY_ID.get(id);
194
+ const art = it?.art;
195
+ if (art && !it.hidden && owned.has(id) && p && typeof p === "object") {
172
196
  out.placement[id] = fitPlacement(art, p);
173
197
  }
174
198
  }
@@ -480,6 +504,7 @@ export {
480
504
  COSMETICS_BY_ID,
481
505
  COSMETIC_ID_PATTERN,
482
506
  COSMETIC_SLOTS,
507
+ EARNED_COSMETICS,
483
508
  PLACEMENT_DEFAULT,
484
509
  PLACEMENT_LIMITS,
485
510
  PLACEMENT_ZONE,
@@ -494,6 +519,7 @@ export {
494
519
  messageHtml,
495
520
  nameHtml,
496
521
  overlayBounds,
522
+ ownedFrom,
497
523
  placementTransform,
498
524
  wornCosmetics
499
525
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hadoku/prefs-client",
3
- "version": "1.1.12",
4
- "gitHead": "c159fe6d0b90efa7e3cc65c00bde50b3f17529fe",
3
+ "version": "1.1.14",
4
+ "gitHead": "f2f471f8417ec144486c7cf530f87a632e0653b6",
5
5
  "description": "Client SDK for the hadoku unified preferences store. Wraps /prefs/api/v1/* with localStorage caching, debounced writes, Zod-validated reads, and a usePrefs() React hook, plus match history, ratings and leaderboards for every game (./games), the one resolved avatar per user (./avatar), and the avatar cosmetics renderer (./cosmetics).",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",