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, "private": true,
"scripts": { "scripts": {
"start": "bun src/server.tsx", "start": "bun src/server.tsx",
"dev": "bun --hot src/server.tsx" "dev": "env BUN_HOT=1 bun --hot src/server.tsx"
}, },
"alias": { "alias": {
"@utils": "./src/utils.tsx", "@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 { transpile, isFile } from "./utils"
import { apps, serveApp } from "./webapp" import { apps, serveApp } from "./webapp"
import { Layout } from "./components/layout"
import { Terminal } from "./components/terminal"
// //
// Hono setup // Hono setup
// //
@ -56,38 +59,44 @@ app.use("*", async (c, next) => {
return next() return next()
}) })
app.get("/", c => { app.get("/apps", c => {
const url = new URL(c.req.url) const url = new URL(c.req.url)
const domain = url.hostname const domain = url.hostname
const port = url.port let port = url.port
port = port && port !== "80" ? `:${port}` : ""
return c.html(<> return c.html(<>
<h1>apps</h1> <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> </ul>
</>) </>)
}) })
app.get("/", c => c.html(<Layout><Terminal /></Layout>))
// //
// server shutdown // hot mode cleanup
// //
// @ts-ignore if (process.env.BUN_HOT) {
globalThis.__nose_cleanup?.() // @ts-ignore
globalThis.__hot_reload_cleanup?.()
// @ts-ignore // @ts-ignore
globalThis.__nose_cleanup = () => { globalThis.__hot_reload_cleanup = () => {
} }
for (const sig of ["SIGINT", "SIGTERM"] as const) { for (const sig of ["SIGINT", "SIGTERM"] as const) {
process.on(sig, () => { process.on(sig, () => {
// @ts-ignore // @ts-ignore
globalThis.__nose_cleanup?.() globalThis.__hot_reload_cleanup?.()
process.exit() process.exit()
}) })
}
} }
// //
// server start // server start
// //

View File

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