feat: new website
This commit is contained in:
commit
63a654aab4
64 changed files with 2904 additions and 0 deletions
1
src/ambient.d.ts
vendored
Normal file
1
src/ambient.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
import "@hypeup/lexicon"
|
||||
1
src/routes/cv.html.ts
Normal file
1
src/routes/cv.html.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "ui/CV"
|
||||
1
src/routes/experience.html.ts
Normal file
1
src/routes/experience.html.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "ui/Experience"
|
||||
1
src/routes/index.html.ts
Normal file
1
src/routes/index.html.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "ui/Home"
|
||||
1
src/routes/projects.html.ts
Normal file
1
src/routes/projects.html.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "ui/Projects"
|
||||
1
src/routes/tech.html.ts
Normal file
1
src/routes/tech.html.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "ui/Tech"
|
||||
4
src/types.d.ts
vendored
Normal file
4
src/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
declare type Content = any
|
||||
|
||||
declare module "*?raw" { const src: string; export default src }
|
||||
declare module "*?url" { const src: string; export default src }
|
||||
192
src/ui/CV/CV.ts
Normal file
192
src/ui/CV/CV.ts
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
import Reset from "ui/Layout/Reset"
|
||||
import Markdown from "ui/shared/Markdown"
|
||||
import intro from "ui/Home/intro.md?raw"
|
||||
import details from "ui/Home/details.md?raw"
|
||||
import skills from "ui/Home/skills.md?raw"
|
||||
import experience from "ui/Experience/experience.md?raw"
|
||||
import * as Tech from "ui/Tech/Sections"
|
||||
import roboto from "@fontsource/roboto-condensed?url"
|
||||
|
||||
export default function CV() {
|
||||
return [
|
||||
doctype.html5,
|
||||
html(
|
||||
head(
|
||||
link({ rel: "stylesheet", href: roboto }),
|
||||
Theme(),
|
||||
),
|
||||
body(
|
||||
main(
|
||||
article(
|
||||
Header(),
|
||||
About(),
|
||||
Skills(),
|
||||
Experience(),
|
||||
Expertise(),
|
||||
),
|
||||
),
|
||||
a.pdf.noprint({ href: "cv.pdf" }, "PDF"),
|
||||
),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
function Header() {
|
||||
return header(
|
||||
h1("Dan Finch"),
|
||||
div.links(
|
||||
div.www(
|
||||
a({ href: "https://da.nfin.ch" }, "Website"),
|
||||
a(
|
||||
{ href: "https://www.sigitex.com" },
|
||||
"Open Source",
|
||||
),
|
||||
a(
|
||||
{
|
||||
href: "https://www.linkedin.com/in/danfinch",
|
||||
},
|
||||
"LinkedIn",
|
||||
),
|
||||
),
|
||||
div.contact(
|
||||
a(
|
||||
{ href: "mailto:danfinch@outlook.com" },
|
||||
"danfinch@outlook.com",
|
||||
),
|
||||
a({ href: "tel:+436645047583" }, "+43 664 5047583"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
function About() {
|
||||
return section.about(
|
||||
h2("About Me"),
|
||||
Markdown(intro),
|
||||
Markdown(details),
|
||||
)
|
||||
}
|
||||
|
||||
function Skills() {
|
||||
return section.skills(h2("Skills"), Markdown(skills))
|
||||
}
|
||||
|
||||
function Experience() {
|
||||
return section.experience(
|
||||
h2("Experience"),
|
||||
Markdown(experience),
|
||||
)
|
||||
}
|
||||
|
||||
function Expertise() {
|
||||
return section.expertise(
|
||||
h2("Expertise"),
|
||||
div.blocks(
|
||||
Skillset("Core Languages", Tech.languages),
|
||||
Skillset("Platforms", Tech.platforms),
|
||||
Skillset("Frontend", Tech.frontend),
|
||||
Skillset("Backend", Tech.backend),
|
||||
Skillset("Data", Tech.data),
|
||||
Skillset("Principles", Tech.principles),
|
||||
Skillset("Architecture", Tech.architecture),
|
||||
Skillset("Delivery", Tech.delivery),
|
||||
Skillset("Quality", Tech.quality),
|
||||
Skillset("Collaboration", Tech.collaboration),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
function Skillset(name: string, content: string) {
|
||||
return div.block(h3(name), Markdown(content))
|
||||
}
|
||||
|
||||
const Color = {
|
||||
backdrop: "#f0f0f0",
|
||||
edge: "#808080",
|
||||
} as const
|
||||
|
||||
function Theme() {
|
||||
return style(
|
||||
Reset(),
|
||||
rule.pdf(
|
||||
display.flex,
|
||||
width("50px"),
|
||||
height("50px"),
|
||||
borderRadius("12px"),
|
||||
backgroundColor("#345c42"),
|
||||
color.white,
|
||||
position.fixed,
|
||||
top("25px"),
|
||||
right("25px"),
|
||||
justifyContent.center,
|
||||
alignItems.center,
|
||||
fontWeight.bold,
|
||||
textDecoration.none,
|
||||
),
|
||||
$page(
|
||||
prop("size", "auto"),
|
||||
margin("0 0 0 0"),
|
||||
padding("0.5in"),
|
||||
),
|
||||
rule(body, [
|
||||
fontFamily("Roboto Condensed"),
|
||||
fontSize("12pt"),
|
||||
display.flex,
|
||||
justifyContent.center,
|
||||
backgroundColor(Color.backdrop),
|
||||
]),
|
||||
$media("print", [
|
||||
rule(body, backgroundColor.white),
|
||||
rule.noprint(raw("display: none !important;")),
|
||||
]),
|
||||
rule(main, [
|
||||
backgroundColor.white,
|
||||
width("8.5in"),
|
||||
display.flex,
|
||||
justifyContent.center,
|
||||
paddingBottom("32pt"),
|
||||
]),
|
||||
rule(article, [width("7.0in")]),
|
||||
rule(header, [
|
||||
marginTop("0.5in"),
|
||||
display.flex,
|
||||
justifyContent.spaceBetween,
|
||||
alignItems.flexEnd,
|
||||
paddingBottom("8pt"),
|
||||
]),
|
||||
rule(h1, fontSize("28pt")),
|
||||
rule.links(
|
||||
fontSize("11pt"),
|
||||
width("2.75in"),
|
||||
display.flex,
|
||||
justifyContent.spaceBetween,
|
||||
alignItems.center,
|
||||
rule(">div", [
|
||||
display.flex,
|
||||
flexDirection.column,
|
||||
gap("2pt"),
|
||||
]),
|
||||
),
|
||||
rule.contact(alignItems.flexEnd, fontSize("12pt")),
|
||||
rule(section, [
|
||||
borderTop(`double 3px ${Color.edge}`),
|
||||
paddingTop("16pt"),
|
||||
paddingBottom("16pt"),
|
||||
]),
|
||||
rule(h2, [marginBottom("12pt")]),
|
||||
rule(p, [marginTop("12pt")]),
|
||||
rule(ul, [marginTop("12pt")]),
|
||||
rule(hr, [margin("12pt 0"), borderColor("#f0f0f0")]),
|
||||
rule(".experience .content h2", [fontSize("14pt")]),
|
||||
rule.expertise(
|
||||
pageBreakInside.avoid,
|
||||
rule(ul, marginBottom("12pt")),
|
||||
rule.blocks(columns(3)),
|
||||
rule.block(
|
||||
flex(1),
|
||||
minWidth("3in"),
|
||||
breakInside.avoid,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
1
src/ui/CV/index.ts
Normal file
1
src/ui/CV/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./CV"
|
||||
26
src/ui/Experience/Experience.ts
Normal file
26
src/ui/Experience/Experience.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import Layout from "ui/Layout"
|
||||
import Window from "ui/Layout/Window"
|
||||
import Markdown from "ui/shared/Markdown"
|
||||
import experience from "./experience.md?raw"
|
||||
|
||||
const title = "Dan Finch - Experience"
|
||||
|
||||
export default function Experience() {
|
||||
return [
|
||||
backgroundColor.white,
|
||||
Layout(
|
||||
{ title, url: "/experience" },
|
||||
Window(
|
||||
{ title },
|
||||
div(
|
||||
padding("16px"),
|
||||
div.content(
|
||||
h1("My Experience"),
|
||||
hr(),
|
||||
Markdown(experience),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
}
|
||||
91
src/ui/Experience/experience.md
Normal file
91
src/ui/Experience/experience.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
## Dynatrace - Senior Software Engineer
|
||||
> July 2023 - Present (Graz, Austria - Hybrid)
|
||||
|
||||
- Implemented an array of features and bug fixes for several Business Analytics applications using **React** and **TypeScript**.
|
||||
- Designed and implemented a strongly-typed embedded DSL to generate the data warehouse queries which power the apps, significantly reducing maintenance overhead.
|
||||
- Created an end-to-end testing framework suite based on **Playwright** that substantially streamlined writing and running tests.
|
||||
- Created and integrated a state management framework based on **Zustand** and **Immer** which greatly simplified and optimized front-end code.
|
||||
- Initiated ongoing effort to improve code quality and project structure.
|
||||
- Migrated a large project from Bitbucket+Jenkins to Github+Actions.
|
||||
|
||||
---
|
||||
|
||||
## Independent Consultant
|
||||
> Dec 2014 - July 2023 (Remote)
|
||||
|
||||
My freelance work in this time included:
|
||||
|
||||
- Created a mobile social network app for a startup conference and shared working space provider. This was written in functional **TypeScript** and **React Native**, with some Python/Django and Postgres on a Heroku backend.
|
||||
- Consulted on JavaScript and front-end tech for Bitbanger Labs for their ColorSpike hybrid mobile app built on Cordova.
|
||||
- Wrote a Chrome extension using the **Mithril** framework to inspect advertisements embedded in web pages for Bringhub.
|
||||
- Designed and prototyped an app and backend for a IOT equipment monitoring startup in Sandpoint, ID using **React**, **ElasticSearch**, and **Postgres**.
|
||||
- Created an **Electron** app connected to the QuickBooks API to build quotes for a greenhouse manufacturer.
|
||||
|
||||
---
|
||||
|
||||
## RiskLens - Senior Software Engineer
|
||||
> Jun 2017 - Dec 2017 (Spokane, WA)
|
||||
|
||||
Helped lead the front-end development of FAIR-U - a **React** + **Redux**-based app backed by **C\#**, Sql Server, and
|
||||
Azure & transitioned the team to TypeScript and other modern front-end tools. Gained experience with a
|
||||
security-focused and process-intensive team.
|
||||
|
||||
---
|
||||
|
||||
## TenX Logic - Contract Developer
|
||||
> Oct 2015 - Mar 2016 (Remote)
|
||||
|
||||
This project for the oil + gas industry targeted the Mono platform on ARM processors. I designed and
|
||||
implemented a **C\#**-based driver model to control equipment in the field, with a frontend written in AngularJS.
|
||||
|
||||
---
|
||||
|
||||
## Kochava - Senior Developer
|
||||
> Apr 2014 - Nov 2014 (Sandpoint, ID)
|
||||
|
||||
Worked on infrastructure and systems to manage large volumes of real-time analytics data. I tackled
|
||||
complex challenges requiring constant inventiveness and collaboration to help the company scale at
|
||||
a rapid rate.
|
||||
|
||||
My work centered around **Node.js**, **Redis**, Linux, and **MongoDB**. I also led the early implementation of a
|
||||
scalable high-traffic analytics backend.
|
||||
|
||||
---
|
||||
|
||||
## Coldwater Creek - Web Developer
|
||||
> Oct 2012 - Apr 2014 (Sandpoint, ID)
|
||||
|
||||
Worked with many teams to finish an amazing number of projects. My work on both internal tooling and
|
||||
customer experience put the company ahead of its industry in many respects.
|
||||
|
||||
- Implemented a product search which outperformed all competitors, including a novel infinite scrolling method.
|
||||
- Provided an array of features and user interfaces widgets for website with around one million hits per day.
|
||||
- Wrote or re-wrote around 30 internal web-driven tools for e-commerce & marketing teams.
|
||||
- Wrote custom jQuery UI powered framework and widgets for tool suite.
|
||||
- Initiated effort to significantly improve client-side page load performance.
|
||||
- Created a framework allowing mutual embedding between MVC and WebForms to allow a smooth transition off of legacy code.
|
||||
|
||||
---
|
||||
|
||||
## Promitheia - Technical Director
|
||||
> Sep 2006 - Sep 2012 (Austin, TX)
|
||||
|
||||
As co-founder of this startup I was responsible for software design, programming, project management,
|
||||
operations, testing, maintenance, and security.
|
||||
|
||||
- Created complex e-commerce website featuring retail and affiliate marketing management. Owner of dozens of projects within the company.
|
||||
- Configured and managed production web servers including Microsoft IIS and LAMP stack.
|
||||
- Worked closely with DBA on data-driven applications powered by SQL Server 2008 and ADO.NET.
|
||||
- Analyzed and selected supporting software and services, including early adoption of technologies such as ASP.NET MVC, jQuery, F#, AWS, and distributed source control.
|
||||
- Developed solutions using Service Oriented Architecture (SOA), WCF, and Windows services. Integrated software with various cloud data and web service providers, including AWS, Authorize.NET, and Zencoder.
|
||||
- Studied and implemented e-commerce best practices, web security standards, and PCI compliance.
|
||||
|
||||
---
|
||||
|
||||
## Mercury ML - Founder
|
||||
> 2002 - 2004 (Lapeer, MI)
|
||||
|
||||
I got my start right out of high school helping to scale two companies out of working with unwieldy spreadsheets.
|
||||
|
||||
- Released two major versions of a VB6 application to catalog marketing affiliates, track orders, and calculate payouts.
|
||||
- Wrote and administered affiliate websites in Classic ASP and VBScript.
|
||||
1
src/ui/Experience/index.ts
Normal file
1
src/ui/Experience/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./Experience"
|
||||
30
src/ui/Home/Home.ts
Normal file
30
src/ui/Home/Home.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import Layout from "ui/Layout"
|
||||
import Markdown from "ui/shared/Markdown"
|
||||
import intro from "./intro.md?raw"
|
||||
import details from "./details.md?raw"
|
||||
import skills from "./skills.md?raw"
|
||||
import interests from "./interests.md?raw"
|
||||
import Window from "ui/Layout/Window"
|
||||
|
||||
const title = "Dan Finch - About Me"
|
||||
|
||||
export default function Home() {
|
||||
return Layout(
|
||||
{ title, url: "/" },
|
||||
Window(
|
||||
{ title },
|
||||
div.content(padding("8px"), [
|
||||
h1("About Me"),
|
||||
Markdown(intro),
|
||||
aside.fieldBorderDisabled(
|
||||
img({ src: "/pfp.jpg", width: "200" }),
|
||||
),
|
||||
Markdown(details),
|
||||
h2("Skills"),
|
||||
Markdown(skills),
|
||||
h2("Interests"),
|
||||
Markdown(interests),
|
||||
]),
|
||||
),
|
||||
)
|
||||
}
|
||||
5
src/ui/Home/details.md
Normal file
5
src/ui/Home/details.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
My first principle is **user experience**. Software is interesting because of what it can do for people, and we all put up with mediocre software every day. This dissatisfaction drives me to set a high standard for our work.
|
||||
|
||||
As a programmer, I've learned to make code **small, simple, and readable**. My aim is prolific and expeditious code output while maintaining quality. I make loosely-coupled components, understandable abstractions, and low-ceremony frameworks.
|
||||
|
||||
As a teammate and leader, I am devoted to **friendliness** and **respect**. I am a good communicator, love sharing my knowledge, and learning from others. I've given talks at local user groups and mentored at work and in my free time.
|
||||
1
src/ui/Home/index.ts
Normal file
1
src/ui/Home/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./Home"
|
||||
4
src/ui/Home/interests.md
Normal file
4
src/ui/Home/interests.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
- Compilers, interpreters & parsers.
|
||||
- Metaprogramming, language-oriented programming.
|
||||
- Developer tools - hacking on build tools and bundlers.
|
||||
- Automating self-hosted cloud tech.
|
||||
1
src/ui/Home/intro.md
Normal file
1
src/ui/Home/intro.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
I own software end-to-end: building or renewing stacks and evaluating the tools and techniques that will serve the product well. I assist my teams in creating frameworks, automation, standards, and guidance that clear the path to delivering good software and user experiences. I am comfortable working hands-on across code, architecture, UX, and product.
|
||||
8
src/ui/Home/skills.md
Normal file
8
src/ui/Home/skills.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
- Consciousness of mainstream & emerging technology and trends.
|
||||
- Quickly vetting and adapting to new technologies - building and improving stacks.
|
||||
- High standards for great user and developer experiences.
|
||||
- Interacting directly and empathetically with stakeholders and end users.
|
||||
- Managing work through all phases of a development cycle.
|
||||
- Eliminating friction in processes through automation.
|
||||
- Understanding all the areas of a codebase and recognizing where waste can be reduced.
|
||||
- Coordinating large, complex upgrades and refactors.
|
||||
18
src/ui/Layout/Head.ts
Normal file
18
src/ui/Layout/Head.ts
Normal 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
50
src/ui/Layout/Layout.ts
Normal 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
14
src/ui/Layout/Reset.ts
Normal 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
101
src/ui/Layout/Taskbar.ts
Normal 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
39
src/ui/Layout/Theme.ts
Normal 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
32
src/ui/Layout/Window.ts
Normal 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
1
src/ui/Layout/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./Layout"
|
||||
21
src/ui/Projects/Projects.ts
Normal file
21
src/ui/Projects/Projects.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import Layout from "ui/Layout"
|
||||
import Window from "ui/Layout/Window"
|
||||
|
||||
const title = "Dan Finch - Projects"
|
||||
const sigitex = "https://sigitex.com"
|
||||
|
||||
export default function Projects() {
|
||||
return Layout(
|
||||
{ title, url: "/projects" },
|
||||
Window(
|
||||
{
|
||||
title: span(title, " (", a({ href: sigitex }, color.yellow, "SIGITEX.COM"), ")")
|
||||
},
|
||||
backgroundColor.black,
|
||||
display.flex,
|
||||
iframe({ src: "https://sigitex.com" },
|
||||
flex(1)
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
1
src/ui/Projects/index.ts
Normal file
1
src/ui/Projects/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./Projects"
|
||||
118
src/ui/Tech/Sections.ts
Normal file
118
src/ui/Tech/Sections.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
export const languages = `
|
||||
- TypeScript
|
||||
- JavaScript
|
||||
- C#
|
||||
- F#
|
||||
- SQL
|
||||
- HTML
|
||||
- CSS
|
||||
`
|
||||
|
||||
export const platforms = `
|
||||
- Node.js
|
||||
- Deno
|
||||
- Bun
|
||||
- Linux
|
||||
- Electron
|
||||
- Browser Extensions
|
||||
- React Native
|
||||
- .NET
|
||||
`
|
||||
|
||||
export const frontend = `
|
||||
- React
|
||||
- Vite
|
||||
- Zustand
|
||||
- Immer
|
||||
- Webpack
|
||||
- Parcel
|
||||
- Emotion
|
||||
- Styled Components
|
||||
- Mithril
|
||||
- Redux
|
||||
- React Router
|
||||
- React Query
|
||||
- Vanilla Extract
|
||||
- CSS Modules
|
||||
- SCSS, Stylus, Less
|
||||
`
|
||||
|
||||
export const backend = `
|
||||
- Express
|
||||
- Koa
|
||||
- ASP.NET
|
||||
- Fastify
|
||||
- Hapi
|
||||
- NestJS
|
||||
- Cloudflare
|
||||
- Firebase
|
||||
- Supabase
|
||||
- AWS
|
||||
`
|
||||
|
||||
export const data = `
|
||||
- Postgres
|
||||
- Redis
|
||||
- Sqlite
|
||||
- MongoDB
|
||||
- MySQL
|
||||
- SQL Server
|
||||
`
|
||||
|
||||
export const principles = `
|
||||
- DDD
|
||||
- FP
|
||||
- Explicit data flow
|
||||
- Modular design
|
||||
- Testable boundaries
|
||||
- Composability
|
||||
- Separation of concerns
|
||||
- SOLID
|
||||
- DRY
|
||||
- KISS
|
||||
- YAGNI
|
||||
`
|
||||
|
||||
export const architecture = `
|
||||
- Domain modeling
|
||||
- API design
|
||||
- REST
|
||||
- Event-driven systems
|
||||
- Service boundaries
|
||||
- State management
|
||||
- Caching
|
||||
- Data modeling
|
||||
- Progressive enhancement
|
||||
`
|
||||
|
||||
export const delivery = `
|
||||
- CI/CD
|
||||
- Build and release automation
|
||||
- Docker
|
||||
- GitHub Actions
|
||||
- IaC
|
||||
- Feature flags
|
||||
- Observability
|
||||
`
|
||||
|
||||
export const quality = `
|
||||
- TDD
|
||||
- Unit testing
|
||||
- Integration testing
|
||||
- E2E testing
|
||||
- Code review
|
||||
- Refactoring
|
||||
- Accessibility
|
||||
- Responsive design
|
||||
`
|
||||
|
||||
export const collaboration = `
|
||||
- Async-first
|
||||
- Agile
|
||||
- Pairing
|
||||
- Mentoring
|
||||
- Technical leadership
|
||||
- Product collaboration
|
||||
- Design collaboration
|
||||
- Documentation
|
||||
`
|
||||
48
src/ui/Tech/Tech.ts
Normal file
48
src/ui/Tech/Tech.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import Layout from "ui/Layout"
|
||||
import Window from "ui/Layout/Window"
|
||||
import Markdown from "ui/shared/Markdown"
|
||||
import * as Sections from "./Sections"
|
||||
|
||||
const title = "Dan Finch - Expertise"
|
||||
|
||||
export default function Expertise() {
|
||||
return Layout(
|
||||
{ title, url: "/tech" },
|
||||
Window(
|
||||
{ title, flat: true },
|
||||
div(
|
||||
[
|
||||
display.flex,
|
||||
flexWrap.wrap,
|
||||
marginRight("8px"),
|
||||
paddingBottom("16px"),
|
||||
gap("8px"),
|
||||
],
|
||||
Skillset("Core Languages", Sections.languages),
|
||||
Skillset("Platforms", Sections.platforms),
|
||||
Skillset("Frontend", Sections.frontend),
|
||||
Skillset("Backend", Sections.backend),
|
||||
Skillset("Data", Sections.data),
|
||||
Skillset("Principles", Sections.principles),
|
||||
Skillset("Architecture", Sections.architecture),
|
||||
Skillset("Delivery", Sections.delivery),
|
||||
Skillset("Quality", Sections.quality),
|
||||
Skillset("Collaboration", Sections.collaboration),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
function Skillset(title: string, markdown: Content) {
|
||||
return div(
|
||||
[
|
||||
flex(1),
|
||||
minWidth("250px"),
|
||||
display.flex,
|
||||
flexDirection.column,
|
||||
gap("4px"),
|
||||
],
|
||||
h2(title),
|
||||
div.fieldBorder(flex(1), Markdown(markdown)),
|
||||
)
|
||||
}
|
||||
1
src/ui/Tech/index.ts
Normal file
1
src/ui/Tech/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./Tech"
|
||||
9
src/ui/shared/Markdown.ts
Normal file
9
src/ui/shared/Markdown.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { Marked } from "marked"
|
||||
import { gfmHeadingId } from "marked-gfm-heading-id"
|
||||
|
||||
export default function Markdown(md: string) {
|
||||
return div.content(raw(marked.parse(md)))
|
||||
}
|
||||
|
||||
const marked = new Marked({ gfm: true })
|
||||
marked.use(gfmHeadingId())
|
||||
Loading…
Add table
Add a link
Reference in a new issue