@mauricode/token-derby 2.12.4 → 2.13.1

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/bin.js CHANGED
@@ -152,48 +152,52 @@ function hatById(id) {
152
152
  }
153
153
  var HATS = [
154
154
  // ── COMMON (18) ────────────────────────────────────────────────────────
155
- { id: "flat_cap", name: "Flat Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...........", "....AAAA...", "...AAAAAA..", "..AAAAAAA.."], variants: [{ A: "#8B6914" }, { A: "#12301b" }, { A: "#aab5cb" }, { A: "#bdcbaa" }] },
156
- { id: "bucket_hat", name: "Bucket Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...QQQQQ...", "...AAAAA...", "...AAAAA...", ".AAAAAAAAA."], variants: [{ A: "#CC2200", Q: "#FFFFFF" }, { A: "#0007cc", Q: "#FFFFFF" }, { A: "#becc00", Q: "#4d3dc7" }, { A: "#cc00c5", Q: "#d6d6dc" }] },
157
- { id: "beanie", name: "Beanie", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....AAA....", "...AAAAA...", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#4A7C59" }, { A: "#b21f35" }, { A: "#ec9418" }, { A: "#3a17e6" }] },
158
- { id: "stetson", name: "Stetson", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...AA.AA...", "...AAAAA...", "...AAAAA...", "A..AAAAA..A", "AAAAAAAAAAA", "AAAAAAAAAAA"], variants: [{ A: "#C49A00" }, { A: "#272004" }, { A: "#040404" }, { A: "#373606" }] },
159
- { id: "party_hat", name: "Party Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "....AQA....", "...AQAQA...", "...AAAAA..."], variants: [{ A: "#FF69B4", Q: "#FFD700" }, { A: "#c46bff", Q: "#ff000d" }, { A: "#fdff6b", Q: "#0400ff" }, { A: "#6bff97", Q: "#05050a" }] },
160
- { id: "fez", name: "Fez", rarity: "common", width: 11, anchor_x: 22, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....Q......", "....AAA....", "....AAA....", "....AAA...."], variants: [{ A: "#CC0000", Q: "#8B0000" }, { A: "#CC0000", Q: "#f5e5e5" }, { A: "#CC0000", Q: "#f1e209" }, { A: "#CC0000", Q: "#0956f1" }] },
161
- { id: "sailor_hat", name: "Sailor Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...QQQQQ...", "..AAAAAAA..", "..QQQQQQQ..", "..AAAAAAA.."], variants: [{ A: "#FFFFFF", Q: "#000080" }, { A: "#FFFFFF", Q: "#c0c0ed" }, { A: "#FFFFFF", Q: "#00000a" }, { A: "#FFFFFF", Q: "#0505ef" }] },
162
- { id: "newsboy_cap", name: "Newsboy Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "..AAAQAAA..", "..AAAAAAA..", "..AAAAAA..."], variants: [{ A: "#5C4033", Q: "#3D2B1F" }, { A: "#e96020", Q: "#131211" }, { A: "#342c28", Q: "#131211" }, { A: "#0a0300", Q: "#ddab78" }] },
163
- { id: "tam_o_shanter", name: "Tam O'Shanter", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....Q.....", "....AAA....", "..AAAAAAA..", "..AAAAAAA..", "...AAAAA..."], variants: [{ A: "#006400", Q: "#FF0000" }, { A: "#ff0000", Q: "#016400" }] },
164
- { id: "trucker_cap", name: "Trucker Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAQQ....", "...AAQQ....", "..AAQQQAA..", "..AAQQQAA.."], variants: [{ A: "#2196F3", Q: "#FFFFFF" }, { A: "#f41fe3", Q: "#FFFFFF" }, { A: "#1ff45f", Q: "#FFFFFF" }] },
165
- { id: "hard_hat", name: "Hard Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....AAA....", "...AAAAA...", "..AAAAAAA..", "..AAAAAAAAA"], variants: [{ A: "#FFD600" }, { A: "#d74c1d" }] },
166
- { id: "chef_toque", name: "Chef's Toque", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...AAAAA...", "..AAQAQAA..", "...AQAQA...", "...AQAQA...", "...AQAQA...", "...AAAAA..."], variants: [{ A: "#FFFFFF", Q: "#f9f9f9" }] },
167
- { id: "bobble_hat", name: "Bobble Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", ".....Q.....", "...AAAAA...", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#C62828", Q: "#FFFFFF" }, { A: "#293bc7", Q: "#FFFFFF" }, { A: "#29c775", Q: "#231f1f" }, { A: "#a529c7", Q: "#d50707" }] },
168
- { id: "cowboy_hat", name: "Cowboy Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "...AAAAA...", "A.AAAAAAA.A", ".AAAAAAAAA."], variants: [{ A: "#8B4513" }, { A: "#0a0400" }, { A: "#956c50" }] },
169
- { id: "baseball_cap", name: "Baseball Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "..AAAAAA...", "..AAAAAAA..", "..AAAAAAAAA"], variants: [{ A: "#1565C0" }, { A: "#8515c1" }, { A: "#15c157" }, { A: "#ef2d0b" }] },
170
- { id: "tinfoil_hat", name: "Tinfoil Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#B0BEC5" }, { A: "#6a6c6c" }] },
171
- { id: "dunce_cap", name: "Dunce Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", ".....Q.....", "....QAQ....", "...QAAAQ...", "...QAAAQ...", "..QAAAAAQ..", "..AAAAAAA.."], variants: [{ A: "#F8F8F8", Q: "#F44336" }] },
172
- { id: "mini_top_hat", name: "Mini Top Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...AAAAA...", "...AAAAA...", "...AAAAA...", "..AAAAAAA..", "..AAAAAAA.."], variants: [{ A: "#212121" }, { A: "#626060" }] },
155
+ { id: "flat_cap", name: "Flat Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...........", "....AAAA...", "...AAAAAA..", "..AAAAAAA.."], variants: [{ A: "#8B6914" }, { A: "#12301b" }, { A: "#aab5cb" }, { A: "#bdcbaa" }], rollable: true },
156
+ { id: "bucket_hat", name: "Bucket Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...QQQQQ...", "...AAAAA...", "...AAAAA...", ".AAAAAAAAA."], variants: [{ A: "#CC2200", Q: "#FFFFFF" }, { A: "#0007cc", Q: "#FFFFFF" }, { A: "#becc00", Q: "#4d3dc7" }, { A: "#cc00c5", Q: "#d6d6dc" }], rollable: true },
157
+ { id: "beanie", name: "Beanie", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....AAA....", "...AAAAA...", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#4A7C59" }, { A: "#b21f35" }, { A: "#ec9418" }, { A: "#3a17e6" }], rollable: true },
158
+ { id: "stetson", name: "Stetson", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...AA.AA...", "...AAAAA...", "...AAAAA...", "A..AAAAA..A", "AAAAAAAAAAA", "AAAAAAAAAAA"], variants: [{ A: "#C49A00" }, { A: "#272004" }, { A: "#040404" }, { A: "#373606" }], rollable: true },
159
+ { id: "party_hat", name: "Party Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "....AQA....", "...AQAQA...", "...AAAAA..."], variants: [{ A: "#FF69B4", Q: "#FFD700" }, { A: "#c46bff", Q: "#ff000d" }, { A: "#fdff6b", Q: "#0400ff" }, { A: "#6bff97", Q: "#05050a" }], rollable: true },
160
+ { id: "fez", name: "Fez", rarity: "common", width: 11, anchor_x: 22, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....Q......", "....AAA....", "....AAA....", "....AAA...."], variants: [{ A: "#CC0000", Q: "#8B0000" }, { A: "#CC0000", Q: "#f5e5e5" }, { A: "#CC0000", Q: "#f1e209" }, { A: "#CC0000", Q: "#0956f1" }], rollable: true },
161
+ { id: "sailor_hat", name: "Sailor Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...QQQQQ...", "..AAAAAAA..", "..QQQQQQQ..", "..AAAAAAA.."], variants: [{ A: "#FFFFFF", Q: "#000080" }, { A: "#FFFFFF", Q: "#c0c0ed" }, { A: "#FFFFFF", Q: "#00000a" }, { A: "#FFFFFF", Q: "#0505ef" }], rollable: true },
162
+ { id: "newsboy_cap", name: "Newsboy Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "..AAAQAAA..", "..AAAAAAA..", "..AAAAAA..."], variants: [{ A: "#5C4033", Q: "#3D2B1F" }, { A: "#e96020", Q: "#131211" }, { A: "#342c28", Q: "#131211" }, { A: "#0a0300", Q: "#ddab78" }], rollable: true },
163
+ { id: "tam_o_shanter", name: "Tam O'Shanter", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....Q.....", "....AAA....", "..AAAAAAA..", "..AAAAAAA..", "...AAAAA..."], variants: [{ A: "#006400", Q: "#FF0000" }, { A: "#ff0000", Q: "#016400" }], rollable: true },
164
+ { id: "trucker_cap", name: "Trucker Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAQQ....", "...AAQQ....", "..AAQQQAA..", "..AAQQQAA.."], variants: [{ A: "#2196F3", Q: "#FFFFFF" }, { A: "#f41fe3", Q: "#FFFFFF" }, { A: "#1ff45f", Q: "#FFFFFF" }], rollable: true },
165
+ { id: "hard_hat", name: "Hard Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....AAA....", "...AAAAA...", "..AAAAAAA..", "..AAAAAAAAA"], variants: [{ A: "#FFD600" }, { A: "#d74c1d" }], rollable: true },
166
+ { id: "chef_toque", name: "Chef's Toque", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...AAAAA...", "..AAQAQAA..", "...AQAQA...", "...AQAQA...", "...AQAQA...", "...AAAAA..."], variants: [{ A: "#FFFFFF", Q: "#f9f9f9" }], rollable: true },
167
+ { id: "bobble_hat", name: "Bobble Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", ".....Q.....", "...AAAAA...", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#C62828", Q: "#FFFFFF" }, { A: "#293bc7", Q: "#FFFFFF" }, { A: "#29c775", Q: "#231f1f" }, { A: "#a529c7", Q: "#d50707" }], rollable: true },
168
+ { id: "cowboy_hat", name: "Cowboy Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "...AAAAA...", "A.AAAAAAA.A", ".AAAAAAAAA."], variants: [{ A: "#8B4513" }, { A: "#0a0400" }, { A: "#956c50" }], rollable: true },
169
+ { id: "baseball_cap", name: "Baseball Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "..AAAAAA...", "..AAAAAAA..", "..AAAAAAAAA"], variants: [{ A: "#1565C0" }, { A: "#8515c1" }, { A: "#15c157" }, { A: "#ef2d0b" }], rollable: true },
170
+ { id: "tinfoil_hat", name: "Tinfoil Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#B0BEC5" }, { A: "#6a6c6c" }], rollable: true },
171
+ { id: "dunce_cap", name: "Dunce Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", ".....Q.....", "....QAQ....", "...QAAAQ...", "...QAAAQ...", "..QAAAAAQ..", "..AAAAAAA.."], variants: [{ A: "#F8F8F8", Q: "#F44336" }], rollable: true },
172
+ { id: "mini_top_hat", name: "Mini Top Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...AAAAA...", "...AAAAA...", "...AAAAA...", "..AAAAAAA..", "..AAAAAAA.."], variants: [{ A: "#212121" }, { A: "#626060" }], rollable: true },
173
173
  // ── RARE (10) ──────────────────────────────────────────────────────────
174
- { id: "bicorne", name: "Bicorne", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AAAQQQQ..", "..AAAAAAA..", "..AAAAAAA..", "...QQQQQ..."], variants: [{ A: "#1A237E", Q: "#FFD700" }, { A: "#01052d", Q: "#544803" }] },
175
- { id: "viking_helmet", name: "Viking Helmet", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..A.AAA.A..", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#9E9E9E", Q: "#8D6E63" }, { A: "#dcdbdb", Q: "#bf3908" }] },
176
- { id: "jesters_cap", name: "Jester's Cap", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AQAQAQA..", "..AQAQAQA..", "...AAAAA...", "...AAAAA..."], variants: [{ A: "#E53935", Q: "#FFD600" }, { A: "#e5a434", Q: "#0040ff" }, { A: "#34e537", Q: "#eeff00" }, { A: "#e234e5", Q: "#00ff40" }] },
177
- { id: "plague_doctor", name: "Plague Doctor Beak", rarity: "rare", width: 11, anchor_x: 24, rows: ["...........", "...........", "...........", "...........", "....QQQ....", "...QAAAQQ..", "...QQQQQQQ.", "..QQQQQ.QQQ", "..QQQQ....Q", "..QQQQ....."], variants: [{ A: "#F5F5F5", Q: "#795548" }, { A: "#e1dbdb", Q: "#080707" }] },
178
- { id: "morion", name: "Conquistador Morion", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....Q.....", "...QQQQQ...", "...QAAAQ...", ".A..AAA..A.", "..AAQQQAA.."], variants: [{ A: "#B0BEC5", Q: "#FFD600" }, { A: "#0a4768", Q: "#e6c10a" }] },
179
- { id: "shako", name: "Shako", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "........Q..", "........Q..", "........Q..", "........Q..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#45464f", Q: "#00ff2a" }, { A: "#45464f", Q: "#ff4d00" }, { A: "#45464f", Q: "#ff0019" }, { A: "#45464f", Q: "#fff700" }] },
180
- { id: "centurion_helm", name: "Centurion Helm", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "....QQQ....", "...QQQQQQ..", "..QQQQQQQQ.", ".QQ..A...Q.", ".Q..AAA....", "...AAAAA...", "..AAAAAAA..", "..AAAAAAA.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#2f3131", Q: "#C62828" }] },
181
- { id: "papal_mitre", name: "Papal Mitre", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....QQQ....", "...QQQQQ...", "...QAAAQ...", "..AAAAAAA.."], variants: [{ A: "#FFFFFF", Q: "#FFD700" }] },
182
- { id: "headdress", name: "Headdress", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AQAQAQA..", "..AQAQAQA..", "..AAAAAAA..", "...QQQQQ..."], variants: [{ A: "#FF8F00", Q: "#1565C0" }] },
183
- { id: "sombrero", name: "Sombrero", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "...AQAQA...", "AAAAAAAAAAA"], variants: [{ A: "#F57F17", Q: "#BF360C" }, { A: "#f8b377", Q: "#3e1204" }, { A: "#d6f877", Q: "#04133e" }, { A: "#d6f877", Q: "#04133e" }] },
174
+ { id: "bicorne", name: "Bicorne", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AAAQQQQ..", "..AAAAAAA..", "..AAAAAAA..", "...QQQQQ..."], variants: [{ A: "#1A237E", Q: "#FFD700" }, { A: "#01052d", Q: "#544803" }], rollable: true },
175
+ { id: "viking_helmet", name: "Viking Helmet", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..A.AAA.A..", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#9E9E9E", Q: "#8D6E63" }, { A: "#dcdbdb", Q: "#bf3908" }], rollable: true },
176
+ { id: "jesters_cap", name: "Jester's Cap", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AQAQAQA..", "..AQAQAQA..", "...AAAAA...", "...AAAAA..."], variants: [{ A: "#E53935", Q: "#FFD600" }, { A: "#e5a434", Q: "#0040ff" }, { A: "#34e537", Q: "#eeff00" }, { A: "#e234e5", Q: "#00ff40" }], rollable: true },
177
+ { id: "plague_doctor", name: "Plague Doctor Beak", rarity: "rare", width: 11, anchor_x: 24, rows: ["...........", "...........", "...........", "...........", "....QQQ....", "...QAAAQQ..", "...QQQQQQQ.", "..QQQQQ.QQQ", "..QQQQ....Q", "..QQQQ....."], variants: [{ A: "#F5F5F5", Q: "#795548" }, { A: "#e1dbdb", Q: "#080707" }], rollable: true },
178
+ { id: "morion", name: "Conquistador Morion", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....Q.....", "...QQQQQ...", "...QAAAQ...", ".A..AAA..A.", "..AAQQQAA.."], variants: [{ A: "#B0BEC5", Q: "#FFD600" }, { A: "#0a4768", Q: "#e6c10a" }], rollable: true },
179
+ { id: "shako", name: "Shako", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "........Q..", "........Q..", "........Q..", "........Q..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#45464f", Q: "#00ff2a" }, { A: "#45464f", Q: "#ff4d00" }, { A: "#45464f", Q: "#ff0019" }, { A: "#45464f", Q: "#fff700" }], rollable: true },
180
+ { id: "centurion_helm", name: "Centurion Helm", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "....QQQ....", "...QQQQQQ..", "..QQQQQQQQ.", ".QQ..A...Q.", ".Q..AAA....", "...AAAAA...", "..AAAAAAA..", "..AAAAAAA.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#2f3131", Q: "#C62828" }], rollable: true },
181
+ { id: "papal_mitre", name: "Papal Mitre", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....QQQ....", "...QQQQQ...", "...QAAAQ...", "..AAAAAAA.."], variants: [{ A: "#FFFFFF", Q: "#FFD700" }], rollable: true },
182
+ { id: "headdress", name: "Headdress", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AQAQAQA..", "..AQAQAQA..", "..AAAAAAA..", "...QQQQQ..."], variants: [{ A: "#FF8F00", Q: "#1565C0" }], rollable: true },
183
+ { id: "sombrero", name: "Sombrero", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "...AQAQA...", "AAAAAAAAAAA"], variants: [{ A: "#F57F17", Q: "#BF360C" }, { A: "#f8b377", Q: "#3e1204" }, { A: "#d6f877", Q: "#04133e" }, { A: "#d6f877", Q: "#04133e" }], rollable: true },
184
184
  // ── EPIC (6) ───────────────────────────────────────────────────────────
185
- { id: "papal_tiara", name: "Papal Tiara", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "....QQ.....", "...QQQQQ...", "...QAAQQ...", "...QQQQQ...", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#FFFFFF", Q: "#FFD700" }, { A: "#b18e10", Q: "#f3f3f1" }] },
186
- { id: "samurai_kabuto", name: "Samurai Kabuto", rarity: "epic", width: 11, anchor_x: 23, rows: ["....Q...Q..", "....QQ.QQ..", ".....QQQ...", ".......Q...", "...AAAAQ...", "..AAAAAQQ..", "..AAAAAQQ..", ".AAAAAAAQ..", "AAAAAAAAQ..", "AAAAAAAAQ.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#050505", Q: "#C62828" }, { A: "#675f5f", Q: "#c4c729" }] },
187
- { id: "gladiator_galea", name: "Gladiator Galea", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "....QQQQQQ.", "...QQQQQ...", "...QQQQQ...", "...QAAA....", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#ffe907", Q: "#C62828" }] },
188
- { id: "pharaoh_nemes", name: "Pharaoh Nemes", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "..AAAAAAA..", "..AAQAQAA..", "..QAQAQAQ..", "..QAQAQAQ..", "..QQAAAQQ..", "..QQAAAAA..", "...QAQA...."], variants: [{ A: "#FFD700", Q: "#1565C0" }, { A: "#8d342a", Q: "#63686e" }] },
189
- { id: "spartan_helmet", name: "Spartan Helmet", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "........Q..", ".......QQ..", ".....QQQQ..", "....QQQQQ..", "..QQQQQQ...", ".QQQAA.....", "QQAAAAAAA..", "Q.AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#B0BEC5", Q: "#B71C1C" }, { A: "#B0BEC5", Q: "#b2b51c" }, { A: "#B0BEC5", Q: "#b5611c" }] },
190
- { id: "conquistador_full", name: "Conquistador Helm", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "..AAAAAAA..", "...AQQQA...", "...AQQQA..."], variants: [{ A: "#B0BEC5", Q: "#FFD700" }, { A: "#B0BEC5", Q: "#0040ff" }, { A: "#B0BEC5", Q: "#36123b" }] },
185
+ { id: "papal_tiara", name: "Papal Tiara", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "....QQ.....", "...QQQQQ...", "...QAAQQ...", "...QQQQQ...", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#FFFFFF", Q: "#FFD700" }, { A: "#b18e10", Q: "#f3f3f1" }], rollable: true },
186
+ { id: "samurai_kabuto", name: "Samurai Kabuto", rarity: "epic", width: 11, anchor_x: 23, rows: ["....Q...Q..", "....QQ.QQ..", ".....QQQ...", ".......Q...", "...AAAAQ...", "..AAAAAQQ..", "..AAAAAQQ..", ".AAAAAAAQ..", "AAAAAAAAQ..", "AAAAAAAAQ.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#050505", Q: "#C62828" }, { A: "#675f5f", Q: "#c4c729" }], rollable: true },
187
+ { id: "gladiator_galea", name: "Gladiator Galea", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "....QQQQQQ.", "...QQQQQ...", "...QQQQQ...", "...QAAA....", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#ffe907", Q: "#C62828" }], rollable: true },
188
+ { id: "pharaoh_nemes", name: "Pharaoh Nemes", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "..AAAAAAA..", "..AAQAQAA..", "..QAQAQAQ..", "..QAQAQAQ..", "..QQAAAQQ..", "..QQAAAAA..", "...QAQA...."], variants: [{ A: "#FFD700", Q: "#1565C0" }, { A: "#8d342a", Q: "#63686e" }], rollable: true },
189
+ { id: "spartan_helmet", name: "Spartan Helmet", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "........Q..", ".......QQ..", ".....QQQQ..", "....QQQQQ..", "..QQQQQQ...", ".QQQAA.....", "QQAAAAAAA..", "Q.AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#B0BEC5", Q: "#B71C1C" }, { A: "#B0BEC5", Q: "#b2b51c" }, { A: "#B0BEC5", Q: "#b5611c" }], rollable: true },
190
+ { id: "conquistador_full", name: "Conquistador Helm", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "..AAAAAAA..", "...AQQQA...", "...AQQQA..."], variants: [{ A: "#B0BEC5", Q: "#FFD700" }, { A: "#B0BEC5", Q: "#0040ff" }, { A: "#B0BEC5", Q: "#36123b" }], rollable: true },
191
191
  // ── LEGENDARY (5) ──────────────────────────────────────────────────────
