diff --git a/CLAUDE.md b/CLAUDE.md
index d642a54..e188195 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -35,6 +35,18 @@ On Windows, use the launcher for best UTF-8 support:
OpenTheBox.cmd
```
+### Web Build (itch.io)
+```
+dotnet run --project src/OpenTheBox.Web # Dev local (http://localhost:5000)
+dotnet publish src/OpenTheBox.Web -c Release -o builds/web # Publication
+```
+Upload `builds/web/wwwroot/` sur itch.io en HTML5.
+
+Running locally:
+```
+npx serve builds/web/wwwroot/
+```
+
## Test
```
dotnet test
diff --git a/src/OpenTheBox.Web/WebGameHost.cs b/src/OpenTheBox.Web/WebGameHost.cs
index 9e7fef3..aa1627d 100644
--- a/src/OpenTheBox.Web/WebGameHost.cs
+++ b/src/OpenTheBox.Web/WebGameHost.cs
@@ -592,7 +592,7 @@ public sealed class WebGameHost
ColorSystem = ColorSystemSupport.TrueColor
});
bufferConsole.Profile.Width = WebTerminal.Width;
- bufferConsole.Profile.Height = _terminal.Height;
+ bufferConsole.Profile.Height = WebTerminal.Height;
bufferConsole.Write(InventoryPanel.Render(_state, _registry, _loc, scrollOffset, selectedIndex: selectedIndex));
diff --git a/src/OpenTheBox.Web/WebTerminal.cs b/src/OpenTheBox.Web/WebTerminal.cs
index e2ea27b..63a6305 100644
--- a/src/OpenTheBox.Web/WebTerminal.cs
+++ b/src/OpenTheBox.Web/WebTerminal.cs
@@ -28,7 +28,7 @@ public sealed class WebTerminal
});
public const int Width = 120;
- public int Height { get; private set; } = 30;
+ public const int Height = 30;
public WebTerminal(IJSRuntime js)
{
@@ -37,24 +37,13 @@ public sealed class WebTerminal
}
///
- /// Initializes the xterm.js terminal in the browser and reads actual dimensions.
+ /// Initializes the xterm.js terminal in the browser (fixed 120x30).
///
public async Task InitAsync()
{
await _js.InvokeVoidAsync("terminalInterop.init");
-
- // Read actual terminal dimensions after fitAddon has sized the terminal
- try
- {
- var dims = await _js.InvokeAsync("terminalInterop.getDimensions");
- if (dims.rows >= 30)
- Height = dims.rows;
- }
- catch { /* fallback to 30 */ }
}
- private record TerminalDimensions(int cols, int rows);
-
// ── Output ───────────────────────────────────────────────────────────
///
@@ -94,7 +83,7 @@ public sealed class WebTerminal
ColorSystem = ColorSystemSupport.TrueColor
});
console.Profile.Width = Width;
- console.Profile.Height = _instance?.Height ?? 50;
+ console.Profile.Height = Height;
console.Write(renderable);
return writer.ToString();
}
diff --git a/src/OpenTheBox.Web/wwwroot/css/terminal.css b/src/OpenTheBox.Web/wwwroot/css/terminal.css
index 98a179d..17e1587 100644
--- a/src/OpenTheBox.Web/wwwroot/css/terminal.css
+++ b/src/OpenTheBox.Web/wwwroot/css/terminal.css
@@ -21,17 +21,16 @@ body {
align-items: center;
width: 100%;
height: 100%;
- padding: 8px;
}
#terminal-container.active {
display: flex;
}
+/* Let xterm.js determine the natural size from 120x30 @ fontSize 16.
+ CSS transform scale (set by JS) shrinks it to fit smaller viewports. */
#terminal {
- width: 100%;
- height: 100%;
- max-width: 1200px;
+ flex-shrink: 0;
}
/* Loading screen */
diff --git a/src/OpenTheBox.Web/wwwroot/index.html b/src/OpenTheBox.Web/wwwroot/index.html
index 1dafac2..4f0110c 100644
--- a/src/OpenTheBox.Web/wwwroot/index.html
+++ b/src/OpenTheBox.Web/wwwroot/index.html
@@ -20,7 +20,6 @@
-
diff --git a/src/OpenTheBox.Web/wwwroot/js/terminal-interop.js b/src/OpenTheBox.Web/wwwroot/js/terminal-interop.js
index ea17324..63a5ed6 100644
--- a/src/OpenTheBox.Web/wwwroot/js/terminal-interop.js
+++ b/src/OpenTheBox.Web/wwwroot/js/terminal-interop.js
@@ -1,14 +1,13 @@
// Terminal interop bridge: xterm.js <-> .NET Blazor WASM
window.terminalInterop = {
term: null,
- fitAddon: null,
init: function () {
const term = new Terminal({
cols: 120,
rows: 30,
fontFamily: "'Cascadia Mono', 'Consolas', 'Courier New', monospace",
- fontSize: 14,
+ fontSize: 16,
theme: {
background: '#1a1a2e',
foreground: '#e0e0e0',
@@ -18,43 +17,47 @@ window.terminalInterop = {
},
cursorBlink: true,
allowProposedApi: true,
- scrollback: 1000
+ scrollback: 0
});
- const fitAddon = new FitAddon.FitAddon();
- term.loadAddon(fitAddon);
-
- // Show terminal container BEFORE opening so fitAddon can measure real dimensions
+ // Show terminal container BEFORE opening so xterm can measure
document.getElementById('loading').classList.add('hidden');
document.getElementById('terminal-container').classList.add('active');
term.open(document.getElementById('terminal'));
- // Fit to container, enforce minimum dimensions for game readability
- fitAddon.fit();
- const minCols = 120, minRows = 30;
- if (term.cols < minCols || term.rows < minRows) {
- term.resize(Math.max(term.cols, minCols), Math.max(term.rows, minRows));
- }
+ // Scale the terminal to fit the viewport while keeping exact 120x30
+ this._scaleToFit();
+ window.addEventListener('resize', () => this._scaleToFit());
// Forward key input to C#
term.onData(function (data) {
DotNet.invokeMethodAsync('OpenTheBox.Web', 'OnTerminalInput', data);
});
- // Handle resize
- window.addEventListener('resize', function () {
- fitAddon.fit();
- if (term.cols < minCols || term.rows < minRows) {
- term.resize(Math.max(term.cols, minCols), Math.max(term.rows, minRows));
- }
- });
-
this.term = term;
- this.fitAddon = fitAddon;
term.focus();
},
+ _scaleToFit: function () {
+ const el = document.querySelector('#terminal .xterm-screen');
+ if (!el) return;
+
+ const container = document.getElementById('terminal-container');
+ const pad = 16; // padding around terminal
+ const maxW = container.clientWidth - pad;
+ const maxH = container.clientHeight - pad;
+ const termW = el.offsetWidth;
+ const termH = el.offsetHeight;
+
+ if (termW === 0 || termH === 0) return;
+
+ const scale = Math.min(1, maxW / termW, maxH / termH);
+ const wrapper = document.getElementById('terminal');
+ wrapper.style.transform = scale < 1 ? `scale(${scale})` : '';
+ wrapper.style.transformOrigin = 'center center';
+ },
+
write: function (text) {
if (this.term) {
this.term.write(text);
@@ -75,9 +78,6 @@ window.terminalInterop = {
},
getDimensions: function () {
- if (this.term) {
- return { cols: this.term.cols, rows: this.term.rows };
- }
return { cols: 120, rows: 30 };
}
};