css, js, the works

This commit is contained in:
Chris Wanstrath 2025-09-19 14:43:53 -07:00
parent 0235b8dbf0
commit 25b0ead8b1
10 changed files with 300 additions and 29 deletions

View File

@ -6,8 +6,9 @@ export const Layout: FC = async ({ children, title }) => (
<title>{title || "Nose"}</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/css/main.css" />
<script src="/js/main.js" async></script>
<link href="/css/reset.css" rel="stylesheet" />
<link href="/css/main.css" rel="stylesheet" />
<script src="/js/main.js" type="module" async></script>
</head>
<body data-mode="tall">
<main>

View File

@ -2,7 +2,30 @@ import type { FC } from "hono/jsx"
export const Terminal: FC = async () => (
<>
<h1>Hello NOSE!</h1>
<p>This is 960×540 space.</p>
<link rel="stylesheet" href="/css/terminal.css" />
<div id="command-line">
<span id="command-prompt">&gt;</span>
<textarea
type="text"
id="command-textbox"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck={false}
rows={1}
/>
<textarea
id="command-hint"
readonly={true}
tabIndex={-1}
rows={1}
/>
</div>
<ul id="command-history">
<li class="center">**** NOSE PLUTO V{new Date().getMonth() + 1}.{new Date().getDate()} ****</li>
<li class="center">VRAM <span id="vram-size">000KB</span></li>
</ul>
</>
)

View File

@ -87,6 +87,7 @@ main {
width: 960px;
height: 540px;
background: var(--c64-dark-blue);
color: var(--c64-light-blue);
/* nearest-neighbor scaling */
image-rendering: pixelated;
transform-origin: center center;
@ -95,4 +96,8 @@ main {
body[data-mode=tall] #content {
height: 100%;
overflow-x: hidden;
}
textarea {
color: var(--c64-light-blue);
}

78
src/css/reset.css Normal file
View File

@ -0,0 +1,78 @@
/* https://www.joshwcomeau.com/css/custom-css-reset/ */
/* 1. Use a more-intuitive box-sizing model */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* 2. Remove default margin */
* {
margin: 0;
}
/* 3. Enable keyword animations */
@media (prefers-reduced-motion: no-preference) {
html {
interpolate-size: allow-keywords;
}
}
body {
/* 4. Add accessible line-height */
line-height: 1.5;
/* 5. Improve text rendering */
-webkit-font-smoothing: antialiased;
}
/* 6. Improve media defaults */
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
/* 7. Inherit fonts for form controls */
input,
button,
textarea,
select {
font: inherit;
}
/* 8. Avoid text overflows */
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
/* 9. Improve line wrapping */
p {
text-wrap: pretty;
}
h1,
h2,
h3,
h4,
h5,
h6 {
text-wrap: balance;
}
/*
10. Create a root stacking context
*/
#root,
#__next {
isolation: isolate;
}

85
src/css/terminal.css Normal file
View File

@ -0,0 +1,85 @@
:root {
--cli-spacing-vertical: 5px;
--cli-spacing-horizontal: 5px;
--cli-margin: 10px 0 0 25px;
--cli-font-size: 20px;
--cli-height: 30px;
}
#command-line {
position: relative;
display: inline-block;
width: 100%;
font-size: var(--cli-font-size);
}
#command-prompt {
position: absolute;
top: 10px;
}
#command-textbox {
position: relative;
z-index: 2;
background: transparent;
padding: var(--cli-spacing-vertical) var(--cli-element-spacing-horizontal);
min-height: 1.2em;
resize: none;
overflow: hidden;
font: inherit;
letter-spacing: inherit;
line-height: inherit;
height: var(--cli-height);
width: 100%;
box-shadow: none;
box-sizing: border-box;
outline: 0;
margin: var(--cli-margin);
border: none;
}
#command-hint {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
pointer-events: none;
padding: var(--cli-spacing-vertical) var(--cli-spacing-horizontal);
color: #666;
background: transparent;
resize: none;
overflow: hidden;
min-height: 1.2em;
height: auto;
font: inherit;
letter-spacing: inherit;
line-height: inherit;
height: var(--cli-height);
width: 100%;
box-shadow: none;
box-sizing: border-box;
outline: 0;
margin: var(--cli-margin);
border: none;
}
#command-history {
margin: 0;
padding: 0;
font-size: var(--cli-font-size);
}
#command-history li {
list-style: none;
margin: 0;
}
.center {
text-align: center;
}

13
src/js/dom.ts Normal file
View File

@ -0,0 +1,13 @@
// finds an element by ID
export const $ = (id: string): HTMLElement | null =>
document.getElementById(id)
// creates an HTML element
export const $$ = (tag: string, innerHTML = ""): HTMLElement => {
const el = document.createElement(tag)
if (innerHTML) el.innerHTML = innerHTML
return el
}
// elements we know will be there... right?
export const cmdTextbox = document.getElementById("command-textbox")!

32
src/js/focus.ts Normal file
View File

@ -0,0 +1,32 @@
import { cmdTextbox } from "./dom.js"
export function focusTextbox() {
cmdTextbox.focus()
}
// clicking anywhere outside of a link should focus the prompt, unless the user is
// selecting text or focusing an input
export function focusHandler(e: MouseEvent) {
const target = e.target
// who knows where they clicked... just focus the textbox
if (!(target instanceof HTMLElement)) {
cmdTextbox.focus()
return
}
// let them click on links
const a = target.closest("a")
if (!a) {
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA")
return false
const selection = window.getSelection() || ""
if (selection.toString() === "")
cmdTextbox.focus()
e.preventDefault()
return true
}
}

View File

@ -1,28 +1,11 @@
const content = document.getElementById("content")!
import { resize } from "./resize.js"
import { focusTextbox, focusHandler } from "./focus.js"
import { startVramCounter } from "./vram.js"
function resize() {
if (document.body.dataset.mode === "tall") {
resizeTall()
} else {
resizeCinema()
}
}
function resizeTall() {
const scale = Math.min(1, window.innerWidth / 960)
content.style.transformOrigin = 'top center'
content.style.transform = `scaleX(${scale})`
}
function resizeCinema() {
const scale = Math.min(
window.innerWidth / 960,
window.innerHeight / 540
)
content.style.transformOrigin = 'center center'
content.style.transform = `scale(${scale})`
}
window.addEventListener("click", focusHandler)
focusTextbox()
window.addEventListener("resize", resize)
resize()
resize()
startVramCounter()

26
src/js/resize.ts Normal file
View File

@ -0,0 +1,26 @@
const content = document.getElementById("content")!
export function resize() {
if (document.body.dataset.mode === "tall") {
resizeTall()
} else {
resizeCinema()
}
}
function resizeTall() {
const scale = Math.min(1, window.innerWidth / 960)
content.style.transformOrigin = 'top center'
content.style.transform = `scaleX(${scale})`
}
function resizeCinema() {
const scale = Math.min(
window.innerWidth / 960,
window.innerHeight / 540
)
content.style.transformOrigin = 'center center'
content.style.transform = `scale(${scale})`
}

25
src/js/vram.ts Normal file
View File

@ -0,0 +1,25 @@
//
// vram
//
import { $ } from "./dom.js"
const vramCounter = $("vram-size")!
export const startVramCounter = () => {
const timer = setInterval(() => {
const count = parseInt(vramCounter.textContent!) + 1
let val = count + "KB"
if (count < 10)
val = "00" + val
else if (count < 100)
val = "0" + val
vramCounter.textContent = val
if (count >= 640) {
vramCounter.textContent += " OK"
clearInterval(timer)
}
}, 5)
}