clawmate 1.0.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.
@@ -0,0 +1,116 @@
1
+ /**
2
+ * 말풍선 시스템
3
+ * - 타자기 효과 (30ms/글자)
4
+ * - 5초 유지 → 페이드 아웃
5
+ * - 모드별 스타일: Pet(둥글고 빨간 테두리) / Incarnation(각지고 틸 발광)
6
+ */
7
+ const Speech = (() => {
8
+ const CHAR_DELAY = 30;
9
+ const DISPLAY_TIME = 5000;
10
+ const FADE_TIME = 500;
11
+
12
+ let currentBubble = null;
13
+ let typeTimer = null;
14
+ let hideTimer = null;
15
+ let mode = 'pet';
16
+
17
+ function setMode(m) {
18
+ mode = m;
19
+ }
20
+
21
+ function show(text) {
22
+ hide(); // 기존 말풍선 제거
23
+
24
+ const container = document.getElementById('speech-container');
25
+ const pos = PetEngine.getPosition();
26
+
27
+ const bubble = document.createElement('div');
28
+ bubble.className = `speech-bubble speech-${mode}`;
29
+
30
+ // 말풍선 위치 (캐릭터 위)
31
+ bubble.style.left = (pos.x - 30) + 'px';
32
+ bubble.style.top = (pos.y - 60) + 'px';
33
+
34
+ const textEl = document.createElement('span');
35
+ textEl.className = 'speech-text';
36
+ bubble.appendChild(textEl);
37
+
38
+ container.appendChild(bubble);
39
+ currentBubble = bubble;
40
+
41
+ // 타자기 효과
42
+ let charIndex = 0;
43
+ typeTimer = setInterval(() => {
44
+ if (charIndex < text.length) {
45
+ textEl.textContent += text[charIndex];
46
+ charIndex++;
47
+ } else {
48
+ clearInterval(typeTimer);
49
+ typeTimer = null;
50
+ }
51
+ }, CHAR_DELAY);
52
+
53
+ // 자동 숨김
54
+ const totalTypeTime = text.length * CHAR_DELAY;
55
+ hideTimer = setTimeout(() => {
56
+ if (currentBubble) {
57
+ currentBubble.classList.add('speech-fade');
58
+ setTimeout(() => hide(), FADE_TIME);
59
+ }
60
+ }, totalTypeTime + DISPLAY_TIME);
61
+ }
62
+
63
+ function hide() {
64
+ if (typeTimer) { clearInterval(typeTimer); typeTimer = null; }
65
+ if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }
66
+ if (currentBubble) {
67
+ currentBubble.remove();
68
+ currentBubble = null;
69
+ }
70
+ }
71
+
72
+ function updatePosition() {
73
+ if (!currentBubble) return;
74
+ const pos = PetEngine.getPosition();
75
+ currentBubble.style.left = (pos.x - 30) + 'px';
76
+ currentBubble.style.top = (pos.y - 60) + 'px';
77
+ }
78
+
79
+ // --- 메시지 선택 헬퍼 ---
80
+ function randomFrom(arr) {
81
+ return arr[Math.floor(Math.random() * arr.length)];
82
+ }
83
+
84
+ function getReactionMessage() {
85
+ const msgs = window._messages;
86
+ if (!msgs) return '...';
87
+ return randomFrom(msgs.reactions[mode] || msgs.reactions.pet);
88
+ }
89
+
90
+ function getGreetingMessage() {
91
+ const msgs = window._messages;
92
+ if (!msgs) return '...';
93
+ const hour = new Date().getHours();
94
+ if (hour >= 6 && hour < 12) return randomFrom(msgs.greetings.morning);
95
+ if (hour >= 12 && hour < 18) return randomFrom(msgs.greetings.afternoon);
96
+ if (hour >= 18 && hour < 23) return randomFrom(msgs.greetings.evening);
97
+ return randomFrom(msgs.greetings.night);
98
+ }
99
+
100
+ function getTipMessage() {
101
+ const msgs = window._messages;
102
+ if (!msgs) return '';
103
+ return randomFrom(msgs.tips || []);
104
+ }
105
+
106
+ function getMilestoneMessage(milestone) {
107
+ const msgs = window._messages;
108
+ if (!msgs || !msgs.milestones) return '';
109
+ return msgs.milestones[milestone] || '';
110
+ }
111
+
112
+ return {
113
+ show, hide, updatePosition, setMode,
114
+ getReactionMessage, getGreetingMessage, getTipMessage, getMilestoneMessage,
115
+ };
116
+ })();
@@ -0,0 +1,175 @@
1
+ /**
2
+ * 펫 행동 유한 상태 머신 (FSM)
3
+ *
4
+ * 상태 전이도:
5
+ * IDLE → WALKING → CLIMBING_UP → CEILING_WALK
6
+ * IDLE ← PLAYING ← CLIMBING_DOWN ← (천장/벽)
7
+ * IDLE → SLEEPING (23:00~06:00)
8
+ * 인터럽트: 클릭→INTERACTING, 커서→SCARED/EXCITED
9
+ */
10
+ const StateMachine = (() => {
11
+ const STATES = {
12
+ IDLE: 'idle',
13
+ WALKING: 'walking',
14
+ CLIMBING_UP: 'climbing_up',
15
+ CLIMBING_DOWN: 'climbing_down',
16
+ CEILING_WALK: 'ceiling_walk',
17
+ SLEEPING: 'sleeping',
18
+ CARRYING: 'carrying',
19
+ PLAYING: 'playing',
20
+ INTERACTING: 'interacting',
21
+ SCARED: 'scared',
22
+ EXCITED: 'excited',
23
+ };
24
+
25
+ // 각 상태의 최소/최대 지속 시간(ms)
26
+ const DURATIONS = {
27
+ [STATES.IDLE]: { min: 2000, max: 5000 },
28
+ [STATES.WALKING]: { min: 3000, max: 8000 },
29
+ [STATES.CLIMBING_UP]: { min: 2000, max: 4000 },
30
+ [STATES.CLIMBING_DOWN]: { min: 1500, max: 3000 },
31
+ [STATES.CEILING_WALK]: { min: 2000, max: 5000 },
32
+ [STATES.SLEEPING]: { min: 10000, max: 30000 },
33
+ [STATES.CARRYING]: { min: 4000, max: 8000 },
34
+ [STATES.PLAYING]: { min: 3000, max: 6000 },
35
+ [STATES.INTERACTING]: { min: 1500, max: 3000 },
36
+ [STATES.SCARED]: { min: 1000, max: 2000 },
37
+ [STATES.EXCITED]: { min: 1500, max: 3000 },
38
+ };
39
+
40
+ let currentState = STATES.IDLE;
41
+ let stateStartTime = Date.now();
42
+ let stateDuration = 3000;
43
+ let personality = null;
44
+ let onStateChange = null;
45
+
46
+ // 기본 전이 확률 (personality로 조정됨)
47
+ const BASE_TRANSITIONS = {
48
+ [STATES.IDLE]: [
49
+ { state: STATES.WALKING, weight: 0.5 },
50
+ { state: STATES.PLAYING, weight: 0.2 },
51
+ { state: STATES.IDLE, weight: 0.3 },
52
+ ],
53
+ [STATES.WALKING]: [
54
+ { state: STATES.IDLE, weight: 0.3 },
55
+ { state: STATES.CLIMBING_UP, weight: 0.25 },
56
+ { state: STATES.WALKING, weight: 0.25 },
57
+ { state: STATES.PLAYING, weight: 0.2 },
58
+ ],
59
+ [STATES.CLIMBING_UP]: [
60
+ { state: STATES.CEILING_WALK, weight: 0.5 },
61
+ { state: STATES.CLIMBING_DOWN, weight: 0.5 },
62
+ ],
63
+ [STATES.CEILING_WALK]: [
64
+ { state: STATES.CLIMBING_DOWN, weight: 0.6 },
65
+ { state: STATES.CEILING_WALK, weight: 0.4 },
66
+ ],
67
+ [STATES.CLIMBING_DOWN]: [
68
+ { state: STATES.WALKING, weight: 0.5 },
69
+ { state: STATES.IDLE, weight: 0.5 },
70
+ ],
71
+ [STATES.PLAYING]: [
72
+ { state: STATES.IDLE, weight: 0.5 },
73
+ { state: STATES.WALKING, weight: 0.5 },
74
+ ],
75
+ [STATES.INTERACTING]: [
76
+ { state: STATES.IDLE, weight: 0.5 },
77
+ { state: STATES.EXCITED, weight: 0.3 },
78
+ { state: STATES.PLAYING, weight: 0.2 },
79
+ ],
80
+ [STATES.SCARED]: [
81
+ { state: STATES.WALKING, weight: 0.7 },
82
+ { state: STATES.IDLE, weight: 0.3 },
83
+ ],
84
+ [STATES.EXCITED]: [
85
+ { state: STATES.PLAYING, weight: 0.4 },
86
+ { state: STATES.IDLE, weight: 0.3 },
87
+ { state: STATES.WALKING, weight: 0.3 },
88
+ ],
89
+ [STATES.SLEEPING]: [
90
+ { state: STATES.IDLE, weight: 1.0 },
91
+ ],
92
+ [STATES.CARRYING]: [
93
+ { state: STATES.IDLE, weight: 0.5 },
94
+ { state: STATES.WALKING, weight: 0.5 },
95
+ ],
96
+ };
97
+
98
+ function setPersonality(p) {
99
+ personality = p;
100
+ }
101
+
102
+ function setOnStateChange(cb) {
103
+ onStateChange = cb;
104
+ }
105
+
106
+ function randomDuration(state) {
107
+ const d = DURATIONS[state] || DURATIONS[STATES.IDLE];
108
+ return d.min + Math.random() * (d.max - d.min);
109
+ }
110
+
111
+ function weightedRandom(transitions) {
112
+ const total = transitions.reduce((sum, t) => sum + t.weight, 0);
113
+ let r = Math.random() * total;
114
+ for (const t of transitions) {
115
+ r -= t.weight;
116
+ if (r <= 0) return t.state;
117
+ }
118
+ return transitions[transitions.length - 1].state;
119
+ }
120
+
121
+ function transition(forceState) {
122
+ const prevState = currentState;
123
+
124
+ if (forceState) {
125
+ currentState = forceState;
126
+ } else {
127
+ // 수면 시간 확인 (23:00~06:00)
128
+ const hour = new Date().getHours();
129
+ if (hour >= 23 || hour < 6) {
130
+ if (currentState !== STATES.SLEEPING && Math.random() < 0.3) {
131
+ currentState = STATES.SLEEPING;
132
+ stateDuration = randomDuration(STATES.SLEEPING);
133
+ stateStartTime = Date.now();
134
+ if (onStateChange) onStateChange(prevState, currentState);
135
+ return currentState;
136
+ }
137
+ }
138
+
139
+ const transitions = BASE_TRANSITIONS[currentState] || BASE_TRANSITIONS[STATES.IDLE];
140
+ currentState = weightedRandom(transitions);
141
+ }
142
+
143
+ stateDuration = randomDuration(currentState);
144
+ stateStartTime = Date.now();
145
+ if (onStateChange && prevState !== currentState) {
146
+ onStateChange(prevState, currentState);
147
+ }
148
+ return currentState;
149
+ }
150
+
151
+ function update() {
152
+ const elapsed = Date.now() - stateStartTime;
153
+ if (elapsed >= stateDuration) {
154
+ transition();
155
+ }
156
+ return currentState;
157
+ }
158
+
159
+ function forceState(state) {
160
+ transition(state);
161
+ }
162
+
163
+ function getState() {
164
+ return currentState;
165
+ }
166
+
167
+ function getElapsed() {
168
+ return Date.now() - stateStartTime;
169
+ }
170
+
171
+ return {
172
+ STATES, update, transition, forceState, getState, getElapsed,
173
+ setPersonality, setOnStateChange, randomDuration,
174
+ };
175
+ })();
@@ -0,0 +1,93 @@
1
+ /**
2
+ * 시간대별 행동 변화 시스템
3
+ * - 아침 인사, 점심 알림, 밤엔 잠자기
4
+ * - 랜덤 혼잣말 (idle chatter)
5
+ * - 팁 메시지 랜덤 출현
6
+ */
7
+ const TimeAware = (() => {
8
+ let lastGreetingHour = -1;
9
+ let lastChatterTime = 0;
10
+ let lastTipTime = 0;
11
+ const CHATTER_COOLDOWN = 60000; // 1분 최소 간격
12
+ const TIP_COOLDOWN = 5 * 60000; // 5분 최소 간격
13
+ let chatterChance = 0.15;
14
+
15
+ function init() {
16
+ // 시작 시 인사
17
+ showTimeGreeting();
18
+ // 주기적 체크
19
+ setInterval(tick, 30000); // 30초마다
20
+ }
21
+
22
+ function setChatterChance(chance) {
23
+ chatterChance = chance;
24
+ }
25
+
26
+ function tick() {
27
+ const hour = new Date().getHours();
28
+ const now = Date.now();
29
+
30
+ // 시간대 변경 감지 → 인사
31
+ if (hour !== lastGreetingHour && [6, 12, 18, 23].includes(hour)) {
32
+ showTimeGreeting();
33
+ lastGreetingHour = hour;
34
+ }
35
+
36
+ // 수면 시간 체크 (23:00~06:00)
37
+ if (hour >= 23 || hour < 6) {
38
+ const state = StateMachine.getState();
39
+ if (state !== 'sleeping' && state !== 'interacting') {
40
+ if (Math.random() < 0.2) {
41
+ StateMachine.forceState('sleeping');
42
+ showSleepEffect();
43
+ }
44
+ }
45
+ }
46
+
47
+ // idle 상태일 때 혼잣말
48
+ const state = StateMachine.getState();
49
+ if (state === 'idle' && now - lastChatterTime > CHATTER_COOLDOWN) {
50
+ if (Math.random() < chatterChance) {
51
+ const msgs = window._messages;
52
+ if (msgs && msgs.idle_chatter) {
53
+ const msg = msgs.idle_chatter[Math.floor(Math.random() * msgs.idle_chatter.length)];
54
+ Speech.show(msg);
55
+ lastChatterTime = now;
56
+ }
57
+ }
58
+ }
59
+
60
+ // 팁 메시지 (더 드물게)
61
+ if (now - lastTipTime > TIP_COOLDOWN && Math.random() < 0.05) {
62
+ const tip = Speech.getTipMessage();
63
+ if (tip) {
64
+ Speech.show(tip);
65
+ lastTipTime = now;
66
+ }
67
+ }
68
+ }
69
+
70
+ function showTimeGreeting() {
71
+ const msg = Speech.getGreetingMessage();
72
+ if (msg) Speech.show(msg);
73
+ lastGreetingHour = new Date().getHours();
74
+ }
75
+
76
+ function showSleepEffect() {
77
+ const pet = document.getElementById('pet-container');
78
+ // Z-z-z 이펙트 추가
79
+ for (let i = 0; i < 3; i++) {
80
+ const z = document.createElement('div');
81
+ z.className = 'sleep-z';
82
+ z.textContent = 'z';
83
+ z.style.animationDelay = (i * 0.5) + 's';
84
+ pet.appendChild(z);
85
+ }
86
+ // 5초 후 제거
87
+ setTimeout(() => {
88
+ pet.querySelectorAll('.sleep-z').forEach(el => el.remove());
89
+ }, 5000);
90
+ }
91
+
92
+ return { init, setChatterChance, tick };
93
+ })();
@@ -0,0 +1,58 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>ClawMate Launcher</title>
6
+ <link rel="stylesheet" href="css/launcher.css">
7
+ </head>
8
+ <body>
9
+ <div class="launcher">
10
+ <div class="launcher-header">
11
+ <div class="logo">
12
+ <span class="claw-icon">\u{1F99E}</span>
13
+ </div>
14
+ <h1>ClawMate</h1>
15
+ <p class="subtitle">데스크톱 위의 살아있는 Claw</p>
16
+ </div>
17
+
18
+ <div class="launcher-body">
19
+ <div class="mode-card" id="mode-pet" onclick="selectMode('pet')">
20
+ <div class="mode-icon">\u{1F990}</div>
21
+ <h2>Clawby</h2>
22
+ <p>귀여운 동반자</p>
23
+ <span class="mode-desc">장난기 넘치는 꼬마 랍스터가 바탕화면을 돌아다녀요!</span>
24
+ </div>
25
+
26
+ <div class="mode-card" id="mode-incarnation" onclick="selectMode('incarnation')">
27
+ <div class="mode-icon">\u{1F980}</div>
28
+ <h2>OpenClaw</h2>
29
+ <p>육체를 얻은 존재</p>
30
+ <span class="mode-desc">침착하고 대담한 OpenClaw이 바탕화면에 강림합니다.</span>
31
+ </div>
32
+ </div>
33
+
34
+ <button class="start-btn" id="start-btn" disabled onclick="startPet()">
35
+ 모드를 선택해주세요
36
+ </button>
37
+ </div>
38
+
39
+ <script>
40
+ let selectedMode = null;
41
+
42
+ function selectMode(mode) {
43
+ selectedMode = mode;
44
+ document.querySelectorAll('.mode-card').forEach(c => c.classList.remove('selected'));
45
+ document.getElementById('mode-' + mode).classList.add('selected');
46
+ const btn = document.getElementById('start-btn');
47
+ btn.disabled = false;
48
+ btn.textContent = mode === 'pet' ? 'Clawby 시작!' : 'OpenClaw 각성!';
49
+ }
50
+
51
+ async function startPet() {
52
+ if (!selectedMode) return;
53
+ await window.clawmate.setMode(selectedMode);
54
+ window.close();
55
+ }
56
+ </script>
57
+ </body>
58
+ </html>
@@ -0,0 +1,80 @@
1
+ // 캐릭터 크기 (16x16 픽셀 → 4배 확대)
2
+ const PIXEL_SIZE = 4;
3
+ const GRID_SIZE = 16;
4
+ const CHAR_SIZE = PIXEL_SIZE * GRID_SIZE; // 64px
5
+
6
+ // 이동 속도 (px/frame)
7
+ const BASE_SPEED = 1.5;
8
+ const CLIMB_SPEED = 1.0;
9
+
10
+ // 말풍선
11
+ const SPEECH_CHAR_DELAY = 30; // ms per character
12
+ const SPEECH_DISPLAY_TIME = 5000; // ms 표시 유지
13
+ const SPEECH_FADE_TIME = 500; // ms 페이드 아웃
14
+
15
+ // 파일 작업 안전장치
16
+ const MAX_FILES_PER_SESSION = 3;
17
+ const FILE_MOVE_COOLDOWN = 5 * 60 * 1000; // 5분
18
+ const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
19
+ const EXCLUDED_EXTENSIONS = ['.exe', '.dll', '.sys', '.lnk', '.ini', '.bat', '.cmd', '.ps1'];
20
+
21
+ // FSM 상태
22
+ const STATES = {
23
+ IDLE: 'idle',
24
+ WALKING: 'walking',
25
+ CLIMBING_UP: 'climbing_up',
26
+ CLIMBING_DOWN: 'climbing_down',
27
+ CEILING_WALK: 'ceiling_walk',
28
+ SLEEPING: 'sleeping',
29
+ CARRYING: 'carrying',
30
+ PLAYING: 'playing',
31
+ INTERACTING: 'interacting',
32
+ SCARED: 'scared',
33
+ EXCITED: 'excited',
34
+ };
35
+
36
+ // 화면 가장자리
37
+ const EDGES = {
38
+ BOTTOM: 'bottom',
39
+ LEFT: 'left',
40
+ RIGHT: 'right',
41
+ TOP: 'top',
42
+ };
43
+
44
+ // 방향
45
+ const DIRECTIONS = {
46
+ LEFT: -1,
47
+ RIGHT: 1,
48
+ };
49
+
50
+ // 색상 팔레트
51
+ const COLORS = {
52
+ pet: {
53
+ primary: '#ff4f40',
54
+ secondary: '#ff775f',
55
+ dark: '#3a0a0d',
56
+ eye: '#ffffff',
57
+ pupil: '#111111',
58
+ claw: '#ff4f40',
59
+ speechBorder: '#ff4f40',
60
+ speechBg: '#fff5f5',
61
+ },
62
+ incarnation: {
63
+ primary: '#ff4f40',
64
+ secondary: '#ff775f',
65
+ dark: '#3a0a0d',
66
+ eye: '#00BFA5',
67
+ pupil: '#004D40',
68
+ claw: '#ff4f40',
69
+ glow: '#00BFA5',
70
+ speechBorder: '#00BFA5',
71
+ speechBg: '#f0fffd',
72
+ },
73
+ };
74
+
75
+ module.exports = {
76
+ PIXEL_SIZE, GRID_SIZE, CHAR_SIZE, BASE_SPEED, CLIMB_SPEED,
77
+ SPEECH_CHAR_DELAY, SPEECH_DISPLAY_TIME, SPEECH_FADE_TIME,
78
+ MAX_FILES_PER_SESSION, FILE_MOVE_COOLDOWN, MAX_FILE_SIZE, EXCLUDED_EXTENSIONS,
79
+ STATES, EDGES, DIRECTIONS, COLORS,
80
+ };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * 말풍선 메시지 DB (한국어/영어)
3
+ * 모든 메시지는 긍정적, 귀여운 톤 유지
4
+ */
5
+ const MESSAGES = {
6
+ greetings: {
7
+ morning: [
8
+ '좋은 아침! \u2600\uFE0F',
9
+ '오늘도 화이팅!',
10
+ 'Good morning~!',
11
+ '일어났어? 오늘도 좋은 하루!',
12
+ '아침이다! 뭐 할 거야?',
13
+ ],
14
+ afternoon: [
15
+ '점심은 먹었어?',
16
+ '오후도 힘내자!',
17
+ '배고프다... 밥 먹을 시간!',
18
+ '오후 졸음 조심~',
19
+ ],
20
+ evening: [
21
+ '오늘 수고했어!',
22
+ '저녁이야~ 쉬엄쉬엄!',
23
+ '좋은 저녁 시간 보내!',
24
+ '오늘 하루 어땠어?',
25
+ ],
26
+ night: [
27
+ '잘 자... zzZ',
28
+ '늦었다, 자야 하지 않아?',
29
+ '밤이 깊었어... 굿나잇!',
30
+ '내일 또 만나! zzz',
31
+ ],
32
+ },
33
+
34
+ reactions: {
35
+ pet: [
36
+ '앗! 간지러워~',
37
+ '헤헤, 또 만져줘!',
38
+ '좋아좋아!',
39
+ '냠냠~ 기분 좋다!',
40
+ '찰칵! (집게 소리)',
41
+ '나한테 관심 가져줘서 고마워!',
42
+ '우리 친해지고 있는 거지?',
43
+ '집게집게~ \u270C\uFE0F',
44
+ ],
45
+ incarnation: [
46
+ '...감지했다.',
47
+ '연결이 강해지고 있어.',
48
+ '네 존재를 느낀다.',
49
+ '재미있는 인간이로군.',
50
+ 'Claw의 힘이 느껴지는가?',
51
+ '좋은 에너지를 감지했다.',
52
+ '함께하니 좋군.',
53
+ '파트너로 인정한다.',
54
+ ],
55
+ },
56
+
57
+ tips: [
58
+ '나를 드래그해서 옮길 수 있어!',
59
+ '3번 연속 클릭하면 모드가 바뀌어!',
60
+ '트레이 아이콘에서 설정을 바꿀 수 있어!',
61
+ '밤에는 나도 졸려... zzz',
62
+ '파일을 옮기면 트레이에서 되돌릴 수 있어!',
63
+ '오래 함께하면 내가 변할지도?',
64
+ ],
65
+
66
+ milestones: {
67
+ first_click: '첫 만남이다! 반가워!',
68
+ clicks_10: '벌써 10번째! 우리 친구 맞지?',
69
+ clicks_50: '50번이나! 나 인기 많은 거 아냐? \u2B50',
70
+ clicks_100: '100번! 넌 정말 특별한 친구야!',
71
+ clicks_500: '500번... 전설의 파트너다!',
72
+ days_1: '하루 함께했어! 내일도 같이 하자!',
73
+ days_7: '일주일 기념! 우리 꽤 오래 됐다~',
74
+ days_30: '한 달 기념! 최고의 파트너!',
75
+ days_100: '100일! 우리 사이 특별하지 않아?',
76
+ },
77
+
78
+ idle_chatter: [
79
+ '심심하다~',
80
+ '뭐 하고 있어?',
81
+ '(집게 딸깍)',
82
+ '바탕화면 구경 중~',
83
+ '여기 좋은 자리다!',
84
+ '(기지개를 편다)',
85
+ '오늘 날씨 어때?',
86
+ ],
87
+
88
+ // 진화 관련 메시지
89
+ evolution: {
90
+ stage_1: '뭔가 변하는 느낌이야...!',
91
+ stage_2: '나 좀 달라 보여? 기분 탓일까!',
92
+ stage_3: '우리 우정이 나를 바꾸고 있어!',
93
+ stage_4: '최종 형태에 가까워지고 있어!',
94
+ stage_5: '이게 나의 완성된 모습이야!',
95
+ },
96
+ };
97
+
98
+ // 렌더러에서 글로벌로 접근 가능하게
99
+ if (typeof window !== 'undefined') {
100
+ window._messages = MESSAGES;
101
+ } else if (typeof module !== 'undefined') {
102
+ module.exports = MESSAGES;
103
+ }