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 {
const overlay = document.createElement('div');
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;
}
@ -231,24 +212,11 @@ function createDialogueOverlay(): HTMLElement {
*/
function createDialogueContainer(power1: string, power2: string, title?: string, moment?: Moment): HTMLElement {
const container = document.createElement('div');
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;
`;
container.className = "dialogue-container"
// Create header section with title and moment info
const headerSection = document.createElement('div');
headerSection.style.cssText = `
margin-bottom: 15px;
text-align: center;
`;
headerSection.id = "dialogue-header"
// Add main title
const titleElement = document.createElement('h2');
@ -267,7 +235,7 @@ function createDialogueContainer(power1: string, power2: string, title?: string,
const momentTypeElement = document.createElement('div');
momentTypeElement.textContent = `${moment.category} (Interest: ${moment.interest_score}/10)`;
momentTypeElement.style.cssText = `
background: rgba(75, 59, 22, 0.8);
background: rgba(75, 59, 22);
color: #f7ecd1;
padding: 5px 15px;
border-radius: 15px;
@ -344,7 +312,7 @@ function createDiaryBox(power: PowerENUM, diaryContent: string): HTMLElement {
diaryBox.style.cssText = `
flex: 1;
min-height: 0;
background: rgba(255, 255, 255, 0.4);
background: rgba(255, 255, 255);
border: 2px solid #8b7355;
border-radius: 8px;
padding: 10px;
@ -363,7 +331,7 @@ function createDiaryBox(power: PowerENUM, diaryContent: string): HTMLElement {
font-weight: bold;
text-align: center;
padding: 5px;
background: rgba(75, 59, 22, 0.1);
background: rgba(75, 59, 22, 0.8);
border-radius: 4px;
border-bottom: 1px solid #8b7355;
`;
@ -391,7 +359,7 @@ function createDiaryBox(power: PowerENUM, diaryContent: string): HTMLElement {
p.style.cssText = `
margin: 0 0 4px 0;
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;
`;
contentArea.appendChild(p);
@ -428,7 +396,7 @@ function createConversationArea(): HTMLElement {
padding: 8px;
border: 2px solid #8b7355;
border-radius: 5px;
background: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.8);
display: flex;
flex-direction: column;
gap: 8px;

View file

@ -158,6 +158,7 @@ class GameState {
boardName: string
currentPower!: PowerENUM
isPlaying: boolean
isSpeaking: boolean
audio: GameAudio
@ -189,6 +190,7 @@ class GameState {
this.boardName = boardName
this.gameId = 0
this.isPlaying = false
this.isSpeaking = false
this.audio = new GameAudio()
this.scene = new THREE.Scene()
@ -244,7 +246,7 @@ class GameState {
while (true) {
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) {
this.phaseIndex = phaseIdx
updateMapOwnership()

View file

@ -1,6 +1,7 @@
import { config } from "./config";
import { ScheduledEvent } from "./events";
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
// These pieces of audio are predetermined.
@ -63,7 +64,18 @@ async function testElevenLabsKey() {
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;
}
}
/* -----------------
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;
}