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.
- package/package.json +1 -1
- package/src/display.mjs +151 -91
package/package.json
CHANGED
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 {
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
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
|
+
}
|