momentModal styling, making speech search fire and forget

Speech creation was holding up the queue, as the promise only resolved
when the speech was returned. Made it fire and forget with a toggle to
the gameState.isSpeaking to make sure we wait until the turn ends.
This commit is contained in:
Tyler Marques 2025-08-06 21:51:45 -07:00
parent 79a2eceef4
commit fe18f5ee52
No known key found for this signature in database
GPG key ID: CB99EDCF41D3016F
4 changed files with 59 additions and 41 deletions

View file

@ -204,25 +204,6 @@ export function closeMomentModal(immediate: boolean = false): void {
function createDialogueOverlay(): HTMLElement { function createDialogueOverlay(): HTMLElement {
const overlay = document.createElement('div'); const overlay = document.createElement('div');
overlay.id = 'dialogue-overlay'; overlay.id = 'dialogue-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
transition: opacity 0.3s ease;
`;
// Trigger fade in
// Trigger fade in with a small timeout
setTimeout(() => overlay.style.opacity = '1', 10);
return overlay; return overlay;
} }
@ -231,24 +212,11 @@ function createDialogueOverlay(): HTMLElement {
*/ */
function createDialogueContainer(power1: string, power2: string, title?: string, moment?: Moment): HTMLElement { function createDialogueContainer(power1: string, power2: string, title?: string, moment?: Moment): HTMLElement {
const container = document.createElement('div'); const container = document.createElement('div');
container.className = 'dialogue-container'; container.className = "dialogue-container"
container.style.cssText = `
background: radial-gradient(ellipse at center, #f7ecd1 0%, #dbc08w8px;
box-shadow: 0 0 15px rgba(0,0,0,0.5);
width: 90%;
height: 85%;
position: relative;
padding: 20px;
display: flex;
flex-direction: column;
`;
// Create header section with title and moment info // Create header section with title and moment info
const headerSection = document.createElement('div'); const headerSection = document.createElement('div');
headerSection.style.cssText = ` headerSection.id = "dialogue-header"
margin-bottom: 15px;
text-align: center;
`;
// Add main title // Add main title
const titleElement = document.createElement('h2'); const titleElement = document.createElement('h2');
@ -267,7 +235,7 @@ function createDialogueContainer(power1: string, power2: string, title?: string,
const momentTypeElement = document.createElement('div'); const momentTypeElement = document.createElement('div');
momentTypeElement.textContent = `${moment.category} (Interest: ${moment.interest_score}/10)`; momentTypeElement.textContent = `${moment.category} (Interest: ${moment.interest_score}/10)`;
momentTypeElement.style.cssText = ` momentTypeElement.style.cssText = `
background: rgba(75, 59, 22, 0.8); background: rgba(75, 59, 22);
color: #f7ecd1; color: #f7ecd1;
padding: 5px 15px; padding: 5px 15px;
border-radius: 15px; border-radius: 15px;
@ -344,7 +312,7 @@ function createDiaryBox(power: PowerENUM, diaryContent: string): HTMLElement {
diaryBox.style.cssText = ` diaryBox.style.cssText = `
flex: 1; flex: 1;
min-height: 0; min-height: 0;
background: rgba(255, 255, 255, 0.4); background: rgba(255, 255, 255);
border: 2px solid #8b7355; border: 2px solid #8b7355;
border-radius: 8px; border-radius: 8px;
padding: 10px; padding: 10px;
@ -363,7 +331,7 @@ function createDiaryBox(power: PowerENUM, diaryContent: string): HTMLElement {
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
padding: 5px; padding: 5px;
background: rgba(75, 59, 22, 0.1); background: rgba(75, 59, 22, 0.8);
border-radius: 4px; border-radius: 4px;
border-bottom: 1px solid #8b7355; border-bottom: 1px solid #8b7355;
`; `;
@ -391,7 +359,7 @@ function createDiaryBox(power: PowerENUM, diaryContent: string): HTMLElement {
p.style.cssText = ` p.style.cssText = `
margin: 0 0 4px 0; margin: 0 0 4px 0;
padding: 3px; padding: 3px;
background: ${index % 2 === 0 ? 'rgba(255,255,255,0.2)' : 'transparent'}; background: ${index % 2 === 0 ? 'rgba(255,255,255)' : 'transparent'};
border-radius: 3px; border-radius: 3px;
`; `;
contentArea.appendChild(p); contentArea.appendChild(p);
@ -428,7 +396,7 @@ function createConversationArea(): HTMLElement {
padding: 8px; padding: 8px;
border: 2px solid #8b7355; border: 2px solid #8b7355;
border-radius: 5px; border-radius: 5px;
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.8);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;

View file

@ -158,6 +158,7 @@ class GameState {
boardName: string boardName: string
currentPower!: PowerENUM currentPower!: PowerENUM
isPlaying: boolean isPlaying: boolean
isSpeaking: boolean
audio: GameAudio audio: GameAudio
@ -189,6 +190,7 @@ class GameState {
this.boardName = boardName this.boardName = boardName
this.gameId = 0 this.gameId = 0
this.isPlaying = false this.isPlaying = false
this.isSpeaking = false
this.audio = new GameAudio() this.audio = new GameAudio()
this.scene = new THREE.Scene() this.scene = new THREE.Scene()
@ -244,7 +246,7 @@ class GameState {
while (true) { while (true) {
let narrator = this.audio.getNarratorPlayer() let narrator = this.audio.getNarratorPlayer()
let narratorFinished = (narrator === null) || narrator.ended let narratorFinished = (narrator === null) || narrator.ended || !this.isSpeaking
if (this.unitAnimations.length === 0 && narratorFinished) { if (this.unitAnimations.length === 0 && narratorFinished) {
this.phaseIndex = phaseIdx this.phaseIndex = phaseIdx
updateMapOwnership() updateMapOwnership()

View file

@ -1,6 +1,7 @@
import { config } from "./config"; import { config } from "./config";
import { ScheduledEvent } from "./events"; import { ScheduledEvent } from "./events";
import { GamePhase } from "./types/gameState"; import { GamePhase } from "./types/gameState";
import { gameState } from "./gameState";
// TODO: We need to get these pieces of audio ahead of time, instead of paying for them each time we load the front end // TODO: We need to get these pieces of audio ahead of time, instead of paying for them each time we load the front end
// These pieces of audio are predetermined. // These pieces of audio are predetermined.
@ -63,7 +64,18 @@ async function testElevenLabsKey() {
export function createNarratorAudioEvent(phase: GamePhase): ScheduledEvent { export function createNarratorAudioEvent(phase: GamePhase): ScheduledEvent {
return new ScheduledEvent(`narratorSpeech-${phase.name}`, () => speakSummary(phase.summary)) return new ScheduledEvent(`narratorSpeech-${phase.name}`, () => {
// Immediately set isSpeaking flag and resolve promise
gameState.isSpeaking = true;
// Start audio generation in background (don't wait for it)
speakSummary(phase.summary).finally(() => {
gameState.isSpeaking = false;
});
// Resolve immediately so event queue can continue
return Promise.resolve();
})
} }
/** /**

View file

@ -903,3 +903,39 @@
margin-bottom: 5px; margin-bottom: 5px;
} }
} }
/* -----------------
Moment Model
----------------- */
#dialogue-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
transition: opacity 0.3s ease;
}
.dialogue-container {
background: radial-gradient(ellipse at center, #f7ecd1 0%, #dbc08c 100%);
border: 3px solid #4f3b16;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0,0,0,0.5);
width: 90%;
height: 85%;
position: relative;
padding: 20px;
display: flex;
flex-direction: column;
}
#dialogue-header {
margin-bottom: 15px;
text-align: center;
}