first commit

This commit is contained in:
Henry Hiles 2023-04-19 16:37:00 -04:00
commit fa58768553
57 changed files with 4134 additions and 0 deletions

View file

@ -0,0 +1,83 @@
---
import styles from "../styles/About.module.css"
import Divider from "./Divider.astro"
---
<section class={styles.about} id="about">
<h2>About</h2>
<Divider />
<div class={styles.row}>
<article>
<img
src={`https://raw.githubusercontent.com/Henry-Hiles/github-stats/master/generated/overview.svg${
false // TODO: add light mode again
? "#gh-light-mode-only"
: "#gh-dark-mode-only"
}`}
alt="My Github Stats"
/>
</article>
<section>
<article class={styles.textArticle}>
<p>
Hello, my name is Henry Hiles, Full Stack Developer. I have
extensive experience with React, SolidJS, Node.js, and
ASP.NET Razor Pages. I have used my Node.js knowledge to
create{" "}
<a href="/project/0">QuadraticBot 2.0</a>, a giveaway bot
for discord.
</p>
<p>
For more projects, check out{" "}
<a
href="https://github.com/Henry-Hiles"
target="_blank"
rel="noreferrer"
>
my GitHub profile.
</a>
</p>
</article>
<section class={styles.images}>
<a
href="https://www.credly.com/badges/37008ee1-69e0-44aa-82cb-33e4bdf153a8/public_url"
target="_blank"
rel="noreferrer"
>
<img
src="/images/az-900.png"
alt="Azure AI Fundamentals Badge"
/>
</a>
<a
href="https://www.credly.com/badges/1fd0fc1c-052a-4311-9938-6d38057305ce/public_url"
target="_blank"
rel="noreferrer"
>
<img
src="/images/az-900.png"
alt="Azure Data Fundamentals Badge"
/>
</a>
<a
href="https://www.credly.com/badges/bd5a8213-4e3d-4b93-9b7d-4cbce07ef960/public_url"
target="_blank"
rel="noreferrer"
>
<img
src="/images/az-900.png"
alt="Azure Fundamentals Badge"
/>
</a>
</section>
</section>
<article>
<img
src={`https://raw.githubusercontent.com/Henry-Hiles/github-stats/master/generated/languages.svg${
false ? "#gh-light-mode-only" : "#gh-dark-mode-only"
}`}
alt="My Github Stats"
/>
</article>
</div>
</section>

View file

@ -0,0 +1,19 @@
---
import styles from "../styles/ButtonLink.module.css"
export interface Props {
href: string
newTab: boolean
}
const { href, newTab } = Astro.props
---
<a
href={href}
class={styles.button}
target={newTab ? "_blank" : ""}
rel={newTab ? "noreferrer" : ""}
>
<slot />
</a>

View file

@ -0,0 +1,10 @@
---
import styles from "../styles/Divider.module.css"
import { Icon } from "astro-icon"
---
<div class={styles.container}>
<span class={styles.dividerIcon}>
<Icon name="fa:star" />
</span>
</div>

View file

@ -0,0 +1,17 @@
---
import RoundDivider from "./RoundDivider.astro"
import styles from "../styles/Jumbo.module.css"
import Divider from "./Divider.astro"
---
<header>
<section id={styles.jumbo}>
<h1>
<img src="images/logo.png" alt="Henry Hiles" />
</h1>
<Divider />
<span id={styles.shortAbout}>
Full Stack .NET Developer & Discord Bot Developer
</span>
<RoundDivider />
</section>
</header>

48
src/components/Nav.astro Normal file
View file

