First Commit
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
export const skills = {
|
||||
Devops: {
|
||||
description: "Server management, deployment, and automation",
|
||||
href: "/work/devops",
|
||||
portfolio: [
|
||||
{
|
||||
title: "Server Management",
|
||||
description: "Server management, deployment, and automation",
|
||||
href: "/work/devops/server-management",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: [
|
||||
"Linux",
|
||||
"Docker",
|
||||
"Shell Scripting",
|
||||
"Kubernetes",
|
||||
"Terraform",
|
||||
"AWS",
|
||||
"Hetzner",
|
||||
"Azure",
|
||||
"Google Cloud",
|
||||
"Debian",
|
||||
"Ubuntu",
|
||||
"Alpine",
|
||||
"...",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Email Server Setup",
|
||||
description:
|
||||
"Self Hosted Email solution. Setup and Deployment.",
|
||||
href: "/work/devops/server-management",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: ["Linux", "Docker", "Mailinabox", "Hetzner"],
|
||||
},
|
||||
{
|
||||
title: "DNS Configuration",
|
||||
description: "Domain names setup, configuration, and security.",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: [
|
||||
"Cloudflare DNS",
|
||||
"Cloudflare Zero Trust",
|
||||
"Cloudflare Registry",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Web Servers and Reverse Proxies",
|
||||
description:
|
||||
"Web Server Configuration and deployment. Plus SSL Certificates provisioning.",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: [
|
||||
"NGINX",
|
||||
"Node JS",
|
||||
"Bun JS",
|
||||
"Certbot",
|
||||
"Let's Encrypt",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Continuous Integration and Deployment",
|
||||
description:
|
||||
"Self hosted and managed solutions for deployment and continuous integration",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: [
|
||||
"Coolify",
|
||||
"Vercel",
|
||||
"Github Runners",
|
||||
"Gitea Runners",
|
||||
"Dokploy",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Customer Service",
|
||||
description:
|
||||
"Self hosted and managed solutions for handling customers",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: ["Chatwoot", "Tidio"],
|
||||
},
|
||||
],
|
||||
},
|
||||
"Full Stack": {
|
||||
description: "Frontend and Backend development",
|
||||
href: "/work/devops",
|
||||
portfolio: [
|
||||
{
|
||||
title: "Server Development",
|
||||
description:
|
||||
"Easy development and development of Web servers and APIs",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: ["Node JS", "Bun JS", "Shell Script", "Python"],
|
||||
},
|
||||
{
|
||||
title: "React JS",
|
||||
description: "Development of React JS applications for the web",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: [
|
||||
"React",
|
||||
"Next JS",
|
||||
"Vite",
|
||||
"React Hooks",
|
||||
"Functional Components",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "HTML & CSS",
|
||||
description: "HTML and CSS Development for the web",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: [
|
||||
"HTML 5",
|
||||
"CSS 3",
|
||||
"SCSS",
|
||||
"Less",
|
||||
"Tailwind CSS",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Database",
|
||||
description: "Database Development and Management",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: [
|
||||
"MySQL",
|
||||
"Mariadb",
|
||||
"PostgreSQL",
|
||||
"MongoDB",
|
||||
"Redis",
|
||||
"SQLite",
|
||||
"Firebase",
|
||||
"Supabase",
|
||||
"Prisma",
|
||||
"Mongoose",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "User Authentication",
|
||||
description: "User Authentication and Authorization",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: [
|
||||
"Google Login",
|
||||
"Github Login",
|
||||
"Next Auth",
|
||||
"Firebase",
|
||||
"Cloudflare Zero Trust",
|
||||
"Clerk",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
"Software Development": {
|
||||
description: "Application development for server, desktop, and web",
|
||||
href: "/work/devops",
|
||||
portfolio: [
|
||||
{
|
||||
title: "Node Modules Development",
|
||||
description:
|
||||
"Development of Node Modules for server, desktop, and web. Deployed on NPM package registry.",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: ["Node JS", "Bun JS", "Shell Scripting", "Rsync"],
|
||||
},
|
||||
{
|
||||
title: "Shell Scripting",
|
||||
description:
|
||||
"Development of shell scripts for automation and task execution",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: ["Shell Script", "Bash Scripting", "GNU/Linux"],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
export const work = {
|
||||
Fullstack: {
|
||||
href: "/work/full-stack",
|
||||
portfolio: [
|
||||
{
|
||||
title: "Datasquirel",
|
||||
description: "Clould-based SQL data management system.",
|
||||
href: "https://datasquirel.com",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: [
|
||||
"Node JS",
|
||||
"SQL",
|
||||
"Mariadb",
|
||||
"Shell Scripting",
|
||||
"Docker",
|
||||
"Docker Compose",
|
||||
"Next JS",
|
||||
"Tailwind CSS",
|
||||
"Cloudflare Zero Trust",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Summit Lending",
|
||||
description: "Mortgage Broker in Utah",
|
||||
href: "https://summitlending.com",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: [
|
||||
"Next JS",
|
||||
"Tailwind CSS",
|
||||
"Zapier",
|
||||
"Google APIs",
|
||||
"SQL",
|
||||
"Spreadsheet to JSON",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Coderank",
|
||||
description: "A new age of remote work. Targeted at developers",
|
||||
href: "https://summitlending.com",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: [
|
||||
"Next JS",
|
||||
"Docker",
|
||||
"VSCode Web Editor",
|
||||
"NGINX Reverse Proxy",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
Devops: {
|
||||
href: "/work/devops",
|
||||
portfolio: [
|
||||
{
|
||||
title: "Personal Mail Server",
|
||||
description:
|
||||
"Self Hosted Email solution for all my personal projects",
|
||||
href: "https://box.mailben.xyz/mail",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: ["Linux", "Docker", "Mailinabox"],
|
||||
},
|
||||
{
|
||||
title: "Git, Packge, Container repository",
|
||||
description:
|
||||
"Self Hosted repository for Git projects, NPM modules, Docker images, and more",
|
||||
href: "https://git.tben.me/tben",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: ["Gitea", "Linux"],
|
||||
},
|
||||
],
|
||||
},
|
||||
Software: {
|
||||
description: "Application development for server, desktop, and web",
|
||||
href: "/work/devops",
|
||||
portfolio: [
|
||||
{
|
||||
title: "Turbo Sync NPM Module",
|
||||
description:
|
||||
"The easiest way to synchronize local and remote directories in real time",
|
||||
href: "https://git.tben.me/Moduletrace/-/packages/npm/turbosync",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: ["Node JS", "Bun JS", "Shell Scripting", "Rsync"],
|
||||
},
|
||||
{
|
||||
title: "Batchrun",
|
||||
description: "Run multiple concurrent processes",
|
||||
href: "https://git.tben.me/Moduletrace/-/packages/npm/@moduletrace%2Fbatchrun",
|
||||
image: "/images/work/devops/server-management.png",
|
||||
technologies: ["Node JS"],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import H2 from "@/components/lib/layout/H2";
|
||||
import Section from "@/components/lib/layout/Section";
|
||||
import Span from "@/components/lib/layout/Span";
|
||||
import Stack from "@/components/lib/layout/Stack";
|
||||
|
||||
export default function AboutSection() {
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full max-w-full xl:max-w-[50vw]">
|
||||
<H2 className="leading-snug">About Me</H2>
|
||||
<Span>
|
||||
I'm passionate and dedicated to solving problems using the
|
||||
best technologies available.
|
||||
</Span>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import Card from "@/components/lib/elements/Card";
|
||||
import H2 from "@/components/lib/layout/H2";
|
||||
import H3 from "@/components/lib/layout/H3";
|
||||
import Section from "@/components/lib/layout/Section";
|
||||
import Span from "@/components/lib/layout/Span";
|
||||
import Stack from "@/components/lib/layout/Stack";
|
||||
import { skills } from "../(data)/skills";
|
||||
import React from "react";
|
||||
import Row from "@/components/lib/layout/Row";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Divider from "@/components/lib/layout/Divider";
|
||||
|
||||
type Props = {
|
||||
noTitle?: boolean;
|
||||
expand?: boolean;
|
||||
};
|
||||
|
||||
export default function MySkillsSection({ noTitle, expand }: Props) {
|
||||
const [category, setCategory] =
|
||||
React.useState<keyof typeof skills>("Devops");
|
||||
const categories = Object.keys(skills) as (keyof typeof skills)[];
|
||||
|
||||
if (expand) {
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full">
|
||||
{!noTitle && (
|
||||
<React.Fragment>
|
||||
<H2 className="leading-snug m-0">My Skills</H2>
|
||||
<Span>
|
||||
A summary of the vast array of tools I've
|
||||
mastered over the years
|
||||
</Span>
|
||||
<Divider className="opacity-0 my-2" />
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<Stack className="items-stretch gap-20 flex-row xl:flex-col flex-wrap md:flex-nowrap">
|
||||
{categories.map((ctgr, i) => {
|
||||
return (
|
||||
<Stack key={i}>
|
||||
<Stack className="gap-6">
|
||||
<Stack className="gap-2">
|
||||
<H3 className="m-0 text-lg">
|
||||
{ctgr}
|
||||
</H3>
|
||||
<Span className="text-sm !leading-5">
|
||||
{skills[ctgr].description}
|
||||
</Span>
|
||||
</Stack>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 w-full">
|
||||
{skills[ctgr].portfolio.map(
|
||||
(portfolio, index) => {
|
||||
return (
|
||||
<MySkillsCard
|
||||
portfolio={
|
||||
portfolio
|
||||
}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full">
|
||||
{!noTitle && (
|
||||
<React.Fragment>
|
||||
<H2 className="leading-snug m-0">My Skills</H2>
|
||||
<Span>
|
||||
A summary of the vast array of tools I've mastered
|
||||
over the years
|
||||
</Span>
|
||||
<Divider className="opacity-0 my-2" />
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<Row className="flex-nowrap items-start gap-x-10 flex-col xl:flex-row gap-y-10">
|
||||
<Stack className="xl:max-w-[200px] items-stretch gap-10 flex-row xl:flex-col flex-wrap md:flex-nowrap">
|
||||
{categories.map((ctgr, i) => {
|
||||
const isActive = category === ctgr;
|
||||
return (
|
||||
<Stack
|
||||
key={i}
|
||||
className={twMerge(
|
||||
"cursor-pointer",
|
||||
isActive
|
||||
? ""
|
||||
: "opacity-40 hover:opacity-70"
|
||||
)}
|
||||
onClick={() => setCategory(ctgr)}
|
||||
>
|
||||
<Stack className="gap-1">
|
||||
<H3 className="m-0 text-lg">{ctgr}</H3>
|
||||
<Span className="text-sm !leading-5">
|
||||
{skills[ctgr].description}
|
||||
</Span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 w-full">
|
||||
{skills[category].portfolio.map((portfolio, index) => {
|
||||
return (
|
||||
<MySkillsCard
|
||||
portfolio={portfolio}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Row>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export function MySkillsCard({
|
||||
portfolio,
|
||||
}: {
|
||||
portfolio: (typeof skills.Devops.portfolio)[number];
|
||||
}) {
|
||||
return (
|
||||
<Card className="grow w-full items-start">
|
||||
<Stack className="gap-4">
|
||||
<H3 className="m-0">{portfolio.title}</H3>
|
||||
<Span>{portfolio.description}</Span>
|
||||
{portfolio.technologies?.[0] && (
|
||||
<Row className="gap-4">
|
||||
{portfolio.technologies.map((tch, _i) => (
|
||||
<React.Fragment key={_i}>
|
||||
<Span className="text-sm dark:text-white/40">
|
||||
{tch}
|
||||
</Span>
|
||||
{_i < portfolio.technologies.length - 1 && (
|
||||
<Divider vertical />
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import Card from "@/components/lib/elements/Card";
|
||||
import H2 from "@/components/lib/layout/H2";
|
||||
import H3 from "@/components/lib/layout/H3";
|
||||
import Section from "@/components/lib/layout/Section";
|
||||
import Span from "@/components/lib/layout/Span";
|
||||
import Stack from "@/components/lib/layout/Stack";
|
||||
import { work } from "../(data)/work";
|
||||
import Link from "@/components/lib/layout/Link";
|
||||
import React from "react";
|
||||
import Row from "@/components/lib/layout/Row";
|
||||
import Divider from "@/components/lib/layout/Divider";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = {
|
||||
noTitle?: boolean;
|
||||
expand?: boolean;
|
||||
};
|
||||
|
||||
export default function MyWorkSection({ noTitle, expand }: Props) {
|
||||
const categories = Object.keys(work) as (keyof typeof work)[];
|
||||
const [category, setCategory] = React.useState<keyof typeof work>(
|
||||
categories[0]
|
||||
);
|
||||
|
||||
if (expand) {
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full">
|
||||
{!noTitle && (
|
||||
<React.Fragment>
|
||||
<H2 className="leading-snug m-0">My Work</H2>
|
||||
<Span>Some work I've done in the past</Span>
|
||||
<Divider className="opacity-0 my-2" />
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<Stack className="items-stretch gap-20 flex-row xl:flex-col flex-wrap md:flex-nowrap">
|
||||
{categories.map((ctgr, i) => {
|
||||
const isActive = category === ctgr;
|
||||
return (
|
||||
<Stack key={i}>
|
||||
<Stack className="gap-6">
|
||||
<H3 className="m-0 text-lg">{ctgr}</H3>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 w-full">
|
||||
{work[ctgr].portfolio.map(
|
||||
(portfolio, index) => {
|
||||
return (
|
||||
<MyWorkPortfolioCard
|
||||
portfolio={
|
||||
portfolio
|
||||
}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full">
|
||||
{!noTitle && (
|
||||
<React.Fragment>
|
||||
<H2 className="leading-snug m-0">My Work</H2>
|
||||
<Span>Some work I've done in the past</Span>
|
||||
<Divider className="opacity-0 my-2" />
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<Row className="flex-nowrap items-start gap-x-14 flex-col xl:flex-row gap-y-10">
|
||||
<Stack className="xl:max-w-[200px] items-stretch gap-6 flex-row xl:flex-col flex-wrap md:flex-nowrap">
|
||||
{categories.map((ctgr, i) => {
|
||||
const isActive = category === ctgr;
|
||||
return (
|
||||
<Stack
|
||||
key={i}
|
||||
className={twMerge(
|
||||
"cursor-pointer",
|
||||
isActive
|
||||
? ""
|
||||
: "opacity-40 hover:opacity-70"
|
||||
)}
|
||||
onClick={() => setCategory(ctgr)}
|
||||
>
|
||||
<Stack className="gap-1">
|
||||
<H3 className="m-0 text-lg">{ctgr}</H3>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 w-full">
|
||||
{work[category].portfolio.map((portfolio, index) => {
|
||||
return (
|
||||
<MyWorkPortfolioCard
|
||||
portfolio={portfolio}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Row>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyWorkPortfolioCard({
|
||||
portfolio,
|
||||
}: {
|
||||
portfolio: (typeof work.Devops.portfolio)[number];
|
||||
}) {
|
||||
return (
|
||||
<Card className="grow w-full items-start">
|
||||
<Stack className="gap-4">
|
||||
<H3 className="m-0">{portfolio.title}</H3>
|
||||
|
||||
<Link
|
||||
target="_blank"
|
||||
href={portfolio.href}
|
||||
className="text-sm text-wrap break-all border-none"
|
||||
>
|
||||
{portfolio.href}
|
||||
</Link>
|
||||
<Span>{portfolio.description}</Span>
|
||||
{portfolio.technologies?.[0] && (
|
||||
<Row className="gap-4">
|
||||
{portfolio.technologies.map((tch, _i) => (
|
||||
<React.Fragment key={_i}>
|
||||
<Span className="text-sm dark:text-white/40">
|
||||
{tch}
|
||||
</Span>
|
||||
{_i < portfolio.technologies.length - 1 && (
|
||||
<Divider vertical />
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import Button from "@/components/lib/layout/Button";
|
||||
import H1 from "@/components/lib/layout/H1";
|
||||
import Row from "@/components/lib/layout/Row";
|
||||
import Section from "@/components/lib/layout/Section";
|
||||
import Span from "@/components/lib/layout/Span";
|
||||
import Stack from "@/components/lib/layout/Stack";
|
||||
import { Contact, ScrollText } from "lucide-react";
|
||||
|
||||
export default function Main() {
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full max-w-full xl:max-w-[50vw]">
|
||||
<Span>Howdy Tech Enthusiasts! I'm Benjamin Toby</Span>
|
||||
<H1 className="leading-snug">
|
||||
Software Engineer, DevOps Engineer, Full Stack Developer,
|
||||
Software Architect, Philosopher, Solar Energy Enthusiast.
|
||||
</H1>
|
||||
<Row className="items-stretch flex-col md:flex-row w-full md:w-auto">
|
||||
<Button
|
||||
beforeIcon={
|
||||
<Contact size={17} className="font-normal" />
|
||||
}
|
||||
href="/contact"
|
||||
className="grow w-full"
|
||||
>
|
||||
Contact Me
|
||||
</Button>
|
||||
<Button
|
||||
beforeIcon={
|
||||
<ScrollText size={17} className="font-normal" />
|
||||
}
|
||||
href="/contact"
|
||||
variant="outlined"
|
||||
className="grow w-full"
|
||||
>
|
||||
Resume
|
||||
</Button>
|
||||
</Row>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import H1 from "@/components/lib/layout/H1";
|
||||
import Link from "@/components/lib/layout/Link";
|
||||
import Row from "@/components/lib/layout/Row";
|
||||
import Section from "@/components/lib/layout/Section";
|
||||
import Span from "@/components/lib/layout/Span";
|
||||
import Stack from "@/components/lib/layout/Stack";
|
||||
import { Mail } from "lucide-react";
|
||||
|
||||
export default function Main() {
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full max-w-full xl:max-w-[50vw]">
|
||||
<H1 className="leading-snug">About Me</H1>
|
||||
<Span>
|
||||
I'm a man of few words. My{" "}
|
||||
<Link href="/skills">Skills</Link> and{" "}
|
||||
<Link href="/work">Work</Link> speaks for themselves.
|
||||
</Span>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import H1 from "@/components/lib/layout/H1";
|
||||
import Link from "@/components/lib/layout/Link";
|
||||
import Row from "@/components/lib/layout/Row";
|
||||
import Section from "@/components/lib/layout/Section";
|
||||
import Span from "@/components/lib/layout/Span";
|
||||
import Stack from "@/components/lib/layout/Stack";
|
||||
import { Mail } from "lucide-react";
|
||||
|
||||
export default function Main() {
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full max-w-full xl:max-w-[50vw]">
|
||||
<H1 className="leading-snug">Contact Me</H1>
|
||||
<Span>
|
||||
Have a great idea? Want to collaborate? Let's make it
|
||||
happen.
|
||||
</Span>
|
||||
|
||||
<Link href="mailto:ben@tben.me" className="border-none">
|
||||
<Row className="items-center">
|
||||
<Mail size={20} className="mt-1" />
|
||||
<Span className="text-2xl">ben@tben.me</Span>
|
||||
</Row>
|
||||
</Link>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import H1 from "@/components/lib/layout/H1";
|
||||
import Section from "@/components/lib/layout/Section";
|
||||
import Span from "@/components/lib/layout/Span";
|
||||
import Stack from "@/components/lib/layout/Stack";
|
||||
|
||||
export default function Main() {
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full max-w-full xl:max-w-[50vw]">
|
||||
<H1 className="leading-snug">My Skills</H1>
|
||||
<Span>
|
||||
A summary of the vast array of tools I've mastered over the
|
||||
years
|
||||
</Span>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import H1 from "@/components/lib/layout/H1";
|
||||
import Section from "@/components/lib/layout/Section";
|
||||
import Span from "@/components/lib/layout/Span";
|
||||
import Stack from "@/components/lib/layout/Stack";
|
||||
|
||||
export default function Main() {
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full max-w-full xl:max-w-[50vw]">
|
||||
<H1 className="leading-snug">My Work</H1>
|
||||
<Span>Some of My Work</Span>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user