kawaijs 0.1.1 → 0.1.3

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,630 @@
1
+ import * as http from 'node:http';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { compileScript, formatDiagnostic, KawaError } from '@kawaijs/parser';
5
+ export function startDevServer(projectDir = '.', options = {}) {
6
+ const rootDir = path.resolve(process.cwd(), projectDir);
7
+ const scriptPath = path.join(rootDir, 'game', 'script.kawa');
8
+ const stylePath = path.join(rootDir, 'game', 'style.css');
9
+ const assetsDir = path.join(rootDir, 'game', 'assets');
10
+ let port = options.port ?? 3000;
11
+ if (!fs.existsSync(scriptPath)) {
12
+ console.error(`❌ Error: Cannot find '${scriptPath}'. Make sure you are inside a Kawaijs project directory.`);
13
+ return;
14
+ }
15
+ const clients = new Set();
16
+ // File watcher for auto-reload
17
+ const gameDir = path.join(rootDir, 'game');
18
+ if (fs.existsSync(gameDir)) {
19
+ fs.watch(gameDir, { recursive: true }, (_eventType, filename) => {
20
+ if (filename && (filename.endsWith('.kawa') || filename.endsWith('.css') || filename.startsWith('assets'))) {
21
+ console.log(`🔄 [Kawa Dev] File changed: ${filename}. Reloading...`);
22
+ for (const client of clients) {
23
+ client.write(`data: reload\n\n`);
24
+ }
25
+ }
26
+ });
27
+ }
28
+ const server = http.createServer((req, res) => {
29
+ const url = req.url ?? '/';
30
+ // 1. SSE Live Reload Endpoint
31
+ if (url === '/__kawa_reload') {
32
+ res.writeHead(200, {
33
+ 'Content-Type': 'text/event-stream',
34
+ 'Cache-Control': 'no-cache',
35
+ 'Connection': 'keep-alive',
36
+ 'Access-Control-Allow-Origin': '*'
37
+ });
38
+ clients.add(res);
39
+ req.on('close', () => clients.delete(res));
40
+ return;
41
+ }
42
+ // 2. Dynamic Story JSON API
43
+ if (url === '/api/story.json') {
44
+ try {
45
+ const source = fs.readFileSync(scriptPath, 'utf-8');
46
+ const story = compileScript(source, path.basename(scriptPath));
47
+ res.writeHead(200, { 'Content-Type': 'application/json' });
48
+ res.end(JSON.stringify(story));
49
+ }
50
+ catch (err) {
51
+ res.writeHead(500, { 'Content-Type': 'application/json' });
52
+ let errMsg = 'Compilation Error';
53
+ if (err instanceof KawaError) {
54
+ const source = fs.readFileSync(scriptPath, 'utf-8');
55
+ errMsg = formatDiagnostic(err.diagnostic, source);
56
+ }
57
+ else if (err instanceof Error) {
58
+ errMsg = err.message;
59
+ }
60
+ res.end(JSON.stringify({ error: errMsg }));
61
+ }
62
+ return;
63
+ }
64
+ // 3. User & Default Stylesheet
65
+ if (url === '/style.css') {
66
+ res.writeHead(200, { 'Content-Type': 'text/css' });
67
+ let combinedCss = `/* Kawaijs Base Theme */\n`;
68
+ // Load default renderer theme CSS
69
+ const themeCssPath = path.resolve(path.dirname(import.meta.url.replace('file:///', '')), '..', '..', '..', 'renderer-dom', 'src', 'theme.css');
70
+ if (fs.existsSync(themeCssPath)) {
71
+ combinedCss += fs.readFileSync(themeCssPath, 'utf-8') + '\n';
72
+ }
73
+ if (fs.existsSync(stylePath)) {
74
+ combinedCss += `/* User Custom Styles */\n` + fs.readFileSync(stylePath, 'utf-8');
75
+ }
76
+ res.end(combinedCss);
77
+ return;
78
+ }
79
+ // 4. Game Assets (/assets/*)
80
+ if (url.startsWith('/assets/')) {
81
+ const relPath = decodeURIComponent(url.replace('/assets/', ''));
82
+ let filePath = path.join(assetsDir, relPath);
83
+ if (!fs.existsSync(filePath)) {
84
+ // Try fallback extensions (.svg, .png, .webp, .jpg, .jpeg, .mp3, .ogg)
85
+ const parsed = path.parse(filePath);
86
+ for (const ext of ['.svg', '.png', '.webp', '.jpg', '.jpeg', '.mp3', '.ogg']) {
87
+ const candidate = path.join(parsed.dir, parsed.name + ext);
88
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
89
+ filePath = candidate;
90
+ break;
91
+ }
92
+ }
93
+ }
94
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
95
+ const ext = path.extname(filePath).toLowerCase();
96
+ const mimeTypes = {
97
+ '.png': 'image/png',
98
+ '.jpg': 'image/jpeg',
99
+ '.jpeg': 'image/jpeg',
100
+ '.webp': 'image/webp',
101
+ '.svg': 'image/svg+xml',
102
+ '.mp3': 'audio/mpeg',
103
+ '.ogg': 'audio/ogg',
104
+ '.wav': 'audio/wav'
105
+ };
106
+ res.writeHead(200, { 'Content-Type': mimeTypes[ext] ?? 'application/octet-stream' });
107
+ fs.createReadStream(filePath).pipe(res);
108
+ return;
109
+ }
110
+ else {
111
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
112
+ res.end('Asset not found');
113
+ return;
114
+ }
115
+ }
116
+ // 5. HTML Shell & Web Runtime
117
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
118
+ res.end(`<!DOCTYPE html>
119
+ <html lang="en">
120
+ <head>
121
+ <meta charset="UTF-8">
122
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
123
+ <title>Kawaijs Visual Novel</title>
124
+ <link rel="stylesheet" href="/style.css">
125
+ <style>
126
+ body { margin: 0; padding: 0; background: #000; overflow: hidden; }
127
+ #error-overlay {
128
+ display: none; position: fixed; inset: 0; background: rgba(15, 23, 42, 0.95);
129
+ color: #f43f5e; font-family: monospace; padding: 32px; z-index: 9999;
130
+ white-space: pre-wrap; font-size: 1.1rem; line-height: 1.5;
131
+ }
132
+ </style>
133
+ </head>
134
+ <body>
135
+ <div id="error-overlay"></div>
136
+ <div id="app"></div>
137
+
138
+ <script type="module">
139
+ // Live Reload Connection
140
+ const sse = new EventSource('/__kawa_reload');
141
+ sse.onmessage = (e) => {
142
+ if (e.data === 'reload') window.location.reload();
143
+ };
144
+
145
+ // Load story and start game
146
+ async function init() {
147
+ const errorEl = document.getElementById('error-overlay');
148
+ try {
149
+ const res = await fetch('/api/story.json');
150
+ const story = await res.json();
151
+ if (story.error) {
152
+ errorEl.style.display = 'block';
153
+ errorEl.textContent = story.error;
154
+ return;
155
+ }
156
+
157
+ // Inline runtime VM + DOM Renderer
158
+ ${getInlineRuntimeScript()}
159
+
160
+ window.__kawa_app = mountKawaApp(story, document.getElementById('app'));
161
+ } catch (err) {
162
+ errorEl.style.display = 'block';
163
+ errorEl.textContent = err.stack || err.message;
164
+ }
165
+ }
166
+
167
+ init();
168
+ </script>
169
+ </body>
170
+ </html>`);
171
+ });
172
+ server.on('error', (e) => {
173
+ if (e.code === 'EADDRINUSE') {
174
+ console.log(`Port ${port} is in use, trying ${port + 1}...`);
175
+ port += 1;
176
+ server.listen(port);
177
+ }
178
+ else {
179
+ console.error('Server error:', e);
180
+ }
181
+ });
182
+ server.listen(port, () => {
183
+ console.log(`\n🌸 Kawaijs Dev Server running at:`);
184
+ console.log(` > Local: \x1b[36mhttp://localhost:${port}\x1b[0m`);
185
+ console.log(` > Project: ${rootDir}`);
186
+ console.log(` > Watching: game/script.kawa, game/style.css, game/assets/\n`);
187
+ });
188
+ }
189
+ function getInlineRuntimeScript() {
190
+ return `
191
+ class MemoryStorageAdapter {
192
+ constructor() { this.store = new Map(); }
193
+ getItem(k) { return this.store.get(k) || null; }
194
+ setItem(k, v) { this.store.set(k, v); }
195
+ removeItem(k) { this.store.delete(k); }
196
+ }
197
+ class LocalStorageAdapter {
198
+ getItem(k) { return localStorage.getItem(k); }
199
+ setItem(k, v) { localStorage.setItem(k, v); }
200
+ removeItem(k) { localStorage.removeItem(k); }
201
+ }
202
+ class SaveManager {
203
+ constructor() { this.storage = typeof localStorage !== 'undefined' ? new LocalStorageAdapter() : new MemoryStorageAdapter(); }
204
+ async saveSlot(id, snapshot, previewText) {
205
+ const slot = { id, name: 'Slot ' + id, timestamp: Date.now(), snapshot, previewText };
206
+ this.storage.setItem('kawaijs_save_' + id, JSON.stringify(slot));
207
+ return slot;
208
+ }
209
+ async loadSlot(id) {
210
+ const raw = this.storage.getItem('kawaijs_save_' + id);
211
+ return raw ? JSON.parse(raw) : null;
212
+ }
213
+ async deleteSlot(id) {
214
+ this.storage.removeItem('kawaijs_save_' + id);
215
+ }
216
+ async listSlots(total = 6) {
217
+ const slots = [];
218
+ for (let i = 1; i <= total; i++) {
219
+ slots.push(await this.loadSlot(String(i)));
220
+ }
221
+ return slots;
222
+ }
223
+ }
224
+ function evaluateCondition(cond, vars) {
225
+ const t = (cond || '').trim();
226
+ if (!t || t === 'true') return true;
227
+ if (t === 'false') return false;
228
+ const ops = ['>=', '<=', '!=', '==', '>', '<'];
229
+ for (const op of ops) {
230
+ const idx = t.indexOf(op);
231
+ if (idx !== -1) {
232
+ const l = resolveVal(t.slice(0, idx), vars);
233
+ const r = resolveVal(t.slice(idx + op.length), vars);
234
+ if (op === '>=') return Number(l) >= Number(r);
235
+ if (op === '<=') return Number(l) <= Number(r);
236
+ if (op === '>') return Number(l) > Number(r);
237
+ if (op === '<') return Number(l) < Number(r);
238
+ if (op === '==') return l == r;
239
+ if (op === '!=') return l != r;
240
+ }
241
+ }
242
+ if (t.startsWith('!')) return !Boolean(vars[t.slice(1).trim()]);
243
+ return Boolean(vars[t]);
244
+ }
245
+ function resolveVal(token, vars) {
246
+ const t = token.trim();
247
+ if (t === 'true') return true;
248
+ if (t === 'false') return false;
249
+ if (!isNaN(Number(t)) && t !== '') return Number(t);
250
+ if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) return t.slice(1, -1);
251
+ if (t in vars) return vars[t];
252
+ return t;
253
+ }
254
+ function applySetOp(curr, op, val, vars) {
255
+ const res = (typeof val === 'string' && val in vars) ? vars[val] : val;
256
+ if (op === '+=') return Number(curr || 0) + Number(res);
257
+ if (op === '-=') return Number(curr || 0) - Number(res);
258
+ return res;
259
+ }
260
+ class StoryVM {
261
+ constructor(story) {
262
+ this.story = story;
263
+ this.state = {
264
+ currentLabel: story.meta.startLabel || 'start',
265
+ instructionPointer: 0,
266
+ callStack: [],
267
+ variables: {},
268
+ visual: { background: null, transition: null, characters: {} },
269
+ audio: { music: null, voice: null },
270
+ dialogue: null,
271
+ choices: null,
272
+ isWaitingForInput: false,
273
+ isFinished: false
274
+ };
275
+ this.snapshotStack = [];
276
+ this.listeners = new Set();
277
+ this.audioListeners = new Set();
278
+ this.history = [];
279
+ this.saveManager = new SaveManager();
280
+ this.isExecuting = false;
281
+ }
282
+ getState() { return this.state; }
283
+ onStateChange(cb) { this.listeners.add(cb); return () => this.listeners.delete(cb); }
284
+ onAudioEvent(cb) { this.audioListeners.add(cb); return () => this.audioListeners.delete(cb); }
285
+ start() { this.execute(); }
286
+ next() {
287
+ if (this.state.isFinished || this.isExecuting || (this.state.choices && this.state.choices.length > 0)) return;
288
+ this.state.isWaitingForInput = false;
289
+ this.execute();
290
+ }
291
+ choose(idx) {
292
+ if (this.isExecuting || !this.state.choices || !this.state.choices[idx]) return;
293
+ const choice = this.state.choices[idx];
294
+ this.state.choices = null;
295
+ this.state.isWaitingForInput = false;
296
+ this.state.currentLabel = choice.targetLabel;
297
+ this.state.instructionPointer = 0;
298
+ this.execute();
299
+ }
300
+ rollback() {
301
+ if (this.snapshotStack.length <= 1) return false;
302
+ this.snapshotStack.pop();
303
+ const prev = this.snapshotStack[this.snapshotStack.length - 1];
304
+ if (prev) {
305
+ this.state = JSON.parse(JSON.stringify(prev));
306
+ this.notify();
307
+ return true;
308
+ }
309
+ return false;
310
+ }
311
+ canRollback() { return this.snapshotStack.length > 1; }
312
+ async save(slot) {
313
+ await this.saveManager.saveSlot(slot, JSON.parse(JSON.stringify(this.state)), this.state.dialogue ? this.state.dialogue.text : '');
314
+ }
315
+ async load(slot) {
316
+ const data = await this.saveManager.loadSlot(slot);
317
+ if (data && data.snapshot) {
318
+ this.state = JSON.parse(JSON.stringify(data.snapshot.state || data.snapshot));
319
+ this.snapshotStack = [JSON.parse(JSON.stringify(this.state))];
320
+ this.notify();
321
+ return true;
322
+ }
323
+ return false;
324
+ }
325
+ execute() {
326
+ if (this.isExecuting) return;
327
+ this.isExecuting = true;
328
+ try {
329
+ while (!this.state.isWaitingForInput && !this.state.isFinished) {
330
+ const list = this.story.labels[this.state.currentLabel];
331
+ if (!list || this.state.instructionPointer >= list.length) {
332
+ if (this.state.callStack.length > 0) {
333
+ const frame = this.state.callStack.pop();
334
+ this.state.currentLabel = frame.returnLabel;
335
+ this.state.instructionPointer = frame.returnPointer;
336
+ continue;
337
+ }
338
+ this.state.isFinished = true;
339
+ break;
340
+ }
341
+ const inst = list[this.state.instructionPointer++];
342
+ if (inst.type === 'scene') {
343
+ this.state.visual.background = inst.background;
344
+ this.state.visual.characters = {};
345
+ } else if (inst.type === 'show') {
346
+ this.state.visual.characters[inst.character] = {
347
+ expression: inst.expression,
348
+ position: inst.position || 'center'
349
+ };
350
+ } else if (inst.type === 'hide') {
351
+ delete this.state.visual.characters[inst.character];
352
+ } else if (inst.type === 'dialogue') {
353
+ const charDef = inst.speaker ? this.story.characters[inst.speaker] : null;
354
+ this.state.dialogue = {
355
+ speaker: inst.speaker,
356
+ speakerDisplayName: charDef ? charDef.name : inst.speaker,
357
+ speakerColor: charDef ? charDef.color : null,
358
+ text: inst.text
359
+ };
360
+ this.state.isWaitingForInput = true;
361
+ this.history.push(this.state.dialogue);
362
+ } else if (inst.type === 'choice') {
363
+ this.state.choices = inst.choices;
364
+ this.state.isWaitingForInput = true;
365
+ } else if (inst.type === 'jump') {
366
+ this.state.currentLabel = inst.targetLabel;
367
+ this.state.instructionPointer = 0;
368
+ } else if (inst.type === 'set') {
369
+ const curr = this.state.variables[inst.variable];
370
+ this.state.variables[inst.variable] = applySetOp(curr, inst.operator, inst.value, this.state.variables);
371
+ } else if (inst.type === 'branch') {
372
+ const ok = evaluateCondition(inst.condition, this.state.variables);
373
+ this.state.currentLabel = ok ? inst.thenLabel : (inst.elseLabel || inst.thenLabel);
374
+ this.state.instructionPointer = 0;
375
+ } else if (inst.type === 'return') {
376
+ this.state.isFinished = true;
377
+ }
378
+ }
379
+ if (this.state.isWaitingForInput) {
380
+ this.snapshotStack.push(JSON.parse(JSON.stringify(this.state)));
381
+ }
382
+ } finally {
383
+ this.isExecuting = false;
384
+ }
385
+ this.notify();
386
+ }
387
+ notify() { for (const l of this.listeners) l(this.state); }
388
+ }
389
+ class DOMRenderer {
390
+ constructor(vm, container) {
391
+ this.vm = vm;
392
+ this.container = container;
393
+ this.isChoicePending = false;
394
+ this.build();
395
+ vm.onStateChange(s => this.render(s));
396
+ this.render(vm.getState());
397
+ }
398
+ build() {
399
+ this.container.innerHTML = \`
400
+ <div class="kawa-root">
401
+ <div class="kawa-stage">
402
+ <div class="kawa-background"></div>
403
+ <div class="kawa-characters kawa-sprites"></div>
404
+ <div class="kawa-ui-layer">
405
+ <div class="kawa-choice-container kawa-choices" style="display:none"></div>
406
+ <div class="kawa-dialogue-box kawa-dialogue">
407
+ <div class="kawa-speaker-tag kawa-speaker" style="display:none"></div>
408
+ <div class="kawa-dialogue-text kawa-text"></div>
409
+ <div class="kawa-continue-indicator">&#9660;</div>
410
+ </div>
411
+ <nav class="kawa-quick-menu">
412
+ <button class="kawa-btn kawa-back">Back</button>
413
+ <button class="kawa-btn kawa-hist">History</button>
414
+ <button class="kawa-btn kawa-save">Save</button>
415
+ <button class="kawa-btn kawa-load">Load</button>
416
+ </nav>
417
+ </div>
418
+ </div>
419
+ </div>\`;
420
+ this.rootEl = this.container.querySelector('.kawa-root');
421
+ this.bgEl = this.container.querySelector('.kawa-background');
422
+ this.charsEl = this.container.querySelector('.kawa-characters');
423
+ this.boxEl = this.container.querySelector('.kawa-dialogue-box');
424
+ this.spkEl = this.container.querySelector('.kawa-speaker-tag');
425
+ this.txtEl = this.container.querySelector('.kawa-dialogue-text');
426
+ this.choiceEl = this.container.querySelector('.kawa-choice-container');
427
+ this.backBtn = this.container.querySelector('.kawa-back');
428
+ this.histBtn = this.container.querySelector('.kawa-hist');
429
+ this.saveBtn = this.container.querySelector('.kawa-save');
430
+ this.loadBtn = this.container.querySelector('.kawa-load');
431
+
432
+ this.boxEl.addEventListener('click', () => this.vm.next());
433
+ this.backBtn.addEventListener('click', (e) => { e.stopPropagation(); this.vm.rollback(); });
434
+ this.histBtn.addEventListener('click', (e) => { e.stopPropagation(); this.showHistory(); });
435
+ this.saveBtn.addEventListener('click', (e) => { e.stopPropagation(); this.showSaveLoad('save'); });
436
+ this.loadBtn.addEventListener('click', (e) => { e.stopPropagation(); this.showSaveLoad('load'); });
437
+ window.addEventListener('keydown', (e) => {
438
+ const modal = this.rootEl.querySelector('.kawa-modal-overlay');
439
+ if (modal) {
440
+ if (e.key === 'Escape') modal.remove();
441
+ return;
442
+ }
443
+ if (e.code === 'Space' || e.code === 'Enter') { e.preventDefault(); this.vm.next(); }
444
+ if (e.code === 'Backspace') { e.preventDefault(); this.vm.rollback(); }
445
+ if (e.key === 's' || e.key === 'S') this.showSaveLoad('save');
446
+ if (e.key === 'l' || e.key === 'L') this.showSaveLoad('load');
447
+ if (e.key === 'h' || e.key === 'H') this.showHistory();
448
+ });
449
+ }
450
+ render(state) {
451
+ this.isChoicePending = false;
452
+ this.backBtn.disabled = !this.vm.canRollback();
453
+ if (state.visual.background) {
454
+ const bg = state.visual.background.replace(/^bg\\s+/, '');
455
+ this.bgEl.style.backgroundImage = 'url("/assets/backgrounds/' + bg + (bg.includes('.') ? '' : '.svg') + '"), url("/assets/backgrounds/' + bg + '.png")';
456
+ this.bgEl.style.opacity = '1';
457
+ } else {
458
+ this.bgEl.style.opacity = '0';
459
+ }
460
+
461
+ this.charsEl.innerHTML = '';
462
+ for (const [id, char] of Object.entries(state.visual.characters)) {
463
+ const div = document.createElement('div');
464
+ div.className = 'kawa-sprite kawa-pos-' + (char.position || 'center');
465
+ const img = document.createElement('img');
466
+ const expr = char.expression ? '/' + char.expression : '';
467
+ img.src = '/assets/characters/' + id + expr + '.svg';
468
+ img.alt = id;
469
+ let fallback = 0;
470
+ img.onerror = () => {
471
+ fallback++;
472
+ if (fallback === 1) img.src = '/assets/characters/' + id + expr + '.png';
473
+ else if (fallback === 2 && char.expression) img.src = '/assets/characters/' + id + '_' + char.expression + '.svg';
474
+ else if (fallback === 3 && char.expression) img.src = '/assets/characters/' + id + '_' + char.expression + '.png';
475
+ else {
476
+ img.style.display = 'none';
477
+ div.style.width = '220px'; div.style.height = '420px';
478
+ div.style.background = 'rgba(244,63,94,0.25)'; div.style.border = '2px dashed #f43f5e';
479
+ div.style.borderRadius = '16px'; div.style.display = 'flex'; div.style.alignItems = 'center';
480
+ div.style.justifyContent = 'center'; div.style.color = '#fff'; div.style.fontWeight = '600';
481
+ div.textContent = id + (char.expression ? ' (' + char.expression + ')' : '');
482
+ }
483
+ };
484
+ div.appendChild(img);
485
+ this.charsEl.appendChild(div);
486
+ }
487
+
488
+ if (state.dialogue) {
489
+ this.boxEl.style.display = 'block';
490
+ if (state.dialogue.speakerDisplayName) {
491
+ this.spkEl.style.display = 'inline-block';
492
+ this.spkEl.textContent = state.dialogue.speakerDisplayName;
493
+ if (state.dialogue.speakerColor) this.spkEl.style.backgroundColor = state.dialogue.speakerColor;
494
+ } else {
495
+ this.spkEl.style.display = 'none';
496
+ }
497
+ this.txtEl.textContent = state.dialogue.text;
498
+ } else {
499
+ this.boxEl.style.display = 'none';
500
+ }
501
+
502
+ if (state.choices && state.choices.length > 0) {
503
+ this.choiceEl.innerHTML = '';
504
+ this.choiceEl.style.display = 'flex';
505
+ this.choiceEl.style.pointerEvents = 'auto';
506
+ this.choiceEl.style.opacity = '1';
507
+ state.choices.forEach((c, idx) => {
508
+ const btn = document.createElement('button');
509
+ btn.className = 'kawa-choice-btn kawa-choice';
510
+ btn.textContent = c.text;
511
+ btn.addEventListener('click', (e) => {
512
+ e.stopPropagation();
513
+ if (this.isChoicePending) return;
514
+ this.isChoicePending = true;
515
+ this.choiceEl.style.pointerEvents = 'none';
516
+ this.choiceEl.style.opacity = '0.5';
517
+ this.vm.choose(idx);
518
+ });
519
+ this.choiceEl.appendChild(btn);
520
+ });
521
+ } else {
522
+ this.choiceEl.style.display = 'none';
523
+ }
524
+ }
525
+ async showSaveLoad(mode) {
526
+ const ex = this.rootEl.querySelector('.kawa-modal-overlay');
527
+ if (ex) ex.remove();
528
+ const ov = document.createElement('div');
529
+ ov.className = 'kawa-modal-overlay';
530
+ const card = document.createElement('div');
531
+ card.className = 'kawa-modal-card';
532
+ card.innerHTML = \`
533
+ <div class="kawa-modal-header">
534
+ <div class="kawa-modal-title">\${mode === 'save' ? '💾 Save Game' : '📂 Load Game'}</div>
535
+ <button class="kawa-btn kawa-close-btn">✕ Close</button>
536
+ </div>
537
+ <div class="kawa-modal-body"><div class="kawa-slots-grid"></div></div>\`;
538
+ card.querySelector('.kawa-close-btn').addEventListener('click', () => ov.remove());
539
+ const grid = card.querySelector('.kawa-slots-grid');
540
+ const slots = await this.vm.saveManager.listSlots(6);
541
+ slots.forEach((s, i) => {
542
+ const num = String(i + 1);
543
+ const c = document.createElement('div');
544
+ c.className = 'kawa-slot-card';
545
+ c.innerHTML = \`
546
+ <div class="kawa-slot-header">
547
+ <span class="kawa-slot-badge">Slot \${num}</span>
548
+ <span class="kawa-slot-time">\${s ? new Date(s.timestamp).toLocaleTimeString() : 'Empty'}</span>
549
+ </div>
550
+ <div class="kawa-slot-preview">\${s ? (s.previewText || 'Game in progress') : '<span class="kawa-slot-empty-text">No save data</span>'}</div>
551
+ <div class="kawa-slot-actions"></div>\`;
552
+ const act = c.querySelector('.kawa-slot-actions');
553
+ if (mode === 'save') {
554
+ const b = document.createElement('button');
555
+ b.className = 'kawa-slot-btn kawa-slot-btn-save';
556
+ b.textContent = 'Save Here';
557
+ b.addEventListener('click', async () => {
558
+ await this.vm.save(num);
559
+ ov.remove();
560
+ this.showSaveLoad('save');
561
+ });
562
+ act.appendChild(b);
563
+ } else if (s) {
564
+ const b = document.createElement('button');
565
+ b.className = 'kawa-slot-btn kawa-slot-btn-load';
566
+ b.textContent = 'Load';
567
+ b.addEventListener('click', async () => {
568
+ if (await this.vm.load(num)) ov.remove();
569
+ });
570
+ act.appendChild(b);
571
+ }
572
+ if (s) {
573
+ const d = document.createElement('button');
574
+ d.className = 'kawa-slot-btn kawa-slot-btn-del';
575
+ d.textContent = '🗑';
576
+ d.addEventListener('click', async () => {
577
+ await this.vm.saveManager.deleteSlot(num);
578
+ ov.remove();
579
+ this.showSaveLoad(mode);
580
+ });
581
+ act.appendChild(d);
582
+ }
583
+ grid.appendChild(c);
584
+ });
585
+ ov.appendChild(card);
586
+ this.rootEl.appendChild(ov);
587
+ }
588
+ showHistory() {
589
+ const ex = this.rootEl.querySelector('.kawa-modal-overlay');
590
+ if (ex) ex.remove();
591
+ const ov = document.createElement('div');
592
+ ov.className = 'kawa-modal-overlay';
593
+ const card = document.createElement('div');
594
+ card.className = 'kawa-modal-card';
595
+ card.innerHTML = \`
596
+ <div class="kawa-modal-header">
597
+ <div class="kawa-modal-title">📜 Dialogue History</div>
598
+ <button class="kawa-btn kawa-close-btn">✕ Close</button>
599
+ </div>
600
+ <div class="kawa-modal-body"></div>\`;
601
+ card.querySelector('.kawa-close-btn').addEventListener('click', () => ov.remove());
602
+ const body = card.querySelector('.kawa-modal-body');
603
+ if (this.vm.history.length === 0) {
604
+ body.innerHTML = '<div style="opacity:0.6">No dialogue history yet.</div>';
605
+ } else {
606
+ this.vm.history.forEach(h => {
607
+ const item = document.createElement('div');
608
+ item.className = 'kawa-history-item';
609
+ if (h.speakerDisplayName) {
610
+ item.innerHTML = '<div class="kawa-history-speaker">' + h.speakerDisplayName + '</div>';
611
+ }
612
+ const txt = document.createElement('div');
613
+ txt.textContent = h.text;
614
+ item.appendChild(txt);
615
+ body.appendChild(item);
616
+ });
617
+ }
618
+ ov.appendChild(card);
619
+ this.rootEl.appendChild(ov);
620
+ }
621
+ }
622
+ function mountKawaApp(story, container) {
623
+ const vm = new StoryVM(story);
624
+ const renderer = new DOMRenderer(vm, container);
625
+ vm.start();
626
+ return { vm, renderer };
627
+ }
628
+ `;
629
+ }
630
+ //# sourceMappingURL=dev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,8 +2,12 @@ export * from '@kawaijs/ast';
2
2
  export * from '@kawaijs/parser';
3
3
  export * from '@kawaijs/runtime';
4
4
  export * from '@kawaijs/renderer-dom';
5
+ export * from '@kawaijs/audio';
6
+ export * from '@kawaijs/vite-plugin';
5
7
  import { createProject } from './commands/create.js';
6
8
  import { validateProject } from './commands/validate.js';
7
- export { createProject, validateProject };
9
+ import { startDevServer } from './commands/dev.js';
10
+ import { buildProject } from './commands/build.js';
11
+ export { createProject, validateProject, startDevServer, buildProject };
8
12
  export declare function runCLI(args: string[]): void;
9
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,CAAC;AAE1C,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAqD3C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AAErC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC;AAExE,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA4E3C"}