192
- { id: "rainbow_crown", name: "Rainbow Crown", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", ".....A.....", "....AAA....", "....AQA....", "....AAA....", "...AAQAA...", "...AAAAA...", "..AAAQAAA..", "..AAAAAAA.."], colors: { A: "#FFD700", Q: "#553f3f" }, animation: { type: "cycle", frames: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#8B00FF"], fps: 8 } },
193
- { id: "inferno_cap", name: "Inferno Cap", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "....AAA....", "...AAAAA...", "....AAA....", ".....Q.....", "A....Q....A", "A...QQQ...A", ".A..QQQ..A.", "..AAAAAAA..", "..AAAAAAA.."], colors: { A: "#FF4500", Q: "#FFD700" }, animation: { type: "cycle", frames: ["#FF0000", "#FF2200", "#FF4500", "#FF6600", "#FF8C00", "#FFA500"], fps: 12 } },
194
- { id: "void_hood", name: "Void Hood", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...AAA.....", "..AAAAAA...", ".AAAAAAA...", ".AAAAAAAA..", "AAAAAAAAA..", "AAAAQQQAA..", "AAAAQQQAA.."], colors: { A: "#1A0033", Q: "#d9cfe3" }, animation: { type: "cycle", frames: ["#0D0019", "#1A0033", "#2D004D", "#3D0066", "#2D004D", "#1A0033"], fps: 3 } },
195
- { id: "prismatic_jester", name: "Prismatic Jester", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "..Q.Q.Q.Q..", "Q..A.A.A..Q", ".Q.A.A.A.Q.", "..AQAQAQA..", "..AQAQAQA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA.."], colors: { A: "#FF0000", Q: "#0000FF" }, animation: { type: "cycle", frames: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#8B00FF", "#FF00FF", "#00FFFF"], fps: 15 } },
196
- { id: "aurora_helm", name: "Aurora Helm", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...QQQQQ...", "...QQQQQ...", "..AAAAAAA..", "..AAAAAAA..", "..AQAAAQA..", "..AQAAAQA.."], colors: { A: "#00CED1", Q: "#00FF7F" }, animation: { type: "cycle", frames: ["#0000FF", "#0066FF", "#00BFFF", "#00CED1", "#00FF7F", "#7CFC00", "#00FF7F", "#00CED1"], fps: 4 } }
192
+ { id: "rainbow_crown", name: "Rainbow Crown", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", ".....A.....", "....AAA....", "....AQA....", "....AAA....", "...AAQAA...", "...AAAAA...", "..AAAQAAA..", "..AAAAAAA.."], colors: { A: "#FFD700", Q: "#553f3f" }, animation: { type: "cycle", frames: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#8B00FF"], fps: 8 }, rollable: true },
193
+ { id: "inferno_cap", name: "Inferno Cap", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "....AAA....", "...AAAAA...", "....AAA....", ".....Q.....", "A....Q....A", "A...QQQ...A", ".A..QQQ..A.", "..AAAAAAA..", "..AAAAAAA.."], colors: { A: "#FF4500", Q: "#FFD700" }, animation: { type: "cycle", frames: ["#FF0000", "#FF2200", "#FF4500", "#FF6600", "#FF8C00", "#FFA500"], fps: 12 }, rollable: true },
194
+ { id: "void_hood", name: "Void Hood", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...AAA.....", "..AAAAAA...", ".AAAAAAA...", ".AAAAAAAA..", "AAAAAAAAA..", "AAAAQQQAA..", "AAAAQQQAA.."], colors: { A: "#1A0033", Q: "#d9cfe3" }, animation: { type: "cycle", frames: ["#0D0019", "#1A0033", "#2D004D", "#3D0066", "#2D004D", "#1A0033"], fps: 3 }, rollable: true },
195
+ { id: "prismatic_jester", name: "Prismatic Jester", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "..Q.Q.Q.Q..", "Q..A.A.A..Q", ".Q.A.A.A.Q.", "..AQAQAQA..", "..AQAQAQA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA.."], colors: { A: "#FF0000", Q: "#0000FF" }, animation: { type: "cycle", frames: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#8B00FF", "#FF00FF", "#00FFFF"], fps: 15 }, rollable: true },
196
+ { id: "aurora_helm", name: "Aurora Helm", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...QQQQQ...", "...QQQQQ...", "..AAAAAAA..", "..AAAAAAA..", "..AQAAAQA..", "..AQAAAQA.."], colors: { A: "#00CED1", Q: "#00FF7F" }, animation: { type: "cycle", frames: ["#0000FF", "#0066FF", "#00BFFF", "#00CED1", "#00FF7F", "#7CFC00", "#00FF7F", "#00CED1"], fps: 4 }, rollable: true },
197
+ // Claim-only: awarded by an admin, never from a roll. Only the A channel cycles,
198
+ // so the inset motif shifts colour while the cap body stays a static rust brown.
199
+ // The body colour is kept out of the frame list so the motif never merges into it.
200
+ { id: "contributor_cap", name: "Contributor Cap", rarity: "legendary", width: 11, anchor_x: 23, rows: [".....Q.....", "....QQQ....", "...QQQQQ...", "..QAAAQQQ..", "..QQAQQQQ..", ".QQQAQAAQQ.", ".QQQAQAQAQ.", ".QQQQQAQAQ.", ".QQQQQAAQQ.", ".QQQQQQQQQ."], colors: { A: "#21138b", Q: "#6f2801" }, animation: { type: "cycle", frames: ["#FF8C42", "#E0457B", "#8B5CF6", "#3B82F6", "#22D3EE", "#34D399", "#A3E635", "#FBBF24"], fps: 6 }, rollable: false }
197
201
  ];
198
202
 
199
203
  // ../shared/dist/series-transform.js
@@ -252,6 +256,39 @@ function toTag(c, y) {
252
256
  }
253
257
  }
254
258
 
259
+ // ../shared/dist/scoring.js
260
+ function scoredOf(horse) {
261
+ return horse.scored_tokens ?? horse.current_tokens;
262
+ }
263
+ var STAMINA = {
264
+ SUSTAINABLE_PACE: 4e3,
265
+ DRAIN_PER_MIN: 4,
266
+ MAX_DRAIN_PER_MIN: 6,
267
+ RECOVER_PER_MIN: 2,
268
+ RECOVER_TICK_CAP_MS: 9e4,
269
+ TAPER_FLOOR: 25,
270
+ TIRED_MULTIPLIER: 0.5
271
+ };
272
+ function resolveStaminaConfig(race) {
273
+ const c = race.stamina_config ?? {};
274
+ return {
275
+ sustainable_pace: c.sustainable_pace ?? STAMINA.SUSTAINABLE_PACE,
276
+ drain_per_min: c.drain_per_min ?? STAMINA.DRAIN_PER_MIN,
277
+ max_drain_per_min: c.max_drain_per_min ?? STAMINA.MAX_DRAIN_PER_MIN,
278
+ recover_per_min: c.recover_per_min ?? STAMINA.RECOVER_PER_MIN,
279
+ taper_floor: c.taper_floor ?? STAMINA.TAPER_FLOOR,
280
+ tired_multiplier: c.tired_multiplier ?? STAMINA.TIRED_MULTIPLIER
281
+ };
282
+ }
283
+ var STAMINA_PARAM_BOUNDS = {
284
+ sustainable_pace: { min: 1e3, max: 2e4, step: 250, default: STAMINA.SUSTAINABLE_PACE },
285
+ drain_per_min: { min: 1, max: 12, step: 1, default: STAMINA.DRAIN_PER_MIN },
286
+ max_drain_per_min: { min: 2, max: 20, step: 1, default: STAMINA.MAX_DRAIN_PER_MIN },
287
+ recover_per_min: { min: 1, max: 8, step: 1, default: STAMINA.RECOVER_PER_MIN },
288
+ taper_floor: { min: 10, max: 60, step: 5, default: STAMINA.TAPER_FLOOR },
289
+ tired_multiplier: { min: 0.2, max: 0.9, step: 0.05, default: STAMINA.TIRED_MULTIPLIER }
290
+ };
291
+
255
292
  // src/ui/HorseSprite.tsx
256
293
  import { Box, Text } from "ink";
257
294
 
@@ -788,8 +825,8 @@ var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
788
825
  // src/version.ts
789
826
  import { createRequire } from "module";
790
827
  function readVersion() {
791
- if ("2.12.4".length > 0) {
792
- return "2.12.4";
828
+ if ("2.13.1".length > 0) {
829
+ return "2.13.1";
793
830
  }
794
831
  try {
795
832
  const req = createRequire(import.meta.url);
@@ -991,6 +1028,12 @@ function equipHat(stableHorseId, body) {
991
1028
  function createWebSession() {
992
1029
  return request("POST", "/web-sessions", void 0, void 0);
993
1030
  }
1031
+ function probeClaim(code) {
1032
+ return request("GET", `/claims/${encodeURIComponent(code)}`, void 0, void 0);
1033
+ }
1034
+ function redeemClaim(code, body) {
1035
+ return request("POST", `/claims/${encodeURIComponent(code)}/redeem`, body, void 0);
1036
+ }
994
1037
 
995
1038
  // src/commands/stable-create.ts
996
1039
  async function stableCreateCommand() {
@@ -1130,7 +1173,7 @@ import { render as render3 } from "ink";
1130
1173
  import { useState as useState3 } from "react";
1131
1174
  import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
1132
1175
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1133
- function HorsePicker({ horses, onPick, onCancel }) {
1176
+ function HorsePicker({ horses, onPick, onCancel, prompt = "Pick a horse to race:" }) {
1134
1177
  const [idx, setIdx] = useState3(0);
1135
1178
  useInput2((input, key) => {
1136
1179
  if (key.escape) {
@@ -1158,7 +1201,7 @@ function HorsePicker({ horses, onPick, onCancel }) {
1158
1201
  ] });
1159
1202
  }
1160
1203
  return /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", children: [
1161
- /* @__PURE__ */ jsx5(Text5, { children: "Pick a horse to race:" }),
1204
+ /* @__PURE__ */ jsx5(Text5, { children: prompt }),
1162
1205
  horses.map((h, i) => /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", children: [
1163
1206
  /* @__PURE__ */ jsx5(Box5, { flexDirection: "row", children: /* @__PURE__ */ jsxs3(Text5, { children: [
1164
1207
  i === idx ? "\u25BA" : " ",
@@ -1326,6 +1369,8 @@ async function createRaceCommand(organisationName) {
1326
1369
  const counts_input = countInputRaw === "y" || countInputRaw === "yes";
1327
1370
  const top5Raw = (await rl.question("Count only each racer's 5 most-active conversations toward their primary model's score? [y/N]: ")).trim().toLowerCase();
1328
1371
  const primary_top5 = top5Raw === "y" || top5Raw === "yes";
1372
+ const staminaRaw = (await rl.question("Stamina \u2014 horses that run flat out tire and score less until they recover? [y/N]: ")).trim().toLowerCase();
1373
+ const stamina = staminaRaw === "y" || staminaRaw === "yes";
1329
1374
  const resp = await createRace({
1330
1375
  name,
1331
1376
  start_time: start,
@@ -1334,7 +1379,8 @@ async function createRaceCommand(organisationName) {
1334
1379
  ...max !== void 0 ? { max_participants: max } : {},
1335
1380
  ...org ? { organisation_name: org } : {},
1336
1381
  ...counts_input ? { counts_input: true } : {},
1337
- ...primary_top5 ? { primary_top5: true } : {}
1382
+ ...primary_top5 ? { primary_top5: true } : {},
1383
+ ...stamina ? { stamina: true } : {}
1338
1384
  });
1339
1385
  console.log("");
1340
1386
  console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
@@ -1353,6 +1399,9 @@ async function createRaceCommand(organisationName) {
1353
1399
  if (primary_top5) {
1354
1400
  console.log(" Primary score counts only each racer's top 5 conversations per beat.");
1355
1401
  }
1402
+ if (stamina) {
1403
+ console.log(" Stamina on \u2014 horses running above a sustainable pace will tire.");
1404
+ }
1356
1405
  console.log(` Share with participants: token-derby join ${resp.join_code}`);
1357
1406
  return 0;
1358
1407
  } catch (e) {
@@ -1442,8 +1491,10 @@ function StatusScreen(props) {
1442
1491
  const divisionField = own?.division === void 0 ? [] : race.horses.filter((h) => h.division === own.division);
1443
1492
  const divisionRank = own ? divisionField.indexOf(own) + 1 : 0;
1444
1493
  const showDivision = (race.league_division_names?.length ?? 0) > 0 && divisionRank > 0;
1494
+ const staminaRow = race.stamina === true ? staminaLine(own, race) : null;
1445
1495
  const rows = [
1446
- { label: "Tokens (race):", value: String(own?.current_tokens ?? 0) },
1496
+ { label: "Tokens (race):", value: String(own?.final_scored_tokens ?? (own ? scoredOf(own) : 0)) },
1497
+ ...staminaRow ? [staminaRow] : [],
1447
1498
  { label: "Position:", value: `${own?.rank ?? "\u2014"} of ${race.horses.length}` },
1448
1499
  ...showDivision ? [{ label: "Position (Division):", value: `${divisionRank} of ${divisionField.length}` }] : [],
1449
1500
  { label: "Leader:", value: leaderText(leader) },
@@ -1514,7 +1565,7 @@ function StatLines(props) {
1514
1565
  }
1515
1566
  function leaderText(h) {
1516
1567
  if (!h) return "\u2014";
1517
- return `${h.name}${h.user_name ? ` (${h.user_name})` : ""} \u2014 ${h.current_tokens}`;
1568
+ return `${h.name}${h.user_name ? ` (${h.user_name})` : ""} \u2014 ${h.final_scored_tokens ?? scoredOf(h)}`;
1518
1569
  }
1519
1570
  function elapsed(race) {
1520
1571
  const start = new Date(race.start_time).getTime();
@@ -1528,6 +1579,22 @@ function bar(pct, width) {
1528
1579
  const filled = Math.round(pct * width);
1529
1580
  return "\u2593".repeat(filled) + "\u2591".repeat(width - filled);
1530
1581
  }
1582
+ function staminaLine(own, race) {
1583
+ const stamina = own?.stamina ?? 100;
1584
+ const cfg = resolveStaminaConfig(race);
1585
+ const floor = cfg.taper_floor;
1586
+ const band = stamina > 50 ? "green" : stamina >= floor ? "amber" : "red";
1587
+ const color = band === "green" ? "green" : band === "amber" ? "yellow" : "red";
1588
+ const pct = Math.max(0, Math.min(1, stamina / 100));
1589
+ const multiplier = band === "red" ? cfg.tired_multiplier + (1 - cfg.tired_multiplier) * (stamina / floor) : null;
1590
+ return {
1591
+ label: "Stamina:",
1592
+ value: /* @__PURE__ */ jsxs5(Text7, { color, children: [
1593
+ `${Math.round(stamina)}% ${bar(pct, 20)}`,
1594
+ multiplier !== null ? ` \xD7${multiplier.toFixed(2)}` : ""
1595
+ ] })
1596
+ };
1597
+ }
1531
1598
  function statusColor(status) {
1532
1599
  if (status === "live") return "green";
1533
1600
  if (status === "pending") return "yellow";
@@ -2732,30 +2799,108 @@ function runNpmUpgrade(spawnImpl) {
2732
2799
  }
2733
2800
 
2734
2801
  // src/commands/roll.ts
2802
+ import React14 from "react";
2803
+ import { render as render6 } from "ink";
2804
+
2805
+ // src/ui/RollHorsePicker.tsx
2806
+ import { useState as useState6 } from "react";
2807
+ import { Box as Box9, Text as Text9, useInput as useInput4 } from "ink";
2808
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2809
+ function RollHorsePicker({ horses, onPick, onCancel }) {
2810
+ const [idx, setIdx] = useState6(0);
2811
+ useInput4((input, key) => {
2812
+ if (key.escape) {
2813
+ onCancel();
2814
+ return;
2815
+ }
2816
+ if (horses.length === 0) return;
2817
+ if (key.upArrow) {
2818
+ setIdx((idx - 1 + horses.length) % horses.length);
2819
+ return;
2820
+ }
2821
+ if (key.downArrow) {
2822
+ setIdx((idx + 1) % horses.length);
2823
+ return;
2824
+ }
2825
+ if (key.return) {
2826
+ onPick(horses[idx]);
2827
+ return;
2828
+ }
2829
+ });
2830
+ return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", children: [
2831
+ /* @__PURE__ */ jsx9(Text9, { children: "Pick a horse to roll for:" }),
2832
+ horses.map((h, i) => /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", children: [
2833
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "row", children: /* @__PURE__ */ jsxs7(Text9, { children: [
2834
+ i === idx ? "\u25BA" : " ",
2835
+ " ",
2836
+ h.name,
2837
+ " ",
2838
+ /* @__PURE__ */ jsxs7(Text9, { color: "cyan", children: [
2839
+ "[Lvl. ",
2840
+ levelFromXp(h.xp),
2841
+ "]"
2842
+ ] }),
2843
+ " ",
2844
+ /* @__PURE__ */ jsxs7(Text9, { color: "yellow", children: [
2845
+ "\u2014 ",
2846
+ h.pending,
2847
+ " roll",
2848
+ h.pending === 1 ? "" : "s"
2849
+ ] })
2850
+ ] }) }),
2851
+ /* @__PURE__ */ jsxs7(Box9, { flexDirection: "row", children: [
2852
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
2853
+ /* @__PURE__ */ jsx9(HorseSprite, { sprite: MINI_SPRITE, colors: h.colors })
2854
+ ] })
2855
+ ] }, h.stable_horse_id)),
2856
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2191/\u2193 choose \xB7 Enter pick \xB7 Esc cancel" }) })
2857
+ ] });
2858
+ }
2859
+
2860
+ // src/ui/prompt.ts
2861
+ async function promptYesNo(question) {
2862
+ resetStdinAfterInk();
2863
+ const readline6 = await import("readline/promises");
2864
+ const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
2865
+ const a = (await rl.question(question)).trim().toLowerCase();
2866
+ rl.close();
2867
+ return a === "" || a === "y" || a === "yes";
2868
+ }
2869
+ function resetStdinAfterInk() {
2870
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
2871
+ process.stdin.setRawMode(false);
2872
+ }
2873
+ while (process.stdin.read() !== null) {
2874
+ }
2875
+ process.stdin.pause();
2876
+ process.stdin.ref();
2877
+ }
2878
+
2879
+ // src/ui/reveal.ts
2735
2880
  import React13 from "react";
2736
2881
  import { render as render5 } from "ink";
2737
2882
 
2738
2883
  // src/ui/RollReveal.tsx
2739
- import { useState as useState7, useEffect as useEffect4, useMemo } from "react";
2740
- import { Box as Box10, Text as Text10 } from "ink";
2884
+ import { useState as useState8, useEffect as useEffect4, useMemo } from "react";
2885
+ import { Box as Box11, Text as Text11 } from "ink";
2741
2886
 
2742
2887
  // src/ui/HatSprite.tsx
2743
- import { useEffect as useEffect3, useState as useState6 } from "react";
2744
- import { Box as Box9, Text as Text9 } from "ink";
2745
- import { jsx as jsx9 } from "react/jsx-runtime";
2888
+ import { useEffect as useEffect3, useState as useState7 } from "react";
2889
+ import { Box as Box10, Text as Text10 } from "ink";
2890
+ import { jsx as jsx10 } from "react/jsx-runtime";
2746
2891
  function HatSprite({ hat, variant, centerIn }) {
2747
2892
  const colors = hatColorsFor2(hat, variant ?? 0);
2748
2893
  const grid = makeHatGrid(hat, colors, centerIn);
2749
2894
  const lines = hexGridToHalfBlocks(grid);
2750
- return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx9(Text9, { children: line }, i)) });
2895
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx10(Text10, { children: line }, i)) });
2751
2896
  }
2752
2897
  function AnimatedHatSprite({ hat, variant, centerIn }) {
2753
2898
  if (hat.rarity !== "legendary") {
2754
- return /* @__PURE__ */ jsx9(HatSprite, { hat, variant, centerIn });
2899
+ return /* @__PURE__ */ jsx10(HatSprite, { hat, variant, centerIn });
2755
2900
  }
2756
2901
  const frames = hat.animation.frames;
2757
2902
  const fps = hat.animation.fps;
2758
- const [idx, setIdx] = useState6(0);
2903
+ const [idx, setIdx] = useState7(0);
2759
2904
  useEffect3(() => {
2760
2905
  if (frames.length <= 1) return;
2761
2906
  const interval = setInterval(
@@ -2765,7 +2910,7 @@ function AnimatedHatSprite({ hat, variant, centerIn }) {
2765
2910
  return () => clearInterval(interval);
2766
2911
  }, [frames.length, fps]);
2767
2912
  const framed = { ...hat, colors: { ...hat.colors, A: frames[idx] } };
2768
- return /* @__PURE__ */ jsx9(HatSprite, { hat: framed, variant, centerIn });
2913
+ return /* @__PURE__ */ jsx10(HatSprite, { hat: framed, variant, centerIn });
2769
2914
  }
2770
2915
  function hatColorsFor2(hat, variantIdx) {
2771
2916
  if (hat.rarity === "legendary") return hat.colors;
@@ -2792,7 +2937,7 @@ function makeHatGrid(hat, colors, centerIn) {
2792
2937
  }
2793
2938
 
2794
2939
  // src/ui/RollReveal.tsx
2795
- import { jsx as jsx10 } from "react/jsx-runtime";
2940
+ import { jsx as jsx11 } from "react/jsx-runtime";
2796
2941
  var RESET3 = "\x1B[0m";
2797
2942
  var BOX_COLOR = "#E5C76B";
2798
2943
  var TIER_PALETTE = {
@@ -2861,7 +3006,7 @@ var BOX_EMPTY = [
2861
3006
  ""
2862
3007
  ].map(pad);
2863
3008
  function GiftBox({ frame, color }) {
2864
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: frame.map((line, i) => /* @__PURE__ */ jsx10(Text10, { children: line ? ansiFg(color) + line + RESET3 : line }, i)) });
3009
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: frame.map((line, i) => /* @__PURE__ */ jsx11(Text11, { children: line ? ansiFg(color) + line + RESET3 : line }, i)) });
2865
3010
  }
2866
3011
  function spawnParticles(tier, count, cx, cy) {
2867
3012
  const palette = TIER_PALETTE[tier];
@@ -2884,7 +3029,7 @@ function ConfettiBurst({ tier }) {
2884
3029
  const cx = Math.floor(SCENE_W / 2);
2885
3030
  const cy = Math.floor(SCENE_H / 2);
2886
3031
  const particles = useMemo(() => spawnParticles(tier, 36, cx, cy), [tier, cx, cy]);
2887
- const [tick, setTick] = useState7(0);
3032
+ const [tick, setTick] = useState8(0);
2888
3033
  useEffect4(() => {
2889
3034
  const i = setInterval(() => setTick((t) => t + 1), 70);
2890
3035
  return () => clearInterval(i);
@@ -2897,13 +3042,13 @@ function ConfettiBurst({ tier }) {
2897
3042
  grid[y][x] = ansiFg(p.color) + p.char + RESET3;
2898
3043
  }
2899
3044
  }
2900
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: grid.map((row, y) => /* @__PURE__ */ jsx10(Text10, { children: row.join("") }, y)) });
3045
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: grid.map((row, y) => /* @__PURE__ */ jsx11(Text11, { children: row.join("") }, y)) });
2901
3046
  }
2902
3047
  var CLOSED_HOLD_MS = 3e3;
2903
3048
  function RollReveal({ outcome, onDone }) {
2904
3049
  const isNoHat = outcome.kind === "no_hat";
2905
3050
  const isLegendary = outcome.kind !== "no_hat" && outcome.hat.rarity === "legendary";
2906
- const [phase, setPhase] = useState7("closed");
3051
+ const [phase, setPhase] = useState8("closed");
2907
3052
  useEffect4(() => {
2908
3053
  const timers = [];
2909
3054
  timers.push(setTimeout(() => setPhase("open1"), CLOSED_HOLD_MS));
@@ -2918,95 +3063,18 @@ function RollReveal({ outcome, onDone }) {
2918
3063
  }
2919
3064
  return () => timers.forEach(clearTimeout);
2920
3065
  }, [isNoHat, isLegendary, onDone]);
2921
- if (phase === "closed") return /* @__PURE__ */ jsx10(GiftBox, { frame: BOX_CLOSED, color: BOX_COLOR });
2922
- if (phase === "open1") return /* @__PURE__ */ jsx10(GiftBox, { frame: BOX_OPENING_1, color: BOX_COLOR });
2923
- if (phase === "open2") return /* @__PURE__ */ jsx10(GiftBox, { frame: BOX_OPENING_2, color: BOX_COLOR });
2924
- if (phase === "empty") return /* @__PURE__ */ jsx10(GiftBox, { frame: BOX_EMPTY, color: BOX_COLOR });
3066
+ if (phase === "closed") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_CLOSED, color: BOX_COLOR });
3067
+ if (phase === "open1") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_OPENING_1, color: BOX_COLOR });
3068
+ if (phase === "open2") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_OPENING_2, color: BOX_COLOR });
3069
+ if (phase === "empty") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_EMPTY, color: BOX_COLOR });
2925
3070
  if (phase === "burst" && outcome.kind !== "no_hat") {
2926
- return /* @__PURE__ */ jsx10(ConfettiBurst, { tier: outcome.hat.rarity });
3071
+ return /* @__PURE__ */ jsx11(ConfettiBurst, { tier: outcome.hat.rarity });
2927
3072
  }