@ -0,0 +1,48 @@
---
import { Icon } from "astro-icon"
import styles from "../styles/Nav.module.css"
---
<!-- export const Nav = ({ lightTheme, setLightTheme }) => { --><!-- class={y == 0 ? "" : styles.navbarShrink} -->
<nav class={styles.nav} data-expanded="true">
<ul class={styles.links}>
<li>
<a href="/#">Home</a>
</li>
<li>
<a href="/#portfolio">My Projects</a>
</li>
<li>
<a href="/#about">About Me</a>
</li>
<li>
<button id="themeToggle">
<Icon name="ph:sun-fill" />
<!-- <Icon name="ph:moon-fill" /> -->
</button>
</li>
<li>
<a
target="_blank"
rel="noreferrer"
href="https://github.com/Henry-Hiles"
>
<Icon name="mdi:github" />
</a>
</li>
</ul>
</nav>
<script defer>
const dataset = document.querySelector(`nav`)?.dataset
document.addEventListener("scroll", () => {
if (dataset) {
if (window.scrollY > 0) dataset.expanded = false
else dataset.expanded = true
}
})
const body = document.querySelector("body")
const themeToggle = document.querySelector("#themeToggle")
themeToggle.addEventListener("click", () => body.classList.toggle("light"))
</script>

View file

@ -0,0 +1,23 @@
---
import projects from "../projects.json"
import styles from "../styles/Portfolio.module.css"
import Divider from "./Divider.astro"
---
<section id="portfolio">
<h2>My Projects</h2>
<Divider />
<article class={styles.portfolioItems}>
{
projects.map((project, index) => (
<a href={`/projects/${index}#`}>
<img
src={`/images/${project.thumbImage}`}
alt={project.name}
/>
<h3 class={styles.projectName}>{project.name}</h3>
</a>
))
}
</article>
</section>

View file

@ -0,0 +1,15 @@
---
import styles from "../styles/RoundDivider.module.css"
---
<svg
class={styles.roundDivider}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 144.54 17.34"
preserveAspectRatio="none"
fill="currentColor"
>
<path
d="M144.54,17.34H0V0H144.54ZM0,0S32.36,17.34,72.27,17.34,144.54,0,144.54,0"
></path>
</svg>

1
src/env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="astro/client" />

33
src/layouts/Layout.astro Normal file
View file

@ -0,0 +1,33 @@
---
import "../styles/index.css"
import Nav from "../components/Nav.astro"
export interface Props {
page: string
}
const { page } = Astro.props
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico" />
<meta name="generator" content={Astro.generator} />
<meta content="Henry Hiles" property="og:title" />
<meta
content="The website of full stack .NET developer & discord bot developer Henry Hiles."
property="og:description"
/>
<meta content="https://henryhiles.com" property="og:url" />
<meta content="favicon.ico" property="og:image" />
<meta content="#14bc9d" data-react-helmet="true" name="theme-color" />
<title>Henry Hiles - {page}</title>
</head>
<body>
<Nav />
<slot />
</body>
</html>

15
src/pages/404.astro Normal file
View file

@ -0,0 +1,15 @@
---
import styles from "../styles/NotFound.module.css"
import Layout from "../layouts/Layout.astro"
---
<Layout page="Not Found">
<div id={styles.notFound}>
<h1 id={styles.header}>Page not found.</h1>
<span id={styles.description}>
We couldn't find that page. Please{" "}
<a href="javascript:window.history.go(-1)"> go back</a>
, or return to our <a href="/">home page</a>.
</span>
</div>
</Layout>

15
src/pages/index.astro Normal file
View file

@ -0,0 +1,15 @@
---
import About from "../components/About.astro"
import Jumbo from "../components/Jumbo.astro"
import Portfolio from "../components/Portfolio.astro"
import Layout from "../layouts/Layout.astro"
import styles from "../styles/Home.module.css"
---
<Layout page={"Home"}>
<div id={styles.container}>
<Jumbo />
<Portfolio />
<About />
</div>
</Layout>

View file

@ -0,0 +1,74 @@
---
import ButtonLink from "../../components/ButtonLink.astro"
import Divider from "../../components/Divider.astro"
import RoundDivider from "../../components/RoundDivider.astro"
import styles from "../../styles/Project.module.css"
import Layout from "../../layouts/Layout.astro"
import projects from "../../projects.json"
export function getStaticPaths() {
return projects.map((_, id) => ({ params: { id: id } }))
}
const { id: idParam } = Astro.params
const id = Number(idParam)
const project = projects[id]
---
<Layout page={project.name}>
<div id={styles.container}>
<section id={styles.jumbo}>
<h1 class={styles.title}>{project.name}</h1>
<Divider />
<p class={styles.overview}>{project.overview}</p>
<RoundDivider />
</section>
<section id={styles.details}>
<div class={styles.row}>
<article class={styles.longDescription}>
<h2>Description</h2>
<Divider />
<section>
<p>
{project.description}
{project.technologies}
</p>
<div class={styles.buttonRow}>
{
project.github && (
<ButtonLink href={project.github} newTab>
Source Code
</ButtonLink>
)
}
{
project.demoLink && (
<ButtonLink href={project.demoLink} newTab>
Go to demo
</ButtonLink>
)
}
{
project.customLink && (
<ButtonLink
href={project.customLink.link}
newTab
>
{project.customLink.name}
</ButtonLink>
)
}
</div>
</section>
</article>
<article class={styles.image}>
<img
src={`/images/${project.computerImages[0]}`}
alt={`Image of ${project.name}`}
class={styles.screenshot}
/>
</article>
</div>
</section>
</div>
</Layout>

66
src/projects.json Normal file
View file

@ -0,0 +1,66 @@
[
{
"name": "QuadraticBot 2.0",
"github": "https://github.com/Henry-Hiles/QuadraticBot2.0",
"overview": "A simple, user-friendly giveaway bot that uses slash commands and buttons.",
"description": "A simple, open-source, user-friendly Discord giveaway bot that uses the latest features, such as slash commands, timestamps, and buttons. It's a rewrite of my original closed-source Discord bot, Quadratic Giveaways.",
"customLink": {
"name": "Invite to your server",
"link": "https://discord.com/api/oauth2/authorize?client_id=930172444910702653&permissions=150528&scope=applications.commands%20bot"
},
"technologies": "This project was built using Node.js and Discord.js.",
"computerImages": ["quadraticBotComputer.png"],
"mobileImage": "flappyMobile.png",
"thumbImage": "quadraticBot.png"
},
{
"name": "Flappy Bird",
"github": "https://github.com/Henry-Hiles/FlappyBird",
"overview": "A simple flappy bird game, made with html elements, CSS, and JavaScript. Made without using canvas.",
"description": "A simple flappy bird game, made with the technologies listed below. This project uses a modular design, with many JS files working together. It uses ES6 arrow functions instead of regular functions.",
"technologies": "This project was built using handcrafted HTML, CSS, and JavaScript, and canvas wasn't used. This project uses no bloated frontend technologies, making for quick loading times and fewer requests.",
"computerImages": ["flappyComputer1.png", "flappyComputer2.png"],
"mobileImage": "flappyMobile.png",
"thumbImage": "flappyThumb.jpg"
},
{
"name": "PokeAPI Searcher",
"github": "https://github.com/Henry-Hiles/PokeAPI-Searcher",
"overview": "A simple website where you can search for Pokemon by type or Pokedex. Uses the PokeAPI for data.",
"description": "A simple website where you can search for Pokemon by type or Pokedex. Uses the PokeAPI for data. Made purely of HTML, CSS, and Vanilla Javascript. Uses the Fetch API for querying the PokeAPI.",
"technologies": "This project was built using handcrafted HTML, CSS, and JavaScript, and canvas wasn't used. This project uses no bloated frontend technologies, making for quick loading times and fewer requests.",
"computerImages": ["pokeAPIComputer1.png", "pokeAPIComputer2.png"],
"mobileImage": "pokeAPIMobile.png",
"thumbImage": "pokeAPIThumb.png"
},
{
"name": "Messaging",
"github": "https://github.com/Henry-Hiles/Messaging",
"overview": "A messaging website where multiple users can chat together.",
"description": "A simple, open-source, online chat room where you can join online chat rooms, or create your own.",
"technologies": "This project was built using handcrafted Javascript, as well as Node.js, Socket.io, and Express.",
"computerImages": ["messagingComputer.png"],
"mobileImage": "messagingMobile.png",
"thumbImage": "messagingThumb.png",
"demoLink": "https://chat.henryhiles.com"
},
{
"name": "Video Chat",
"github": "https://github.com/Henry-Hiles/Video-Chat",
"overview": "A simple Web-RTC powered video chat that uses PeerJS and Socket.IO.",
"description": "A simple Web-RTC powered video chat, great for chatting with friends or family.",
"technologies": "This project was built using EJS, PeerJS, Express, and Socket.IO.",
"computerImages": ["videoChatComputer.png"],
"thumbImage": "videoChatThumb.png",
"demoLink": "https://video.henryhiles.com"
},
{
"name": "MentalMath",
"github": "https://github.com/Henry-Hiles/MentalMath",
"overview": "A simple project to help with learning addition and subtraction.",
"description": "A simple project to help with learning addition and subtraction. You can change the amount of numbers, and switch between addition and subtraction modes.",
"technologies": "This project was built using React and CSS Modules.",
"computerImages": ["mentalMathComputer.png"],
"thumbImage": "mentalMathThumb.webp"
}
]

View file

@ -0,0 +1,36 @@
.row {
display: flex;
align-items: center;
padding: 20px;
gap: 5rem;
justify-content: center;
width: 100%;
}
.about {
text-align: center;
font-size: 1.5em;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px;
}
.textArticle p {
margin: 14px 0 40px;
max-width: 40rem;
}
.images a {
text-decoration: none;
}
.images img {
width: 200px;
}
@media (max-width: 1500px) {
.row {
flex-direction: column;
}
}

View file

@ -0,0 +1,17 @@
.button {
display: flex;
background: var(--secondary);
border: 2px solid var(--text-primary);
border-radius: 20px;
padding: 20px;
text-decoration: none;
white-space: nowrap;
height: 100%;
color: var(--text-primary);
justify-content: center;
}
.button:hover {
background: var(--secondary-hover);
color: white;
}

View file

@ -0,0 +1,35 @@
.container {
width: 100%;
display: flex;
justify-content: center;
}
.dividerIcon {
margin: 10px 0 15px;
position: relative;
}
.dividerIcon svg {
height: 2rem;
width: 2rem;
color: inherit;
}
.dividerIcon::before,
.dividerIcon::after {
border-bottom: 0.5rem solid var(--text-primary);
border-radius: 5em;
content: "";
margin: 0 1em;
position: absolute;
top: 50%;
width: 7rem;
}
.dividerIcon::before {
right: 100%;
}
.dividerIcon::after {
left: 100%;
}

View file

@ -0,0 +1,3 @@
#container {
width: 100%;
}

