Initial commit
commit
bae0ce97ad
@ -0,0 +1,3 @@
|
||||
/target
|
||||
node_modules/
|
||||
build/
|
@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
cp -r content/static build/
|
||||
npx lessc css/style.less build/style.css
|
||||
npx lessc css/resume.less build/resume.css
|
||||
npx esbuild --bundle js/bundle.ts --outfile=build/bundle.js
|
||||
bun gen/main.ts
|
@ -0,0 +1,41 @@
|
||||
<x-meta>
|
||||
<title>About the Author</title>
|
||||
</x-meta>
|
||||
|
||||
<h1>About the Author</h1>
|
||||
<div>
|
||||
<img class="author" src="static/images/author.png" />
|
||||
|
||||
<p>
|
||||
Hello! My name is Nick. Currently I'm located in the Greater Philadelphia
|
||||
area.
|
||||
</p>
|
||||
<p>
|
||||
I <a href="/work.html">work</a> as a programmer, typically remotely.
|
||||
I'm usually tinkering on some sort of project, some of which are available
|
||||
publicly on <a href="https://git.idylls.net">my git service</a>, and others
|
||||
that I keep mostly private for myself and friends (though I'm typically
|
||||
happy to demo/share source code on request).
|
||||
</p>
|
||||
<p class="hidden">
|
||||
Outside of that, I've recently
|
||||
started doing some art again, which I publish under the aliases Opus Manuum
|
||||
(for more traditional art) and 0xFA7E (for pixel art). Eventually there
|
||||
will be a link here where you can view some of my pieces :)
|
||||
</p>
|
||||
<p>
|
||||
You can see what I'm currently up to on my <a href="/now.html">now</a> page.
|
||||
</p>
|
||||
<p>
|
||||
I maintain a list of the gear / tools I use <a href="/uses.html">here</a>.
|
||||
</p>
|
||||
<h2 id="contact">Contact Me</h2>
|
||||
<p>
|
||||
If you'd like to reach me, the best way to do so is by email. My email
|
||||
address is <code class="nobreak">nick at <this domain></code>. <strong>Please add my
|
||||
address to your whitelist, otherwise any responses I send may be sent to
|
||||
spam, or worse, silently dropped.</strong> I try to respond within 24 hours,
|
||||
so if you haven't heard from me by then, either check your spam folder or
|
||||
send me another email.
|
||||
</p>
|
||||
</div>
|
@ -0,0 +1,3 @@
|
||||
<x-meta>
|
||||
<title>Home</title>
|
||||
</x-meta>
|
@ -0,0 +1,5 @@
|
||||
<x-meta>
|
||||
<title>Hello</title>
|
||||
</x-meta>
|
||||
|
||||
<x-home-quote />
|
@ -0,0 +1,25 @@
|
||||
<x-meta>
|
||||
<title>Logs</title>
|
||||
</x-meta>
|
||||
|
||||
<h1>Logs</h1>
|
||||
<p>
|
||||
I have a terrible memory, so I try to keep track of various media that I've
|
||||
enjoyed here. Think of it kind of like Goodreads :)
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Everything is rated out of 10, with the general scale as follows:
|
||||
<div>10: All time favorite</div>
|
||||
<div>9: Almost perfect</div>
|
||||
<div>8: Really good</div>
|
||||
<div>7: Good</div>
|
||||
<div>6: Decent</div>
|
||||
<div>5: Meh</div>
|
||||
<div>4: Not my thing</div>
|
||||
<div>3: Really not my thing</div>
|
||||
<div>2: Bad, but maybe someone out there would like it</div>
|
||||
<div>1: Awful</div>
|
||||
</p>
|
||||
|
||||
<x-logs />
|
@ -0,0 +1,13 @@
|
||||
<x-meta>
|
||||
<title>Sitemap</title>
|
||||
</x-meta>
|
||||
|
||||
<h1>Site map</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="/index.html">Home</a></li>
|
||||
<li><a href="/about.html">About</a></li>
|
||||
<li><a href="/work.html">Work</a></li>
|
||||
<li><a href="/logs.html">Logs</a></li>
|
||||
<li><a href="/now.html">Now</a></li>
|
||||
</ul>
|
@ -0,0 +1,7 @@
|
||||
<x-meta>
|
||||
<title>Now</title>
|
||||
</x-meta>
|
||||
|
||||
|
||||
<h1>Now</h1>
|
||||
<x-now />
|
@ -0,0 +1,80 @@
|
||||
<x-meta>
|
||||
<title>Uses This</title>
|
||||
</x-meta>
|
||||
|
||||
<h1>Uses This</h1>
|
||||
<p>Inspired by <a href="https://usesthis.com">usesthis</a>.</p>
|
||||
|
||||
<section>
|
||||
<h2>Computing</h2>
|
||||
<section>
|
||||
<h3>Desktop</h3>
|
||||
<p>Built February 2022</p>
|
||||
<ul>
|
||||
<li>Case: Corsair 4000D Airflow</li>
|
||||
<li>Motherboard: Asus PRIME Z690-P D4</li>
|
||||
<li>Processor: i7-12700k</li>
|
||||
<li>GPU: EVGA GeForce RTX 3060 Ti FTW Ultra Gaming</li>
|
||||
<li>RAM: 32GB DDR4 @ 3600MHz</li>
|
||||
<li>Cooler: Scythe FUMA 2</li>
|
||||
<li>Power Supply: Corsair RM750x (2021)</li>
|
||||
<li>Drive: Samsung 970 Evo Plus 1TB</li>
|
||||
<li>Operating System: Arch Linux</li>
|
||||
<li>Mouse: Pulsar Xlite V2 mini Wireless</li>
|
||||
<li>Headphones: Sennheiser HD 560 S</li>
|
||||
<li>Microphone: Samson Technologies Q2U Dynamic Microphone</li>
|
||||
<li>Mouse pad: ARTISAN Raiden XL</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Laptop</h3>
|
||||
<p>
|
||||
Lenovo Thinkpad T440s, bought sometime in ~2013. Running Arch Linux.
|
||||
Modded with a 3-button touchpad. Looking to replace this at some point
|
||||
in the near future.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Keyboard</h3>
|
||||
<p>
|
||||
Self-built
|
||||
<a href="https://github.com/davidphilipbarr/Sweep">Ferris Sweep</a>, with
|
||||
<a href="https://boardsource.xyz/store/5fff705f03db380da20f1014">Choc Purpz</a>.
|
||||
<p>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Tools</h3>
|
||||
<ul>
|
||||
<li>Text editor: Emacs</li>
|
||||
<li>Terminal emulator: kitty</li>
|
||||
<li>Interactive shell: fish</li>
|
||||
<li>Browser: Firefox</li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Art</h2>
|
||||
<ul>
|
||||
<li>Digital art software: Krita</li>
|
||||
<li>Tablet: Wacom Intuous S</li>
|
||||
<li>Sketchbook: Some cheap hardcover notebooks from Amazon</li>
|
||||
<li>Pens: Mr. Pen Multiliners</li>
|
||||
<li>Mechanical Pencil: uni Kuru Toga Roulette</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Other</h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Updates</h2>
|
||||
<ul>
|
||||
<li>2023-01-12: Created this page</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Archive</h2>
|
||||
</section>
|
@ -0,0 +1,15 @@
|
||||
<x-meta>
|
||||
<title>Work</title>
|
||||
</x-meta>
|
||||
|
||||
<h1>Work</h1>
|
||||
|
||||
<p>
|
||||
I am currently looking for work! If you'd like to contact me, see
|
||||
<a href="/about.html#contact">here</a>.
|
||||
</p>
|
||||
<p>
|
||||
My full résumé is available <a href="/resume.html"> here</a>.
|
||||
</p>
|
||||
|
||||
<x-work />
|
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
@ -0,0 +1,165 @@
|
||||
/* Reset */
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:root {
|
||||
min-width: 100vw;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Style */
|
||||
:root {
|
||||
font-family: "Inter";
|
||||
font-size: 18pt;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 1.5rem;
|
||||
line-height: 1.3;
|
||||
max-width: 100ch;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content;
|
||||
|
||||
.name {
|
||||
grid-row: 1 / 3;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
section {
|
||||
header {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.experience {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr max-content;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.company {
|
||||
font-style: italic;
|
||||
|
||||
&::before {
|
||||
font-style: normal;
|
||||
display: inline-block;
|
||||
content: "\00a0|\00a0";
|
||||
}
|
||||
}
|
||||
|
||||
.skills {
|
||||
grid-column: 1 / 4;
|
||||
}
|
||||
|
||||
.highlights {
|
||||
grid-column: 1 / 4;
|
||||
font-size: 0.9rem;
|
||||
|
||||
padding-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.projects {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
aside {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
font-style: italic;
|
||||
text-align: right;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.project {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
grid-template-rows: repeat(2, min-content);
|
||||
|
||||
.name {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.skills {
|
||||
&::before {
|
||||
font-style: normal;
|
||||
display: inline-block;
|
||||
content: "\00a0|\00a0";
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.education {
|
||||
display: grid;
|
||||
grid-template-columns: fit-content(100%) max-content;
|
||||
|
||||
header {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
|
||||
.university {
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
font-weight: normal;
|
||||
display: inline-block;
|
||||
content: "\00a0\2014\00a0"
|
||||
}
|
||||
}
|
||||
|
||||
.degree {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media print {
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 1.5rem;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:root {
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
section.projects {
|
||||
padding-top: 22px;
|
||||
}
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
/* Reset */
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
min-width: 100vw;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-size: unset;
|
||||
margin: 0;
|
||||
font-style: unset;
|
||||
font-weight: unset;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
line-height: unset;
|
||||
}
|
||||
|
||||
/* Site Elements */
|
||||
:root {
|
||||
--background: black;
|
||||
--foreground: white;
|
||||
--base-font-size: 18pt;
|
||||
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-size: 18pt;
|
||||
font-family: sans;
|
||||
}
|
||||
|
||||
a {
|
||||
position: relative;
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
|
||||
&[href^="http"]::after {
|
||||
display: inline-block;
|
||||
content: "🡕";
|
||||
margin-left: 3px;
|
||||
font-weight: bold;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration-thickness: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
padding: 1em;
|
||||
ul {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 1rem auto;
|
||||
padding: 0 1em;
|
||||
max-width: 75ch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.home-quote {
|
||||
font-size: 1.25rem;
|
||||
margin: 1.5em;
|
||||
|
||||
cite {
|
||||
font-style: normal;
|
||||
font-size: 1.1rem;
|
||||
margin-top: 1em;
|
||||
display: block;
|
||||
|
||||
&::before {
|
||||
display: inline-block;
|
||||
content: "\2014\00a0";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img.author {
|
||||
float: left;
|
||||
max-width: 256px;
|
||||
margin-right: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.2rem;
|
||||
margin-bottom: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.now {
|
||||
section {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.nobreak {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.full-bleed {
|
||||
width: 100vw;
|
||||
margin-left: calc(50% - 50vw);
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 25px 10px;
|
||||
}
|
||||
|
||||
table.work {
|
||||
max-width: 85vw;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
./build.sh
|
||||
|
||||
rsync -avz -e 'ssh -p 8022' build/* root@idylls.net:/var/www/htdocs/idylls.net/
|
@ -0,0 +1,18 @@
|
||||
import { buildPages } from "./pages/build";
|
||||
|
||||
const abort = (e: any) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
};
|
||||
|
||||
const check = <T>(p: Promise<T>) => p.catch(abort);
|
||||
|
||||
const gen = async () => {
|
||||
const bp = check(buildPages());
|
||||
|
||||
await bp;
|
||||
|
||||
console.log("Generation done");
|
||||
};
|
||||
|
||||
await gen();
|
@ -0,0 +1,131 @@
|
||||
import { readdir, mkdir, stat, readFile, writeFile } from "node:fs/promises";
|
||||
import { HTMLElement, parse, NodeType } from "node-html-parser";
|
||||
|
||||
import { render as renderNow } from "./now";
|
||||
import { render as renderHomeQuote } from "./quote";
|
||||
import { render as renderLogs } from "./logs";
|
||||
import { render as renderWork, renderResume } from "./work";
|
||||
|
||||
const walkdir = async (path: string) => {
|
||||
const paths = await readdir(path);
|
||||
const out = [] as string[];
|
||||
|
||||
for (const p of paths) {
|
||||
const p_ = `${path}/${p}`;
|
||||
const st = await stat(p_);
|
||||
if (st.isDirectory()) {
|
||||
// TODO: do something special
|
||||
continue;
|
||||
}
|
||||
|
||||
out.push(p_);
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
const RENDERERS = {
|
||||
"x-meta": () => "",
|
||||
"x-now": renderNow,
|
||||
"x-logs": renderLogs,
|
||||
"x-home-quote": renderHomeQuote,
|
||||
"x-work": renderWork,
|
||||
};
|
||||
const renderElement = (el: HTMLElement) => {
|
||||
if (el.nodeType == NodeType.TEXT_NODE) {
|
||||
return el.rawText;
|
||||
}
|
||||
|
||||
const renderer = RENDERERS[el.tagName.toLowerCase()] ?? renderBasic;
|
||||
|
||||
return renderer(el);
|
||||
};
|
||||
|
||||
const renderBasic = (el: HTMLElement) => {
|
||||
const tag = el.tagName.toLowerCase();
|
||||
|
||||
let out = `<${tag}`;
|
||||
for (const attr in el.attributes) {
|
||||
out += ` ${attr}="${el.attributes[attr]}"`;
|
||||
}
|
||||
out += ">";
|
||||
|
||||
const children = el.childNodes.map(renderElement).join("");
|
||||
out += children;
|
||||
out += `</${tag}>`;
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
const buildPage = async (pagePath: string) => {
|
||||
const contents = (await readFile(pagePath)).toString();
|
||||
const parsed = parse(contents);
|
||||
|
||||
const pageOpts = (() => {
|
||||
const el = parsed.querySelector("x-meta");
|
||||
if (!el) {
|
||||
return {
|
||||
title: null,
|
||||
};
|
||||
}
|
||||
|
||||
const title = el.querySelector("title");
|
||||
|
||||
return {
|
||||
title: title?.innerText ?? null,
|
||||
};
|
||||
})();
|
||||
|
||||
const els = parsed.childNodes.map(renderElement).join("");
|
||||
const rendered = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
<script src="/bundle.js"></script>
|
||||
${
|
||||
pageOpts.title
|
||||
? `<title>${pageOpts.title} - idylls.net</title>`
|
||||
: "<title>idylls.net</title>"
|
||||
}
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/index.html">Home</a></li>
|
||||
<li><a href="/about.html">About</a></li>
|
||||
<li><a href="/about.html#contact">Contact</a></li>
|
||||
<li><a href="/work.html">Work</a></li>
|
||||
<li><a href="https://git.idylls.net">Git</a></li>
|
||||
<li><a href="https://status.idylls.net">Services</a></li>
|
||||
<li><a href="/nav.html">More</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main>
|
||||
${els}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const [, p] = pagePath.split("content/pages/");
|
||||
const outPath = `build/${p}`;
|
||||
|
||||
return writeFile(outPath, rendered);
|
||||
};
|
||||
|
||||
export const buildPages = async () => {
|
||||
await mkdir("build/", { recursive: true });
|
||||
|
||||
const paths = await walkdir("content/pages");
|
||||
|
||||
for (const p of paths) {
|
||||
await buildPage(p);
|
||||
}
|
||||
|
||||
const resumeContent = renderResume();
|
||||
|
||||
await writeFile("build/resume.html", resumeContent);
|
||||
};
|
@ -0,0 +1,165 @@
|
||||
import { isoDateStr } from "../util";
|
||||
|
||||
type LogBase = {
|
||||
name: string;
|
||||
started?: Date;
|
||||
finished?: Date;
|
||||
dropped?: true;
|
||||
rating?: number;
|
||||
picUrl?: string;
|
||||
};
|
||||
|
||||
const status = (log: LogBase) => {
|
||||
if (log.dropped) {
|
||||
return "dropped" as const;
|
||||
}
|
||||
|
||||
if (!log.started && !log.finished && !log.rating) {
|
||||
return "wantTo" as const;
|
||||
}
|
||||
|
||||
if (log.started && !log.finished) {
|
||||
return "inProgress" as const;
|
||||
}
|
||||
|
||||
if (log.finished || log.rating) {
|
||||
return "finished" as const;
|
||||
}
|
||||
|
||||
throw new Error("unreachable");
|
||||
};
|
||||
|
||||
const byStatusSorted = (items: LogBase[]) => {
|
||||
const inProgress = items.filter((i) => status(i) == "inProgress");
|
||||
inProgress.sort((a, b) => +(a.started ?? 0) - +(b.started ?? 0));
|
||||
|
||||
const finished = items.filter((i) => status(i) == "finished");
|
||||
finished.sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0));
|
||||
|
||||
const wantTo = items.filter((i) => status(i) == "wantTo");
|
||||
wantTo.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const dropped = items.filter((i) => status(i) == "dropped");
|
||||
dropped.sort((a, b) => +((a.started ?? 0) > (b.started ?? 0)));
|
||||
|
||||
return { dropped, wantTo, finished, inProgress };
|
||||
};
|
||||
|
||||
type Anime = LogBase & { season?: number };
|
||||
const ANIME: Anime[] = [
|
||||
{
|
||||
name: "Violet Evergarden",
|
||||
rating: 10,
|
||||
},
|
||||
{
|
||||
name: "Sugar Apple Fairy Tale",
|
||||
started: new Date("2023-01-22"),
|
||||
},
|
||||
{
|
||||
name: "REVENGER",
|
||||
started: new Date("2023-01-21"),
|
||||
},
|
||||
{
|
||||
name: "Ice Guy and the Cool Female Colleague",
|
||||
},
|
||||
{
|
||||
name: "High Card",
|
||||
started: new Date("2023-01-21"),
|
||||
},
|
||||
{
|
||||
name: "Bocchi the Rock!",
|
||||
started: new Date("2023-01-12"),
|
||||
season: 1,
|
||||
},
|
||||
{
|
||||
name: "Blue Period",
|
||||
started: new Date("2023-01-02"),
|
||||
finished: new Date("2023-01-12"),
|
||||
season: 1,
|
||||
rating: 10,
|
||||
},
|
||||
{
|
||||
name: "Chainsaw Man",
|
||||
started: new Date("2022-12-01"),
|
||||
finished: new Date("2023-01-04"),
|
||||
season: 1,
|
||||
rating: 7.5,
|
||||
},
|
||||
{
|
||||
name: "Do it Yourself!!",
|
||||
started: new Date("2022-12-01"),
|
||||
season: 1,
|
||||
},
|
||||
{
|
||||
name: "Cyberpunk: Edgerunners",
|
||||
season: 1,
|
||||
rating: 10,
|
||||
},
|
||||
{
|
||||
name: "The Bear",
|
||||
rating: 8,
|
||||
},
|
||||
{
|
||||
name: "The Witcher",
|
||||
rating: 9,
|
||||
},
|
||||
{
|
||||
name: "Arcane",
|
||||
rating: 10,
|
||||
},
|
||||
{
|
||||
name: "Castlevania",
|
||||
rating: 10,
|
||||
},
|
||||
{
|
||||
name: "Over the Garden Wall",
|
||||
rating: 10,
|
||||
},
|
||||
];
|
||||
const renderAnime = () => {
|
||||
const renderSection = (title: string, items: Anime[]) => {
|
||||
let out = `<section><h3>${title}</h3>`;
|
||||
for (const a of items) {
|
||||
out += `<div class="log anime"><h4>${a.name}`;
|
||||
if (a.season) {
|
||||
out += `, Season ${a.season}`;
|
||||
}
|
||||
out += "</h4>";
|
||||
|
||||
if (a.started) {
|
||||
out += `<div class="started">Started: ${isoDateStr(a.started)}</div>`;
|
||||
}
|
||||
|
||||
if (a.finished) {
|
||||
out += `<div class="finished">Finished: ${isoDateStr(
|
||||
a.finished,
|
||||
)}</div>`;
|
||||
}
|
||||
|
||||
if (a.rating) {
|
||||
out += `<div class="rating">Rating: ${a.rating} / 10</div>`;
|
||||
}
|
||||
|
||||
out += "</div>";
|
||||
}
|
||||
out += "</section>";
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
const { wantTo, dropped, finished, inProgress } = byStatusSorted(ANIME);
|
||||
|
||||
return `
|
||||
<section>
|
||||
<h2>Anime / TV</h2>
|
||||
${renderSection("Currently Watching", inProgress)}
|
||||
${renderSection("Finished", finished)}
|
||||
${renderSection("Dropped", dropped)}
|
||||
${renderSection("Want to Watch", wantTo)}
|
||||
</section>
|
||||
`;
|
||||
};
|
||||
|
||||
export const render = () => {
|
||||
return renderAnime();
|
||||
};
|
@ -0,0 +1,95 @@
|
||||
import { isoDateStr, dbg } from "../util";
|
||||
|
||||
type EntryKind =
|
||||
| {
|
||||
kind: "free";
|
||||
text: string;
|
||||
}
|
||||
| {
|
||||
kind: "listeningTo";
|
||||
artist: string;
|
||||
song: string;
|
||||
url?: string;
|
||||
}
|
||||
| {
|
||||
kind: "workingOn";
|
||||
what: string;
|
||||
url?: string;
|
||||
};
|
||||
type Entry = {
|
||||
date: Date;
|
||||
entries: EntryKind[];
|
||||
};
|
||||
|
||||
const ENTRIES: Entry[] = [
|
||||
{
|
||||
date: new Date("2023-01-10"),
|
||||
entries: [{ kind: "workingOn", what: "this site!" }],
|
||||
},
|
||||
{
|
||||
date: new Date("2023-01-18"),
|
||||
entries: [
|
||||
{ kind: "free", text: "Looking for work!" },
|
||||
{
|
||||
kind: "listeningTo",
|
||||
artist: "Saint Pepsi",
|
||||
song: "Better",
|
||||
url: "https://music.youtube.com/watch?v=WYvji5AXOfk",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
date: new Date("2023-01-12"),
|
||||
entries: [
|
||||
{
|
||||
kind: "listeningTo",
|
||||
artist: "Darius (feat. Amaria)",
|
||||
song: "FADED",
|
||||
url: "https://music.youtube.com/watch?v=SfTOJ9PeQlY",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const render = () => {
|
||||
const entries = [...ENTRIES];
|
||||
entries.sort((a, b) => +b.date - +a.date);
|
||||
|
||||
let out = '<div class="now">';
|
||||
for (const entry of entries) {
|
||||
out += `
|
||||
<section>
|
||||
<div class="date">
|
||||
${isoDateStr(entry.date)}
|
||||
</div>`;
|
||||
|
||||
for (const en of entry.entries) {
|
||||
switch (en.kind) {
|
||||
case "workingOn":
|
||||
out += `<div class="working-on">Working on: ${en.what}</div>`;
|
||||
break;
|
||||
case "free":
|
||||
out += `<div class="free">${en.text}</div>`;
|
||||
break;
|
||||
case "listeningTo":
|
||||
out += `
|
||||
<div class="listening-to">
|
||||
Listening to:
|
||||
<a href="${en.url}">
|
||||
<span class="artist">${en.artist}</span>
|
||||
-
|
||||
<span class="song">${en.song}</span>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out += `</section>`;
|
||||
}
|
||||
|
||||
out += "</div>";
|
||||
|
||||
return out;
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
// TODO: now that this is in typescript, maybe share quote list and
|
||||
// dynamically pick one here?
|
||||
|
||||
export const render = () => `
|
||||
<div class="home-quote">
|
||||
<blockquote>
|
||||
The world ain't all sunshine and rainbows. It's a very mean and
|
||||
nasty place, and I don't care how tough you are it will beat you
|
||||
to your knees and keep you there permanently if you let it. You,
|
||||
me, or nobody is gonna hit as hard as life. But it ain't about
|
||||
how hard you hit. It's about how hard you can get hit and keep
|
||||
moving forward. How much you can take and keep moving forward.
|
||||
That's how winning is done!
|
||||
<cite>Rocky</cite>
|
||||
</blockquote>
|
||||
<script>initQuote()</script>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,327 @@
|
||||
import { isoDateStr } from "../util";
|
||||
|
||||
type Info = {
|
||||
name: string;
|
||||
emailAddress: string;
|
||||
website: string;
|
||||
};
|
||||
|
||||
type Education = {
|
||||
university: string;
|
||||
college: string;
|
||||
graduationYear: number;
|
||||
degree: string;
|
||||
};
|
||||
|
||||
type Timeframe = { start: Date; end?: Date } | string;
|
||||
|
||||
type Skill =
|
||||
| "TypeScript"
|
||||
| "React"
|
||||
| "Python"
|
||||
| "Django"
|
||||
| "CSS"
|
||||
| "SCSS"
|
||||
| "PostgreSQL"
|
||||
| "Java"
|
||||
| "Go"
|
||||
| "MongoDB"
|
||||
| "WordPress"
|
||||
| "PHP"
|
||||
| "Kotlin"
|
||||
| "HTML"
|
||||
| "Elixir"
|
||||
| "Phoenix"
|
||||
| "JavaScript"
|
||||
| "Docker"
|
||||
| "Rust"
|
||||
| "C++"
|
||||
| "C"
|
||||
| "Haskell"
|
||||
| "GraphQL";
|
||||
|
||||
type Experience = {
|
||||
companyName: string;
|
||||
title: string;
|
||||
skills: Skill[];
|
||||
timeframe: Timeframe;
|
||||
highlights: string[];
|
||||
};
|
||||
|
||||
type Project = {
|
||||
name: string;
|
||||
skills: Skill[];
|
||||
description: string;
|
||||
link?: string;
|
||||
};
|
||||
|
||||
type Resume = {
|
||||
info: Info;
|
||||
education: Education;
|
||||
experiences: Experience[];
|
||||
selectedProjects: Project[];
|
||||
skills: Skill[];
|
||||
};
|
||||
|
||||
const RESUME: Resume = {
|
||||
info: {
|
||||
name: "Nick",
|
||||
emailAddress: "hello@idylls.net",
|
||||
website: "https://idylls.net",
|
||||
},
|
||||
education: {
|
||||
university: "Northeastern University",
|
||||
college: "Khoury College of Computer Sciences",
|
||||
degree: "Bachelor of Science in Computer Science with Honors, Cum Laude",
|
||||
graduationYear: 2019,
|
||||
},
|
||||
experiences: [
|
||||
{
|
||||
companyName: "Massachusetts Bay Transportation Authority",
|
||||
title: "Full Stack Software Engineer",
|
||||
highlights: [
|
||||
"Led maintenance and development of critical, high-availability service delivering transit alerts to riders via both SMS and email",
|
||||
"Developed and deployed various improvements to public-facing rider website",
|
||||
],
|
||||
skills: [
|
||||
"Elixir",
|
||||
"Phoenix",
|
||||
"TypeScript",
|
||||
"JavaScript",
|
||||
"SCSS",
|
||||
"Docker",
|
||||
"PostgreSQL",
|
||||
],
|
||||
timeframe: "2022",
|
||||
},
|
||||
{
|
||||
companyName: "KickUp",
|
||||
title: "Full Stack Software Engineer",
|
||||
highlights: [
|
||||
"Designed and implemented several features improving client abilities to visualize, analyze, and action teacher growth data",
|
||||
"Led effort to integrate TypeScript into existing JavaScript codebase, as well as provided mentorship to other team members",
|
||||
],
|
||||
skills: [
|
||||
"TypeScript",
|
||||
"JavaScript",
|
||||
"React",
|
||||
"Python",
|
||||
"Django",
|
||||
"CSS",
|
||||
"PostgreSQL",
|
||||
"GraphQL",
|
||||
],
|
||||
timeframe: {
|
||||
start: new Date("2020-10-01"),
|
||||
end: new Date("2022-02-01"),
|
||||
},
|
||||
},
|
||||
{
|
||||
companyName: "TripAdvisor",
|
||||
title: "Full Stack Software Engineer",
|
||||
highlights: [
|
||||
"Led design and implementation of project surfacing location outliers to suppliers, increasing sales and click-through rate on 10% of products",
|
||||
"Implemented improved online photo editor, increasing the percentage of listings with owner-provided photos by 5%",
|
||||
],
|
||||
timeframe: {
|
||||
start: new Date("2019-09-01"),
|
||||
end: new Date("2020-06-01"),
|
||||
},
|
||||
skills: ["Java", "JavaScript", "React", "CSS", "PostgreSQL", "GraphQL"],
|
||||
},
|
||||
{
|
||||
companyName: "TripAdvisor, Rentals",
|
||||
title: "Full Stack Engineering Co-op",
|
||||
highlights: [
|
||||
"Assisted in redesigning the external calendar syncing system, reducing the time to update hundreds of thousands of external calendars from hours to fifteen minutes",
|
||||
"Designed, developed, and tested endpoints for internal and external REST APIs in a large Java codebase, as well as modified user-facing web pages",
|
||||
],
|
||||
timeframe: "Spring/Summer 2018",
|
||||
skills: ["Java", "JavaScript", "CSS"],
|
||||
},
|
||||
{
|
||||
companyName: "clypd",
|
||||
title: "Backend Engineering Co-op",
|
||||
highlights: [
|
||||
"Acted as feature lead on redesign of large portions of existing API to surface additional data and improve ergonomics for major analytics project",
|
||||
"Organized meetups originally related to Rust but later expanding to general code jams",
|
||||
],
|
||||
skills: ["Go", "PostgreSQL"],
|
||||
timeframe: "Spring/Summer 2017",
|
||||
},
|
||||
{
|
||||
companyName: "I'm From the Future",
|
||||
title: "Junior Full Stack Software Engineer",
|
||||
highlights: [
|
||||
"Led development on an internal marketing tool using React, D3.js, PhantomJS, and MongoDB",
|
||||
],
|
||||
skills: ["PHP", "JavaScript", "React", "MongoDB", "CSS", "WordPress"],
|
||||
timeframe: "Summer/Fall 2016",
|
||||
},
|
||||
{
|
||||
companyName: "ProTech Internet Group",
|
||||
title: "Junior Web Developer",
|
||||
skills: ["PHP", "JavaScript", "CSS", "WordPress"],
|
||||
timeframe: {
|
||||
start: new Date("2015-06-01"),
|
||||
end: new Date("2016-06-01"),
|
||||
},
|
||||
highlights: [],
|
||||
},
|
||||
{
|
||||
companyName: "SiteTechs",
|
||||
title: "Web Content Admin",
|
||||
skills: ["JavaScript", "HTML", "CSS", "WordPress"],
|
||||
timeframe: {
|
||||
start: new Date("2012-06-01"),
|
||||
end: new Date("2015-09-01"),
|
||||
},
|
||||
highlights: [],
|
||||
},
|
||||
],
|
||||
selectedProjects: [
|
||||
{
|
||||
name: "Pathos",
|
||||
description:
|
||||
"An Old School RuneScape plugin for displaying pathing information",
|
||||
skills: ["Kotlin"],
|
||||
link: "https://git.idylls.net/idylls/pathos",
|
||||
},
|
||||
{
|
||||
name: "Statue",
|
||||
description: "A lightweight web host / service status monitor",
|
||||
skills: ["Rust", "TypeScript"],
|
||||
link: "https://status.idylls.net",
|
||||
},
|
||||
],
|
||||
skills: ["Rust", "TypeScript", "C++", "C", "Haskell", "Java"],
|
||||
};
|
||||
|
||||
export const renderResume = () => {
|
||||
const renderInfo = () => `
|
||||
<section class="info">
|
||||
<div class="name">${RESUME.info.name}</div>
|
||||
<div class="email">${RESUME.info.emailAddress}</div>
|
||||
<div class="website">
|
||||
<a href="${RESUME.info.website}">
|
||||
${RESUME.info.website}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
const renderExperiences = () => {
|
||||
let out = `<section class="experiences"><header>Experience</header>`;
|
||||
|
||||
for (const e of RESUME.experiences) {
|
||||
out += `<div class="experience">`;
|
||||
|
||||
out += `<div class="title">${e.title}</div>`;
|
||||
out += `<div class="company">${e.companyName}</div>`;
|
||||
out += `<div class="timeframe">${renderTimeframe(e.timeframe)}</div>`;
|
||||
out += `<div class="skills">${e.skills.join(", ")}</div>`;
|
||||
|
||||
out += `<ul class="highlights">`;
|
||||
for (const h of e.highlights) {
|
||||
out += `<li>${h}</li>`;
|
||||
}
|
||||
out += `</ul>`;
|
||||
|
||||
out += `</div>`;
|
||||
}
|
||||
|
||||
out += `</section>`;
|
||||
|
||||
return out;
|
||||
};
|
||||
const renderProjects = () => {
|
||||
let out = `<section class="projects"><header>Selected Projects</header>`;
|
||||
out += `<aside>See more at <a href="https://git.idylls.net/idylls">https://git.idylls.net/idylls</a></aside>`;
|
||||
|
||||
for (const p of RESUME.selectedProjects) {
|
||||
out += `<div class="project">`;
|
||||
|
||||
out += `<div class="name">${p.name}</div>`;
|
||||
out += `<div class="skills">${p.skills.join(", ")}</div>`;
|
||||
out += `<div class="description">${p.description}</div>`;
|
||||
|
||||
out += `</div>`;
|
||||
}
|
||||
|
||||
out += "</section>";
|
||||
|
||||
return out;
|
||||
};
|
||||
const renderEducation = () => `
|
||||
<section class="education">
|
||||
<header>Education</header>
|
||||
<div class="university">${RESUME.education.university}</div>
|
||||
<div class="college">${RESUME.education.college} Class of ${RESUME.education.graduationYear}</div>
|
||||
<div class="degree">${RESUME.education.degree}</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
const renderSkills = () => `
|
||||
<section class="skills">
|
||||
<header>Skills</header>
|
||||
<div>${RESUME.skills.join(", ")}</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/resume.css" />
|
||||
<title>Résumé</title>
|
||||
</head>
|
||||
<body>
|
||||
${renderInfo()}
|
||||
${renderExperiences()}
|
||||
${renderProjects()}
|
||||
${renderEducation()}
|
||||
${renderSkills()}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
};
|
||||
|
||||
const renderTimeframe = (t: Timeframe) => {
|
||||
if (typeof t == "string") {
|
||||
return t;
|
||||
}
|
||||
|
||||
return `${t.start.getFullYear()} — ${
|
||||
t.end ? t.end.getFullYear() : "Present"
|
||||
}`;
|
||||
};
|
||||
|
||||
export const render = () => {
|
||||
let out = `
|
||||
<div class="full-bleed">
|
||||
<table class="full-bleed work">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Company</th>
|
||||
<th>Title</th>
|
||||
<th>Timeframe</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
for (const e of RESUME.experiences) {
|
||||
out += `
|
||||
<tr>
|
||||
<td>${e.companyName}</td>
|
||||
<td>${e.title}</td>
|
||||
<td>${renderTimeframe(e.timeframe)}</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
out += `</tbody></table></div>`;
|
||||
|
||||
return out;
|
||||
};
|
@ -0,0 +1,11 @@
|
||||
export const isoDateStr = (date: Date) =>
|
||||
`${date.getUTCFullYear()}-${`${date.getUTCMonth() + 1}`.padStart(
|
||||
2,
|
||||
"0",
|
||||
)}-${`${date.getUTCDate()}`.padStart(2, "0")}`;
|
||||
|
||||
export const dbg = <T>(a: T) => {
|
||||
console.debug(a);
|
||||
|
||||
return a;
|
||||
};
|
@ -0,0 +1,3 @@
|
||||
import { init as initQuote } from "./quote";
|
||||
|
||||
window["initQuote"] = initQuote;
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "sike",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"devDependencies": {
|
||||
"bun-types": "^0.5.0",
|
||||
"esbuild": "^0.16.17",
|
||||
"less": "^4.1.3",
|
||||
"node-html-parser": "^6.1.4",
|
||||
"swel": "file:../swel"
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
printWidth: 80,
|
||||
useTabs: true,
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
quoteProps: "as-needed",
|
||||
trailingComma: "all",
|
||||
bracketSpacing: true,
|
||||
arrowParens: "always",
|
||||
parser: "typescript",
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"strictNullChecks": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noImplicitOverride": true,
|
||||
"checkJs": true,
|
||||
"allowJs": true,
|
||||
"types": ["bun-types"]
|
||||
},
|
||||
"include": ["gen/**/*.ts", "gen/**/*.js"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in New Issue