2928
- if (outcome.kind === "no_hat") return /* @__PURE__ */ jsx10(GiftBox, { frame: BOX_EMPTY, color: BOX_COLOR });
2929
- return outcome.hat.rarity === "legendary" ? /* @__PURE__ */ jsx10(AnimatedHatSprite, { hat: outcome.hat, centerIn: { w: SCENE_W, h: SCENE_H } }) : /* @__PURE__ */ jsx10(HatSprite, { hat: outcome.hat, variant: outcome.variant, centerIn: { w: SCENE_W, h: SCENE_H } });
2930
- }
2931
-
2932
- // src/ui/RollHorsePicker.tsx
2933
- import { useState as useState8 } from "react";
2934
- import { Box as Box11, Text as Text11, useInput as useInput4 } from "ink";
2935
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
2936
- function RollHorsePicker({ horses, onPick, onCancel }) {
2937
- const [idx, setIdx] = useState8(0);
2938
- useInput4((input, key) => {
2939
- if (key.escape) {
2940
- onCancel();
2941
- return;
2942
- }
2943
- if (horses.length === 0) return;
2944
- if (key.upArrow) {
2945
- setIdx((idx - 1 + horses.length) % horses.length);
2946
- return;
2947
- }
2948
- if (key.downArrow) {
2949
- setIdx((idx + 1) % horses.length);
2950
- return;
2951
- }
2952
- if (key.return) {
2953
- onPick(horses[idx]);
2954
- return;
2955
- }
2956
- });
2957
- return /* @__PURE__ */ jsxs7(Box11, { flexDirection: "column", children: [
2958
- /* @__PURE__ */ jsx11(Text11, { children: "Pick a horse to roll for:" }),
2959
- horses.map((h, i) => /* @__PURE__ */ jsxs7(Box11, { flexDirection: "column", children: [
2960
- /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", children: /* @__PURE__ */ jsxs7(Text11, { children: [
2961
- i === idx ? "\u25BA" : " ",
2962
- " ",
2963
- h.name,
2964
- " ",
2965
- /* @__PURE__ */ jsxs7(Text11, { color: "cyan", children: [
2966
- "[Lvl. ",
2967
- levelFromXp(h.xp),
2968
- "]"
2969
- ] }),
2970
- " ",
2971
- /* @__PURE__ */ jsxs7(Text11, { color: "yellow", children: [
2972
- "\u2014 ",
2973
- h.pending,
2974
- " roll",
2975
- h.pending === 1 ? "" : "s"
2976
- ] })
2977
- ] }) }),
2978
- /* @__PURE__ */ jsxs7(Box11, { flexDirection: "row", children: [
2979
- /* @__PURE__ */ jsx11(Text11, { children: " " }),
2980
- /* @__PURE__ */ jsx11(HorseSprite, { sprite: MINI_SPRITE, colors: h.colors })
2981
- ] })
2982
- ] }, h.stable_horse_id)),
2983
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "\u2191/\u2193 choose \xB7 Enter pick \xB7 Esc cancel" }) })
2984
- ] });
3073
+ if (outcome.kind === "no_hat") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_EMPTY, color: BOX_COLOR });
3074
+ return outcome.hat.rarity === "legendary" ? /* @__PURE__ */ jsx11(AnimatedHatSprite, { hat: outcome.hat, centerIn: { w: SCENE_W, h: SCENE_H } }) : /* @__PURE__ */ jsx11(HatSprite, { hat: outcome.hat, variant: outcome.variant, centerIn: { w: SCENE_W, h: SCENE_H } });
2985
3075
  }
