E
diff --git a/webapp/src/index.css b/webapp/src/index.css
index 519614e..3fb1f12 100644
--- a/webapp/src/index.css
+++ b/webapp/src/index.css
@@ -15,16 +15,48 @@
--accent-purple: #9D7BD8;
--accent-orange: #D08770;
}
-* { box-sizing: border-box; }
-html, body { margin:0; padding:0; height:100%; overflow:hidden; }
-body { background:var(--bg); color:var(--text); font-family: system-ui, -apple-system, sans-serif; -webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: transparent; }
-button { font-family: inherit; cursor:pointer; user-select:none; }
-button:active { transform: scale(0.97); }
-::-webkit-scrollbar { width:6px; height:6px; }
-::-webkit-scrollbar-thumb { background:#4C566A; border-radius:3px; }
-::-webkit-scrollbar-track { background:transparent; }
-/* 800x480 specific */
-@media (max-width: 800px), (max-height: 480px) {
- header { height:48px !important; }
+* { box-sizing: border-box; }
+html, body, #root { width: 100%; height: 100%; margin: 0; overflow: hidden; }
+body {
+ background: #151923;
+ color: var(--text);
+ font-family: system-ui, -apple-system, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -webkit-tap-highlight-color: transparent;
+}
+button { font-family: inherit; cursor: pointer; user-select: none; }
+button:active { transform: scale(0.97); }
+::-webkit-scrollbar { width: 6px; height: 6px; }
+::-webkit-scrollbar-thumb { background: #4C566A; border-radius: 3px; }
+::-webkit-scrollbar-track { background: transparent; }
+
+.kiddos-stage {
+ width: 100%;
+ height: 100%;
+ min-height: 480px;
+ display: grid;
+ place-items: center;
+ padding: 24px;
+ overflow: auto;
+ background: radial-gradient(circle at 50% 20%, #3B4252 0, #232A36 48%, #151923 100%);
+}
+
+.kiddos-screen {
+ width: 800px !important;
+ height: 480px !important;
+ /* Browser preview: enlarge the 800×480 composition to 160%. */
+ zoom: 1.6;
+ overflow: hidden;
+ background: var(--bg);
+ border: 10px solid #11151D;
+ border-radius: 24px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.55), inset 0 0 0 1px #59647A;
+}
+
+/* The DSI is the native screen: use every pixel and avoid a clipped frame. */
+@media (max-width: 800px), (max-height: 480px) {
+ .kiddos-stage { min-height: 0; padding: 0; background: var(--bg); }
+ .kiddos-screen { width: 100vw !important; height: 100vh !important; zoom: 1 !important; border: 0; border-radius: 0; box-shadow: none; }
+ header { height: 48px !important; }
}
diff --git a/webapp/src/main.jsx b/webapp/src/main.jsx
index 5e8d112..1477707 100644
--- a/webapp/src/main.jsx
+++ b/webapp/src/main.jsx
@@ -5,6 +5,8 @@ import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
-
+
)
diff --git a/webapp/test/framed-screen-scaling.test.mjs b/webapp/test/framed-screen-scaling.test.mjs
new file mode 100644
index 0000000..df2b8ba
--- /dev/null
+++ b/webapp/test/framed-screen-scaling.test.mjs
@@ -0,0 +1,14 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import { readFile } from 'node:fs/promises'
+
+const css = await readFile(new URL('../src/index.css', import.meta.url), 'utf8')
+
+test('keeps the native screen size ahead of inline app dimensions', () => {
+ assert.match(css, /\.kiddos-screen\s*\{[^}]*width:\s*800px\s*!important/s)
+ assert.match(css, /\.kiddos-screen\s*\{[^}]*height:\s*480px\s*!important/s)
+})
+
+test('uses the requested 1.6 zoom for the browser preview', () => {
+ assert.match(css, /\.kiddos-screen\s*\{[^}]*zoom:\s*1\.6/s)
+})
diff --git a/webapp/test/native-viewport.test.mjs b/webapp/test/native-viewport.test.mjs
new file mode 100644
index 0000000..0d77b05
--- /dev/null
+++ b/webapp/test/native-viewport.test.mjs
@@ -0,0 +1,18 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import { readFile } from 'node:fs/promises'
+
+const css = await readFile(new URL('../src/index.css', import.meta.url), 'utf8')
+
+test('defines a centered 800 by 480 KiddOs screen frame', () => {
+ assert.match(css, /\.kiddos-stage\s*\{[^}]*place-items:\s*center/s)
+ assert.match(css, /\.kiddos-screen\s*\{[^}]*width:\s*800px/s)
+ assert.match(css, /\.kiddos-screen\s*\{[^}]*height:\s*480px/s)
+ assert.match(css, /\.kiddos-screen\s*\{[^}]*border:/s)
+})
+
+test('uses the full physical display without a desktop frame at 800 by 480', () => {
+ assert.match(css, /@media\s*\(max-width:\s*800px\),\s*\(max-height:\s*480px\)/s)
+ assert.match(css, /\.kiddos-screen\s*\{[^}]*width:\s*100vw/s)
+ assert.match(css, /\.kiddos-screen\s*\{[^}]*height:\s*100vh/s)
+})
diff --git a/webapp/test/public-hmr.test.mjs b/webapp/test/public-hmr.test.mjs
new file mode 100644
index 0000000..3b83d64
--- /dev/null
+++ b/webapp/test/public-hmr.test.mjs
@@ -0,0 +1,11 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import { readFile } from 'node:fs/promises'
+
+const viteConfig = await readFile(new URL('../vite.config.js', import.meta.url), 'utf8')
+
+test('routes HMR through the public HTTPS development host', () => {
+ assert.match(viteConfig, /host:\s*'elias\.reynafamily\.com'/)
+ assert.match(viteConfig, /protocol:\s*'wss'/)
+ assert.match(viteConfig, /clientPort:\s*443/)
+})
diff --git a/webapp/vite.config.js b/webapp/vite.config.js
index 4b25fcf..f1be872 100644
--- a/webapp/vite.config.js
+++ b/webapp/vite.config.js
@@ -7,8 +7,10 @@ export default defineConfig({
host: '0.0.0.0',
port: 5173,
hmr: {
- clientPort: 5173,
- // overlay shows errors on DSI too
+ // Caddy terminates TLS and proxies WebSocket upgrades from the public dev URL.
+ host: 'elias.reynafamily.com',
+ protocol: 'wss',
+ clientPort: 443,
overlay: true
},
cors: true