diff --git a/css/person-chat-minigame.css b/css/person-chat-minigame.css
index 093f7f3..0fdf3f5 100644
--- a/css/person-chat-minigame.css
+++ b/css/person-chat-minigame.css
@@ -92,22 +92,41 @@
width: 100%;
background: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.95));
display: flex;
- flex-direction: column;
- justify-content: flex-end;
- align-items: stretch;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: flex-end;
padding: 20px;
- gap: 15px;
+ gap: 20px;
z-index: 10;
box-sizing: border-box;
}
+/* Talk right area - speaker name and dialogue (left side, takes more space) */
+.person-chat-talk-right {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ flex: 1 1 auto;
+ min-width: 0;
+}
+
+/* Header row: speaker name on left, controls on right */
+.person-chat-header-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 20px;
+ width: 100%;
+ padding-bottom: 8px;
+ border-bottom: 2px solid #333;
+}
+
/* Speaker name */
.person-chat-speaker-name {
font-size: 20px;
font-weight: bold;
- padding-bottom: 8px;
- border-bottom: 2px solid #333;
min-height: 20px;
+ flex: 0 0 auto;
}
.person-chat-speaker-name.npc-speaker {
@@ -126,9 +145,8 @@
min-height: auto;
max-height: none;
overflow: visible;
- display: flex;
- align-items: flex-start;
- flex: 0 0 auto;
+ display: block;
+ width: 100%;
}
.person-chat-dialogue-text {
@@ -141,15 +159,17 @@
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
}
-/* Choices and continue button area */
+/* Choices and continue button area (right side, fixed width) */
.person-chat-controls-area {
display: flex;
flex-direction: column;
- gap: 10px;
- flex: 0 0 280px;
+ gap: 8px;
+ flex: 0 0 auto;
+ align-items: stretch;
+ min-width: 200px;
}
-/* Choices container - displayed below dialogue in caption area */
+/* Choices container - displayed in controls area */
.person-chat-choices-container {
display: flex;
flex-direction: column;
@@ -168,6 +188,10 @@
cursor: pointer;
text-align: left;
transition: all 0.1s ease;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 180px;
}
.person-chat-choice-button:hover {
diff --git a/js/minigames/person-chat/person-chat-minigame.js b/js/minigames/person-chat/person-chat-minigame.js
index da2d5d7..10c4763 100644
--- a/js/minigames/person-chat/person-chat-minigame.js
+++ b/js/minigames/person-chat/person-chat-minigame.js
@@ -72,7 +72,7 @@ export class PersonChatMinigame extends MinigameScene {
this.isConversationActive = false;
this.currentSpeaker = null; // Track current speaker ID ('player' or NPC id)
this.lastResult = null; // Store last continue() result for choice handling
- this.isClickThroughMode = false; // If true, player must click to advance between dialogue lines
+ this.isClickThroughMode = false; // If true, player must click to advance between dialogue lines (starts in AUTO mode)
this.pendingContinueCallback = null; // Callback waiting for player click in click-through mode
console.log(`🎭 PersonChatMinigame created for NPC: ${this.npcId}`);
@@ -134,6 +134,9 @@ export class PersonChatMinigame extends MinigameScene {
}
super.init();
+ // Initialize timer for auto-advance
+ this.autoAdvanceTimer = null;
+
// Customize header
this.headerElement.innerHTML = `
🎭 ${this.title}
@@ -170,17 +173,37 @@ export class PersonChatMinigame extends MinigameScene {
}
});
- // Continue button to toggle click-through mode
+ // Continue button click handler
if (this.ui.elements.continueButton) {
this.addEventListener(this.ui.elements.continueButton, 'click', (e) => {
e.preventDefault();
e.stopPropagation();
- this.toggleClickThroughMode();
+ this.handleContinueButtonClick();
});
}
}
/**
+ * Handle continue button click - behavior depends on mode
+ */
+ handleContinueButtonClick() {
+ console.log(`🖱️ Button clicked! isClickThroughMode: ${this.isClickThroughMode}, pendingContinueCallback exists: ${!!this.pendingContinueCallback}`);
+ if (this.isClickThroughMode) {
+ // In click-through mode: execute the pending advancement callback
+ if (this.pendingContinueCallback && typeof this.pendingContinueCallback === 'function') {
+ console.log(`🖱️ Executing pending callback`);
+ const callback = this.pendingContinueCallback;
+ this.pendingContinueCallback = null;
+ callback();
+ } else {
+ console.log(`🖱️ No pending callback to execute!`);
+ }
+ } else {
+ // In auto mode: toggle to click-through mode
+ console.log(`🖱️ In auto mode, toggling to click-through`);
+ this.toggleClickThroughMode();
+ }
+ } /**
* Toggle between automatic timing and click-through mode
*/
toggleClickThroughMode() {
@@ -188,15 +211,17 @@ export class PersonChatMinigame extends MinigameScene {
if (this.isClickThroughMode) {
console.log('📋 Switched to CLICK-THROUGH mode');
- this.ui.elements.continueButton.textContent = '■ Auto';
- // Cancel any pending automatic advances
- if (this.pendingContinueCallback) {
- clearTimeout(this.pendingContinueCallback);
- this.pendingContinueCallback = null;
+ this.ui.elements.continueButton.textContent = 'Continue';
+ // Cancel any pending automatic advances (timer only, keep the callback!)
+ if (this.autoAdvanceTimer) {
+ clearTimeout(this.autoAdvanceTimer);
+ this.autoAdvanceTimer = null;
}
} else {
console.log('📋 Switched to AUTOMATIC mode');
- this.ui.elements.continueButton.textContent = '▼ Continue';
+ this.ui.elements.continueButton.textContent = 'Auto';
+ // Resume automatic advancement
+ this.showCurrentDialogue();
}
}
@@ -206,20 +231,25 @@ export class PersonChatMinigame extends MinigameScene {
* @param {number} delay - Delay in milliseconds (ignored in click-through mode)
*/
scheduleDialogueAdvance(callback, delay = 2000) {
+ // Always store the callback function itself
+ this.pendingContinueCallback = callback;
+
if (this.isClickThroughMode) {
- // In click-through mode, show continue button and wait for user click
- this.pendingContinueCallback = callback;
- this.ui.showContinueButton(() => {
- if (this.pendingContinueCallback) {
- const pending = this.pendingContinueCallback;
- this.pendingContinueCallback = null;
- pending();
- }
- });
+ // In click-through mode, wait for button click to execute callback
+ console.log(`⏱️ scheduleDialogueAdvance: Stored callback for click-through mode`);
} else {
- // In automatic mode, use the delay
- this.ui.hideContinueButton();
- this.pendingContinueCallback = setTimeout(callback, delay);
+ // In automatic mode, schedule execution after delay
+ console.log(`⏱️ scheduleDialogueAdvance: Will auto-advance after ${delay}ms`);
+ // Clear any existing timeout
+ if (this.autoAdvanceTimer) {
+ clearTimeout(this.autoAdvanceTimer);
+ }
+ // Set new timeout that will call handleContinueButtonClick
+ this.autoAdvanceTimer = setTimeout(() => {
+ if (this.pendingContinueCallback && typeof this.pendingContinueCallback === 'function') {
+ this.pendingContinueCallback();
+ }
+ }, delay);
}
}
@@ -325,11 +355,12 @@ export class PersonChatMinigame extends MinigameScene {
// At a choice point - display choices
this.ui.showChoices(result.choices);
console.log(`📋 ${result.choices.length} choices available`);
+ console.log(`📋 pendingContinueCallback NOT set - waiting for choice selection`);
// Also display any accompanying text if present
if (result.text && result.text.trim()) {
console.log(`🗣️ Calling showDialogue with speaker: ${speaker}`);
- this.ui.showDialogue(result.text, speaker);
+ this.ui.showDialogue(result.text, speaker, true); // preserveChoices=true
}
} else if (result.text && result.text.trim()) {
// Have text but no choices - display and continue
@@ -338,6 +369,7 @@ export class PersonChatMinigame extends MinigameScene {
if (result.canContinue) {
// Can continue - schedule next advance
+ console.log(`📋 Setting pendingContinueCallback - canContinue: true, no choices`);
this.scheduleDialogueAdvance(() => this.showCurrentDialogue(), 2000);
} else {
// Can't continue but have text - story will end
@@ -463,8 +495,10 @@ export class PersonChatMinigame extends MinigameScene {
}
// Normal dialogue flow: display the result (dialogue blocks) after a small delay
+ console.log(`🎯 After choice: scheduling displayAccumulatedDialogue with result.text length: ${result?.text?.length || 0}`);
this.scheduleDialogueAdvance(() => {
// Process accumulated dialogue by splitting into individual speaker blocks
+ console.log(`🎯 Inside scheduled callback: calling displayAccumulatedDialogue`);
this.displayAccumulatedDialogue(result);
}, 1500);
diff --git a/js/minigames/person-chat/person-chat-ui.js b/js/minigames/person-chat/person-chat-ui.js
index 3d7f3ac..89da3bf 100644
--- a/js/minigames/person-chat/person-chat-ui.js
+++ b/js/minigames/person-chat/person-chat-ui.js
@@ -107,9 +107,14 @@ export default class PersonChatUI {
const captionArea = document.createElement('div');
captionArea.className = 'person-chat-caption-area';
+ // Talk right area - speaker name + dialogue
+ const talkRightArea = document.createElement('div');
+ talkRightArea.className = 'person-chat-talk-right';
+
const speakerName = document.createElement('div');
speakerName.className = 'person-chat-speaker-name';
+ // Dialogue box (spans full width below header)
const dialogueBox = document.createElement('div');
dialogueBox.className = 'person-chat-dialogue-box';
@@ -119,25 +124,34 @@ export default class PersonChatUI {
dialogueBox.appendChild(dialogueText);
- // Continue button - placed next to dialogue
+ // Assemble talk-right area
+ talkRightArea.appendChild(speakerName);
+ talkRightArea.appendChild(dialogueBox);
+
+ // Controls area - continue button + choices
+ const controlsArea = document.createElement('div');
+ controlsArea.className = 'person-chat-controls-area';
+
+ // Continue button
const continueButton = document.createElement('button');
continueButton.className = 'person-chat-continue-button';
- continueButton.textContent = '▼ Continue';
+ continueButton.textContent = 'Auto';
continueButton.id = 'continue-button';
- continueButton.style.display = 'none'; // Hidden by default, shown when there's more dialogue
+ continueButton.style.display = 'inline-block'; // Always visible (hidden only when choices shown)
- dialogueBox.appendChild(continueButton);
+ controlsArea.appendChild(continueButton);
- // Choices container (in caption area, below dialogue)
+ // Choices container (in controls area, below continue button)
const choicesContainer = document.createElement('div');
choicesContainer.className = 'person-chat-choices-container';
choicesContainer.id = 'choices-container';
choicesContainer.style.display = 'none';
- // Assemble caption area: speaker name, dialogue, choices
- captionArea.appendChild(speakerName);
- captionArea.appendChild(dialogueBox);
- captionArea.appendChild(choicesContainer);
+ controlsArea.appendChild(choicesContainer);
+
+ // Assemble caption area: talk-right, controls
+ captionArea.appendChild(talkRightArea);
+ captionArea.appendChild(controlsArea);
// Assemble main content
mainContent.appendChild(portraitSection);
@@ -148,9 +162,11 @@ export default class PersonChatUI {
this.elements.portraitContainer = portraitContainer;
this.elements.portraitLabel = portraitLabel;
this.elements.captionArea = captionArea;
+ this.elements.talkRightArea = talkRightArea;
this.elements.speakerName = speakerName;
this.elements.dialogueBox = dialogueBox;
this.elements.dialogueText = dialogueText;
+ this.elements.controlsArea = controlsArea;
this.elements.continueButton = continueButton;
this.elements.choicesContainer = choicesContainer;
@@ -185,8 +201,9 @@ export default class PersonChatUI {
* Display dialogue text with speaker
* @param {string} text - Dialogue text to display
* @param {string} characterId - Character ID ('player', 'npc', or specific NPC ID)
+ * @param {boolean} preserveChoices - If true, don't hide existing choices
*/
- showDialogue(text, characterId = 'npc') {
+ showDialogue(text, characterId = 'npc', preserveChoices = false) {
this.currentSpeaker = characterId;
console.log(`📝 showDialogue called with character: ${characterId}, text length: ${text?.length || 0}`);
@@ -226,6 +243,11 @@ export default class PersonChatUI {
// Reset portrait for new speaker
this.updatePortraitForSpeaker(characterId, character);
+ // Hide choices only if not preserving them (i.e., when transitioning from choices back to text)
+ if (!preserveChoices) {
+ this.hideChoices();
+ }
+
// Reset continue button state
this.hasContinued = false;
}
@@ -268,7 +290,7 @@ export default class PersonChatUI {
* @param {Array} choices - Array of choice objects {text, index}
*/
showChoices(choices) {
- if (!this.elements.choicesContainer) {
+ if (!this.elements.choicesContainer || !this.elements.continueButton) {
return;
}
@@ -277,10 +299,12 @@ export default class PersonChatUI {
if (!choices || choices.length === 0) {
this.elements.choicesContainer.style.display = 'none';
+ this.elements.continueButton.style.display = 'inline-block';
return;
}
- // Show choices container
+ // Hide continue button and show choices
+ this.elements.continueButton.style.display = 'none';
this.elements.choicesContainer.style.display = 'flex';
// Create button for each choice
@@ -297,12 +321,13 @@ export default class PersonChatUI {
}
/**
- * Hide choices
+ * Hide choices and restore continue button
*/
hideChoices() {
- if (this.elements.choicesContainer) {
+ if (this.elements.choicesContainer && this.elements.continueButton) {
this.elements.choicesContainer.innerHTML = '';
this.elements.choicesContainer.style.display = 'none';
+ this.elements.continueButton.style.display = 'inline-block';
}
}