kawaijs 0.1.0 → 0.1.2

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/README.md ADDED
@@ -0,0 +1,109 @@
1
+ <div align="center">
2
+
3
+ # 🌸 Kawaijs
4
+
5
+ **A modern, web-native visual novel engine and toolchain inspired by Ren'Py.**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/kawaijs.svg?color=crimson)](https://www.npmjs.com/package/kawaijs)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/biagio-scaglia/KawaiJS/blob/main/LICENSE)
9
+ [![GitHub](https://img.shields.io/badge/GitHub-biagio--scaglia%2FKawaiJS-181717?logo=github)](https://github.com/biagio-scaglia/KawaiJS)
10
+
11
+ </div>
12
+
13
+ ---
14
+
15
+ ## 📖 Introduction
16
+
17
+ **Kawaijs** is a developer-friendly, web-native visual novel engine and toolchain. It bridges the clean, human-friendly authoring experience of Ren'Py with modern Web technologies (HTML5, standard CSS, and TypeScript).
18
+
19
+ Write your story in **Kawa Script** (`.kawa`), style dialogue boxes and choices with **standard CSS**, and deploy anywhere as a pure static web application.
20
+
21
+ ```text
22
+ character yumia "Yumia" #f43f5e
23
+
24
+ label start:
25
+ scene bg classroom with fade
26
+ show yumia happy at center
27
+
28
+ yumia "Good morning! Are you ready for the festival?"
29
+
30
+ menu:
31
+ "Yes, absolutely!":
32
+ jump ready
33
+
34
+ "Not yet...":
35
+ jump not_ready
36
+
37
+ label ready:
38
+ show yumia excited
39
+ yumia "Awesome! Let's get to work."
40
+ return
41
+
42
+ label not_ready:
43
+ yumia "Don't worry, we still have time."
44
+ return
45
+ ```
46
+
47
+ ---
48
+
49
+ ## 🚀 Quick Start
50
+
51
+ ### 1. Scaffold a New Visual Novel
52
+
53
+ ```bash
54
+ npx kawaijs create my-novel
55
+ cd my-novel
56
+ ```
57
+
58
+ ### 2. Validate Your Script
59
+
60
+ ```bash
61
+ npx kawaijs validate
62
+ ```
63
+
64
+ ---
65
+
66
+ ## ✨ Features
67
+
68
+ - **🌐 Web-Native**: Runs directly in modern browsers with zero backend dependencies. Deploy to GitHub Pages, Netlify, Vercel, Cloudflare Pages, or itch.io.
69
+ - **✍️ Writer-Friendly**: Indentation-based Kawa Script designed for narrative designers and story writers.
70
+ - **🎨 Deep CSS Theming**: Customize dialogue boxes, choice overlays, buttons, fonts, and animations using standard CSS and CSS custom properties.
71
+ - **⏱️ Deterministic Rollback**: Step backwards through dialogue and choices with instant snapshot rollbacks (`vm.rollback()`).
72
+ - **💾 Save & Load**: Built-in persistence for game save slots using LocalStorage / IndexedDB.
73
+ - **📦 Modular Architecture**: Decoupled compiler (`@kawaijs/parser`), headless state machine (`@kawaijs/runtime`), and presentation layer (`@kawaijs/renderer-dom`).
74
+
75
+ ---
76
+
77
+ ## 💻 CLI Usage
78
+
79
+ ```bash
80
+ # Create a new visual novel project
81
+ kawa create <project-name>
82
+
83
+ # Validate syntax, jump labels, and asset links
84
+ kawa validate [path]
85
+
86
+ # Show version
87
+ kawa version
88
+ ```
89
+
90
+ ---
91
+
92
+ ## 📦 Modular Packages
93
+
94
+ Kawaijs is built as a clean modular toolkit:
95
+
96
+ | Package | Description |
97
+ | :--- | :--- |
98
+ | [`kawaijs`](https://www.npmjs.com/package/kawaijs) | All-in-one engine bundle & CLI |
99
+ | [`@kawaijs/ast`](https://www.npmjs.com/package/@kawaijs/ast) | Syntax AST & Story IR type definitions |
100
+ | [`@kawaijs/parser`](https://www.npmjs.com/package/@kawaijs/parser) | Indentation Lexer, Parser & Diagnostic error reporter |
101
+ | [`@kawaijs/runtime`](https://www.npmjs.com/package/@kawaijs/runtime) | Headless Story VM, rollback engine & persistence |
102
+ | [`@kawaijs/renderer-dom`](https://www.npmjs.com/package/@kawaijs/renderer-dom) | Semantic DOM renderer & CSS design system |
103
+ | [`@kawaijs/cli`](https://www.npmjs.com/package/@kawaijs/cli) | Dedicated CLI binary package |
104
+
105
+ ---
106
+
107
+ ## 📜 License
108
+
109
+ MIT License © 2026 [Biagio Scaglia](https://github.com/biagio-scaglia)
@@ -0,0 +1,5 @@
1
+ export interface BuildOptions {
2
+ outDir?: string;
3
+ }
4
+ export declare function buildProject(projectDir?: string, options?: BuildOptions): boolean;
5
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,YAAY,CAAC,UAAU,SAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAmXlF"}
@@ -0,0 +1,384 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { compileScript, formatDiagnostic, KawaError } from '@kawaijs/parser';
4
+ export function buildProject(projectDir = '.', options = {}) {
5
+ const rootDir = path.resolve(process.cwd(), projectDir);
6
+ const scriptPath = path.join(rootDir, 'game', 'script.kawa');
7
+ const stylePath = path.join(rootDir, 'game', 'style.css');
8
+ const assetsDir = path.join(rootDir, 'game', 'assets');
9
+ const outDir = path.resolve(rootDir, options.outDir ?? 'dist');
10
+ console.log(`\n📦 Building Kawaijs visual novel...`);
11
+ console.log(` Source: ${rootDir}`);
12
+ console.log(` Output: ${outDir}\n`);
13
+ if (!fs.existsSync(scriptPath)) {
14
+ console.error(`❌ Error: Cannot find '${scriptPath}'. Make sure you are inside a Kawaijs project directory.`);
15
+ return false;
16
+ }
17
+ // 1. Compile Kawa Script
18
+ let storyPackage;
19
+ try {
20
+ const source = fs.readFileSync(scriptPath, 'utf-8');
21
+ storyPackage = compileScript(source, path.basename(scriptPath));
22
+ console.log(`✅ Script compiled successfully (${Object.keys(storyPackage.labels).length} labels).`);
23
+ }
24
+ catch (err) {
25
+ if (err instanceof KawaError) {
26
+ const source = fs.readFileSync(scriptPath, 'utf-8');
27
+ console.error(formatDiagnostic(err.diagnostic, source));
28
+ }
29
+ else if (err instanceof Error) {
30
+ console.error(`❌ Compilation error: ${err.message}`);
31
+ }
32
+ return false;
33
+ }
34
+ // 2. Prepare output directory
35
+ if (fs.existsSync(outDir)) {
36
+ fs.rmSync(outDir, { recursive: true, force: true });
37
+ }
38
+ fs.mkdirSync(outDir, { recursive: true });
39
+ // 3. Copy Assets
40
+ const outAssetsDir = path.join(outDir, 'assets');
41
+ if (fs.existsSync(assetsDir)) {
42
+ copyDirectoryRecursive(assetsDir, outAssetsDir);
43
+ console.log(`✅ Assets copied to dist/assets/`);
44
+ }
45
+ // 4. Generate Combined style.css
46
+ let combinedCss = `/* Kawaijs Bundled Stylesheet */\n`;
47
+ const themeCssPath = path.resolve(path.dirname(import.meta.url.replace('file:///', '')), '..', '..', '..', 'renderer-dom', 'src', 'theme.css');
48
+ if (fs.existsSync(themeCssPath)) {
49
+ combinedCss += fs.readFileSync(themeCssPath, 'utf-8') + '\n\n';
50
+ }
51
+ if (fs.existsSync(stylePath)) {
52
+ combinedCss += fs.readFileSync(stylePath, 'utf-8') + '\n';
53
+ }
54
+ fs.writeFileSync(path.join(outDir, 'style.css'), combinedCss, 'utf-8');
55
+ console.log(`✅ Stylesheet bundled to dist/style.css`);
56
+ // 5. Generate Standalone HTML Application
57
+ const htmlContent = `<!DOCTYPE html>
58
+ <html lang="en">
59
+ <head>
60
+ <meta charset="UTF-8">
61
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
62
+ <title>${storyPackage.meta.title ?? 'Kawaijs Visual Novel'}</title>
63
+ <link rel="stylesheet" href="./style.css">
64
+ <style>
65
+ body { margin: 0; padding: 0; background: #000; overflow: hidden; }
66
+ </style>
67
+ </head>
68
+ <body>
69
+ <div id="app"></div>
70
+
71
+ <script type="module">
72
+ const story = ${JSON.stringify(storyPackage, null, 2)};
73
+
74
+ class MemoryStorageAdapter {
75
+ constructor() { this.store = new Map(); }
76
+ getItem(k) { return this.store.get(k) || null; }
77
+ setItem(k, v) { this.store.set(k, v); }
78
+ removeItem(k) { this.store.delete(k); }
79
+ }
80
+ class LocalStorageAdapter {
81
+ getItem(k) { return localStorage.getItem(k); }
82
+ setItem(k, v) { localStorage.setItem(k, v); }
83
+ removeItem(k) { localStorage.removeItem(k); }
84
+ }
85
+ class SaveManager {
86
+ constructor() { this.storage = typeof localStorage !== 'undefined' ? new LocalStorageAdapter() : new MemoryStorageAdapter(); }
87
+ async saveSlot(id, snapshot, previewText) {
88
+ const slot = { id, name: 'Slot ' + id, timestamp: Date.now(), snapshot, previewText };
89
+ this.storage.setItem('kawaijs_save_' + id, JSON.stringify(slot));
90
+ return slot;
91
+ }
92
+ async loadSlot(id) {
93
+ const raw = this.storage.getItem('kawaijs_save_' + id);
94
+ return raw ? JSON.parse(raw) : null;
95
+ }
96
+ }
97
+ function evaluateCondition(cond, vars) {
98
+ const t = (cond || '').trim();
99
+ if (!t || t === 'true') return true;
100
+ if (t === 'false') return false;
101
+ const ops = ['>=', '<=', '!=', '==', '>', '<'];
102
+ for (const op of ops) {
103
+ const idx = t.indexOf(op);
104
+ if (idx !== -1) {
105
+ const l = resolveVal(t.slice(0, idx), vars);
106
+ const r = resolveVal(t.slice(idx + op.length), vars);
107
+ if (op === '>=') return Number(l) >= Number(r);
108
+ if (op === '<=') return Number(l) <= Number(r);
109
+ if (op === '>') return Number(l) > Number(r);
110
+ if (op === '<') return Number(l) < Number(r);
111
+ if (op === '==') return l == r;
112
+ if (op === '!=') return l != r;
113
+ }
114
+ }
115
+ if (t.startsWith('!')) return !Boolean(vars[t.slice(1).trim()]);
116
+ return Boolean(vars[t]);
117
+ }
118
+ function resolveVal(token, vars) {
119
+ const t = token.trim();
120
+ if (t === 'true') return true;
121
+ if (t === 'false') return false;
122
+ if (!isNaN(Number(t)) && t !== '') return Number(t);
123
+ if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) return t.slice(1, -1);
124
+ if (t in vars) return vars[t];
125
+ return t;
126
+ }
127
+ function applySetOp(curr, op, val, vars) {
128
+ const res = (typeof val === 'string' && val in vars) ? vars[val] : val;
129
+ if (op === '+=') return Number(curr || 0) + Number(res);
130
+ if (op === '-=') return Number(curr || 0) - Number(res);
131
+ return res;
132
+ }
133
+ class StoryVM {
134
+ constructor(story) {
135
+ this.story = story;
136
+ this.state = {
137
+ currentLabel: story.meta.startLabel || 'start',
138
+ instructionPointer: 0,
139
+ callStack: [],
140
+ variables: {},
141
+ visual: { background: null, transition: null, characters: {} },
142
+ audio: { music: null, voice: null },
143
+ dialogue: null,
144
+ choices: null,
145
+ isWaitingForInput: false,
146
+ isFinished: false
147
+ };
148
+ this.snapshotStack = [];
149
+ this.listeners = new Set();
150
+ this.saveManager = new SaveManager();
151
+ }
152
+ getState() { return this.state; }
153
+ onStateChange(cb) { this.listeners.add(cb); return () => this.listeners.delete(cb); }
154
+ start() { this.execute(); }
155
+ next() {
156
+ if (this.state.isFinished || (this.state.choices && this.state.choices.length > 0)) return;
157
+ this.state.isWaitingForInput = false;
158
+ this.execute();
159
+ }
160
+ choose(idx) {
161
+ if (!this.state.choices || !this.state.choices[idx]) return;
162
+ const choice = this.state.choices[idx];
163
+ this.state.choices = null;
164
+ this.state.isWaitingForInput = false;
165
+ this.state.currentLabel = choice.targetLabel;
166
+ this.state.instructionPointer = 0;
167
+ this.execute();
168
+ }
169
+ rollback() {
170
+ if (this.snapshotStack.length <= 1) return false;
171
+ this.snapshotStack.pop();
172
+ const prev = this.snapshotStack[this.snapshotStack.length - 1];
173
+ if (prev) {
174
+ this.state = JSON.parse(JSON.stringify(prev));
175
+ this.notify();
176
+ return true;
177
+ }
178
+ return false;
179
+ }
180
+ canRollback() { return this.snapshotStack.length > 1; }
181
+ async save(slot) {
182
+ await this.saveManager.saveSlot(slot, this.state, this.state.dialogue ? this.state.dialogue.text : '');
183
+ }
184
+ async load(slot) {
185
+ const data = await this.saveManager.loadSlot(slot);
186
+ if (data && data.snapshot) {
187
+ this.state = JSON.parse(JSON.stringify(data.snapshot));
188
+ this.snapshotStack = [this.state];
189
+ this.notify();
190
+ return true;
191
+ }
192
+ return false;
193
+ }
194
+ execute() {
195
+ while (!this.state.isWaitingForInput && !this.state.isFinished) {
196
+ const list = this.story.labels[this.state.currentLabel];
197
+ if (!list || this.state.instructionPointer >= list.length) {
198
+ if (this.state.callStack.length > 0) {
199
+ const frame = this.state.callStack.pop();
200
+ this.state.currentLabel = frame.returnLabel;
201
+ this.state.instructionPointer = frame.returnPointer;
202
+ continue;
203
+ }
204
+ this.state.isFinished = true;
205
+ break;
206
+ }
207
+ const inst = list[this.state.instructionPointer++];
208
+ if (inst.type === 'scene') {
209
+ this.state.visual.background = inst.background;
210
+ this.state.visual.characters = {};
211
+ } else if (inst.type === 'show') {
212
+ this.state.visual.characters[inst.character] = {
213
+ expression: inst.expression,
214
+ position: inst.position || 'center'
215
+ };
216
+ } else if (inst.type === 'hide') {
217
+ delete this.state.visual.characters[inst.character];
218
+ } else if (inst.type === 'dialogue') {
219
+ const charDef = inst.speaker ? this.story.characters[inst.speaker] : null;
220
+ this.state.dialogue = {
221
+ speaker: inst.speaker,
222
+ speakerDisplayName: charDef ? charDef.name : inst.speaker,
223
+ speakerColor: charDef ? charDef.color : null,
224
+ text: inst.text
225
+ };
226
+ this.state.isWaitingForInput = true;
227
+ } else if (inst.type === 'choice') {
228
+ this.state.choices = inst.choices;
229
+ this.state.isWaitingForInput = true;
230
+ } else if (inst.type === 'jump') {
231
+ this.state.currentLabel = inst.targetLabel;
232
+ this.state.instructionPointer = 0;
233
+ } else if (inst.type === 'set') {
234
+ const curr = this.state.variables[inst.variable];
235
+ this.state.variables[inst.variable] = applySetOp(curr, inst.operator, inst.value, this.state.variables);
236
+ } else if (inst.type === 'branch') {
237
+ const ok = evaluateCondition(inst.condition, this.state.variables);
238
+ this.state.currentLabel = ok ? inst.thenLabel : (inst.elseLabel || inst.thenLabel);
239
+ this.state.instructionPointer = 0;
240
+ } else if (inst.type === 'return') {
241
+ this.state.isFinished = true;
242
+ }
243
+ }
244
+ if (this.state.isWaitingForInput) {
245
+ this.snapshotStack.push(JSON.parse(JSON.stringify(this.state)));
246
+ }
247
+ this.notify();
248
+ }
249
+ notify() { for (const l of this.listeners) l(this.state); }
250
+ }
251
+ class DOMRenderer {
252
+ constructor(vm, container) {
253
+ this.vm = vm;
254
+ this.container = container;
255
+ this.build();
256
+ vm.onStateChange(s => this.render(s));
257
+ this.render(vm.getState());
258
+ }
259
+ build() {
260
+ this.container.innerHTML = \`
261
+ <div class="kawa-root">
262
+ <div class="kawa-stage">
263
+ <div class="kawa-background"></div>
264
+ <div class="kawa-characters kawa-sprites"></div>
265
+ <div class="kawa-ui-layer">
266
+ <div class="kawa-choice-container kawa-choices" style="display:none"></div>
267
+ <div class="kawa-dialogue-box kawa-dialogue">
268
+ <div class="kawa-speaker-tag kawa-speaker" style="display:none"></div>
269
+ <div class="kawa-dialogue-text kawa-text"></div>
270
+ <div class="kawa-continue-indicator">&#9660;</div>
271
+ </div>
272
+ <nav class="kawa-quick-menu">
273
+ <button class="kawa-btn kawa-back">Back</button>
274
+ <button class="kawa-btn kawa-save">Save</button>
275
+ <button class="kawa-btn kawa-load">Load</button>
276
+ </nav>
277
+ </div>
278
+ </div>
279
+ </div>\`;
280
+ this.bgEl = this.container.querySelector('.kawa-background');
281
+ this.charsEl = this.container.querySelector('.kawa-characters');
282
+ this.boxEl = this.container.querySelector('.kawa-dialogue-box');
283
+ this.spkEl = this.container.querySelector('.kawa-speaker-tag');
284
+ this.txtEl = this.container.querySelector('.kawa-dialogue-text');
285
+ this.choiceEl = this.container.querySelector('.kawa-choice-container');
286
+ this.backBtn = this.container.querySelector('.kawa-back');
287
+ this.saveBtn = this.container.querySelector('.kawa-save');
288
+ this.loadBtn = this.container.querySelector('.kawa-load');
289
+
290
+ this.boxEl.addEventListener('click', () => this.vm.next());
291
+ this.backBtn.addEventListener('click', (e) => { e.stopPropagation(); this.vm.rollback(); });
292
+ this.saveBtn.addEventListener('click', async (e) => { e.stopPropagation(); await this.vm.save('1'); alert('Saved to Slot 1'); });
293
+ this.loadBtn.addEventListener('click', async (e) => { e.stopPropagation(); if (await this.vm.load('1')) alert('Loaded Slot 1'); });
294
+ window.addEventListener('keydown', (e) => {
295
+ if (e.code === 'Space' || e.code === 'Enter') { e.preventDefault(); this.vm.next(); }
296
+ if (e.code === 'Backspace') { e.preventDefault(); this.vm.rollback(); }
297
+ });
298
+ }
299
+ render(state) {
300
+ this.backBtn.disabled = !this.vm.canRollback();
301
+ if (state.visual.background) {
302
+ const bg = state.visual.background.replace(/^bg\\s+/, '');
303
+ this.bgEl.style.backgroundImage = 'url("./assets/backgrounds/' + bg + (bg.includes('.') ? '' : '.png') + '")';
304
+ this.bgEl.style.opacity = '1';
305
+ } else {
306
+ this.bgEl.style.opacity = '0';
307
+ }
308
+
309
+ this.charsEl.innerHTML = '';
310
+ for (const [id, char] of Object.entries(state.visual.characters)) {
311
+ const div = document.createElement('div');
312
+ div.className = 'kawa-sprite kawa-pos-' + (char.position || 'center');
313
+ const img = document.createElement('img');
314
+ const expr = char.expression ? '/' + char.expression : '';
315
+ img.src = './assets/characters/' + id + expr + '.png';
316
+ img.alt = id;
317
+ img.onerror = () => {
318
+ img.style.display = 'none';
319
+ div.style.width = '200px'; div.style.height = '400px';
320
+ div.style.background = 'rgba(244,63,94,0.3)'; div.style.border = '2px dashed #f43f5e';
321
+ div.style.borderRadius = '16px'; div.style.display = 'flex'; div.style.alignItems = 'center';
322
+ div.style.justifyContent = 'center'; div.style.color = '#fff';
323
+ div.textContent = id + (char.expression ? ' (' + char.expression + ')' : '');
324
+ };
325
+ div.appendChild(img);
326
+ this.charsEl.appendChild(div);
327
+ }
328
+
329
+ if (state.dialogue) {
330
+ this.boxEl.style.display = 'block';
331
+ if (state.dialogue.speakerDisplayName) {
332
+ this.spkEl.style.display = 'inline-block';
333
+ this.spkEl.textContent = state.dialogue.speakerDisplayName;
334
+ if (state.dialogue.speakerColor) this.spkEl.style.backgroundColor = state.dialogue.speakerColor;
335
+ } else {
336
+ this.spkEl.style.display = 'none';
337
+ }
338
+ this.txtEl.textContent = state.dialogue.text;
339
+ } else {
340
+ this.boxEl.style.display = 'none';
341
+ }
342
+
343
+ if (state.choices && state.choices.length > 0) {
344
+ this.choiceEl.innerHTML = '';
345
+ this.choiceEl.style.display = 'flex';
346
+ state.choices.forEach((c, idx) => {
347
+ const btn = document.createElement('button');
348
+ btn.className = 'kawa-choice-btn kawa-choice';
349
+ btn.textContent = c.text;
350
+ btn.addEventListener('click', (e) => { e.stopPropagation(); this.vm.choose(idx); });
351
+ this.choiceEl.appendChild(btn);
352
+ });
353
+ } else {
354
+ this.choiceEl.style.display = 'none';
355
+ }
356
+ }
357
+ }
358
+
359
+ const vm = new StoryVM(story);
360
+ new DOMRenderer(vm, document.getElementById('app'));
361
+ vm.start();
362
+ </script>
363
+ </body>
364
+ </html>`;
365
+ fs.writeFileSync(path.join(outDir, 'index.html'), htmlContent, 'utf-8');
366
+ console.log(`✅ Static web application generated: dist/index.html`);
367
+ console.log(`\n🎉 Production build complete! Ready for GitHub Pages, Netlify, itch.io, etc.\n`);
368
+ return true;
369
+ }
370
+ function copyDirectoryRecursive(src, dest) {
371
+ fs.mkdirSync(dest, { recursive: true });
372
+ const entries = fs.readdirSync(src, { withFileTypes: true });
373
+ for (const entry of entries) {
374
+ const srcPath = path.join(src, entry.name);
375
+ const destPath = path.join(dest, entry.name);
376
+ if (entry.isDirectory()) {
377
+ copyDirectoryRecursive(srcPath, destPath);
378
+ }
379
+ else {
380
+ fs.copyFileSync(srcPath, destPath);
381
+ }
382
+ }
383
+ }
384
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAM7E,MAAM,UAAU,YAAY,CAAC,UAAU,GAAG,GAAG,EAAE,UAAwB,EAAE;IACvE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;IAE/D,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,cAAc,OAAO,EAAE,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,cAAc,MAAM,IAAI,CAAC,CAAC;IAEtC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,KAAK,CAAC,yBAAyB,UAAU,0DAA0D,CAAC,CAAC;QAC7G,OAAO,KAAK,CAAC;IACf,CAAC;IAED,yBAAyB;IACzB,IAAI,YAAY,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACpD,YAAY,GAAG,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,GAAG,CAAC,mCAAmC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,MAAM,WAAW,CAAC,CAAC;IACrG,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACpD,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1D,CAAC;aAAM,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;YAChC,OAAO,CAAC,KAAK,CAAC,wBAAwB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACvD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,8BAA8B;IAC9B,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,iBAAiB;IACjB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACjD,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,sBAAsB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IACjD,CAAC;IAED,iCAAiC;IACjC,IAAI,WAAW,GAAG,oCAAoC,CAAC;IACvD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IAC/I,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAChC,WAAW,IAAI,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IACjE,CAAC;IACD,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,WAAW,IAAI,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;IAEtD,0CAA0C;IAC1C,MAAM,WAAW,GAAG;;;;;WAKX,YAAY,CAAC,IAAI,CAAC,KAAK,IAAI,sBAAsB;;;;;;;;;;oBAUxC,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAoSjD,CAAC;IAEP,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IACxE,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,kFAAkF,CAAC,CAAC;IAChG,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,sBAAsB,CAAC,GAAW,EAAE,IAAY;IACvD,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAE7C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,sBAAsB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC5C,CAAC;aAAM,CAAC;YACN,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ export interface DevServerOptions {
2
+ port?: number;
3
+ open?: boolean;
4
+ }
5
+ export declare function startDevServer(projectDir?: string, options?: DevServerOptions): void;
6
+ //# sourceMappingURL=dev.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,cAAc,CAAC,UAAU,SAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,IAAI,CAkMrF"}
@@ -0,0 +1,485 @@
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
+ }
214
+ function evaluateCondition(cond, vars) {
215
+ const t = (cond || '').trim();
216
+ if (!t || t === 'true') return true;
217
+ if (t === 'false') return false;
218
+ const ops = ['>=', '<=', '!=', '==', '>', '<'];
219
+ for (const op of ops) {
220
+ const idx = t.indexOf(op);
221
+ if (idx !== -1) {
222
+ const l = resolveVal(t.slice(0, idx), vars);
223
+ const r = resolveVal(t.slice(idx + op.length), vars);
224
+ if (op === '>=') return Number(l) >= Number(r);
225
+ if (op === '<=') return Number(l) <= Number(r);
226
+ if (op === '>') return Number(l) > Number(r);
227
+ if (op === '<') return Number(l) < Number(r);
228
+ if (op === '==') return l == r;
229
+ if (op === '!=') return l != r;
230
+ }
231
+ }
232
+ if (t.startsWith('!')) return !Boolean(vars[t.slice(1).trim()]);
233
+ return Boolean(vars[t]);
234
+ }
235
+ function resolveVal(token, vars) {
236
+ const t = token.trim();
237
+ if (t === 'true') return true;
238
+ if (t === 'false') return false;
239
+ if (!isNaN(Number(t)) && t !== '') return Number(t);
240
+ if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) return t.slice(1, -1);
241
+ if (t in vars) return vars[t];
242
+ return t;
243
+ }
244
+ function applySetOp(curr, op, val, vars) {
245
+ const res = (typeof val === 'string' && val in vars) ? vars[val] : val;
246
+ if (op === '+=') return Number(curr || 0) + Number(res);
247
+ if (op === '-=') return Number(curr || 0) - Number(res);
248
+ return res;
249
+ }
250
+ class StoryVM {
251
+ constructor(story) {
252
+ this.story = story;
253
+ this.state = {
254
+ currentLabel: story.meta.startLabel || 'start',
255
+ instructionPointer: 0,
256
+ callStack: [],
257
+ variables: {},
258
+ visual: { background: null, transition: null, characters: {} },
259
+ audio: { music: null, voice: null },
260
+ dialogue: null,
261
+ choices: null,
262
+ isWaitingForInput: false,
263
+ isFinished: false
264
+ };
265
+ this.snapshotStack = [];
266
+ this.listeners = new Set();
267
+ this.history = [];
268
+ this.saveManager = new SaveManager();
269
+ }
270
+ getState() { return this.state; }
271
+ onStateChange(cb) { this.listeners.add(cb); return () => this.listeners.delete(cb); }
272
+ start() { this.execute(); }
273
+ next() {
274
+ if (this.state.isFinished || (this.state.choices && this.state.choices.length > 0)) return;
275
+ this.state.isWaitingForInput = false;
276
+ this.execute();
277
+ }
278
+ choose(idx) {
279
+ if (!this.state.choices || !this.state.choices[idx]) return;
280
+ const choice = this.state.choices[idx];
281
+ this.state.choices = null;
282
+ this.state.isWaitingForInput = false;
283
+ this.state.currentLabel = choice.targetLabel;
284
+ this.state.instructionPointer = 0;
285
+ this.execute();
286
+ }
287
+ rollback() {
288
+ if (this.snapshotStack.length <= 1) return false;
289
+ this.snapshotStack.pop();
290
+ const prev = this.snapshotStack[this.snapshotStack.length - 1];
291
+ if (prev) {
292
+ this.state = JSON.parse(JSON.stringify(prev));
293
+ this.notify();
294
+ return true;
295
+ }
296
+ return false;
297
+ }
298
+ canRollback() { return this.snapshotStack.length > 1; }
299
+ async save(slot) {
300
+ await this.saveManager.saveSlot(slot, this.state, this.state.dialogue ? this.state.dialogue.text : '');
301
+ }
302
+ async load(slot) {
303
+ const data = await this.saveManager.loadSlot(slot);
304
+ if (data && data.snapshot) {
305
+ this.state = JSON.parse(JSON.stringify(data.snapshot));
306
+ this.snapshotStack = [this.state];
307
+ this.notify();
308
+ return true;
309
+ }
310
+ return false;
311
+ }
312
+ execute() {
313
+ while (!this.state.isWaitingForInput && !this.state.isFinished) {
314
+ const list = this.story.labels[this.state.currentLabel];
315
+ if (!list || this.state.instructionPointer >= list.length) {
316
+ if (this.state.callStack.length > 0) {
317
+ const frame = this.state.callStack.pop();
318
+ this.state.currentLabel = frame.returnLabel;
319
+ this.state.instructionPointer = frame.returnPointer;
320
+ continue;
321
+ }
322
+ this.state.isFinished = true;
323
+ break;
324
+ }
325
+ const inst = list[this.state.instructionPointer++];
326
+ if (inst.type === 'scene') {
327
+ this.state.visual.background = inst.background;
328
+ this.state.visual.characters = {};
329
+ } else if (inst.type === 'show') {
330
+ this.state.visual.characters[inst.character] = {
331
+ expression: inst.expression,
332
+ position: inst.position || 'center'
333
+ };
334
+ } else if (inst.type === 'hide') {
335
+ delete this.state.visual.characters[inst.character];
336
+ } else if (inst.type === 'dialogue') {
337
+ const charDef = inst.speaker ? this.story.characters[inst.speaker] : null;
338
+ this.state.dialogue = {
339
+ speaker: inst.speaker,
340
+ speakerDisplayName: charDef ? charDef.name : inst.speaker,
341
+ speakerColor: charDef ? charDef.color : null,
342
+ text: inst.text
343
+ };
344
+ this.state.isWaitingForInput = true;
345
+ this.history.push(this.state.dialogue);
346
+ } else if (inst.type === 'choice') {
347
+ this.state.choices = inst.choices;
348
+ this.state.isWaitingForInput = true;
349
+ } else if (inst.type === 'jump') {
350
+ this.state.currentLabel = inst.targetLabel;
351
+ this.state.instructionPointer = 0;
352
+ } else if (inst.type === 'set') {
353
+ const curr = this.state.variables[inst.variable];
354
+ this.state.variables[inst.variable] = applySetOp(curr, inst.operator, inst.value, this.state.variables);
355
+ } else if (inst.type === 'branch') {
356
+ const ok = evaluateCondition(inst.condition, this.state.variables);
357
+ this.state.currentLabel = ok ? inst.thenLabel : (inst.elseLabel || inst.thenLabel);
358
+ this.state.instructionPointer = 0;
359
+ } else if (inst.type === 'return') {
360
+ this.state.isFinished = true;
361
+ }
362
+ }
363
+ if (this.state.isWaitingForInput) {
364
+ this.snapshotStack.push(JSON.parse(JSON.stringify(this.state)));
365
+ }
366
+ this.notify();
367
+ }
368
+ notify() { for (const l of this.listeners) l(this.state); }
369
+ }
370
+ class DOMRenderer {
371
+ constructor(vm, container) {
372
+ this.vm = vm;
373
+ this.container = container;
374
+ this.build();
375
+ vm.onStateChange(s => this.render(s));
376
+ this.render(vm.getState());
377
+ }
378
+ build() {
379
+ this.container.innerHTML = \`
380
+ <div class="kawa-root">
381
+ <div class="kawa-stage">
382
+ <div class="kawa-background"></div>
383
+ <div class="kawa-characters kawa-sprites"></div>
384
+ <div class="kawa-ui-layer">
385
+ <div class="kawa-choice-container kawa-choices" style="display:none"></div>
386
+ <div class="kawa-dialogue-box kawa-dialogue">
387
+ <div class="kawa-speaker-tag kawa-speaker" style="display:none"></div>
388
+ <div class="kawa-dialogue-text kawa-text"></div>
389
+ <div class="kawa-continue-indicator">&#9660;</div>
390
+ </div>
391
+ <nav class="kawa-quick-menu">
392
+ <button class="kawa-btn kawa-back">Back</button>
393
+ <button class="kawa-btn kawa-save">Save</button>
394
+ <button class="kawa-btn kawa-load">Load</button>
395
+ </nav>
396
+ </div>
397
+ </div>
398
+ </div>\`;
399
+ this.bgEl = this.container.querySelector('.kawa-background');
400
+ this.charsEl = this.container.querySelector('.kawa-characters');
401
+ this.boxEl = this.container.querySelector('.kawa-dialogue-box');
402
+ this.spkEl = this.container.querySelector('.kawa-speaker-tag');
403
+ this.txtEl = this.container.querySelector('.kawa-dialogue-text');
404
+ this.choiceEl = this.container.querySelector('.kawa-choice-container');
405
+ this.backBtn = this.container.querySelector('.kawa-back');
406
+ this.saveBtn = this.container.querySelector('.kawa-save');
407
+ this.loadBtn = this.container.querySelector('.kawa-load');
408
+
409
+ this.boxEl.addEventListener('click', () => this.vm.next());
410
+ this.backBtn.addEventListener('click', (e) => { e.stopPropagation(); this.vm.rollback(); });
411
+ this.saveBtn.addEventListener('click', async (e) => { e.stopPropagation(); await this.vm.save('1'); alert('Saved to Slot 1'); });
412
+ this.loadBtn.addEventListener('click', async (e) => { e.stopPropagation(); if (await this.vm.load('1')) alert('Loaded Slot 1'); });
413
+ window.addEventListener('keydown', (e) => {
414
+ if (e.code === 'Space' || e.code === 'Enter') { e.preventDefault(); this.vm.next(); }
415
+ if (e.code === 'Backspace') { e.preventDefault(); this.vm.rollback(); }
416
+ });
417
+ }
418
+ render(state) {
419
+ this.backBtn.disabled = !this.vm.canRollback();
420
+ if (state.visual.background) {
421
+ const bg = state.visual.background.replace(/^bg\\s+/, '');
422
+ this.bgEl.style.backgroundImage = 'url("/assets/backgrounds/' + bg + (bg.includes('.') ? '' : '.png') + '")';
423
+ this.bgEl.style.opacity = '1';
424
+ } else {
425
+ this.bgEl.style.opacity = '0';
426
+ }
427
+
428
+ this.charsEl.innerHTML = '';
429
+ for (const [id, char] of Object.entries(state.visual.characters)) {
430
+ const div = document.createElement('div');
431
+ div.className = 'kawa-sprite kawa-pos-' + (char.position || 'center');
432
+ const img = document.createElement('img');
433
+ const expr = char.expression ? '/' + char.expression : '';
434
+ img.src = '/assets/characters/' + id + expr + '.png';
435
+ img.alt = id;
436
+ img.onerror = () => {
437
+ img.style.display = 'none';
438
+ div.style.width = '200px'; div.style.height = '400px';
439
+ div.style.background = 'rgba(244,63,94,0.3)'; div.style.border = '2px dashed #f43f5e';
440
+ div.style.borderRadius = '16px'; div.style.display = 'flex'; div.style.alignItems = 'center';
441
+ div.style.justifyContent = 'center'; div.style.color = '#fff';
442
+ div.textContent = id + (char.expression ? ' (' + char.expression + ')' : '');
443
+ };
444
+ div.appendChild(img);
445
+ this.charsEl.appendChild(div);
446
+ }
447
+
448
+ if (state.dialogue) {
449
+ this.boxEl.style.display = 'block';
450
+ if (state.dialogue.speakerDisplayName) {
451
+ this.spkEl.style.display = 'inline-block';
452
+ this.spkEl.textContent = state.dialogue.speakerDisplayName;
453
+ if (state.dialogue.speakerColor) this.spkEl.style.backgroundColor = state.dialogue.speakerColor;
454
+ } else {
455
+ this.spkEl.style.display = 'none';
456
+ }
457
+ this.txtEl.textContent = state.dialogue.text;
458
+ } else {
459
+ this.boxEl.style.display = 'none';
460
+ }
461
+
462
+ if (state.choices && state.choices.length > 0) {
463
+ this.choiceEl.innerHTML = '';
464
+ this.choiceEl.style.display = 'flex';
465
+ state.choices.forEach((c, idx) => {
466
+ const btn = document.createElement('button');
467
+ btn.className = 'kawa-choice-btn kawa-choice';
468
+ btn.textContent = c.text;
469
+ btn.addEventListener('click', (e) => { e.stopPropagation(); this.vm.choose(idx); });
470
+ this.choiceEl.appendChild(btn);
471
+ });
472
+ } else {
473
+ this.choiceEl.style.display = 'none';
474
+ }
475
+ }
476
+ }
477
+ function mountKawaApp(story, container) {
478
+ const vm = new StoryVM(story);
479
+ const renderer = new DOMRenderer(vm, container);
480
+ vm.start();
481
+ return { vm, renderer };
482
+ }
483
+ `;
484
+ }
485
+ //# 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
@@ -4,6 +4,8 @@ export * from '@kawaijs/runtime';
4
4
  export * from '@kawaijs/renderer-dom';
5
5
  import { createProject } from './commands/create.js';
6
6
  import { validateProject } from './commands/validate.js';
7
- export { createProject, validateProject };
7
+ import { startDevServer } from './commands/dev.js';
8
+ import { buildProject } from './commands/build.js';
9
+ export { createProject, validateProject, startDevServer, buildProject };
8
10
  export declare function runCLI(args: string[]): void;
9
11
  //# 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;AAEtC,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,CAoE3C"}
package/dist/index.js CHANGED
@@ -4,7 +4,9 @@ export * from '@kawaijs/runtime';
4
4
  export * from '@kawaijs/renderer-dom';
5
5
  import { createProject } from './commands/create.js';
6
6
  import { validateProject } from './commands/validate.js';
7
- export { createProject, validateProject };
7
+ import { startDevServer } from './commands/dev.js';
8
+ import { buildProject } from './commands/build.js';
9
+ export { createProject, validateProject, startDevServer, buildProject };
8
10
  export function runCLI(args) {
9
11
  const command = args[0];
10
12
  switch (command) {
@@ -13,6 +15,19 @@ export function runCLI(args) {
13
15
  createProject(projectName);
14
16
  break;
15
17
  }
18
+ case 'dev': {
19
+ const targetPath = args[1] ?? '.';
20
+ startDevServer(targetPath);
21
+ break;
22
+ }
23
+ case 'build': {
24
+ const targetPath = args[1] ?? '.';
25
+ const ok = buildProject(targetPath);
26
+ if (!ok) {
27
+ process.exitCode = 1;
28
+ }
29
+ break;
30
+ }
16
31
  case 'validate': {
17
32
  const targetPath = args[1] ?? '.';
18
33
  const ok = validateProject(targetPath);
@@ -24,7 +39,7 @@ export function runCLI(args) {
24
39
  case 'version':
25
40
  case '-v':
26
41
  case '--version': {
27
- console.log('Kawaijs v0.1.0');
42
+ console.log('Kawaijs v0.1.1');
28
43
  break;
29
44
  }
30
45
  case 'help':
@@ -40,16 +55,16 @@ Usage:
40
55
 
41
56
  Commands:
42
57
  create <name> Scaffold a new visual novel project
58
+ dev [path] Start the local development server (with live reload)
59
+ build [path] Build a static production web bundle (dist/)
43
60
  validate [path] Validate Kawa Script syntax, labels, and links
44
- dev Start the local development server (with HMR)
45
- build Build a static production web bundle
46
61
  help Show this help message
47
62
  version Show version information
48
63
 
49
64
  Example:
50
65
  kawa create my-novel
51
66
  cd my-novel
52
- kawa validate
67
+ kawa dev
53
68
  `);
54
69
  break;
55
70
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","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,MAAM,UAAU,MAAM,CAAC,IAAc;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAExB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC;YAC1C,aAAa,CAAC,WAAW,CAAC,CAAC;YAC3B,MAAM;QACR,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;YAClC,MAAM,EAAE,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;YACvC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACvB,CAAC;YACD,MAAM;QACR,CAAC;QAED,KAAK,SAAS,CAAC;QACf,KAAK,IAAI,CAAC;QACV,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAC9B,MAAM;QACR,CAAC;QAED,KAAK,MAAM,CAAC;QACZ,KAAK,IAAI,CAAC;QACV,KAAK,QAAQ,CAAC;QACd,OAAO,CAAC,CAAC,CAAC;YACR,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;CAmBjB,CAAC,CAAC;YACG,MAAM;QACR,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","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;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,MAAM,UAAU,MAAM,CAAC,IAAc;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAExB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC;YAC1C,aAAa,CAAC,WAAW,CAAC,CAAC;YAC3B,MAAM;QACR,CAAC;QAED,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;YAClC,cAAc,CAAC,UAAU,CAAC,CAAC;YAC3B,MAAM;QACR,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;YAClC,MAAM,EAAE,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;YACpC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACvB,CAAC;YACD,MAAM;QACR,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;YAClC,MAAM,EAAE,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;YACvC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACvB,CAAC;YACD,MAAM;QACR,CAAC;QAED,KAAK,SAAS,CAAC;QACf,KAAK,IAAI,CAAC;QACV,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAC9B,MAAM;QACR,CAAC;QAED,KAAK,MAAM,CAAC;QACZ,KAAK,IAAI,CAAC;QACV,KAAK,QAAQ,CAAC;QACd,OAAO,CAAC,CAAC,CAAC;YACR,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;CAmBjB,CAAC,CAAC;YACG,MAAM;QACR,CAAC;IACH,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kawaijs",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Web-native visual novel engine and toolchain inspired by Ren'Py",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -11,7 +11,8 @@
11
11
  },
12
12
  "files": [
13
13
  "dist",
14
- "bin"
14
+ "bin",
15
+ "README.md"
15
16
  ],
16
17
  "publishConfig": {
17
18
  "access": "public"
@@ -30,10 +31,10 @@
30
31
  "typecheck": "tsc --noEmit"
31
32
  },
32
33
  "dependencies": {
33
- "@kawaijs/ast": "^0.1.0",
34
- "@kawaijs/parser": "^0.1.0",
35
- "@kawaijs/runtime": "^0.1.0",
36
- "@kawaijs/renderer-dom": "^0.1.0"
34
+ "@kawaijs/ast": "^0.1.2",
35
+ "@kawaijs/parser": "^0.1.2",
36
+ "@kawaijs/runtime": "^0.1.2",
37
+ "@kawaijs/renderer-dom": "^0.1.2"
37
38
  },
38
39
  "keywords": [
39
40
  "kawaijs",