feat: new website

This commit is contained in:
Dan Finch 2026-05-13 02:03:11 +02:00
commit 50c1c47e2b
64 changed files with 2908 additions and 0 deletions

18
src/ui/Layout/Head.ts Normal file
View file

@ -0,0 +1,18 @@
const DEFAULT_TITLE = "Dan Finch"
export default function Head(props: { title: string }, ...contents: Content[]) {
return head(
meta({ charset: "utf-8" }),
meta({
name: "viewport",
content: "width=device-width, initial-scale=1.0",
}),
link({
rel: "shortcut icon",
type: "image/png",
href: "/favicon.png",
}),
title(props.title || DEFAULT_TITLE),
contents,
)
}

50
src/ui/Layout/Layout.ts Normal file
View file

@ -0,0 +1,50 @@
import Head from "./Head"
import background from "assets/background.js?raw"
import css98 from "98se.css?url"
import Taskbar from "ui/Layout/Taskbar"
import Theme from "ui/Layout/Theme"
export default function Layout(
props: { title: string, url: string },
...contents: Content[]
) {
return [
doctype.html5,
html(
Head(props, [
link({ rel: "stylesheet", href: css98 }),
style(Theme()),
]),
body(
backgroundColor.black,
canvas(
{ id: "background" },
display.block,
position.fixed,
width("100%"),
height("100%"),
left(0),
top(0),
zIndex(-1),
),
div(
display.flex,
flexDirection.column,
height("100dvh"),
nav(
height.maxContent,
Taskbar({ url: props.url }),
),
main(
flex(1),
overflowY.hidden,
display.flex,
justifyContent.center,
contents,
)
)
),
script(raw(background)),
),
]
}

14
src/ui/Layout/Reset.ts Normal file
View file

@ -0,0 +1,14 @@
export default function Reset() {
return [
rule("*, *::before, *::after", [boxSizing.borderBox]),
rule("*:not(dialog)", [margin(0)]),
rule(
"input, button, textarea, select",
font("inherit"),
),
rule(
"p, h1, h2, h3, h4, h5, h6",
overflowWrap.breakWord,
),
]
}

101
src/ui/Layout/Taskbar.ts Normal file
View file

@ -0,0 +1,101 @@
import favicon from "public/favicon.png?url"
import linkedin from "public/icons/linkedin.svg?raw"
import github from "public/icons/github.svg?raw"
import aboutIcon from "public/icons/about.png?url"
import experienceIcon from "public/icons/experience.png?url"
import projectsIcon from "public/icons/projects.png?url"
import techIcon from "public/icons/tech.png?url"
const vr = hr(height("20px"), margin("0px 2px"))
export default function Taskbar({ url }: { url: string }) {
return div.taskbar.window(
textWrapMode.nowrap,
overflowX.hidden,
margin("10px"),
div(
display.flex,
alignItems.center,
gap("2px"),
div(
display.flex,
paddingLeft("4px"),
img(
{ src: favicon },
width("12px"),
height("12px"),
marginRight("4px"),
),
div(fontWeight.bold, "Dan Finch"),
),
vr,
a(
{ title: "LinkedIn" },
{ href: "https://www.linkedin.com/in/danfinch/" },
raw(linkedin),
),
a(
{ title: "GitHub" },
{ href: "https://github.com/errilaz" },
raw(github),
),
vr,
Task({
icon: aboutIcon,
title: "About",
href: "/",
active: url === "/",
}),
Task({
icon: projectsIcon,
title: "Projects",
href: "/projects",
active: url === "/projects",
}),
Task({
icon: experienceIcon,
title: "Experience",
href: "/experience",
active: url === "/experience",
}),
Task({
icon: techIcon,
title: "Tech",
href: "/tech",
active: url === "/tech",
}),
),
)
}
function Task({
icon,
title,
href,
active,
}: {
icon: string
title: Content
href?: string
active?: boolean
}) {
return a.button(
{ href },
active && className("active"),
padding("0 4px"),
textAlign.left,
div(
width("100%"),
gap("4px"),
display.flex,
justifyContent.flexStart,
img(
{ src: icon },
width("12px"),
height("12px"),
marginRight("4px"),
),
span(title),
),
)
}

39
src/ui/Layout/Theme.ts Normal file
View file

@ -0,0 +1,39 @@
import Reset from "./Reset"
export default function Theme() {
return [
Reset(),
$media(
"(max-width: 468px)",
rule(
".taskbar .button",
minWidth("unset"),
rule("img", display.none),
),
),
rule("a.button", [
textDecoration.none,
cursor._default,
display.flex,
alignItems.center,
]),
rule(h1, fontSize("20px")),
rule(h2, fontSize("16px")),
rule(ul, paddingInlineStart("30px")),
rule.content(
textAlign.justify,
fontFamily("Arial"),
fontSize("14px"),
rule(h1, fontSize("28px")),
rule(h2, fontSize("20px")),
rule(h3, fontSize("14px"),paddingLeft("30px")),
rule(aside,
float.right,
marginLeft("20px"),
paddingBottom(0),
),
rule("p, h1, h2, h3, ul", margin("12px 0")),
),
rule(iframe, border(0)),
]
}

32
src/ui/Layout/Window.ts Normal file
View file

@ -0,0 +1,32 @@
const DEFAULT_WIDTH = "770px"
export default function Window(
props: { title: Content; width?: Content; flat?: boolean },
...contents: Content[]
) {
return article.window(
opacity(0.9),
marginTop("16px"),
marginBottom("24px"),
marginLeft("10px"),
marginRight("10px"),
width(props.width ?? DEFAULT_WIDTH),
maxWidth(props.width ?? DEFAULT_WIDTH),
display.flex,
flexDirection.column,
div.titleBar(
div.titleBarText(props.title),
div.titleBarControls(
button({ "aria-label": "Minimize" }),
button({ "aria-label": "Maximize" }),
button({ "aria-label": "Close" }),
),
),
div.windowBody(
props.flat ? null : className("field-border"),
flex(1),
overflowY.auto,
contents,
),
)
}

1
src/ui/Layout/index.ts Normal file
View file

@ -0,0 +1 @@
export { default } from "./Layout"