Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 94 additions & 30 deletions client/src/app/app.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ body {
margin: 10px 0;
gap: 20px;
border-radius: 36px;
overflow-x: auto;
overflow-y: hidden;
overflow-x: visible;
overflow-y: visible;
white-space: nowrap;

.settings-group {
Expand All @@ -203,56 +203,120 @@ body {
flex-direction: column;
gap: 5px;
min-width: fit-content;
position: relative;

.group-label {
width: 100%;
color: #e2e2e5;
padding: 0 0 5px 5px;
white-space: nowrap;
}

.options-container {
display: flex;
gap: 5px;
white-space: nowrap;
}
}

input[type="radio"] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
.selector-box {
position: relative;
display: inline-block;
}

.option-label {
display: inline-block;
.selector-trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 8px 16px;
background-color: #8ea8f9;
color: #1e1e1e;
border: 1px solid rgba(142, 168, 249, 0.4);
border-radius: 24px;
cursor: pointer;
transition: all 0.2s ease;
color: #e2e2e5;
padding: 8px 16px;
background-color: #e2e2e520;
backdrop-filter: blur(12px);
width: fit-content;
min-width: 100px;
text-align: center;
font-weight: 500;
white-space: nowrap;
transition: all 0.2s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

input[type="radio"]:checked + .option-label {
background-color: #8ea8f9;
color: #1e1e1e;
border-color: #8ea8f9;
.selector-trigger .selected-text {
font-weight: 600;
}

input[type="radio"]:hover + .option-label {
background-color: #e2e2e530;
.selector-trigger .options-hint {
font-size: 11px;
padding: 2px 8px;
background: rgba(30, 30, 30, 0.2);
border-radius: 12px;
color: #1e1e1e;
font-weight: 600;
}

input[type="radio"]:checked:hover + .option-label {
.selector-box:hover .selector-trigger {
background-color: #7b95e6;
}

.selector-menu {
position: absolute;
bottom: calc(100% + 8px);
left: 0;
min-width: 100%;
width: max-content;
background: rgba(26, 26, 30, 0.95);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 16px;
padding: 6px;
display: flex;
flex-direction: column;
gap: 4px;
opacity: 0;
visibility: hidden;
transform: translateY(8px) scale(0.96);
transition: all 0.2s ease;
z-index: 1000;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);
}

.selector-box:hover .selector-menu,
.selector-box:focus-within .selector-menu {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
}

.selector-header {
padding: 6px 10px 4px 10px;
font-size: 11px;
font-weight: 700;
color: #8ea8f9;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 6px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 2px;
}

.selector-header:first-child {
margin-top: 2px;
}

.selector-option {
padding: 8px 14px;
color: #e2e2e5;
border-radius: 10px;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
font-size: 13px;
white-space: nowrap;
}
Comment on lines +300 to +308

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To support converting the .selector-option elements from div to <button type="button"> for accessibility, we should update the CSS to reset default button styles (like background, border, and text alignment).

Suggested change
.selector-option {
padding: 8px 14px;
color: #e2e2e5;
border-radius: 10px;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
font-size: 13px;
white-space: nowrap;
}
.selector-option {
display: block;
width: 100%;
background: none;
border: none;
text-align: left;
padding: 8px 14px;
color: #e2e2e5;
border-radius: 10px;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
font-size: 13px;
white-space: nowrap;
}


.selector-option:hover {
background: rgba(226, 226, 229, 0.15);
color: #ffffff;
}

.selector-option.active {
background: #8ea8f9;
color: #1e1e1e;
font-weight: 600;
}
}

.command-input-container {
Expand Down
170 changes: 71 additions & 99 deletions client/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -144,112 +144,84 @@

<div id="game-settings">
<div class="settings-group">
<label class="group-label">Image backend</label>
<div class="options-container">
<div>
<input
type="radio"
id="gemini"
name="imageGenerator"
value="gemini"
(click)="setConfig('imageGenerator', 'gemini')"
[checked]="gameSettings.imageGenerator === 'gemini'"
/>
<label for="gemini" class="option-label">Gemini 3.1 Flash-Lite</label>
</div>
<div>
<input
type="radio"
id="imagen"
name="imageGenerator"
value="imagen"
(click)="setConfig('imageGenerator', 'imagen')"
[checked]="gameSettings.imageGenerator === 'imagen'"
/>
<label for="imagen" class="option-label">Imagen 4 Fast</label>
</div>
<div>
<input
type="radio"
id="veo"
name="imageGenerator"
value="veo"
(click)="setConfig('imageGenerator', 'veo')"
[checked]="gameSettings.imageGenerator === 'veo'"
/>
<label for="veo" class="option-label">Veo 3.1 Fast</label>
</div>
<div>
<input
type="radio"
id="omni"
name="imageGenerator"
value="omni"
(click)="setConfig('imageGenerator', 'omni')"
[checked]="gameSettings.imageGenerator === 'omni'"
/>
<label for="omni" class="option-label">Omni (Veo 3.1)</label>
<label class="group-label">Graphics backend</label>
<div class="selector-box">
<button class="selector-trigger" type="button">
<span class="selected-text">{{ getSelectedLabel('imageGenerator') }}</span>
<span class="options-hint">+5 models ▾</span>
</button>
<div class="selector-menu">
<div class="selector-header">Image Models</div>
<div class="selector-option" (click)="setConfig('imageGenerator', 'imagen')" [class.active]="gameSettings.imageGenerator === 'imagen'">
Imagen 4 Fast
</div>
Comment on lines +155 to +157

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using non-interactive div elements with (click) handlers for dropdown options makes them inaccessible to keyboard and screen reader users. It is highly recommended to use semantic <button type="button"> elements instead. This ensures they are natively focusable and keyboard-navigable.

Please apply this change to all .selector-option elements in this file.

Suggested change
<div class="selector-option" (click)="setConfig('imageGenerator', 'imagen')" [class.active]="gameSettings.imageGenerator === 'imagen'">
Imagen 4 Fast
</div>
<button type="button" class="selector-option" (click)="setConfig('imageGenerator', 'imagen')" [class.active]="gameSettings.imageGenerator === 'imagen'">
Imagen 4 Fast
</button>

<div class="selector-option" (click)="setConfig('imageGenerator', 'gemini')" [class.active]="gameSettings.imageGenerator === 'gemini'">
Gemini 3.1 Flash-Lite
</div>
<div class="selector-option" (click)="setConfig('imageGenerator', 'gemini-anim')" [class.active]="gameSettings.imageGenerator === 'gemini-anim'">
Gemini 3.1 Animation (4-Frame)
</div>

<div class="selector-header">Video Models (with frame extraction)</div>
<div class="selector-option" (click)="setConfig('imageGenerator', 'veo')" [class.active]="gameSettings.imageGenerator === 'veo'">
Veo 3.1 Fast
</div>
<div class="selector-option" (click)="setConfig('imageGenerator', 'omni')" [class.active]="gameSettings.imageGenerator === 'omni'">
Omni (Veo 3.1)
</div>

<div class="selector-header">Local Models</div>
<div class="selector-option" (click)="setConfig('imageGenerator', 'gemma-cga')" [class.active]="gameSettings.imageGenerator === 'gemma-cga'">
Gemma CGA Pixel Art (MediaPipe)
</div>
</div>
<div>
<input
type="radio"
id="gemini-anim"
name="imageGenerator"
value="gemini-anim"
(click)="setConfig('imageGenerator', 'gemini-anim')"
[checked]="gameSettings.imageGenerator === 'gemini-anim'"
/>
<label for="gemini-anim" class="option-label">Gemini 3.1 Animation (4-Frame)</label>
</div>
</div>

<div class="settings-group">
<label class="group-label">Image-to-text analysis</label>
<div class="selector-box">
<button class="selector-trigger" type="button">
<span class="selected-text">{{ getSelectedLabel('imageAnalysis') }}</span>
<span class="options-hint">+2 models ▾</span>
</button>
<div class="selector-menu">
<div class="selector-header">Server Models</div>
<div class="selector-option" (click)="setConfig('imageAnalysis', 'gemini')" [class.active]="gameSettings.imageAnalysis === 'gemini' || !gameSettings.imageAnalysis">
Gemini 2.5 Flash
</div>

<div class="selector-header">Local Models</div>
<div class="selector-option" (click)="setConfig('imageAnalysis', 'gemma-mediapipe')" [class.active]="gameSettings.imageAnalysis === 'gemma-mediapipe'">
Gemma 4 (MediaPipe)
</div>
<div class="selector-option" (click)="setConfig('imageAnalysis', 'chrome-llm')" [class.active]="gameSettings.imageAnalysis === 'chrome-llm'">
Chrome Built-in Model
</div>
</div>
</div>
</div>

<div class="settings-group">
<label class="group-label">Art style</label>
<div class="options-container">
<div>
<input
type="radio"
id="realistic"
name="visualStyle"
value="realistic"
(click)="setConfig('visualStyle', 'realistic')"
[checked]="gameSettings.visualStyle === 'realistic'"
/>
<label for="realistic" class="option-label">Realistic</label>
</div>
<div>
<input
type="radio"
id="cartoon"
name="visualStyle"
value="cartoon"
(click)="setConfig('visualStyle', 'cartoon')"
[checked]="gameSettings.visualStyle === 'cartoon'"
/>
<label for="cartoon" class="option-label">Cartoon</label>
</div>
<div>
<input
type="radio"
id="pixellated"
name="visualStyle"
value="pixellated"
(click)="setConfig('visualStyle', 'pixellated')"
[checked]="gameSettings.visualStyle === 'pixellated'"
/>
<label for="pixellated" class="option-label">Pixellated</label>
</div>
<div>
<input
type="radio"
id="mask"
name="visualStyle"
value="mask"
(click)="setConfig('visualStyle', 'mask')"
[checked]="gameSettings.visualStyle === 'mask'"
/>
<label for="mask" class="option-label">Mask</label>
<div class="selector-box">
<button class="selector-trigger" type="button">
<span class="selected-text">{{ getSelectedLabel('visualStyle') }}</span>
<span class="options-hint">+3 styles ▾</span>
</button>
<div class="selector-menu">
<div class="selector-option" (click)="setConfig('visualStyle', 'realistic')" [class.active]="gameSettings.visualStyle === 'realistic'">
Realistic
</div>
<div class="selector-option" (click)="setConfig('visualStyle', 'cartoon')" [class.active]="gameSettings.visualStyle === 'cartoon'">
Cartoon
</div>
<div class="selector-option" (click)="setConfig('visualStyle', 'pixellated')" [class.active]="gameSettings.visualStyle === 'pixellated'">
Pixel Art
</div>
<div class="selector-option" (click)="setConfig('visualStyle', 'mask')" [class.active]="gameSettings.visualStyle === 'mask'">
Mask
</div>
</div>
</div>
</div>
Expand Down
32 changes: 32 additions & 0 deletions client/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export class AppComponent implements AfterViewInit {
currentScene: string = 'IOPuzzle_Fire';
gameSettings: any = {
imageGenerator: 'imagen',
imageAnalysis: 'gemini',
visualStyle: 'realistic',
};

Expand Down Expand Up @@ -295,6 +296,37 @@ export class AppComponent implements AfterViewInit {

loadPuzzle() {}

getSelectedLabel(category: string): string {
const val = this.gameSettings[category];
if (category === 'imageGenerator') {
const labels: Record<string, string> = {
'gemini': 'Gemini 3.1 Flash-Lite',
'imagen': 'Imagen 4 Fast',
'veo': 'Veo 3.1 Fast',
'omni': 'Omni (Veo 3.1)',
'gemini-anim': 'Gemini 3.1 Animation (4-Frame)',
'gemma-cga': 'Gemma CGA Pixel Art (MediaPipe)',
};
return labels[val] || 'Select Model';
} else if (category === 'imageAnalysis') {
const labels: Record<string, string> = {
'gemini': 'Gemini 2.5 Flash',
'gemma-mediapipe': 'Gemma 4 (MediaPipe)',
'chrome-llm': 'Chrome Built-in Model',
};
return labels[val] || 'Gemini 2.5 Flash';
} else if (category === 'visualStyle') {
const labels: Record<string, string> = {
'realistic': 'Realistic',
'cartoon': 'Cartoon',
'pixellated': 'Pixel Art',
'outline': 'Mask',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The visual style key mapping in getSelectedLabel is incorrect. The key used in the configuration and HTML template is 'mask', but here it is mapped as 'outline'. This causes the dropdown trigger label to fallback to 'Realistic' when 'Mask' is selected.

Suggested change
'outline': 'Mask',
'mask': 'Mask',

};
return labels[val] || 'Realistic';
}
return '';
}

setConfig(key: string, value: any) {
this.gameSettings[key] = value;
this.updatePhaser();
Expand Down
1 change: 1 addition & 0 deletions client/src/game/LivingCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,7 @@ export class LivingCanvasStage extends Scene {
this.logGeminiGenerationResponse('data:image/png;base64,' + imageData);
break;
case 'imagen':
case 'gemma-cga':
this.logImagenGenerationResponse('data:image/png;base64,' + imageData);
break;
case 'veo':
Expand Down
Loading