View file

@ -0,0 +1,18 @@
#jumbo {
color: var(--text-primary);
font-size: 2em;
padding: 3rem 0 0;
text-align: center;
}
#jumbo img {
width: 200px;
}
#jumbo i {
margin: 20px 0 30px;
}
#jumbo #shortAbout {
padding: 20px;
}

70
src/styles/Nav.module.css Normal file
View file

@ -0,0 +1,70 @@
.nav {
background: var(--secondary);
color: var(--primary-text);
padding-top: 1.5rem;
overflow: auto;
display: flex;
font-size: 1.3em;
font-weight: bold;
transition: padding 0.5s, font-size 0.5s;
width: 100%;
z-index: 1;
top: 0;
gap: 20px;
}
@media (min-width: 600px) {
.nav[data-expanded="false"] {
padding: 1rem;
width: unset;
top: 10px;
border-radius: 100px;
border: 3px solid var(--primary);
padding: 0;
}
.nav[data-expanded="true"] {
font-size: 1.5em;
}
.nav {
position: sticky;
}
}
.links svg {
height: 30px;
}
.nav button {
background-color: transparent;
border: none;
cursor: pointer;
color: inherit;
}
.nav button:hover {
color: var(--secondary-text);
}
.links {
margin: auto;
display: flex;
align-items: center;
list-style: none;
padding: 0.7em 1.7em;
}
.links li {
margin: 0 0.5em;
}
.links a {
color: inherit;
text-decoration: none;
white-space: nowrap;
}
.links a:hover {
color: var(--secondary-text);
}

