Scaffold basic page layout and site generation

Set up the basic layout of the site and greybox its content based on
statically generated pages.  Content pages uses general base layouts
defined centrally to avoid duplicating code.
This commit is contained in:
Hornwitser 2025-01-22 04:54:03 +01:00
parent 51b458103b
commit 8fb809fa95
10 changed files with 268 additions and 21 deletions

24
cli.js
View file

@ -1,9 +1,23 @@
import { index } from "./build/node/index.js" import { pages } from "./build/node/content/pages.js"
import { prettify, htmlDocument } from "antihtml";
import * as fs from "node:fs" import * as fs from "node:fs"
if (!fs.existsSync("build/web")) { const outDir = "build/web";
fs.mkdirSync("build/web"); if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir);
} }
console.log("writing build/web/index.html"); for (const page of pages) {
fs.writeFileSync("build/web/index.html", index); const dirSep = page.ref.indexOf("/");
if (dirSep !== -1) {
const dir = `${outDir}/${page.ref.slice(0, dirSep)}`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
}
console.log(`writing ${outDir}/${page.ref}`);
fs.writeFileSync(`${outDir}/${page.ref}`, htmlDocument(prettify(page.content)));
}
console.log(`writing ${outDir}/style.css`);
fs.writeFileSync(`${outDir}/style.css`, fs.readFileSync("public/style.css"));

23
content/bases.tsx Normal file
View file

@ -0,0 +1,23 @@
import type { Node } from "antihtml";
interface BaseProps {
title: string;
children: Node | Node[],
}
export function BasePage(props: BaseProps) {
return <html lang="en">
<head>
<meta charset="utf-8" />
<title>{props.title}</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<header class="header">
<nav>
<a href="./index.html">Home</a> <a href="./updates.html">Updates</a> <a href="./projects.html">Projects</a>
</nav>
</header>
{props.children}
</body>
</html>
}

34
content/index.tsx Normal file
View file

@ -0,0 +1,34 @@
import { BasePage } from "./bases.js";
import { projects } from "./projects.js"
import { updates } from "./updates.js"
const title = "Hornwitser's Site";
export const index = {
title,
ref: "index.html",
content: <BasePage title={title}>
<main>
<div class="hero" />
<div class="author">
<div style="width: 4em; height: 4em; background-color: grey" />
<hgroup>
<h1>Hi, I'm Hornwitser!</h1>
<p>
Grown up, he/him, aro, gray ace
</p>
</hgroup>
</div>
<p>
I'm a red dragon that mostly dabble in hobby programming and the occasional artwork.
</p>
<h2>Latest <a href="./updates.html">Updates</a></h2>
<ul>
{ updates.map(update => <li><a href={"./" + update.ref}>{update.title}</a></li>)}
</ul>
<h2>Projects</h2>
<ul>
{ projects.map(project => <li><a href={"./" + project.ref}>{project.title}</a></li>)}
</ul>
</main>
</BasePage>,
};

12
content/pages.tsx Normal file
View file

@ -0,0 +1,12 @@
import type { Page } from "./types.js";
import { index } from "./index.js";
import { updates, updatesIndex } from "./updates.js";
import { projects, projectsIndex } from "./projects.js";
export const pages: Page[] = [
index,
updatesIndex,
...updates,
projectsIndex,
...projects,
];

47
content/projects.tsx Normal file
View file

@ -0,0 +1,47 @@
import { BasePage } from "./bases.js";
import type { Page } from "./types.js";
export const projects: Page[] = [
{
title: "Buddhabrot renderer",
ref: "projects/buddhabrot.html",
},
{
title: "Wooden Drawing Board",
ref: "projects/drafting-board.html",
},
{
title: "Flying Hornwitser Paper Craft",
ref: "projects/paper-hornwitser.html",
},
{
title: "Prototype Soren Plush",
ref: "projects/plush-soren.html",
},
{
title: "Blender to CSS export script",
ref: "projects/blender-css.html",
},
].map(page => ({
title: page.title,
ref: page.ref,
content: <BasePage title={page.title}>
<h1>{page.title}</h1>
<p>Placeholder content</p>
</BasePage>
}));
const title = "Hornwitser's Projects";
export const projectsIndex: Page = {
title,
ref: "projects.html",
content: <BasePage title={title}>
<main>
<h1>{title}</h1>
<ul>
{ projects.map(project => <li><a href={"./" + project.ref}>{project.title}</a></li>)}
</ul>
</main>
</BasePage>
}

7
content/types.ts Normal file
View file

@ -0,0 +1,7 @@
import { Element } from "antihtml";
export interface Page {
title: string,
ref: string,
content: Element,
}

32
content/updates.tsx Normal file
View file

@ -0,0 +1,32 @@
import { BasePage } from "./bases.js";
import type { Page } from "./types.js";
export const updates: Page[] = [
{
published: "2025-xx-xx",
title: "Website Launch",
ref: "updates/site-launch.html",
}
].map(page => ({
title: page.title,
ref: page.ref,
content: <BasePage title={page.title}>
<h1>{page.title}</h1>
<p>Published: {page.published}</p>
<p>Placeholder content</p>
</BasePage>
}));
const title = "Website Updates";
export const updatesIndex: Page = {
title,
ref: "updates.html",
content: <BasePage title={title}>
<main>
<h1>{title}</h1>
<ul>
{ updates.map(update => <li><a href={"./" + update.ref}>{update.title}</a></li>)}
</ul>
</main>
</BasePage>
}

View file

@ -1,15 +0,0 @@
import { htmlDocument, prettify } from "antihtml";
export const index = htmlDocument(
prettify(
<html>
<head>
<title>My Website</title>
</head>
<body>
<h1>My Website</h1>
<p>Hello world!</p>
</body>
</html>
)
);

92
public/style.css Normal file
View file

@ -0,0 +1,92 @@
/* CSS Reset based on https://piccalil.li/blog/a-more-modern-css-reset/ */
*,
*::before,
*::after {
box-sizing: border-box;
/* Kill all default margins and paddings */
margin: 0;
padding: 0;
}
html {
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
h1, h2, h3, h4, button, input, label {
line-height: 1.1;
}
img, svg, picture {
max-width: 100%;
display: block;
}
input, button, textarea, select {
font-family: inherit;
font-size: inherit;
}
textarea:not([rows]) {
min-height: 10em;
}
:target {
scroll-margin-block: 2.5em;
}
/* Overall styling */
html {
color-scheme: light dark;
font-family: sans-serif;
}
hgroup h1 {
margin-bottom: 0.1em;
}
hgroup p {
font-style: italic;
margin-bottom: 1em;
}
h1, h2, h3, h4 {
margin-block-start: 1.25em;
margin-block-end: 0.5em;
}
ol, ul {
padding-inline-start: 1.25em;
}
/* Base Page Layout */
body {
max-width: 50rem;
padding: 0;
margin-block: 0;
margin-inline: auto;
}
.header {
padding-block: 1em;
}
.hero {
height: 30em;
background-color: grey;
}
/* index */
.author {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: 1em;
margin-block: 1em;
}
.author h1 {
margin-block-start: 0;
}
.author p {
margin-block-end: 0;
}

View file

@ -1,7 +1,8 @@
{ {
"files": ["index.tsx"], "include": ["content"],
"compilerOptions": { "compilerOptions": {
"outDir": "build/node", "outDir": "build/node",
"rootDir": ".",
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "antihtml", "jsxImportSource": "antihtml",