html, 16:9

This commit is contained in:
Chris Wanstrath 2025-09-15 20:31:11 -07:00
parent ab3877ff51
commit 7c37ba9e80
9 changed files with 165 additions and 25 deletions

View File

@ -5,7 +5,7 @@
"private": true,
"scripts": {
"start": "bun src/server.tsx",
"dev": "bun --hot src/server.tsx"
"dev": "env BUN_HOT=1 bun --hot src/server.tsx"
},
"alias": {
"@utils": "./src/utils.tsx",

BIN
public/vendor/C64_Pro_Mono-STYLE.woff2 vendored Normal file

Binary file not shown.

21
src/components/layout.tsx Normal file
View File

@ -0,0 +1,21 @@
import type { FC } from "hono/jsx"
export const Layout: FC = async ({ children, title }) => (
<html lang="en">
<head>
<title>{title || "Nose"}</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" />
<link rel="stylesheet" href="/css/main.css" />
<script src="/js/main.js" async></script>
</head>
<body>
<main>
<div id="content">
{children}
</div>
</main>
</body>
</html>
)

View File

@ -0,0 +1,8 @@
import type { FC } from "hono/jsx"
export const Terminal: FC = async () => (
<>
<h1>Hello NOSE!</h1>
<p>This is 960×540 space.</p>
</>
)

88
src/css/main.css Normal file
View File

@ -0,0 +1,88 @@
@font-face {
font-family: 'C64ProMono';
src: url('/vendor/C64_Pro_Mono-STYLE.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
:root {
--font-family: 'C64ProMono', monospace;
--black: #000000;
--white: #E0E0E0;
--cyan: #00A8C8;
--red: #C62828;
--green: green;
--yellow: #C4A000;
--purple: #7C3AED;
--blue: #1565C0;
--magenta: #ff66cc;
}
.black {
color: var(--black);
}
.white {
color: var(--white);
}
.cyan {
color: var(--cyan);
}
.red {
color: var(--red);
}
.green {
color: var(--green);
}
.yellow {
color: var(--yellow);
}
.purple {
color: var(--purple);
}
.blue {
color: var(--blue);
}
.magenta {
color: var(--magenta);
}
:fullscreen::backdrop {
background: var(--background-color);
}
html,
body {
font-family: var(--font-family);
margin: 0;
height: 100%;
/* black bars */
background: black;
display: flex;
justify-content: center;
align-items: center;
}
main {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
#content {
width: 960px;
height: 540px;
background: white;
/* nearest-neighbor scaling */
image-rendering: pixelated;
transform-origin: center center;
}

0
src/js/.gitignore vendored
View File

11
src/js/main.ts Normal file
View File

@ -0,0 +1,11 @@
function resize() {
const scale = Math.min(
window.innerWidth / 960,
window.innerHeight / 540
);
const content = document.getElementById("content")!
content.style.transform = `scale(${scale})`
}
window.addEventListener("resize", resize)
resize()

View File

@ -7,6 +7,9 @@ import { NOSE_ICON, NOSE_DIR, NOSE_BIN, NOSE_APP } from "./config"
import { transpile, isFile } from "./utils"
import { apps, serveApp } from "./webapp"
import { Layout } from "./components/layout"
import { Terminal } from "./components/terminal"
//
// Hono setup
//
@ -56,36 +59,42 @@ app.use("*", async (c, next) => {
return next()
})
app.get("/", c => {
app.get("/apps", c => {
const url = new URL(c.req.url)
const domain = url.hostname
const port = url.port
let port = url.port
port = port && port !== "80" ? `:${port}` : ""
return c.html(<>
<h1>apps</h1>
<ul>{apps().map(app => <li><a href={`http://${app}.${domain}:${port}`}>{app}</a></li>)}
<ul>{apps().map(app => <li><a href={`http://${app}.${domain}${port}`}>{app}</a></li>)}
</ul>
</>)
})
app.get("/", c => c.html(<Layout><Terminal /></Layout>))
//
// server shutdown
// hot mode cleanup
//
// @ts-ignore
globalThis.__nose_cleanup?.()
if (process.env.BUN_HOT) {
// @ts-ignore
globalThis.__hot_reload_cleanup?.()
// @ts-ignore
globalThis.__nose_cleanup = () => {
// @ts-ignore
globalThis.__hot_reload_cleanup = () => {
}
for (const sig of ["SIGINT", "SIGTERM"] as const) {
process.on(sig, () => {
// @ts-ignore
globalThis.__hot_reload_cleanup?.()
process.exit()
})
}
}
for (const sig of ["SIGINT", "SIGTERM"] as const) {
process.on(sig, () => {
// @ts-ignore
globalThis.__nose_cleanup?.()
process.exit()
})
}
//

View File

@ -1,37 +1,40 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"lib": [
"ESNext",
"DOM"
],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false,
// paths?
"baseUrl": ".",
"paths": {
"@utils": ["src/utils.tsx"],
"@/*": ["src/*"]
"@utils": [
"src/utils.tsx"
],
"@/*": [
"src/*"
]
},
}
}