@sharpee/sharpee 0.9.75-beta → 0.9.77-beta
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/cli/init.d.ts.map +1 -1
- package/cli/init.js +6 -2
- package/cli/init.js.map +1 -1
- package/package.json +18 -18
- package/templates/browser/browser-entry.ts.template +274 -0
- package/templates/browser/index.html +75 -0
- package/templates/browser/styles.css +495 -0
- package/templates/story/index.ts.template +74 -0
- package/templates/story/package.json.template +25 -0
- package/templates/story/tsconfig.json.template +14 -0
package/cli/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../repos/sharpee/packages/sharpee/src/cli/init.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../repos/sharpee/packages/sharpee/src/cli/init.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA4DH;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuFlE"}
|
package/cli/init.js
CHANGED
|
@@ -42,8 +42,12 @@ exports.runInitCommand = runInitCommand;
|
|
|
42
42
|
const fs = __importStar(require("fs"));
|
|
43
43
|
const path = __importStar(require("path"));
|
|
44
44
|
const readline = __importStar(require("readline"));
|
|
45
|
-
// Template directory relative to this file
|
|
46
|
-
|
|
45
|
+
// Template directory relative to this file.
|
|
46
|
+
// In source: src/cli/ → ../../templates. In published output: cli/ → ../templates.
|
|
47
|
+
// Use a fallback to support both layouts.
|
|
48
|
+
const TEMPLATES_DIR = fs.existsSync(path.join(__dirname, '..', 'templates', 'story'))
|
|
49
|
+
? path.join(__dirname, '..', 'templates', 'story')
|
|
50
|
+
: path.join(__dirname, '..', '..', 'templates', 'story');
|
|
47
51
|
/**
|
|
48
52
|
* Prompt user for input
|
|
49
53
|
*/
|
package/cli/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../../repos/sharpee/packages/sharpee/src/cli/init.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../../repos/sharpee/packages/sharpee/src/cli/init.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sharpee/sharpee",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.77-beta",
|
|
4
4
|
"description": "Sharpee - Interactive Fiction Engine for TypeScript",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -15,23 +15,23 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@sharpee/core": "^0.9.
|
|
19
|
-
"@sharpee/engine": "^0.9.
|
|
20
|
-
"@sharpee/event-processor": "^0.9.
|
|
21
|
-
"@sharpee/ext-testing": "^0.9.
|
|
22
|
-
"@sharpee/if-domain": "^0.9.
|
|
23
|
-
"@sharpee/if-services": "^0.9.
|
|
24
|
-
"@sharpee/lang-en-us": "^0.9.
|
|
25
|
-
"@sharpee/parser-en-us": "^0.9.
|
|
26
|
-
"@sharpee/platform-browser": "^0.9.
|
|
27
|
-
"@sharpee/plugin-npc": "^0.9.
|
|
28
|
-
"@sharpee/plugin-scheduler": "^0.9.
|
|
29
|
-
"@sharpee/plugin-state-machine": "^0.9.
|
|
30
|
-
"@sharpee/plugins": "^0.9.
|
|
31
|
-
"@sharpee/stdlib": "^0.9.
|
|
32
|
-
"@sharpee/text-blocks": "^0.9.
|
|
33
|
-
"@sharpee/text-service": "^0.9.
|
|
34
|
-
"@sharpee/world-model": "^0.9.
|
|
18
|
+
"@sharpee/core": "^0.9.77-beta",
|
|
19
|
+
"@sharpee/engine": "^0.9.77-beta",
|
|
20
|
+
"@sharpee/event-processor": "^0.9.77-beta",
|
|
21
|
+
"@sharpee/ext-testing": "^0.9.77-beta",
|
|
22
|
+
"@sharpee/if-domain": "^0.9.77-beta",
|
|
23
|
+
"@sharpee/if-services": "^0.9.77-beta",
|
|
24
|
+
"@sharpee/lang-en-us": "^0.9.77-beta",
|
|
25
|
+
"@sharpee/parser-en-us": "^0.9.77-beta",
|
|
26
|
+
"@sharpee/platform-browser": "^0.9.77-beta",
|
|
27
|
+
"@sharpee/plugin-npc": "^0.9.77-beta",
|
|
28
|
+
"@sharpee/plugin-scheduler": "^0.9.77-beta",
|
|
29
|
+
"@sharpee/plugin-state-machine": "^0.9.77-beta",
|
|
30
|
+
"@sharpee/plugins": "^0.9.77-beta",
|
|
31
|
+
"@sharpee/stdlib": "^0.9.77-beta",
|
|
32
|
+
"@sharpee/text-blocks": "^0.9.77-beta",
|
|
33
|
+
"@sharpee/text-service": "^0.9.77-beta",
|
|
34
|
+
"@sharpee/world-model": "^0.9.77-beta",
|
|
35
35
|
"fflate": "^0.8.2"
|
|
36
36
|
},
|
|
37
37
|
"keywords": [
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Entry Point for {{STORY_TITLE}}
|
|
3
|
+
*
|
|
4
|
+
* This file connects your story to the browser UI.
|
|
5
|
+
* Generated by: npx sharpee init-browser
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { GameEngine } from '@sharpee/engine';
|
|
9
|
+
import { WorldModel, EntityType } from '@sharpee/world-model';
|
|
10
|
+
import { Parser } from '@sharpee/parser-en-us';
|
|
11
|
+
import { LanguageProvider } from '@sharpee/lang-en-us';
|
|
12
|
+
import { PerceptionService } from '@sharpee/stdlib';
|
|
13
|
+
import { story, config } from './index';
|
|
14
|
+
|
|
15
|
+
// DOM elements
|
|
16
|
+
let statusLocation: HTMLElement | null;
|
|
17
|
+
let statusScore: HTMLElement | null;
|
|
18
|
+
let textContent: HTMLElement | null;
|
|
19
|
+
let mainWindow: HTMLElement | null;
|
|
20
|
+
let commandInput: HTMLInputElement | null;
|
|
21
|
+
|
|
22
|
+
// Game state
|
|
23
|
+
let engine: GameEngine;
|
|
24
|
+
let world: WorldModel;
|
|
25
|
+
let commandHistory: string[] = [];
|
|
26
|
+
let historyIndex = -1;
|
|
27
|
+
let currentTurn = 0;
|
|
28
|
+
let currentScore = 0;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the game
|
|
32
|
+
*/
|
|
33
|
+
function initializeGame(): void {
|
|
34
|
+
// Create world and player
|
|
35
|
+
world = new WorldModel();
|
|
36
|
+
const player = world.createEntity('player', EntityType.ACTOR);
|
|
37
|
+
world.setPlayer(player.id);
|
|
38
|
+
|
|
39
|
+
// Create parser and language
|
|
40
|
+
const language = new LanguageProvider();
|
|
41
|
+
const parser = new Parser(language);
|
|
42
|
+
|
|
43
|
+
// Extend parser and language with story-specific vocabulary
|
|
44
|
+
if (story.extendParser) {
|
|
45
|
+
story.extendParser(parser);
|
|
46
|
+
}
|
|
47
|
+
if (story.extendLanguage) {
|
|
48
|
+
story.extendLanguage(language);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Create perception service
|
|
52
|
+
const perceptionService = new PerceptionService();
|
|
53
|
+
|
|
54
|
+
// Create engine
|
|
55
|
+
engine = new GameEngine({
|
|
56
|
+
world,
|
|
57
|
+
player,
|
|
58
|
+
parser,
|
|
59
|
+
language,
|
|
60
|
+
perceptionService,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Set up event handlers
|
|
64
|
+
engine.on('text:output', (text: string, turn: number) => {
|
|
65
|
+
displayText(text);
|
|
66
|
+
currentTurn = turn;
|
|
67
|
+
updateStatusLine();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
engine.on('event', (event: any) => {
|
|
71
|
+
// Track score changes
|
|
72
|
+
if (event.type === 'game.score_changed' && event.data) {
|
|
73
|
+
currentScore = event.data.newScore ?? currentScore;
|
|
74
|
+
updateStatusLine();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Set the story
|
|
79
|
+
engine.setStory(story);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Set up DOM elements and event handlers
|
|
84
|
+
*/
|
|
85
|
+
function setupDOM(): void {
|
|
86
|
+
statusLocation = document.getElementById('location-name');
|
|
87
|
+
statusScore = document.getElementById('score-turns');
|
|
88
|
+
textContent = document.getElementById('text-content');
|
|
89
|
+
mainWindow = document.getElementById('main-window');
|
|
90
|
+
commandInput = document.getElementById('command-input') as HTMLInputElement;
|
|
91
|
+
|
|
92
|
+
if (!commandInput) {
|
|
93
|
+
console.error('Command input element not found');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Handle keyboard input
|
|
98
|
+
commandInput.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
99
|
+
if (e.key === 'Enter') {
|
|
100
|
+
handleCommand();
|
|
101
|
+
} else if (e.key === 'ArrowUp') {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
navigateHistory(-1);
|
|
104
|
+
} else if (e.key === 'ArrowDown') {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
navigateHistory(1);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Keep focus on input
|
|
111
|
+
document.addEventListener('click', () => {
|
|
112
|
+
if (commandInput && !commandInput.disabled) {
|
|
113
|
+
commandInput.focus();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Handle command submission
|
|
120
|
+
*/
|
|
121
|
+
async function handleCommand(): Promise<void> {
|
|
122
|
+
if (!commandInput) return;
|
|
123
|
+
|
|
124
|
+
const command = commandInput.value.trim();
|
|
125
|
+
if (!command) return;
|
|
126
|
+
|
|
127
|
+
// Add to history
|
|
128
|
+
commandHistory.push(command);
|
|
129
|
+
historyIndex = commandHistory.length;
|
|
130
|
+
|
|
131
|
+
// Clear input
|
|
132
|
+
commandInput.value = '';
|
|
133
|
+
|
|
134
|
+
// Display command echo
|
|
135
|
+
displayCommand(command);
|
|
136
|
+
|
|
137
|
+
// Execute command
|
|
138
|
+
try {
|
|
139
|
+
await engine.executeTurn(command);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
142
|
+
displayText(`[Error: ${message}]`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Navigate command history
|
|
148
|
+
*/
|
|
149
|
+
function navigateHistory(direction: number): void {
|
|
150
|
+
if (!commandInput) return;
|
|
151
|
+
|
|
152
|
+
const newIndex = historyIndex + direction;
|
|
153
|
+
|
|
154
|
+
if (newIndex < 0) return;
|
|
155
|
+
|
|
156
|
+
if (newIndex >= commandHistory.length) {
|
|
157
|
+
historyIndex = commandHistory.length;
|
|
158
|
+
commandInput.value = '';
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
historyIndex = newIndex;
|
|
163
|
+
commandInput.value = commandHistory[historyIndex];
|
|
164
|
+
|
|
165
|
+
// Move cursor to end
|
|
166
|
+
commandInput.setSelectionRange(
|
|
167
|
+
commandInput.value.length,
|
|
168
|
+
commandInput.value.length
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Display text in the main window
|
|
174
|
+
*/
|
|
175
|
+
function displayText(text: string): void {
|
|
176
|
+
if (!textContent) return;
|
|
177
|
+
|
|
178
|
+
// Split on double newlines to get paragraphs
|
|
179
|
+
const paragraphs = text.split(/\n\n+/);
|
|
180
|
+
|
|
181
|
+
for (const para of paragraphs) {
|
|
182
|
+
const trimmed = para.trim();
|
|
183
|
+
if (trimmed) {
|
|
184
|
+
const p = document.createElement('p');
|
|
185
|
+
p.style.whiteSpace = 'pre-line';
|
|
186
|
+
p.textContent = trimmed;
|
|
187
|
+
textContent.appendChild(p);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
scrollToBottom();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Display command echo
|
|
196
|
+
*/
|
|
197
|
+
function displayCommand(command: string): void {
|
|
198
|
+
if (!textContent) return;
|
|
199
|
+
|
|
200
|
+
const div = document.createElement('div');
|
|
201
|
+
div.className = 'command-echo';
|
|
202
|
+
div.textContent = `> ${command}`;
|
|
203
|
+
textContent.appendChild(div);
|
|
204
|
+
|
|
205
|
+
scrollToBottom();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Update the status line
|
|
210
|
+
*/
|
|
211
|
+
function updateStatusLine(): void {
|
|
212
|
+
const player = world.getPlayer();
|
|
213
|
+
let locationName = '';
|
|
214
|
+
|
|
215
|
+
if (player) {
|
|
216
|
+
const locationId = world.getLocation(player.id);
|
|
217
|
+
if (locationId) {
|
|
218
|
+
const room = world.getEntity(locationId);
|
|
219
|
+
if (room) {
|
|
220
|
+
locationName = room.name || 'Unknown';
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (statusLocation) {
|
|
226
|
+
statusLocation.textContent = locationName;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (statusScore) {
|
|
230
|
+
statusScore.textContent = `Score: ${currentScore} | Turns: ${currentTurn}`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Scroll main window to bottom
|
|
236
|
+
*/
|
|
237
|
+
function scrollToBottom(): void {
|
|
238
|
+
if (mainWindow) {
|
|
239
|
+
mainWindow.scrollTop = mainWindow.scrollHeight;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Start the game
|
|
245
|
+
*/
|
|
246
|
+
async function start(): Promise<void> {
|
|
247
|
+
console.log('=== {{STORY_TITLE}} BROWSER START ===');
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
setupDOM();
|
|
251
|
+
initializeGame();
|
|
252
|
+
|
|
253
|
+
// Start the engine
|
|
254
|
+
await engine.start();
|
|
255
|
+
|
|
256
|
+
// Initial look
|
|
257
|
+
await engine.executeTurn('look');
|
|
258
|
+
|
|
259
|
+
// Focus input
|
|
260
|
+
if (commandInput) {
|
|
261
|
+
commandInput.focus();
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.error('=== STARTUP ERROR ===', error);
|
|
265
|
+
displayText(`[Startup Error: ${error}]`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Start when DOM is ready
|
|
270
|
+
if (document.readyState === 'loading') {
|
|
271
|
+
document.addEventListener('DOMContentLoaded', start);
|
|
272
|
+
} else {
|
|
273
|
+
start();
|
|
274
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{STORY_TITLE}}</title>
|
|
7
|
+
<link rel="stylesheet" href="styles.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="game-container">
|
|
11
|
+
<div id="status-line">
|
|
12
|
+
<span id="location-name"></span>
|
|
13
|
+
<span id="score-turns">Score: 0 | Turns: 0</span>
|
|
14
|
+
</div>
|
|
15
|
+
<div id="main-window">
|
|
16
|
+
<div id="text-content"></div>
|
|
17
|
+
</div>
|
|
18
|
+
<div id="input-area">
|
|
19
|
+
<span class="prompt">></span>
|
|
20
|
+
<input id="command-input" type="text"
|
|
21
|
+
autocomplete="off" autocapitalize="none"
|
|
22
|
+
spellcheck="false" autofocus>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<!-- Save/Restore Modal Dialogs -->
|
|
27
|
+
<div id="modal-overlay" class="modal-hidden">
|
|
28
|
+
<!-- Save Dialog -->
|
|
29
|
+
<div id="save-dialog" class="modal-dialog modal-hidden">
|
|
30
|
+
<div class="modal-title">SAVE GAME</div>
|
|
31
|
+
<div class="modal-content">
|
|
32
|
+
<div class="save-input-row">
|
|
33
|
+
<label for="save-name-input">Save name:</label>
|
|
34
|
+
<input type="text" id="save-name-input" maxlength="30"
|
|
35
|
+
autocomplete="off" spellcheck="false">
|
|
36
|
+
</div>
|
|
37
|
+
<div class="saves-list-label">Existing saves (click to overwrite):</div>
|
|
38
|
+
<div id="save-slots-list" class="saves-list"></div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="modal-buttons">
|
|
41
|
+
<button id="save-confirm-btn" class="modal-btn">Save</button>
|
|
42
|
+
<button id="save-cancel-btn" class="modal-btn">Cancel</button>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- Restore Dialog -->
|
|
47
|
+
<div id="restore-dialog" class="modal-dialog modal-hidden">
|
|
48
|
+
<div class="modal-title">RESTORE GAME</div>
|
|
49
|
+
<div class="modal-content">
|
|
50
|
+
<div class="saves-list-label">Select a saved game:</div>
|
|
51
|
+
<div id="restore-slots-list" class="saves-list"></div>
|
|
52
|
+
<div id="no-saves-message" class="no-saves-message modal-hidden">No saved games found.</div>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="modal-buttons">
|
|
55
|
+
<button id="restore-confirm-btn" class="modal-btn">Restore</button>
|
|
56
|
+
<button id="restore-cancel-btn" class="modal-btn">Cancel</button>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<!-- Startup Dialog (continue saved game?) -->
|
|
61
|
+
<div id="startup-dialog" class="modal-dialog modal-hidden">
|
|
62
|
+
<div class="modal-title">CONTINUE GAME?</div>
|
|
63
|
+
<div class="modal-content">
|
|
64
|
+
<p id="startup-save-info" class="startup-info"></p>
|
|
65
|
+
<p class="startup-question">Continue where you left off?</p>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="modal-buttons">
|
|
68
|
+
<button id="startup-continue-btn" class="modal-btn">Continue</button>
|
|
69
|
+
<button id="startup-new-btn" class="modal-btn">New Game</button>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<script src="{{STORY_ID}}.js"></script>
|
|
74
|
+
</body>
|
|
75
|
+
</html>
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/* Infocom DOS-era styling for Sharpee IF games */
|
|
2
|
+
|
|
3
|
+
/* DOS Color Palette */
|
|
4
|
+
:root {
|
|
5
|
+
--dos-blue: #0000aa;
|
|
6
|
+
--dos-cyan: #00aaaa;
|
|
7
|
+
--dos-white: #aaaaaa;
|
|
8
|
+
--dos-bright-white: #ffffff;
|
|
9
|
+
--dos-black: #000000;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
* {
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
margin: 0;
|
|
15
|
+
padding: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
html, body {
|
|
19
|
+
height: 100%;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
body {
|
|
24
|
+
background: var(--dos-blue);
|
|
25
|
+
color: var(--dos-bright-white);
|
|
26
|
+
font-family: "Perfect DOS VGA 437", "Consolas", "Courier New", monospace;
|
|
27
|
+
font-size: 16px;
|
|
28
|
+
line-height: 1.4;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#game-container {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
height: 100vh;
|
|
35
|
+
max-width: 80ch;
|
|
36
|
+
margin: 0 auto;
|
|
37
|
+
padding: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Status Line - Cyan background, black text */
|
|
41
|
+
#status-line {
|
|
42
|
+
background: var(--dos-cyan);
|
|
43
|
+
color: var(--dos-black);
|
|
44
|
+
padding: 4px 8px;
|
|
45
|
+
display: flex;
|
|
46
|
+
justify-content: space-between;
|
|
47
|
+
font-weight: bold;
|
|
48
|
+
flex-shrink: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#location-name {
|
|
52
|
+
text-transform: uppercase;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Main Text Window */
|
|
56
|
+
#main-window {
|
|
57
|
+
flex: 1;
|
|
58
|
+
overflow-y: auto;
|
|
59
|
+
padding: 8px;
|
|
60
|
+
padding-bottom: 1em;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#text-content p {
|
|
64
|
+
margin-bottom: 0.8em;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#text-content p:last-child {
|
|
68
|
+
margin-bottom: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Command echo styling */
|
|
72
|
+
.command-echo {
|
|
73
|
+
color: var(--dos-white);
|
|
74
|
+
margin-top: 1em;
|
|
75
|
+
margin-bottom: 0.5em;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Input Area */
|
|
79
|
+
#input-area {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
padding: 4px 8px;
|
|
83
|
+
border-top: 2px solid var(--dos-cyan);
|
|
84
|
+
flex-shrink: 0;
|
|
85
|
+
background: var(--dos-blue);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.prompt {
|
|
89
|
+
color: var(--dos-bright-white);
|
|
90
|
+
margin-right: 4px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#command-input {
|
|
94
|
+
flex: 1;
|
|
95
|
+
background: transparent;
|
|
96
|
+
border: none;
|
|
97
|
+
color: var(--dos-bright-white);
|
|
98
|
+
font-family: inherit;
|
|
99
|
+
font-size: inherit;
|
|
100
|
+
line-height: inherit;
|
|
101
|
+
outline: none;
|
|
102
|
+
caret-color: var(--dos-bright-white);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#command-input::placeholder {
|
|
106
|
+
color: var(--dos-white);
|
|
107
|
+
opacity: 0.5;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Hide scrollbar but allow scrolling (Webkit) */
|
|
111
|
+
#main-window::-webkit-scrollbar {
|
|
112
|
+
width: 8px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#main-window::-webkit-scrollbar-track {
|
|
116
|
+
background: var(--dos-blue);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#main-window::-webkit-scrollbar-thumb {
|
|
120
|
+
background: var(--dos-cyan);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Firefox scrollbar */
|
|
124
|
+
#main-window {
|
|
125
|
+
scrollbar-width: thin;
|
|
126
|
+
scrollbar-color: var(--dos-cyan) var(--dos-blue);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* System messages */
|
|
130
|
+
.system-message {
|
|
131
|
+
color: var(--dos-cyan);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Game over / victory */
|
|
135
|
+
.game-status {
|
|
136
|
+
color: var(--dos-bright-white);
|
|
137
|
+
text-align: center;
|
|
138
|
+
font-weight: bold;
|
|
139
|
+
margin: 1em 0;
|
|
140
|
+
padding: 0.5em;
|
|
141
|
+
border: 1px solid var(--dos-cyan);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Mobile adjustments */
|
|
145
|
+
@media (max-width: 600px) {
|
|
146
|
+
body {
|
|
147
|
+
font-size: 14px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#game-container {
|
|
151
|
+
max-width: 100%;
|
|
152
|
+
padding: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#status-line {
|
|
156
|
+
font-size: 12px;
|
|
157
|
+
padding: 6px 12px;
|
|
158
|
+
/* Safe area for notched phones */
|
|
159
|
+
padding-top: max(6px, env(safe-area-inset-top));
|
|
160
|
+
padding-left: max(12px, env(safe-area-inset-left));
|
|
161
|
+
padding-right: max(12px, env(safe-area-inset-right));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#main-window {
|
|
165
|
+
padding: 12px;
|
|
166
|
+
padding-left: max(12px, env(safe-area-inset-left));
|
|
167
|
+
padding-right: max(12px, env(safe-area-inset-right));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#input-area {
|
|
171
|
+
padding: 8px 12px;
|
|
172
|
+
padding-bottom: max(8px, env(safe-area-inset-bottom));
|
|
173
|
+
padding-left: max(12px, env(safe-area-inset-left));
|
|
174
|
+
padding-right: max(12px, env(safe-area-inset-right));
|
|
175
|
+
/* Larger touch target */
|
|
176
|
+
min-height: 48px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#command-input {
|
|
180
|
+
font-size: 16px; /* Prevents iOS zoom on focus */
|
|
181
|
+
padding: 8px 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.prompt {
|
|
185
|
+
font-size: 16px;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Small phones */
|
|
190
|
+
@media (max-width: 380px) {
|
|
191
|
+
body {
|
|
192
|
+
font-size: 13px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#status-line {
|
|
196
|
+
font-size: 11px;
|
|
197
|
+
flex-wrap: wrap;
|
|
198
|
+
gap: 4px;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#location-name {
|
|
202
|
+
flex: 1 1 100%;
|
|
203
|
+
text-align: center;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#score-turns {
|
|
207
|
+
flex: 1 1 100%;
|
|
208
|
+
text-align: center;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* Landscape phone */
|
|
213
|
+
@media (max-height: 500px) and (orientation: landscape) {
|
|
214
|
+
body {
|
|
215
|
+
font-size: 13px;
|
|
216
|
+
line-height: 1.3;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#status-line {
|
|
220
|
+
padding: 2px 8px;
|
|
221
|
+
padding-left: max(8px, env(safe-area-inset-left));
|
|
222
|
+
padding-right: max(8px, env(safe-area-inset-right));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#main-window {
|
|
226
|
+
padding: 4px 8px;
|
|
227
|
+
padding-left: max(8px, env(safe-area-inset-left));
|
|
228
|
+
padding-right: max(8px, env(safe-area-inset-right));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#text-content p {
|
|
232
|
+
margin-bottom: 0.5em;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
#input-area {
|
|
236
|
+
padding: 4px 8px;
|
|
237
|
+
padding-left: max(8px, env(safe-area-inset-left));
|
|
238
|
+
padding-right: max(8px, env(safe-area-inset-right));
|
|
239
|
+
min-height: 36px;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* When virtual keyboard is likely open - use dvh for dynamic viewport */
|
|
244
|
+
@supports (height: 100dvh) {
|
|
245
|
+
#game-container {
|
|
246
|
+
height: 100dvh;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* Touch-specific: larger tap targets, no hover effects */
|
|
251
|
+
@media (pointer: coarse) {
|
|
252
|
+
#command-input {
|
|
253
|
+
padding: 12px 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#input-area {
|
|
257
|
+
min-height: 52px;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* Ensure scrollbar is visible on touch */
|
|
261
|
+
#main-window {
|
|
262
|
+
scrollbar-width: auto;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
#main-window::-webkit-scrollbar {
|
|
266
|
+
width: 12px;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* Blinking cursor effect */
|
|
271
|
+
@keyframes blink {
|
|
272
|
+
0%, 50% { opacity: 1; }
|
|
273
|
+
51%, 100% { opacity: 0; }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
#command-input:focus {
|
|
277
|
+
caret-color: var(--dos-bright-white);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* ============================================
|
|
281
|
+
Save/Restore Modal Dialogs
|
|
282
|
+
============================================ */
|
|
283
|
+
|
|
284
|
+
.modal-hidden {
|
|
285
|
+
display: none !important;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#modal-overlay {
|
|
289
|
+
position: fixed;
|
|
290
|
+
top: 0;
|
|
291
|
+
left: 0;
|
|
292
|
+
right: 0;
|
|
293
|
+
bottom: 0;
|
|
294
|
+
background: rgba(0, 0, 0, 0.7);
|
|
295
|
+
display: flex;
|
|
296
|
+
align-items: center;
|
|
297
|
+
justify-content: center;
|
|
298
|
+
z-index: 1000;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.modal-dialog {
|
|
302
|
+
background: var(--dos-blue);
|
|
303
|
+
border: 2px solid var(--dos-cyan);
|
|
304
|
+
min-width: 300px;
|
|
305
|
+
max-width: 90vw;
|
|
306
|
+
max-height: 80vh;
|
|
307
|
+
display: flex;
|
|
308
|
+
flex-direction: column;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.modal-title {
|
|
312
|
+
background: var(--dos-cyan);
|
|
313
|
+
color: var(--dos-black);
|
|
314
|
+
padding: 4px 8px;
|
|
315
|
+
font-weight: bold;
|
|
316
|
+
text-align: center;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.modal-content {
|
|
320
|
+
padding: 12px;
|
|
321
|
+
flex: 1;
|
|
322
|
+
overflow-y: auto;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.save-input-row {
|
|
326
|
+
display: flex;
|
|
327
|
+
align-items: center;
|
|
328
|
+
gap: 8px;
|
|
329
|
+
margin-bottom: 12px;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.save-input-row label {
|
|
333
|
+
white-space: nowrap;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
#save-name-input {
|
|
337
|
+
flex: 1;
|
|
338
|
+
background: var(--dos-black);
|
|
339
|
+
border: 1px solid var(--dos-cyan);
|
|
340
|
+
color: var(--dos-bright-white);
|
|
341
|
+
font-family: inherit;
|
|
342
|
+
font-size: inherit;
|
|
343
|
+
padding: 4px 6px;
|
|
344
|
+
outline: none;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
#save-name-input:focus {
|
|
348
|
+
border-color: var(--dos-bright-white);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.saves-list-label {
|
|
352
|
+
color: var(--dos-white);
|
|
353
|
+
margin-bottom: 8px;
|
|
354
|
+
font-size: 0.9em;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.saves-list {
|
|
358
|
+
background: var(--dos-black);
|
|
359
|
+
border: 1px solid var(--dos-cyan);
|
|
360
|
+
max-height: 200px;
|
|
361
|
+
overflow-y: auto;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.save-slot {
|
|
365
|
+
padding: 6px 8px;
|
|
366
|
+
cursor: pointer;
|
|
367
|
+
display: flex;
|
|
368
|
+
justify-content: space-between;
|
|
369
|
+
gap: 12px;
|
|
370
|
+
border-bottom: 1px solid #333;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.save-slot:last-child {
|
|
374
|
+
border-bottom: none;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.save-slot:hover {
|
|
378
|
+
background: var(--dos-blue);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.save-slot.selected {
|
|
382
|
+
background: var(--dos-bright-white);
|
|
383
|
+
color: var(--dos-blue);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.save-slot-name {
|
|
387
|
+
font-weight: bold;
|
|
388
|
+
flex: 1;
|
|
389
|
+
overflow: hidden;
|
|
390
|
+
text-overflow: ellipsis;
|
|
391
|
+
white-space: nowrap;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.save-slot-info {
|
|
395
|
+
color: var(--dos-white);
|
|
396
|
+
font-size: 0.85em;
|
|
397
|
+
white-space: nowrap;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.save-slot.selected .save-slot-info {
|
|
401
|
+
color: var(--dos-blue);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.no-saves-message {
|
|
405
|
+
color: var(--dos-white);
|
|
406
|
+
font-style: italic;
|
|
407
|
+
padding: 12px;
|
|
408
|
+
text-align: center;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.modal-buttons {
|
|
412
|
+
display: flex;
|
|
413
|
+
justify-content: center;
|
|
414
|
+
gap: 16px;
|
|
415
|
+
padding: 12px;
|
|
416
|
+
border-top: 1px solid var(--dos-cyan);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.modal-btn {
|
|
420
|
+
background: var(--dos-black);
|
|
421
|
+
border: 1px solid var(--dos-cyan);
|
|
422
|
+
color: var(--dos-bright-white);
|
|
423
|
+
font-family: inherit;
|
|
424
|
+
font-size: inherit;
|
|
425
|
+
padding: 6px 20px;
|
|
426
|
+
cursor: pointer;
|
|
427
|
+
min-width: 80px;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.modal-btn:hover {
|
|
431
|
+
background: var(--dos-blue);
|
|
432
|
+
border-color: var(--dos-bright-white);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.modal-btn:focus {
|
|
436
|
+
outline: 1px solid var(--dos-bright-white);
|
|
437
|
+
outline-offset: 2px;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.modal-btn:disabled {
|
|
441
|
+
color: var(--dos-white);
|
|
442
|
+
opacity: 0.5;
|
|
443
|
+
cursor: not-allowed;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/* Startup dialog */
|
|
447
|
+
.startup-info {
|
|
448
|
+
color: var(--dos-cyan);
|
|
449
|
+
margin: 0 0 12px 0;
|
|
450
|
+
line-height: 1.4;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.startup-question {
|
|
454
|
+
color: var(--dos-bright-white);
|
|
455
|
+
margin: 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/* Modal scrollbar styling */
|
|
459
|
+
.saves-list::-webkit-scrollbar,
|
|
460
|
+
.modal-content::-webkit-scrollbar {
|
|
461
|
+
width: 8px;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.saves-list::-webkit-scrollbar-track,
|
|
465
|
+
.modal-content::-webkit-scrollbar-track {
|
|
466
|
+
background: var(--dos-black);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.saves-list::-webkit-scrollbar-thumb,
|
|
470
|
+
.modal-content::-webkit-scrollbar-thumb {
|
|
471
|
+
background: var(--dos-cyan);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* Mobile modal adjustments */
|
|
475
|
+
@media (max-width: 600px) {
|
|
476
|
+
.modal-dialog {
|
|
477
|
+
min-width: 280px;
|
|
478
|
+
margin: 12px;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.save-input-row {
|
|
482
|
+
flex-direction: column;
|
|
483
|
+
align-items: stretch;
|
|
484
|
+
gap: 4px;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.modal-buttons {
|
|
488
|
+
gap: 12px;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.modal-btn {
|
|
492
|
+
flex: 1;
|
|
493
|
+
padding: 10px 16px;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{STORY_TITLE}}
|
|
3
|
+
*
|
|
4
|
+
* A Sharpee Interactive Fiction story
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Story,
|
|
9
|
+
StoryConfig,
|
|
10
|
+
WorldModel,
|
|
11
|
+
IFEntity,
|
|
12
|
+
Parser,
|
|
13
|
+
LanguageProvider,
|
|
14
|
+
} from '@sharpee/sharpee';
|
|
15
|
+
|
|
16
|
+
export const config: StoryConfig = {
|
|
17
|
+
id: '{{STORY_ID}}',
|
|
18
|
+
title: '{{STORY_TITLE}}',
|
|
19
|
+
author: '{{AUTHOR}}',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
description: '{{DESCRIPTION}}',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const story: Story = {
|
|
25
|
+
config,
|
|
26
|
+
|
|
27
|
+
initializeWorld(world: WorldModel): IFEntity {
|
|
28
|
+
// Get the player entity
|
|
29
|
+
const player = world.getPlayer();
|
|
30
|
+
|
|
31
|
+
// === CREATE YOUR ROOMS ===
|
|
32
|
+
|
|
33
|
+
// Starting room
|
|
34
|
+
const startRoom = world.createRoom('start-room', {
|
|
35
|
+
name: 'Starting Room',
|
|
36
|
+
description: 'You are in a small, dimly lit room. This is where your adventure begins.',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// === CREATE YOUR OBJECTS ===
|
|
40
|
+
|
|
41
|
+
// Example: a simple object
|
|
42
|
+
// const key = world.createEntity('key', 'object', {
|
|
43
|
+
// name: 'brass key',
|
|
44
|
+
// description: 'A small brass key.',
|
|
45
|
+
// });
|
|
46
|
+
// world.moveEntity(key.id, startRoom.id);
|
|
47
|
+
|
|
48
|
+
// === CONNECT YOUR ROOMS ===
|
|
49
|
+
|
|
50
|
+
// Example: connect rooms
|
|
51
|
+
// world.connectRooms(startRoom.id, 'north', anotherRoom.id);
|
|
52
|
+
|
|
53
|
+
// Place player in starting room
|
|
54
|
+
world.moveEntity(player.id, startRoom.id);
|
|
55
|
+
|
|
56
|
+
// Return the starting room
|
|
57
|
+
return startRoom;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
// Optional: extend the parser with custom vocabulary
|
|
61
|
+
extendParser(parser: Parser): void {
|
|
62
|
+
// const grammar = parser.getStoryGrammar();
|
|
63
|
+
// grammar.define('custom command').mapsTo('story.action.custom').build();
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// Optional: extend the language with custom messages
|
|
67
|
+
extendLanguage(language: LanguageProvider): void {
|
|
68
|
+
// language.addMessages({
|
|
69
|
+
// 'story.message.custom': 'A custom message.',
|
|
70
|
+
// });
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default story;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{STORY_ID}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "{{DESCRIPTION}}",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"build:browser": "npx sharpee build-browser",
|
|
10
|
+
"dev": "tsc --watch"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@sharpee/sharpee": "^0.9.61-beta"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.0.0"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"interactive-fiction",
|
|
20
|
+
"sharpee",
|
|
21
|
+
"text-adventure"
|
|
22
|
+
],
|
|
23
|
+
"author": "{{AUTHOR}}",
|
|
24
|
+
"license": "MIT"
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"skipLibCheck": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|