View file

@ -0,0 +1,33 @@
#notFound {
display: flex;
padding: 50px 0px;
border-radius: 20px;
background: #161f27;
align-items: center;
justify-content: center;
gap: 10px;
text-align: center;
flex-direction: column;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
}
#notFound a {
color: var(--text-primary);
}
#header {
font-size: 2.5em;
}
#description {
font-size: 1.2em;
width: 50%;
}
@media (min-width: 800px) {
.notFound {
width: 60vw;
border-radius: 20px;
height: 65vh;
}
}

View file

@ -0,0 +1,45 @@
.portfolioItems {
display: flex;
gap: 30px;
flex-wrap: wrap;
justify-content: center;
padding: 30px;
}
.portfolioItems a {
color: var(--text-primary);
}
.portfolioItems a:not(:hover) {
text-decoration: none;
}
.portfolioItems img {
border-radius: 1rem;
background: #f5f5f5;
box-shadow: 0.5em 0.5em 3em 0.7em rgba(0, 0, 0, 0.25),
-0.5em -0.5em 3em 0.7em rgba(0, 0, 0, 0.22);
height: 13em;
width: 13em;
}
.portfolioItems div {
background: rgba(0, 0, 0, 0.3);
border-bottom-left-radius: 1rem;
border-bottom-right-radius: 1rem;
bottom: 0;
color: #f5f5f5;
font-size: 1em;
left: 0%;
padding: 0.1em;
position: absolute;
text-align: center;
width: 100%;
}
.projectName {
display: flex;
justify-content: center;
margin: 0;
padding: 5px;
}

