craigman1211 commited on
Commit
f784d2c
·
verified ·
1 Parent(s): 8c8ad25

Add 1 files

Browse files
Files changed (1) hide show
  1. index.html +378 -256
index.html CHANGED
@@ -79,15 +79,6 @@
79
  transition: width 0.3s ease;
80
  }
81
 
82
- .dialog-choice {
83
- transition: all 0.2s ease;
84
- }
85
-
86
- .dialog-choice:hover {
87
- transform: translateX(5px);
88
- background-color: rgba(214, 158, 46, 0.2);
89
- }
90
-
91
  .typewriter {
92
  overflow: hidden;
93
  border-right: 2px solid var(--accent);
@@ -121,35 +112,6 @@
121
  100% { transform: rotate(360deg); }
122
  }
123
 
124
- .tooltip {
125
- position: relative;
126
- display: inline-block;
127
- }
128
-
129
- .tooltip .tooltiptext {
130
- visibility: hidden;
131
- width: 120px;
132
- background-color: #2d3748;
133
- color: #fff;
134
- text-align: center;
135
- border-radius: 6px;
136
- padding: 5px;
137
- position: absolute;
138
- z-index: 1;
139
- bottom: 125%;
140
- left: 50%;
141
- margin-left: -60px;
142
- opacity: 0;
143
- transition: opacity 0.3s;
144
- font-size: 12px;
145
- border: 1px solid #d69e2e;
146
- }
147
-
148
- .tooltip:hover .tooltiptext {
149
- visibility: visible;
150
- opacity: 1;
151
- }
152
-
153
  .notification {
154
  position: fixed;
155
  top: 1rem;
@@ -206,6 +168,37 @@
206
  opacity: 0.5;
207
  cursor: not-allowed;
208
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  </style>
210
  </head>
211
  <body class="h-screen flex flex-col">
@@ -213,10 +206,6 @@
213
  <header class="bg-gray-900 text-yellow-500 p-4 flex justify-between items-center border-b border-yellow-700">
214
  <div class="flex items-center space-x-4">
215
  <h1 class="text-2xl font-bold medieval">Isekai RPG Adventure</h1>
216
- <div class="flex space-x-2">
217
- <button id="visualNovelBtn" class="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 text-white rounded medieval transition btn-click">Visual Novel</button>
218
- <button id="textRpgBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded medieval transition btn-click">Text RPG</button>
219
- </div>
220
  </div>
221
  <div class="flex items-center space-x-4">
222
  <button id="newGameBtn" class="text-yellow-400 hover:text-yellow-300 btn-click">
@@ -359,64 +348,36 @@
359
 
360
  <!-- Center Panel - Game Content -->
361
  <div class="flex-1 flex flex-col">
362
- <!-- Visual Novel Mode -->
363
- <div id="visualNovelMode" class="flex-1 flex flex-col">
364
- <div class="flex-1 relative">
365
- <!-- Scene Image -->
366
- <div id="sceneImage" class="absolute inset-0 bg-gray-900 flex items-center justify-center">
367
- <div class="w-full h-full bg-cover bg-center" style="background-image: url('https://images.unsplash.com/photo-1518709766631-a6a7f45921c3?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80');"></div>
368
- <div class="absolute inset-0 bg-black bg-opacity-40"></div>
369
- </div>
370
-
371
- <!-- Text Display -->
372
- <div id="vnTextContainer" class="absolute bottom-0 left-0 right-0 p-6">
373
- <div class="bg-gray-900 bg-opacity-80 rounded-lg p-4 parchment">
374
- <div id="vnCharacterName" class="text-yellow-400 font-bold mb-1 medieval">Old Man</div>
375
- <div id="vnText" class="text-white mb-4">
376
- Welcome, traveler, to the Rusty Tankard. What brings you to our humble establishment this fine evening?
377
- </div>
378
- <div id="vnChoices" class="space-y-2">
379
- <button class="dialog-choice w-full text-left p-2 bg-gray-800 hover:bg-gray-700 rounded border border-yellow-700 text-yellow-100 btn-click">
380
- "I'm looking for work. Any jobs for a capable adventurer?"
381
- </button>
382
- <button class="dialog-choice w-full text-left p-2 bg-gray-800 hover:bg-gray-700 rounded border border-yellow-700 text-yellow-100 btn-click">
383
- "Just passing through. What's good to drink here?"
384
- </button>
385
- <button class="dialog-choice w-full text-left p-2 bg-gray-800 hover:bg-gray-700 rounded border border-yellow-700 text-yellow-100 btn-click">
386
- "I heard rumors of trouble in these parts. Know anything about that?"
387
- </button>
388
- </div>
389
- </div>
390
- </div>
391
  </div>
392
  </div>
393
 
394
- <!-- Text RPG Mode -->
395
- <div id="textRpgMode" class="flex-1 flex flex-col hidden">
396
- <div class="flex-1 p-4 overflow-y-auto scrollbar-custom">
397
- <div id="rpgTextLog" class="space-y-3">
398
- <div class="text-yellow-400 font-bold medieval">System:</div>
399
- <div class="text-gray-300">Welcome to the world of Eldoria. Your adventure begins now.</div>
400
-
401
- <div class="text-yellow-400 font-bold medieval">Narrator:</div>
402
- <div class="text-gray-300">You find yourself standing in the middle of a bustling tavern. The air is thick with the smell of ale and roasted meat. Patrons laugh loudly at nearby tables while a bard strums a lute in the corner. The tavern keeper wipes down the bar with a rag, occasionally glancing in your direction.</div>
403
-
404
- <div class="text-yellow-400 font-bold medieval">System:</div>
405
- <div class="text-gray-300">What would you like to do? You can examine your surroundings, talk to NPCs, or check your inventory.</div>
406
- </div>
407
  </div>
408
-
409
- <div class="p-4 border-t border-gray-700">
410
- <div class="flex">
411
- <input type="text" id="rpgInput" placeholder="What will you do?" class="flex-1 bg-gray-700 border border-yellow-700 rounded-l px-4 py-2 text-white focus:outline-none focus:ring-1 focus:ring-yellow-500">
412
- <button id="rpgSubmit" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded-r medieval flex items-center justify-center min-w-[100px] btn-click">
413
- <span id="submitText">Submit</span>
414
- <div id="submitSpinner" class="loading-spinner ml-2 hidden"></div>
415
- </button>
416
- </div>
417
- <div class="mt-2 text-xs text-gray-400">
418
- Try commands like: look, examine [thing], talk to [person], inventory, stats, go [direction]
419
- </div>
 
 
 
 
 
420
  </div>
421
  </div>
422
  </div>
@@ -577,42 +538,6 @@
577
  </button>
578
  </div>
579
  </div>
580
-
581
- <!-- Summarizer Configuration -->
582
- <div class="bg-gray-700 p-4 rounded border border-gray-600">
583
- <h5 class="font-bold text-yellow-300 mb-2">Summarizer Model</h5>
584
- <div class="space-y-2">
585
- <div>
586
- <label class="block text-gray-300 mb-1">API Key</label>
587
- <input type="text" id="summarizerApiKey" class="w-full bg-gray-800 border border-gray-600 rounded px-3 py-2 text-white" placeholder="Enter API key">
588
- </div>
589
- <div>
590
- <label class="block text-gray-300 mb-1">URL/IP</label>
591
- <input type="text" id="summarizerUrl" class="w-full bg-gray-800 border border-gray-600 rounded px-3 py-2 text-white" placeholder="http://localhost:5002/summarize">
592
- </div>
593
- <button id="testSummarizer" class="mt-2 bg-gray-600 hover:bg-gray-500 text-white px-3 py-1 rounded text-sm medieval btn-click">
594
- Test Connection
595
- </button>
596
- </div>
597
- </div>
598
-
599
- <!-- Diffusion Model Configuration -->
600
- <div class="bg-gray-700 p-4 rounded border border-gray-600">
601
- <h5 class="font-bold text-yellow-300 mb-2">Diffusion Model</h5>
602
- <div class="space-y-2">
603
- <div>
604
- <label class="block text-gray-300 mb-1">API Key</label>
605
- <input type="text" id="diffusionApiKey" class="w-full bg-gray-800 border border-gray-600 rounded px-3 py-2 text-white" placeholder="Enter API key">
606
- </div>
607
- <div>
608
- <label class="block text-gray-300 mb-1">URL/IP</label>
609
- <input type="text" id="diffusionUrl" class="w-full bg-gray-800 border border-gray-600 rounded px-3 py-2 text-white" placeholder="http://localhost:5003/diffusion">
610
- </div>
611
- <button id="testDiffusion" class="mt-2 bg-gray-600 hover:bg-gray-500 text-white px-3 py-1 rounded text-sm medieval btn-click">
612
- Test Connection
613
- </button>
614
- </div>
615
- </div>
616
  </div>
617
  </div>
618
 
@@ -644,8 +569,7 @@
644
 
645
  <!-- Save/Load Modal -->
646
  <div id="saveLoadModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
647
- <div class="bg-gray-8
648
- 00 rounded-lg w-full max-w-md border border-yellow-700">
649
  <div class="p-4 border-b border-gray-700 flex justify-between items-center">
650
  <h3 id="modalTitle" class="text-xl font-bold text-yellow-400 medieval">Save Game</h3>
651
  <button id="closeSaveLoad" class="text-gray-400 hover:text-white btn-click">
@@ -678,7 +602,6 @@ document.addEventListener('DOMContentLoaded', () => {
678
  * This object holds all the game state data including character info, inventory, etc.
679
  */
680
  const gameState = {
681
- mode: 'visualNovel',
682
  character: {
683
  name: 'Sir Aldric',
684
  class: 'Knight',
@@ -744,9 +667,7 @@ document.addEventListener('DOMContentLoaded', () => {
744
  }
745
  ],
746
  worldMap: [],
747
- currentDialogue: null,
748
  gameLog: ["> Entered the Rusty Tankard"],
749
- summaries: [],
750
  settings: {
751
  autoSave: true,
752
  typewriterEffect: true,
@@ -757,9 +678,7 @@ document.addEventListener('DOMContentLoaded', () => {
757
  // AI Configuration
758
  let aiConfig = {
759
  storyTeller: { apiKey: "", url: "http://localhost:5000/story" },
760
- mechanics: { apiKey: "", url: "http://localhost:5001/mechanics" },
761
- summarizer: { apiKey: "", url: "http://localhost:5002/summarize" },
762
- diffusion: { apiKey: "", url: "http://localhost:5003/diffusion" }
763
  };
764
 
765
  // Load saved AI config from localStorage if available
@@ -777,10 +696,6 @@ document.addEventListener('DOMContentLoaded', () => {
777
  * DOM Elements
778
  * Cache all frequently accessed DOM elements for better performance
779
  */
780
- const visualNovelBtn = document.getElementById('visualNovelBtn');
781
- const textRpgBtn = document.getElementById('textRpgBtn');
782
- const visualNovelMode = document.getElementById('visualNovelMode');
783
- const textRpgMode = document.getElementById('textRpgMode');
784
  const newGameBtn = document.getElementById('newGameBtn');
785
  const settingsBtn = document.getElementById('settingsBtn');
786
  const saveBtn = document.getElementById('saveBtn');
@@ -796,14 +711,11 @@ document.addEventListener('DOMContentLoaded', () => {
796
  const modalTitle = document.getElementById('modalTitle');
797
  const confirmSaveLoad = document.getElementById('confirmSaveLoad');
798
  const cancelSaveLoad = document.getElementById('cancelSaveLoad');
799
- const rpgInput = document.getElementById('rpgInput');
800
- const rpgSubmit = document.getElementById('rpgSubmit');
801
  const submitText = document.getElementById('submitText');
802
  const submitSpinner = document.getElementById('submitSpinner');
803
- const rpgTextLog = document.getElementById('rpgTextLog');
804
- const vnChoices = document.getElementById('vnChoices');
805
- const vnText = document.getElementById('vnText');
806
- const vnCharacterName = document.getElementById('vnCharacterName');
807
  const sceneImage = document.getElementById('sceneImage');
808
  const gameLog = document.getElementById('gameLog');
809
  const questLog = document.getElementById('questLog');
@@ -832,14 +744,8 @@ document.addEventListener('DOMContentLoaded', () => {
832
  const storyTellerUrl = document.getElementById('storyTellerUrl');
833
  const mechanicsApiKey = document.getElementById('mechanicsApiKey');
834
  const mechanicsUrl = document.getElementById('mechanicsUrl');
835
- const summarizerApiKey = document.getElementById('summarizerApiKey');
836
- const summarizerUrl = document.getElementById('summarizerUrl');
837
- const diffusionApiKey = document.getElementById('diffusionApiKey');
838
- const diffusionUrl = document.getElementById('diffusionUrl');
839
  const testStoryTeller = document.getElementById('testStoryTeller');
840
  const testMechanics = document.getElementById('testMechanics');
841
- const testSummarizer = document.getElementById('testSummarizer');
842
- const testDiffusion = document.getElementById('testDiffusion');
843
  const saveSettings = document.getElementById('saveSettings');
844
  const cancelSettings = document.getElementById('cancelSettings');
845
  const autoSave = document.getElementById('autoSave');
@@ -851,12 +757,12 @@ document.addEventListener('DOMContentLoaded', () => {
851
 
852
  // Verify all required elements exist
853
  const requiredElements = [
854
- visualNovelBtn, textRpgBtn, visualNovelMode, textRpgMode, newGameBtn, settingsBtn,
855
- saveBtn, loadBtn, settingsModal, closeSettings, newGameModal, closeNewGame,
856
- startNewGame, cancelNewGame, saveLoadModal, closeSaveLoad, modalTitle,
857
- confirmSaveLoad, cancelSaveLoad, rpgInput, rpgSubmit, submitText, submitSpinner,
858
- rpgTextLog, vnChoices, vnText, vnCharacterName, sceneImage, gameLog, questLog,
859
- inventoryItems, equippedItems, skillsList, miniMapGrid, statPointsDisplay, skillNotification
860
  ];
861
 
862
  requiredElements.forEach(element => {
@@ -870,10 +776,6 @@ document.addEventListener('DOMContentLoaded', () => {
870
  * Set up all event handlers for user interactions
871
  */
872
  function setupEventListeners() {
873
- // Mode switching buttons
874
- visualNovelBtn?.addEventListener('click', switchToVisualNovelMode);
875
- textRpgBtn?.addEventListener('click', switchToTextRpgMode);
876
-
877
  // Main menu buttons
878
  newGameBtn?.addEventListener('click', () => toggleModal(newGameModal));
879
  settingsBtn?.addEventListener('click', openSettingsModal);
@@ -898,26 +800,17 @@ document.addEventListener('DOMContentLoaded', () => {
898
  // Test connection buttons
899
  testStoryTeller?.addEventListener('click', () => testAIConnection('storyTeller'));
900
  testMechanics?.addEventListener('click', () => testAIConnection('mechanics'));
901
- testSummarizer?.addEventListener('click', () => testAIConnection('summarizer'));
902
- testDiffusion?.addEventListener('click', () => testAIConnection('diffusion'));
903
 
904
  // Save/Load Modal
905
  closeSaveLoad?.addEventListener('click', () => toggleModal(saveLoadModal));
906
  cancelSaveLoad?.addEventListener('click', () => toggleModal(saveLoadModal));
907
  confirmSaveLoad?.addEventListener('click', handleSaveLoadConfirm);
908
 
909
- // RPG Mode Input
910
- rpgSubmit?.addEventListener('click', handleRpgInput);
911
- rpgInput?.addEventListener('keypress', (e) => {
912
  if (e.key === 'Enter') {
913
- handleRpgInput();
914
- }
915
- });
916
-
917
- // Visual Novel Choices - Using event delegation for dynamic elements
918
- vnChoices?.addEventListener('click', function(e) {
919
- if (e.target && e.target.classList.contains('dialog-choice')) {
920
- handleVNChoice(e.target.textContent.trim());
921
  }
922
  });
923
  }
@@ -952,9 +845,6 @@ document.addEventListener('DOMContentLoaded', () => {
952
  * Initialize game state and UI
953
  */
954
  function initGame() {
955
- // Set initial mode
956
- switchToVisualNovelMode();
957
-
958
  // Initialize world map
959
  initWorldMap();
960
 
@@ -969,36 +859,6 @@ document.addEventListener('DOMContentLoaded', () => {
969
  updateSaveSlots();
970
  }
971
 
972
- /**
973
- * Switch to Visual Novel mode
974
- */
975
- function switchToVisualNovelMode() {
976
- console.log('Switching to Visual Novel mode');
977
- gameState.mode = 'visualNovel';
978
- visualNovelMode?.classList.remove('hidden');
979
- textRpgMode?.classList.add('hidden');
980
- visualNovelBtn?.classList.add('bg-yellow-600', 'hover:bg-yellow-700');
981
- visualNovelBtn?.classList.remove('bg-gray-700', 'hover:bg-gray-600');
982
- textRpgBtn?.classList.add('bg-gray-700', 'hover:bg-gray-600');
983
- textRpgBtn?.classList.remove('bg-yellow-600', 'hover:bg-yellow-700');
984
- addToGameLog("Switched to Visual Novel mode");
985
- }
986
-
987
- /**
988
- * Switch to Text RPG mode
989
- */
990
- function switchToTextRpgMode() {
991
- console.log('Switching to Text RPG mode');
992
- gameState.mode = 'textRpg';
993
- visualNovelMode?.classList.add('hidden');
994
- textRpgMode?.classList.remove('hidden');
995
- textRpgBtn?.classList.add('bg-yellow-600', 'hover:bg-yellow-700');
996
- textRpgBtn?.classList.remove('bg-gray-700', 'hover:bg-gray-600');
997
- visualNovelBtn?.classList.add('bg-gray-700', 'hover:bg-gray-600');
998
- visualNovelBtn?.classList.remove('bg-yellow-600', 'hover:bg-yellow-700');
999
- addToGameLog("Switched to Text RPG mode");
1000
- }
1001
-
1002
  /**
1003
  * Toggle modal visibility
1004
  * @param {HTMLElement} modal - The modal element to toggle
@@ -1015,15 +875,13 @@ document.addEventListener('DOMContentLoaded', () => {
1015
  * Open settings modal and load current settings
1016
  */
1017
  function openSettingsModal() {
 
 
1018
  // Load current config into settings modal
1019
  if (storyTellerApiKey) storyTellerApiKey.value = aiConfig.storyTeller.apiKey;
1020
  if (storyTellerUrl) storyTellerUrl.value = aiConfig.storyTeller.url;
1021
  if (mechanicsApiKey) mechanicsApiKey.value = aiConfig.mechanics.apiKey;
1022
  if (mechanicsUrl) mechanicsUrl.value = aiConfig.mechanics.url;
1023
- if (summarizerApiKey) summarizerApiKey.value = aiConfig.summarizer.apiKey;
1024
- if (summarizerUrl) summarizerUrl.value = aiConfig.summarizer.url;
1025
- if (diffusionApiKey) diffusionApiKey.value = aiConfig.diffusion.apiKey;
1026
- if (diffusionUrl) diffusionUrl.value = aiConfig.diffusion.url;
1027
 
1028
  // Load game settings
1029
  if (autoSave) autoSave.checked = gameState.settings.autoSave;
@@ -1037,6 +895,7 @@ document.addEventListener('DOMContentLoaded', () => {
1037
  * Open save modal
1038
  */
1039
  function openSaveModal() {
 
1040
  if (!modalTitle || !saveLoadModal) return;
1041
  modalTitle.textContent = 'Save Game';
1042
  updateSaveSlots();
@@ -1047,6 +906,7 @@ document.addEventListener('DOMContentLoaded', () => {
1047
  * Open load modal
1048
  */
1049
  function openLoadModal() {
 
1050
  if (!modalTitle || !saveLoadModal) return;
1051
  modalTitle.textContent = 'Load Game';
1052
  updateSaveSlots();
@@ -1538,58 +1398,320 @@ document.addEventListener('DOMContentLoaded', () => {
1538
  }
1539
 
1540
  /**
1541
- * Handle RPG mode input
1542
  */
1543
- async function handleRpgInput() {
1544
- if (!rpgInput || !rpgSubmit || !submitText || !submitSpinner || !rpgTextLog) return;
1545
 
1546
- const input = rpgInput.value.trim();
1547
  if (!input) return;
1548
 
1549
  try {
1550
  // Show loading state
1551
  submitText.textContent = "Processing...";
1552
  submitSpinner.classList.remove('hidden');
1553
- rpgSubmit.disabled = true;
1554
 
1555
- // Add player input to log
1556
- addToRpgLog(`<div class="text-green-400 font-bold medieval">You:</div><div class="text-gray-300">${input}</div>`);
1557
  addToGameLog(`> ${input}`);
1558
 
1559
- // Check for special commands
1560
- if (input.toLowerCase().startsWith('equip ')) {
1561
- const itemName = input.substring(6).trim();
1562
- handleEquipItem(itemName);
1563
- } else if (input.toLowerCase().startsWith('use ')) {
1564
- const itemName = input.substring(4).trim();
1565
- await useItem(gameState.inventory.find(item =>
1566
- item.name.toLowerCase() === itemName.toLowerCase()
1567
- ));
1568
- } else if (input.toLowerCase().startsWith('go ')) {
1569
- const direction = input.substring(3).trim().toLowerCase();
1570
- await handleMovement(direction);
1571
- } else {
1572
- // Process input with Story Teller AI
1573
- const response = await getStoryTellerResponse(input);
1574
-
1575
- // Add response to log
1576
- addToRpgLog(`<div class="text-yellow-400 font-bold medieval">${response.character}:</div><div class="text-gray-300">${response.text}</div>`);
1577
-
1578
- // Handle mechanics if present
1579
- if (response.mechanics) {
1580
- if (response.mechanics.quest) {
1581
- const questExists = gameState.quests.some(q => q.title === response.mechanics.quest);
1582
- if (!questExists) {
1583
- gameState.quests.push({
1584
- title: response.mechanics.quest,
1585
- description: response.mechanics.description,
1586
- status: 'active'
1587
- });
1588
- updateQuestLog();
1589
- }
1590
  }
1591
  }
1592
 
1593
- // Generate image if in visual novel mode
1594
- if (gameState
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1595
  </html>
 
79
  transition: width 0.3s ease;
80
  }
81
 
 
 
 
 
 
 
 
 
 
82
  .typewriter {
83
  overflow: hidden;
84
  border-right: 2px solid var(--accent);
 
112
  100% { transform: rotate(360deg); }
113
  }
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  .notification {
116
  position: fixed;
117
  top: 1rem;
 
168
  opacity: 0.5;
169
  cursor: not-allowed;
170
  }
171
+
172
+ /* Chat message styling */
173
+ .chat-message {
174
+ max-width: 80%;
175
+ margin-bottom: 1rem;
176
+ padding: 0.75rem 1rem;
177
+ border-radius: 0.5rem;
178
+ word-wrap: break-word;
179
+ }
180
+
181
+ .player-message {
182
+ background-color: #2d3748;
183
+ border-left: 3px solid #d69e2e;
184
+ margin-left: auto;
185
+ margin-right: 0;
186
+ }
187
+
188
+ .npc-message {
189
+ background-color: #2d3748;
190
+ border-left: 3px solid #4299e1;
191
+ margin-right: auto;
192
+ margin-left: 0;
193
+ }
194
+
195
+ .system-message {
196
+ background-color: #2d3748;
197
+ border-left: 3px solid #48bb78;
198
+ margin-right: auto;
199
+ margin-left: 0;
200
+ font-style: italic;
201
+ }
202
  </style>
203
  </head>
204
  <body class="h-screen flex flex-col">
 
206
  <header class="bg-gray-900 text-yellow-500 p-4 flex justify-between items-center border-b border-yellow-700">
207
  <div class="flex items-center space-x-4">
208
  <h1 class="text-2xl font-bold medieval">Isekai RPG Adventure</h1>
 
 
 
 
209
  </div>
210
  <div class="flex items-center space-x-4">
211
  <button id="newGameBtn" class="text-yellow-400 hover:text-yellow-300 btn-click">
 
348
 
349
  <!-- Center Panel - Game Content -->
350
  <div class="flex-1 flex flex-col">
351
+ <!-- Scene Image -->
352
+ <div class="h-1/3 relative">
353
+ <div id="sceneImage" class="absolute inset-0 bg-gray-900 flex items-center justify-center">
354
+ <div class="w-full h-full bg-cover bg-center" style="background-image: url('https://images.unsplash.com/photo-1518709766631-a6a7f45921c3?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80');"></div>
355
+ <div class="absolute inset-0 bg-black bg-opacity-40"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  </div>
357
  </div>
358
 
359
+ <!-- Chat Log -->
360
+ <div id="chatLog" class="flex-1 p-4 overflow-y-auto scrollbar-custom">
361
+ <div class="chat-message system-message">
362
+ Welcome to the world of Eldoria. Your adventure begins now.
 
 
 
 
 
 
 
 
 
363
  </div>
364
+ <div class="chat-message npc-message">
365
+ <div class="font-bold text-blue-400 medieval">Old Man:</div>
366
+ Welcome, traveler, to the Rusty Tankard. What brings you to our humble establishment this fine evening?
367
+ </div>
368
+ </div>
369
+
370
+ <!-- Chat Input -->
371
+ <div class="p-4 border-t border-gray-700">
372
+ <div class="flex">
373
+ <input type="text" id="chatInput" placeholder="What will you do or say?" class="flex-1 bg-gray-700 border border-yellow-700 rounded-l px-4 py-2 text-white focus:outline-none focus:ring-1 focus:ring-yellow-500">
374
+ <button id="chatSubmit" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded-r medieval flex items-center justify-center min-w-[100px] btn-click">
375
+ <span id="submitText">Send</span>
376
+ <div id="submitSpinner" class="loading-spinner ml-2 hidden"></div>
377
+ </button>
378
+ </div>
379
+ <div class="mt-2 text-xs text-gray-400">
380
+ Examples: "I ask the old man about work", "I draw my sword", "I look around the tavern"
381
  </div>
382
  </div>
383
  </div>
 
538
  </button>
539
  </div>
540
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  </div>
542
  </div>
543
 
 
569
 
570
  <!-- Save/Load Modal -->
571
  <div id="saveLoadModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
572
+ <div class="bg-gray-800 rounded-lg w-full max-w-md border border-yellow-700">
 
573
  <div class="p-4 border-b border-gray-700 flex justify-between items-center">
574
  <h3 id="modalTitle" class="text-xl font-bold text-yellow-400 medieval">Save Game</h3>
575
  <button id="closeSaveLoad" class="text-gray-400 hover:text-white btn-click">
 
602
  * This object holds all the game state data including character info, inventory, etc.
603
  */
604
  const gameState = {
 
605
  character: {
606
  name: 'Sir Aldric',
607
  class: 'Knight',
 
667
  }
668
  ],
669
  worldMap: [],
 
670
  gameLog: ["> Entered the Rusty Tankard"],
 
671
  settings: {
672
  autoSave: true,
673
  typewriterEffect: true,
 
678
  // AI Configuration
679
  let aiConfig = {
680
  storyTeller: { apiKey: "", url: "http://localhost:5000/story" },
681
+ mechanics: { apiKey: "", url: "http://localhost:5001/mechanics" }
 
 
682
  };
683
 
684
  // Load saved AI config from localStorage if available
 
696
  * DOM Elements
697
  * Cache all frequently accessed DOM elements for better performance
698
  */
 
 
 
 
699
  const newGameBtn = document.getElementById('newGameBtn');
700
  const settingsBtn = document.getElementById('settingsBtn');
701
  const saveBtn = document.getElementById('saveBtn');
 
711
  const modalTitle = document.getElementById('modalTitle');
712
  const confirmSaveLoad = document.getElementById('confirmSaveLoad');
713
  const cancelSaveLoad = document.getElementById('cancelSaveLoad');
714
+ const chatInput = document.getElementById('chatInput');
715
+ const chatSubmit = document.getElementById('chatSubmit');
716
  const submitText = document.getElementById('submitText');
717
  const submitSpinner = document.getElementById('submitSpinner');
718
+ const chatLog = document.getElementById('chatLog');
 
 
 
719
  const sceneImage = document.getElementById('sceneImage');
720
  const gameLog = document.getElementById('gameLog');
721
  const questLog = document.getElementById('questLog');
 
744
  const storyTellerUrl = document.getElementById('storyTellerUrl');
745
  const mechanicsApiKey = document.getElementById('mechanicsApiKey');
746
  const mechanicsUrl = document.getElementById('mechanicsUrl');
 
 
 
 
747
  const testStoryTeller = document.getElementById('testStoryTeller');
748
  const testMechanics = document.getElementById('testMechanics');
 
 
749
  const saveSettings = document.getElementById('saveSettings');
750
  const cancelSettings = document.getElementById('cancelSettings');
751
  const autoSave = document.getElementById('autoSave');
 
757
 
758
  // Verify all required elements exist
759
  const requiredElements = [
760
+ newGameBtn, settingsBtn, saveBtn, loadBtn, settingsModal, closeSettings,
761
+ newGameModal, closeNewGame, startNewGame, cancelNewGame, saveLoadModal,
762
+ closeSaveLoad, modalTitle, confirmSaveLoad, cancelSaveLoad, chatInput,
763
+ chatSubmit, submitText, submitSpinner, chatLog, sceneImage, gameLog,
764
+ questLog, inventoryItems, equippedItems, skillsList, miniMapGrid,
765
+ statPointsDisplay, skillNotification
766
  ];
767
 
768
  requiredElements.forEach(element => {
 
776
  * Set up all event handlers for user interactions
777
  */
778
  function setupEventListeners() {
 
 
 
 
779
  // Main menu buttons
780
  newGameBtn?.addEventListener('click', () => toggleModal(newGameModal));
781
  settingsBtn?.addEventListener('click', openSettingsModal);
 
800
  // Test connection buttons
801
  testStoryTeller?.addEventListener('click', () => testAIConnection('storyTeller'));
802
  testMechanics?.addEventListener('click', () => testAIConnection('mechanics'));
 
 
803
 
804
  // Save/Load Modal
805
  closeSaveLoad?.addEventListener('click', () => toggleModal(saveLoadModal));
806
  cancelSaveLoad?.addEventListener('click', () => toggleModal(saveLoadModal));
807
  confirmSaveLoad?.addEventListener('click', handleSaveLoadConfirm);
808
 
809
+ // Chat Input
810
+ chatSubmit?.addEventListener('click', handleChatInput);
811
+ chatInput?.addEventListener('keypress', (e) => {
812
  if (e.key === 'Enter') {
813
+ handleChatInput();
 
 
 
 
 
 
 
814
  }
815
  });
816
  }
 
845
  * Initialize game state and UI
846
  */
847
  function initGame() {
 
 
 
848
  // Initialize world map
849
  initWorldMap();
850
 
 
859
  updateSaveSlots();
860
  }
861
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
862
  /**
863
  * Toggle modal visibility
864
  * @param {HTMLElement} modal - The modal element to toggle
 
875
  * Open settings modal and load current settings
876
  */
877
  function openSettingsModal() {
878
+ console.log('Opening settings modal');
879
+
880
  // Load current config into settings modal
881
  if (storyTellerApiKey) storyTellerApiKey.value = aiConfig.storyTeller.apiKey;
882
  if (storyTellerUrl) storyTellerUrl.value = aiConfig.storyTeller.url;
883
  if (mechanicsApiKey) mechanicsApiKey.value = aiConfig.mechanics.apiKey;
884
  if (mechanicsUrl) mechanicsUrl.value = aiConfig.mechanics.url;
 
 
 
 
885
 
886
  // Load game settings
887
  if (autoSave) autoSave.checked = gameState.settings.autoSave;
 
895
  * Open save modal
896
  */
897
  function openSaveModal() {
898
+ console.log('Opening save modal');
899
  if (!modalTitle || !saveLoadModal) return;
900
  modalTitle.textContent = 'Save Game';
901
  updateSaveSlots();
 
906
  * Open load modal
907
  */
908
  function openLoadModal() {
909
+ console.log('Opening load modal');
910
  if (!modalTitle || !saveLoadModal) return;
911
  modalTitle.textContent = 'Load Game';
912
  updateSaveSlots();
 
1398
  }
1399
 
1400
  /**
1401
+ * Handle chat input
1402
  */
1403
+ async function handleChatInput() {
1404
+ if (!chatInput || !chatSubmit || !submitText || !submitSpinner || !chatLog) return;
1405
 
1406
+ const input = chatInput.value.trim();
1407
  if (!input) return;
1408
 
1409
  try {
1410
  // Show loading state
1411
  submitText.textContent = "Processing...";
1412
  submitSpinner.classList.remove('hidden');
1413
+ chatSubmit.disabled = true;
1414
 
1415
+ // Add player message to chat
1416
+ addToChatLog(input, 'player');
1417
  addToGameLog(`> ${input}`);
1418
 
1419
+ // Clear input
1420
+ chatInput.value = '';
1421
+
1422
+ // Process input with Story Teller AI
1423
+ const response = await getStoryTellerResponse(input);
1424
+
1425
+ // Add response to chat
1426
+ addToChatLog(response.text, 'npc', response.character);
1427
+
1428
+ // Handle mechanics if present
1429
+ if (response.mechanics) {
1430
+ if (response.mechanics.quest) {
1431
+ const questExists = gameState.quests.some(q => q.title === response.mechanics.quest);
1432
+ if (!questExists) {
1433
+ gameState.quests.push({
1434
+ title: response.mechanics.quest,
1435
+ description: response.mechanics.description,
1436
+ status: 'active'
1437
+ });
1438
+ updateQuestLog();
 
 
 
 
 
 
 
 
 
 
 
1439
  }
1440
  }
1441
 
1442
+ if (response.mechanics.location) {
1443
+ gameState.location.name = response.mechanics.location.name;
1444
+ gameState.location.description = response.mechanics.location.description;
1445
+ updateCharacterDisplay();
1446
+ }
1447
+
1448
+ if (response.mechanics.image) {
1449
+ updateSceneImage(response.mechanics.image);
1450
+ }
1451
+ }
1452
+
1453
+ // Auto-save if enabled
1454
+ if (gameState.settings.autoSave) {
1455
+ saveGame();
1456
+ }
1457
+ } catch (error) {
1458
+ console.error('Error processing input:', error);
1459
+ addToChatLog("An error occurred while processing your input.", 'system');
1460
+ } finally {
1461
+ // Reset button state
1462
+ submitText.textContent = "Send";
1463
+ submitSpinner.classList.add('hidden');
1464
+ chatSubmit.disabled = false;
1465
+ }
1466
+ }
1467
+
1468
+ /**
1469
+ * Add message to chat log
1470
+ * @param {string} message - The message text
1471
+ * @param {string} type - The message type (player, npc, system)
1472
+ * @param {string} [character] - The character name for NPC messages
1473
+ */
1474
+ function addToChatLog(message, type, character = '') {
1475
+ if (!chatLog) return;
1476
+
1477
+ const messageElement = document.createElement('div');
1478
+ messageElement.className = `chat-message ${type}-message`;
1479
+
1480
+ if (type === 'player') {
1481
+ messageElement.innerHTML = `
1482
+ <div class="font-bold text-yellow-400 medieval">You:</div>
1483
+ <div class="text-gray-300">${message}</div>
1484
+ `;
1485
+ } else if (type === 'npc') {
1486
+ messageElement.innerHTML = `
1487
+ <div class="font-bold text-blue-400 medieval">${character}:</div>
1488
+ <div class="text-gray-300">${message}</div>
1489
+ `;
1490
+ } else {
1491
+ messageElement.innerHTML = `
1492
+ <div class="text-gray-300">${message}</div>
1493
+ `;
1494
+ }
1495
+
1496
+ chatLog.appendChild(messageElement);
1497
+
1498
+ // Scroll to bottom
1499
+ chatLog.scrollTop = chatLog.scrollHeight;
1500
+ }
1501
+
1502
+ /**
1503
+ * Update scene image
1504
+ * @param {string} imageUrl - The URL of the new scene image
1505
+ */
1506
+ function updateSceneImage(imageUrl) {
1507
+ if (!sceneImage) return;
1508
+
1509
+ const imageDiv = sceneImage.querySelector('div');
1510
+ if (imageDiv) {
1511
+ imageDiv.style.backgroundImage = `url('${imageUrl}')`;
1512
+ }
1513
+ }
1514
+
1515
+ /**
1516
+ * Show notification
1517
+ * @param {string} message - The notification message
1518
+ */
1519
+ function showNotification(message) {
1520
+ if (!skillNotification) return;
1521
+
1522
+ skillNotification.textContent = message;
1523
+ skillNotification.classList.remove('hidden');
1524
+
1525
+ setTimeout(() => {
1526
+ skillNotification.classList.add('hidden');
1527
+ }, 3000);
1528
+ }
1529
+
1530
+ /**
1531
+ * Get response from Story Teller AI
1532
+ * @param {string} input - The player's input
1533
+ * @returns {Promise<Object>} - The AI response
1534
+ */
1535
+ async function getStoryTellerResponse(input) {
1536
+ // In a real implementation, this would call your AI API
1537
+ // For demo purposes, we'll simulate a response
1538
+
1539
+ const responses = [
1540
+ {
1541
+ text: "The old man strokes his beard thoughtfully. 'Work, you say? Well, there's been trouble with bandits on the north road. The local merchants would pay handsomely to have that problem dealt with.'",
1542
+ character: "Old Man",
1543
+ mechanics: {
1544
+ quest: "The Bandit Threat",
1545
+ description: "The tavern keeper mentioned bandits attacking travelers on the north road. Investigate and deal with the threat."
1546
+ }
1547
+ },
1548
+ {
1549
+ text: "'Ah, our house specialty is Dragon's Breath Ale!' the old man says with a chuckle. 'Strong enough to put hair on a dwarf's chest, that one.'",
1550
+ character: "Old Man"
1551
+ },
1552
+ {
1553
+ text: "The old man's expression darkens. 'Aye, there's been strange happenings in these parts. Folk disappearing in the night, livestock found drained of blood. Some say it's vampires, others blame the cult that's taken up in the old ruins.'",
1554
+ character: "Old Man",
1555
+ mechanics: {
1556
+ image: "https://images.unsplash.com/photo-1518709766631-a6a7f45921c3?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80"
1557
+ }
1558
+ }
1559
+ ];
1560
+
1561
+ // Return a random response for demo purposes
1562
+ return responses[Math.floor(Math.random() * responses.length)];
1563
+ }
1564
+
1565
+ /**
1566
+ * Calculate game mechanics
1567
+ * @param {string} action - The action being performed
1568
+ * @param {Object} item - The item being used (if applicable)
1569
+ * @returns {Promise<string>} - The result description
1570
+ */
1571
+ async function calculateMechanics(action, item) {
1572
+ // In a real implementation, this would call your mechanics API
1573
+ // For demo purposes, we'll simulate results
1574
+
1575
+ if (action === 'useItem') {
1576
+ if (item.name === 'Health Potion') {
1577
+ const healAmount = item.effect.heal;
1578
+ gameState.character.health = Math.min(
1579
+ gameState.character.health + healAmount,
1580
+ gameState.character.maxHealth
1581
+ );
1582
+ return `Restored ${healAmount} health.`;
1583
+ } else if (item.name === 'Scroll of Fireball') {
1584
+ return "You unleash a fiery explosion! (30 damage)";
1585
+ }
1586
+ }
1587
+
1588
+ return "Action completed.";
1589
+ }
1590
+
1591
+ /**
1592
+ * Test AI connection
1593
+ * @param {string} type - The AI type to test (storyTeller, mechanics)
1594
+ */
1595
+ async function testAIConnection(type) {
1596
+ const apiKey = document.getElementById(`${type}ApiKey`)?.value;
1597
+ const url = document.getElementById(`${type}Url`)?.value;
1598
+
1599
+ if (!apiKey || !url) {
1600
+ alert('Please enter both API Key and URL');
1601
+ return;
1602
+ }
1603
+
1604
+ try {
1605
+ // In a real implementation, this would test the API connection
1606
+ // For demo purposes, we'll simulate a successful test
1607
+ showNotification(`${type} connection successful!`);
1608
+ } catch (error) {
1609
+ console.error(`Error testing ${type} connection:`, error);
1610
+ alert(`Failed to connect to ${type} API`);
1611
+ }
1612
+ }
1613
+
1614
+ /**
1615
+ * Save settings handler
1616
+ */
1617
+ function saveSettingsHandler() {
1618
+ // Save AI config
1619
+ aiConfig.storyTeller.apiKey = storyTellerApiKey?.value || '';
1620
+ aiConfig.storyTeller.url = storyTellerUrl?.value || '';
1621
+ aiConfig.mechanics.apiKey = mechanicsApiKey?.value || '';
1622
+ aiConfig.mechanics.url = mechanicsUrl?.value || '';
1623
+
1624
+ // Save game settings
1625
+ gameState.settings.autoSave = autoSave?.checked || false;
1626
+ gameState.settings.typewriterEffect = typewriterEffect?.checked || false;
1627
+ gameState.settings.showImages = showImages?.checked || false;
1628
+
1629
+ // Save to localStorage
1630
+ try {
1631
+ localStorage.setItem('aiConfig', JSON.stringify(aiConfig));
1632
+ showNotification('Settings saved successfully!');
1633
+ toggleModal(settingsModal);
1634
+ } catch (error) {
1635
+ console.error('Error saving settings:', error);
1636
+ alert('Failed to save settings!');
1637
+ }
1638
+ }
1639
+
1640
+ /**
1641
+ * Start new game handler
1642
+ */
1643
+ function startNewGameHandler() {
1644
+ if (!newCharName || !newCharClass) return;
1645
+
1646
+ const name = newCharName.value.trim();
1647
+ const charClass = newCharClass.value.trim();
1648
+
1649
+ if (!name || !charClass) {
1650
+ alert('Please enter both character name and class');
1651
+ return;
1652
+ }
1653
+
1654
+ // Create new character
1655
+ gameState.character = {
1656
+ name,
1657
+ class: charClass,
1658
+ level: 1,
1659
+ xp: 0,
1660
+ nextLevel: 100,
1661
+ health: 100,
1662
+ maxHealth: 100,
1663
+ mana: 50,
1664
+ maxMana: 50,
1665
+ str: parseInt(newCharStr?.value) || 10,
1666
+ dex: parseInt(newCharDex?.value) || 10,
1667
+ con: parseInt(newCharCon?.value) || 10,
1668
+ int: parseInt(newCharInt?.value) || 10,
1669
+ wis: parseInt(newCharWis?.value) || 10,
1670
+ cha: parseInt(newCharCha?.value) || 10,
1671
+ statPoints: 0,
1672
+ baseStats: {
1673
+ str: 10,
1674
+ dex: 10,
1675
+ con: 10,
1676
+ int: 10,
1677
+ wis: 10,
1678
+ cha: 10
1679
+ }
1680
+ };
1681
+
1682
+ // Reset other game state
1683
+ gameState.inventory = [
1684
+ { name: "Rusty Sword", type: "weapon", equipped: true, effect: { str: 1 }, icon: "fa-sword" },
1685
+ { name: "Health Potion", type: "consumable", quantity: 2, effect: { heal: 20 }, icon: "fa-flask" },
1686
+ { name: "10 Gold", type: "currency", quantity: 10, icon: "fa-coins" }
1687
+ ];
1688
+
1689
+ gameState.equipped = {
1690
+ weapon: "Rusty Sword",
1691
+ armor: null,
1692
+ accessory: null
1693
+ };
1694
+
1695
+ gameState.skills = [
1696
+ { name: "Sword Mastery", level: 1, xp: 0, nextLevel: 100, description: "Basic proficiency with swords", effect: { str: 1 } }
1697
+ ];
1698
+
1699
+ gameState.quests = [];
1700
+ gameState.gameLog = ["> Game started"];
1701
+
1702
+ // Initialize world map
1703
+ initWorldMap();
1704
+
1705
+ // Update all displays
1706
+ updateCharacterDisplay();
1707
+ updateInventory();
1708
+ updateEquippedItems();
1709
+ updateSkillsList();
1710
+ updateGameLog();
1711
+ updateQuestLog();
1712
+ updateMiniMap();
1713
+
1714
+ // Clear chat and add welcome message
1715
+ if (chatLog) chatLog.innerHTML = '';
1716
+ addToChatLog("
1717
  </html>