@sharpee/sharpee 0.9.61-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.
@@ -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
+ }