View file

@ -0,0 +1,76 @@
#container {
width: 100%;
}
.overview {
width: 100%;
padding: 20px;
font-size: x-large;
text-align: center;
}
.screenshot {
width: 40%;
padding: 20px;
}
#jumbo {
padding: 3em 0 0;
}
.longDescription {
font-size: 1.5rem;
padding: 20px;
text-align: justify;
max-width: 700px;
}
.image {
max-width: 900px;
}
.image img {
width: 100%;
}
.longDescription h2 {
display: flex;
justify-content: center;
}
.title {
text-align: center;
}
.row {
display: flex;
align-items: center;
justify-content: space-evenly;
gap: 4rem;
}
.row > * {
flex: 1;
}
#details {
padding: 2rem;
}
@media (max-width: 1300px) {
.row {
flex-direction: column;
}
}
.buttonRow {
display: flex;
align-items: center;
justify-content: space-evenly;
gap: 1rem;
flex-wrap: wrap;
}
.buttonRow > * {
flex: 1;
}

View file

@ -0,0 +1,5 @@
.roundDivider {
left: 0;
width: 100%;
height: 3rem;
}

73
src/styles/index.css Normal file
View file

@ -0,0 +1,73 @@
:root {
color-scheme: dark light;
scroll-behavior: smooth;
--primary: hsl(211, 26%, 39%);
--secondary: hsl(209, 28%, 29%);
--secondary-hover: hsl(209, 28%, 19%);
--text-primary: hsl(0, 0%, 100%);
--secondary-text: hsl(211, 26%, 39%);
}
body {
font-family: "Lato", -apple-system, Roboto, "Helvetica Neue", Arial,
"Noto Sans", sans-serif;
margin: 0;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
background: var(--secondary);
color: var(--text-primary);
}
body.light {
--primary: hsl(220, 27%, 98%);
--secondary: hsl(0, 0%, 100%);
--secondary-hover: hsl(0, 0%, 80%);
--text-primary: hsl(220, 17%, 32%);
--secondary-text: hsl(211, 26%, 39%);
}
*,
*::before,
*::after {
box-sizing: border-box;
}
svg {
vertical-align: middle;
font-size: 0.75em;
}
section:not(section section) {
background: var(--secondary);
padding: 10rem 0;
}
section:nth-child(2n):not(section section) {
background: var(--primary);
}
section > svg {
color: var(--primary);
}
section:nth-child(2n) > svg {
color: var(--secondary);
}
section > :is(h1, h2) {
font-size: 2.5em;
margin: 0;
text-align: center;
text-transform: uppercase;
}
h1 {
margin: 0;
}
img {
max-width: 100%;
}