claude-buddy-reroll 1.9.0 → 1.9.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/display.mjs +151 -91
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-buddy-reroll",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "Speaki-styled npm terminal for buddy rerolls, dex replay, and SALT patching.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/display.mjs CHANGED
@@ -1,91 +1,151 @@
1
- // Display utilities for buddy cards
2
-
3
- import { RARITY_STARS, STAT_FLOORS } from './engine.mjs';
4
- import { renderSprite } from './sprites.mjs';
5
- import { formatEye, formatShinyTag, formatStars, isAsciiOnlyTerminal, toTerminalSafeText } from './terminal.mjs';
6
- import { renderStatBars } from './stat-bars.mjs';
7
-
8
- const RARITY_STYLE = {
9
- common: { border: '─', color: '\x1b[37m', label: '\x1b[37m', badge: 'COMMON' },
10
- uncommon: { border: '═', color: '\x1b[32m', label: '\x1b[32m', badge: 'UNCOMMON' },
11
- rare: { border: '━', color: '\x1b[36m', label: '\x1b[36m', badge: 'RARE' },
12
- epic: { border: '▓', color: '\x1b[35m', label: '\x1b[35m', badge: 'EPIC' },
13
- legendary: { border: '█', color: '\x1b[33m', label: '\x1b[33;1m', badge: 'LEGENDARY' },
14
- };
15
-
16
- const ASCII_BORDER = {
17
- common: '-',
18
- uncommon: '=',
19
- rare: '=',
20
- epic: '#',
21
- legendary: '#',
22
- };
23
-
24
- const RESET = '\x1b[0m';
25
- const DIM = '\x1b[2m';
26
- const BOLD = '\x1b[1m';
27
- const GRAY = '\x1b[90m';
28
-
29
- function statBar(value, max = 100, color = '') {
30
- const width = 14;
31
- const filled = Math.round((value / max) * width);
32
- return `${color}${'█'.repeat(filled)}${GRAY}${'░'.repeat(width - filled)}${RESET} ${String(value).padStart(3)}`;
33
- }
34
-
35
- export function renderCard(result, { showSalt = false, index = null, frame = 0 } = {}) {
36
- const { bones, inspirationSeed } = result;
37
- const { rarity, species, eye, hat, shiny, stats } = bones;
38
- const style = RARITY_STYLE[rarity];
39
- const borderChar = isAsciiOnlyTerminal() ? ASCII_BORDER[rarity] : style.border;
40
- const stars = formatStars(RARITY_STARS[rarity]);
41
- const shinyTag = shiny ? formatShinyTag(' ✨ SHINY!') : '';
42
- const sprite = toTerminalSafeText(renderSprite(species, formatEye(eye), hat, frame));
43
- const rarityBadge = `${style.label}[ ${style.badge} ]${RESET}`;
44
-
45
- const header = index !== null ? `#${index + 1} ` : '';
46
- const saltLine = showSalt && result.salt ? `${DIM}salt: ${result.salt}${RESET}` : '';
47
-
48
- const lines = [
49
- '',
50
- `${style.color}${borderChar.repeat(36)}${RESET}`,
51
- ` ${rarityBadge}`,
52
- `${style.color} ${header}${BOLD}${species.toUpperCase()}${RESET}${style.color} ${stars}${shinyTag}${RESET}`,
53
- `${style.color}${borderChar.repeat(36)}${RESET}`,
54
- '',
55
- ...sprite.split('\n').map((line) => ` ${line}`),
56
- '',
57
- ` ${DIM}Eye:${RESET} ${formatEye(eye)} ${DIM}Hat:${RESET} ${toTerminalSafeText(hat === 'none' ? '(none)' : hat)}`,
58
- '',
59
- ` ${DIM}Stats:${RESET}`,
60
- ];
61
-
62
- const statBarsBlock = renderStatBars(stats, rarity);
63
- if (statBarsBlock) {
64
- for (const line of statBarsBlock.split('\n')) {
65
- lines.push(toTerminalSafeText(line));
66
- }
67
- }
68
-
69
- lines.push('');
70
- if (saltLine) lines.push(` ${saltLine}`);
71
- lines.push(`${style.color}${borderChar.repeat(36)}${RESET}`);
72
- lines.push('');
73
-
74
- return lines.join('\n');
75
- }
76
-
77
- export function renderMiniCard(result, index) {
78
- const { bones } = result;
79
- const { rarity, species, eye, shiny } = bones;
80
- const style = RARITY_STYLE[rarity];
81
- const stars = formatStars(RARITY_STARS[rarity]);
82
- const badge = style.badge.padEnd(9);
83
- const shinyTag = shiny
84
- ? (isAsciiOnlyTerminal() ? '[SHINY]' : formatShinyTag('✨'))
85
- : '';
86
-
87
- const idx = String(index + 1).padStart(2, ' ');
88
- return `${style.color}${idx}. ${formatEye(eye)} ${species.padEnd(10)} ${stars.padEnd(5)} ${badge} ${shinyTag.padEnd(8)}${RESET}`;
89
- }
90
-
91
- // Dex rendering is handled directly in cli.mjs
1
+ // Display utilities for buddy cards — v2 redesign
2
+
3
+ import { RARITY_STARS, STAT_FLOORS, STATS } from './engine.mjs';
4
+ import { renderSprite } from './sprites.mjs';
5
+ import { formatEye, formatShinyTag, formatStars, isAsciiOnlyTerminal, toTerminalSafeText } from './terminal.mjs';
6
+ import { padAnsiEnd, visibleLength } from './ansi.mjs';
7
+
8
+ const RESET = '\x1b[0m';
9
+ const DIM = '\x1b[2m';
10
+ const BOLD = '\x1b[1m';
11
+ const GRAY = '\x1b[90m';
12
+ const ITALIC = '\x1b[3m';
13
+ const BG_RESET = '\x1b[49m';
14
+
15
+ const RARITY_STYLE = {
16
+ common: { color: '\x1b[37m', label: '\x1b[37m', bg: '', badge: 'COMMON', accent: '\x1b[90m', tl: '┌', tr: '┐', bl: '└', br: '┘', h: '─', v: '│' },
17
+ uncommon: { color: '\x1b[32m', label: '\x1b[32m', bg: '', badge: 'UNCOMMON', accent: '\x1b[32m', tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' },
18
+ rare: { color: '\x1b[36m', label: '\x1b[36m', bg: '', badge: 'RARE', accent: '\x1b[36m', tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║' },
19
+ epic: { color: '\x1b[35m', label: '\x1b[35m', bg: '\x1b[48;5;53m', badge: 'EPIC', accent: '\x1b[35;1m', tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║' },
20
+ legendary: { color: '\x1b[33;1m', label: '\x1b[33;1m', bg: '\x1b[48;5;58m', badge: 'LEGENDARY', accent: '\x1b[33;1m', tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║' },
21
+ };
22
+
23
+ const ASCII_STYLE = {
24
+ common: { tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|' },
25
+ uncommon: { tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|' },
26
+ rare: { tl: '+', tr: '+', bl: '+', br: '+', h: '=', v: '|' },
27
+ epic: { tl: '#', tr: '#', bl: '#', br: '#', h: '=', v: '#' },
28
+ legendary: { tl: '#', tr: '#', bl: '#', br: '#', h: '#', v: '#' },
29
+ };
30
+
31
+ const CARD_WIDTH = 38;
32
+ const INNER = CARD_WIDTH - 4; // content area (minus border + padding)
33
+
34
+ function box(style, ascii) {
35
+ const s = ascii ? { ...style, ...ASCII_STYLE[Object.keys(RARITY_STYLE).find(k => RARITY_STYLE[k] === style)] } : style;
36
+ return {
37
+ top: `${style.color}${s.tl}${s.h.repeat(CARD_WIDTH - 2)}${s.tr}${RESET}`,
38
+ bottom: `${style.color}${s.bl}${s.h.repeat(CARD_WIDTH - 2)}${s.br}${RESET}`,
39
+ mid: `${style.color}${s.v}${s.h.repeat(CARD_WIDTH - 2)}${s.v}${RESET}`,
40
+ row: (content) => {
41
+ const padded = padAnsiEnd(content, CARD_WIDTH - 4);
42
+ return `${style.color}${s.v}${RESET} ${padded} ${style.color}${s.v}${RESET}`;
43
+ },
44
+ empty: () => `${style.color}${s.v}${RESET}${' '.repeat(CARD_WIDTH - 2)}${style.color}${s.v}${RESET}`,
45
+ };
46
+ }
47
+
48
+ function centerPad(text, width) {
49
+ const vis = visibleLength(text);
50
+ if (vis >= width) return text;
51
+ const left = Math.floor((width - vis) / 2);
52
+ const right = width - vis - left;
53
+ return ' '.repeat(left) + text + ' '.repeat(right);
54
+ }
55
+
56
+ function statBar(value, color) {
57
+ const width = 12;
58
+ const filled = Math.round((value / 100) * width);
59
+ const bar = `${color}${'█'.repeat(filled)}${GRAY}${'░'.repeat(width - filled)}${RESET}`;
60
+ return bar;
61
+ }
62
+
63
+ export function renderCard(result, { showSalt = false, index = null, frame = 0 } = {}) {
64
+ const { bones } = result;
65
+ const { rarity, species, eye, hat, shiny, stats } = bones;
66
+ const style = RARITY_STYLE[rarity];
67
+ const ascii = isAsciiOnlyTerminal();
68
+ const b = box(style, ascii);
69
+ const stars = formatStars(RARITY_STARS[rarity]);
70
+ const shinyTag = shiny ? formatShinyTag(' ✨') : '';
71
+ const sprite = toTerminalSafeText(renderSprite(species, formatEye(eye), hat, frame));
72
+ const floor = STAT_FLOORS[rarity] || 5;
73
+
74
+ const header = index !== null ? `#${index + 1} ` : '';
75
+ const badgeText = `${style.accent}${BOLD}[ ${style.badge} ]${RESET}`;
76
+ const nameText = `${style.label}${BOLD}${header}${species.toUpperCase()}${RESET} ${style.color}${stars}${shinyTag}${RESET}`;
77
+
78
+ const lines = [];
79
+ lines.push('');
80
+ lines.push(b.top);
81
+
82
+ // Badge row (centered)
83
+ lines.push(b.row(centerPad(badgeText, INNER)));
84
+
85
+ // Name + stars row (centered)
86
+ lines.push(b.row(centerPad(nameText, INNER)));
87
+
88
+ // Separator
89
+ lines.push(b.mid);
90
+
91
+ // Sprite (centered)
92
+ const spriteLines = sprite.split('\n');
93
+ for (const sl of spriteLines) {
94
+ lines.push(b.row(centerPad(`${style.color}${sl}${RESET}`, INNER)));
95
+ }
96
+
97
+ // Separator
98
+ lines.push(b.mid);
99
+
100
+ // Traits row — compact
101
+ const eyeStr = formatEye(eye);
102
+ const hatStr = toTerminalSafeText(hat === 'none' ? '—' : hat);
103
+ lines.push(b.row(`${DIM}Eye${RESET} ${eyeStr} ${DIM}Hat${RESET} ${hatStr} ${DIM}Shiny${RESET} ${shiny ? `${style.accent}YES${RESET}` : `${GRAY}no${RESET}`}`));
104
+
105
+ // Separator
106
+ lines.push(b.mid);
107
+
108
+ // Stats — compact bars with value
109
+ if (stats && Object.keys(stats).length > 0) {
110
+ for (const name of STATS) {
111
+ if (stats[name] === undefined) continue;
112
+ const val = stats[name];
113
+ const isHigh = val >= floor + 40;
114
+ const isLow = val <= floor;
115
+
116
+ const label = name.slice(0, 5).padEnd(5);
117
+ const bar = statBar(val, style.color);
118
+ const valStr = String(val).padStart(3);
119
+ const marker = isHigh ? `${style.accent}▲${RESET}` : isLow ? `\x1b[31m▼${RESET}` : ' ';
120
+
121
+ lines.push(b.row(`${GRAY}${label}${RESET} ${bar} ${valStr} ${marker}`));
122
+ }
123
+ }
124
+
125
+ // Salt line
126
+ if (showSalt && result.salt) {
127
+ lines.push(b.mid);
128
+ lines.push(b.row(`${DIM}${ITALIC}${result.salt}${RESET}`));
129
+ }
130
+
131
+ lines.push(b.bottom);
132
+ lines.push('');
133
+
134
+ return lines.join('\n');
135
+ }
136
+
137
+ export function renderMiniCard(result, index) {
138
+ const { bones } = result;
139
+ const { rarity, species, eye, hat, shiny } = bones;
140
+ const style = RARITY_STYLE[rarity];
141
+ const stars = formatStars(RARITY_STARS[rarity]);
142
+ const shinyTag = shiny
143
+ ? (isAsciiOnlyTerminal() ? ' [S]' : ` ${formatShinyTag('✨')}`)
144
+ : '';
145
+
146
+ const idx = String(index + 1).padStart(2, ' ');
147
+ const badge = style.badge.slice(0, 3);
148
+ const hatTag = hat !== 'none' ? ` ${DIM}+${toTerminalSafeText(hat)}${RESET}` : '';
149
+
150
+ return `${style.color}${idx}. ${formatEye(eye)} ${BOLD}${species.padEnd(10)}${RESET}${style.color} ${stars.padEnd(5)} ${badge}${shinyTag}${hatTag}${RESET}`;
151
+ }