2986
3076
 
2987
- // src/commands/roll.ts
2988
- function pendingFor(horse) {
2989
- const level = levelFromXp(horse.xp);
2990
- const lastRolled = horse.last_rolled_level ?? Math.max(1, level - 1);
2991
- return level - lastRolled;
2992
- }
2993
- async function promptYesNo(question) {
2994
- resetStdinAfterInk();
2995
- const readline6 = await import("readline/promises");
2996
- const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
2997
- const a = (await rl.question(question)).trim().toLowerCase();
2998
- rl.close();
2999
- return a === "" || a === "y" || a === "yes";
3000
- }
3001
- function resetStdinAfterInk() {
3002
- if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
3003
- process.stdin.setRawMode(false);
3004
- }
3005
- while (process.stdin.read() !== null) {
3006
- }
3007
- process.stdin.pause();
3008
- process.stdin.ref();
3009
- }
3077
+ // src/ui/reveal.ts
3010
3078
  async function runReveal(outcome) {
3011
3079
  await new Promise((resolve) => {
3012
3080
  const app = render5(React13.createElement(RollReveal, {
@@ -3018,6 +3086,13 @@ async function runReveal(outcome) {
3018
3086
  }));
3019
3087
  });
3020
3088
  }
3089
+
3090
+ // src/commands/roll.ts
3091
+ function pendingFor(horse) {
3092
+ const level = levelFromXp(horse.xp);
3093
+ const lastRolled = horse.last_rolled_level ?? Math.max(1, level - 1);
3094
+ return level - lastRolled;
3095
+ }
3021
3096
  async function rollCommand() {
3022
3097
  let stable;
3023
3098
  try {
@@ -3035,7 +3110,7 @@ async function rollCommand() {
3035
3110
  return 0;
3036
3111
  }
3037
3112
  const picked = await new Promise((resolve) => {
3038
- const app = render5(React13.createElement(RollHorsePicker, {
3113
+ const app = render6(React14.createElement(RollHorsePicker, {
3039
3114
  horses: eligible,
3040
3115
  onPick: (h) => {
3041
3116
  app.unmount();
@@ -3117,6 +3192,105 @@ No hat this time. +${result.xp_awarded} XP toward your next level.
3117
3192
  }
3118
3193
  }
3119
3194
 
3195
+ // src/commands/claim.ts
3196
+ import React15 from "react";
3197
+ import { render as render7 } from "ink";
3198
+ async function claimCommand(token) {
3199
+ if (!token) {
3200
+ console.error("Usage: token-derby claim <token>");
3201
+ return 2;
3202
+ }
3203
+ try {
3204
+ await probeClaim(token);
3205
+ } catch (e) {
3206
+ if (e instanceof ApiError) {
3207
+ console.error(`Error: ${e.code} ${e.message}`);
3208
+ return 1;
3209
+ }
3210
+ throw e;
3211
+ }
3212
+ let stable;
3213
+ try {
3214
+ stable = await listStable();
3215
+ } catch (e) {
3216
+ if (e instanceof ApiError) {
3217
+ console.error(`Error: ${e.code} ${e.message}`);
3218
+ return 1;
3219
+ }
3220
+ throw e;
3221
+ }
3222
+ if (stable.horses.length === 0) {
3223
+ console.error("No horses in your stable. Run `token-derby stable create` first.");
3224
+ return 1;
3225
+ }
3226
+ console.log("\n\u{1F381} A cosmetic has been awarded to you.\n");
3227
+ const picked = await new Promise((resolve) => {
3228
+ const app = render7(React15.createElement(HorsePicker, {
3229
+ horses: stable.horses,
3230
+ prompt: "Which horse should receive it?",
3231
+ onPick: (h) => {
3232
+ app.unmount();
3233
+ resolve(h);
3234
+ },
3235
+ onCancel: () => {
3236
+ app.unmount();
3237
+ resolve(null);
3238
+ }
3239
+ }));
3240
+ });
3241
+ if (!picked) {
3242
+ console.log("Cancelled. Your token is unspent.");
3243
+ return 0;
3244
+ }
3245
+ let result;
3246
+ try {
3247
+ result = await redeemClaim(token, { stable_horse_id: picked.stable_horse_id });
3248
+ } catch (e) {
3249
+ if (e instanceof ApiError) {
3250
+ console.error(`Error: ${e.code} ${e.message}`);
3251
+ return 1;
3252
+ }
3253
+ throw e;
3254
+ }
3255
+ const outcome = (() => {
3256
+ if (result.result === "hat") {
3257
+ const hat3 = hatById(result.collected.id);
3258
+ return hat3 ? { kind: "hat", hat: hat3, variant: result.collected.variant } : null;
3259
+ }
3260
+ const hat2 = hatById(result.hat_id);
3261
+ return hat2 ? { kind: "duplicate", hat: hat2, variant: result.variant } : null;
3262
+ })();
3263
+ if (!outcome) {
3264
+ console.error("Server returned an unknown hat id \u2014 catalog mismatch.");
3265
+ return 1;
3266
+ }
3267
+ await runReveal(outcome);
3268
+ if (result.result === "hat") {
3269
+ const hat2 = hatById(result.collected.id);
3270
+ const variantSuffix2 = hat2.rarity !== "legendary" && result.collected.variant !== void 0 ? ` #${result.collected.variant + 1}` : "";
3271
+ console.log(`
3272
+ \u2728 ${hat2.name}${variantSuffix2} [${hat2.rarity.toUpperCase()}]
3273
+ `);
3274
+ if (await promptYesNo("Equip now? [Y/n] ")) {
3275
+ try {
3276
+ await equipHat(picked.stable_horse_id, { hat_index: result.hat_index });
3277
+ console.log(`Equipped on ${picked.name}.`);
3278
+ } catch (e) {
3279
+ if (e instanceof ApiError) {
3280
+ console.error(`Equip failed: ${e.code} ${e.message}`);
3281
+ } else throw e;
3282
+ }
3283
+ }
3284
+ return 0;
3285
+ }
3286
+ const hat = hatById(result.hat_id);
3287
+ const variantSuffix = result.variant !== void 0 ? ` #${result.variant + 1}` : "";
3288
+ console.log(`
3289
+ ${picked.name} already has ${hat?.name ?? result.hat_id}${variantSuffix}. +${result.xp_awarded} XP.
3290
+ `);
3291
+ return 0;
3292
+ }
3293
+
3120
3294
  // src/commands/org-join.ts
3121
3295
  async function orgJoinCommand(token) {
3122
3296
  if (!token) {
@@ -3242,6 +3416,8 @@ Races:
3242
3416
  Cosmetics:
3243
3417
  token-derby roll Spend a pending roll to try for a hat.
3244
3418
  Earn rolls by leveling up horses.
3419
+ token-derby claim <token> Redeem a claim token for a cosmetic
3420
+ awarded to you by an admin.
3245
3421
 
3246
3422
  Environment:
3247
3423
  token-derby env Show the active environment (prod|staging)
@@ -3297,6 +3473,7 @@ async function main() {
3297
3473
  if (cmd === "join") return joinCommand(argv[1], argv.slice(2));
3298
3474
  if (cmd === "end") return endCommand(argv[1]);
3299
3475
  if (cmd === "roll") return rollCommand();
3476
+ if (cmd === "claim") return claimCommand(argv[1]);
3300
3477
  if (cmd === "web") return webCommand();
3301
3478
  console.error(`Unknown command: ${cmd}`);
3302
3479
  console.error(HELP);