Compare commits
42 Commits
d96b630aee
...
491c7ba3c1
Author | SHA1 | Date |
---|---|---|
Ethan Reece | 491c7ba3c1 | |
Ethan Reece | d0af8aca8d | |
Ethan Reece | d33dd70562 | |
Ethan Reece | e7b89420a1 | |
Ethan Reece | d13fbbcf43 | |
Ethan Reece | dc42a7d1d4 | |
Ethan Reece | 02916cc9e0 | |
Ethan Reece | 8b5d217509 | |
Ethan Reece | 3fefdfe3f0 | |
Ethan Reece | be0ccc4ea6 | |
Ethan Reece | 8d9ab52626 | |
Ethan Reece | 8d039fa077 | |
Ethan Reece | 2accb1b113 | |
Ethan Reece | 2aead2c112 | |
Ethan Reece | fac67f112c | |
Ethan Reece | bcd69f2fe3 | |
Ethan Reece | 33b2a01c9d | |
Ethan Reece | 51df9d1cf2 | |
Ethan Reece | 9a5e796bb4 | |
Ethan Reece | e5fc8994c1 | |
Ethan Reece | 1c7aa9b0a1 | |
Ethan Reece | b10423fbdf | |
Ethan Reece | a8005943f5 | |
Ethan Reece | 7f90bc9445 | |
Ethan Reece | b26b7f26ce | |
Ethan Reece | ce57be6f01 | |
Ethan Reece | 8f1b069cfa | |
Ethan Reece | 9e14f90d92 | |
Ethan Reece | 7c99b084a1 | |
Ethan Reece | 7b406fb8a1 | |
Ethan Reece | c0a371b510 | |
Ethan Reece | 5b62942f44 | |
Ethan Reece | 89c6cd150a | |
Ethan Reece | 5b53386027 | |
Ethan Reece | 05331675fe | |
Ethan Reece | 075fd87ba4 | |
Ethan Reece | 01700510e6 | |
Ethan Reece | 74f4df9b5a | |
Ethan Reece | 257c86601f | |
Ethan Reece | 9a80307168 | |
Ethan Reece | 3b6dcd02c3 | |
Ethan Reece | 5eee44c205 |
|
@ -0,0 +1,73 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
es2020: true,
|
||||
node: true,
|
||||
'astro/astro': true,
|
||||
},
|
||||
plugins: ['simple-import-sort', 'unused-imports'],
|
||||
extends: [
|
||||
'plugin:astro/recommended',
|
||||
'prettier',
|
||||
'eslint:recommended',
|
||||
'plugin:json/recommended',
|
||||
'plugin:tailwindcss/recommended',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'simple-import-sort/imports': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
'no-unused-vars': 'off', // or "@typescript-eslint/no-unused-vars": "off",
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'unused-imports/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
vars: 'all',
|
||||
varsIgnorePattern: '^_',
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
// Define the configuration for `.astro` file.
|
||||
files: ['*.astro'],
|
||||
// Allows Astro components to be parsed.
|
||||
parser: 'astro-eslint-parser',
|
||||
// Parse the script in `.astro` as TypeScript by adding the following configuration.
|
||||
// It's the setting you need when using TypeScript.
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
extraFileExtensions: ['.astro'],
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
// override/add rules settings here, such as:
|
||||
// "astro/no-set-html-directive": "error"
|
||||
'astro/no-conflict-set-directives': 'error',
|
||||
'astro/no-unused-define-vars-in-style': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.astro/*.js', '*.astro/*.js'],
|
||||
env: {
|
||||
browser: true,
|
||||
es2020: true,
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
// override/add rules settings here, such as:
|
||||
// "no-unused-vars": "error"
|
||||
|
||||
// If you are using "prettier/prettier" rule,
|
||||
// you don't need to format inside <script> as it will be formatted as a `.astro` file.
|
||||
'prettier/prettier': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
require.resolve('prettier-plugin-astro'),
|
||||
require.resolve('prettier-plugin-sort-imports'),
|
||||
require.resolve('prettier-plugin-tailwindcss'),
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: '*.astro',
|
||||
options: {
|
||||
parser: 'astro',
|
||||
},
|
||||
},
|
||||
],
|
||||
pluginSearchDirs: false,
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 4,
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
printWidth: 80,
|
||||
importOrder: ['^@core/(.*)$', '^@server/(.*)$', '^@ui/(.*)$', '^[./]'],
|
||||
importOrderSeparation: true,
|
||||
importOrderSortSpecifiers: true,
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"prettier.documentSelectors": ["**/*.astro"],
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"files.associations": {
|
||||
"*.mdx": "markdown"
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ npm create astro@latest -- --template basics
|
|||
|
||||
![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png)
|
||||
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { defineConfig } from 'astro-imagetools/config'
|
||||
|
||||
export default defineConfig({
|
||||
placeholder: 'blurred',
|
||||
format: ['webp', 'jpg'],
|
||||
fallbackFormat: 'png',
|
||||
includeSourceFormat: false,
|
||||
formatOptions: {
|
||||
jpg: {
|
||||
quality: 80,
|
||||
},
|
||||
png: {
|
||||
quality: 80,
|
||||
},
|
||||
webp: {
|
||||
quality: 50,
|
||||
},
|
||||
tracedSVG: {
|
||||
function: 'trace',
|
||||
options: {
|
||||
background: '#fff',
|
||||
color: '#000',
|
||||
turnPolicy: 'black',
|
||||
turdSize: 1,
|
||||
alphaMax: 1,
|
||||
optCurve: true,
|
||||
threshold: 100,
|
||||
blackOnWhite: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
sizes: (breakpoints) => {
|
||||
const maxWidth = breakpoints[breakpoints.length - 1]
|
||||
return `(min-width: ${maxWidth}px) ${maxWidth}px, 100vw`
|
||||
},
|
||||
})
|
|
@ -1,8 +1,45 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
import mdx from '@astrojs/mdx'
|
||||
import partytown from '@astrojs/partytown'
|
||||
import prefetch from '@astrojs/prefetch'
|
||||
import sitemap from '@astrojs/sitemap'
|
||||
import tailwind from '@astrojs/tailwind'
|
||||
import { defineConfig } from 'astro/config'
|
||||
import compress from 'astro-compress'
|
||||
import icon from 'astro-icon'
|
||||
import { astroImageTools } from 'astro-imagetools'
|
||||
import robotsTxt from 'astro-robots-txt'
|
||||
import rehypeExternalLinks from 'rehype-external-links'
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [tailwind()]
|
||||
});
|
||||
site: 'https://ethanreece.com',
|
||||
integrations: [
|
||||
tailwind(),
|
||||
astroImageTools,
|
||||
robotsTxt(),
|
||||
sitemap(),
|
||||
prefetch(),
|
||||
partytown(),
|
||||
mdx({
|
||||
syntaxHighlight: 'shiki',
|
||||
shikiConfig: { theme: 'dracula' },
|
||||
rehypePlugins: [
|
||||
[
|
||||
rehypeExternalLinks,
|
||||
{
|
||||
target: '_blank',
|
||||
rel: ['noopener', 'noreferrer'],
|
||||
},
|
||||
],
|
||||
],
|
||||
}),
|
||||
icon({
|
||||
include: {
|
||||
heroicons: ['*'],
|
||||
'fa-brands': ['*'],
|
||||
'simple-icons': ['*'],
|
||||
},
|
||||
}),
|
||||
compress(),
|
||||
],
|
||||
})
|
||||
|
|
80
package.json
80
package.json
|
@ -1,25 +1,57 @@
|
|||
{
|
||||
"name": "ethanreece.com",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/tailwind": "^3.1.3",
|
||||
"astro": "^2.5.0",
|
||||
"tailwindcss": "^3.0.24eslint-plugin-jsx-a11y"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "^5.59.7",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-plugin-astro": "^0.27.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-astro": "^0.9.0"
|
||||
}
|
||||
}
|
||||
"name": "ethanreece.com",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^0.19.7",
|
||||
"@astrojs/partytown": "^1.2.3",
|
||||
"@astrojs/prefetch": "^0.2.3",
|
||||
"@astrojs/sitemap": "^1.3.3",
|
||||
"@astrojs/tailwind": "^4.0.0",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.0.5",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"astro": "^2.8.0",
|
||||
"astro-auto-import": "^0.3.0",
|
||||
"astro-compress": "^1.1.50",
|
||||
"astro-imagetools": "^0.8.1",
|
||||
"astro-navbar": "^2.0.1",
|
||||
"astro-robots-txt": "^0.5.0",
|
||||
"astro-seo": "^0.7.4",
|
||||
"rehype-external-links": "^2.1.0",
|
||||
"sharp": "^0.32.1",
|
||||
"tailwind-bootstrap-grid": "^5.0.1",
|
||||
"tailwindcss-hero-patterns": "^0.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/fa-brands": "^1.1.4",
|
||||
"@iconify-json/heroicons": "^1.1.11",
|
||||
"@iconify-json/simple-icons": "^1.1.59",
|
||||
"@typescript-eslint/parser": "^5.61.0",
|
||||
"astro-icon": "1.0.0-next.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"concurrently": "^8.2.0",
|
||||
"cssnano": "^6.0.1",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-astro": "^0.27.2",
|
||||
"eslint-plugin-json": "^3.1.0",
|
||||
"eslint-plugin-jsonc": "^2.9.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"eslint-plugin-tailwindcss": "^3.13.0",
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"find-up": "^6.3.0",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-astro": "^0.10.0",
|
||||
"prettier-plugin-sort-imports": "^1.7.2",
|
||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||
"tailwindcss": "^3.3.2"
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {},
|
||||
cssnano: {},
|
||||
},
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.7 MiB |
Binary file not shown.
After Width: | Height: | Size: 9.2 MiB |
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
import { Icon } from 'astro-icon/components'
|
||||
export interface Props {
|
||||
link: string
|
||||
icon?: string
|
||||
external?: boolean
|
||||
}
|
||||
const { link, icon, external } = Astro.props
|
||||
|
||||
const buttonPaddingX = 'md:px-[.75rem] px-[.5rem] md:gap-[.5rem] gap-[.5rem]'
|
||||
const buttonPaddingY = 'py-[.5rem]'
|
||||
---
|
||||
|
||||
<a
|
||||
href={link}
|
||||
target={external ? '_blank' : '_self'}
|
||||
rel={external ? 'noopener noreferrer' : ''}
|
||||
class={`flex flex-row items-center rounded hover:bg-background-hover hover:text-font-hover focus-visible:bg-background-hover focus-visible:text-font-hover focus-visible:outline-none ${buttonPaddingX} ${buttonPaddingY}`}
|
||||
>
|
||||
{icon && <Icon name={icon} />}
|
||||
<slot />
|
||||
</a>
|
|
@ -1,63 +0,0 @@
|
|||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
body: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { href, title, body } = Astro.props;
|
||||
---
|
||||
|
||||
<li class="link-card">
|
||||
<a href={href}>
|
||||
<h2>
|
||||
{title}
|
||||
<span>→</span>
|
||||
</h2>
|
||||
<p>
|
||||
{body}
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<style>
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0.25rem;
|
||||
background-color: white;
|
||||
background-image: none;
|
||||
background-size: 400%;
|
||||
border-radius: 0.6rem;
|
||||
background-position: 100%;
|
||||
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.link-card > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
padding: 1rem 1.3rem;
|
||||
border-radius: 0.35rem;
|
||||
color: #111;
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
p {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0;
|
||||
color: #444;
|
||||
}
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
background-image: var(--accent-gradient);
|
||||
}
|
||||
.link-card:is(:hover, :focus-within) h2 {
|
||||
color: rgb(var(--accent));
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
const paddingX = 'px-[.75rem]'
|
||||
const paddingY = 'py-[.5rem]'
|
||||
|
||||
interface NavItem {
|
||||
text: string
|
||||
link: string
|
||||
}
|
||||
|
||||
const links: NavItem[] = [{ text: 'site credits', link: '/credits' }]
|
||||
const year = new Date().getFullYear()
|
||||
---
|
||||
|
||||
<section>
|
||||
<footer class={`flex bg-background-light ${paddingX} ${paddingY}`}>
|
||||
<span class="mr-auto">Copyright Ethan Reece {year}</span>
|
||||
<nav>
|
||||
{
|
||||
links.map((link) => (
|
||||
<a
|
||||
href={link.link}
|
||||
class="hover:underline focus-visible:underline focus-visible:outline-none"
|
||||
>
|
||||
{link.text}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
</footer>
|
||||
</section>
|
|
@ -0,0 +1,4 @@
|
|||
export interface NavItem {
|
||||
text: string
|
||||
link: string
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
import ButtonSecondary from '../../ButtonSecondary.astro'
|
||||
|
||||
interface NavItem {
|
||||
text: string
|
||||
link: string
|
||||
}
|
||||
|
||||
const homeText = 'Ethan Reece'
|
||||
const links: NavItem[] = [
|
||||
{ text: 'About Me', link: '/#about' },
|
||||
{ text: 'Projects', link: '/#projects' },
|
||||
{ text: 'Experience', link: '/#experience' },
|
||||
{ text: 'Hobbies', link: '/#hobbies' },
|
||||
{ text: 'Contact Me', link: '/#contact' },
|
||||
]
|
||||
|
||||
const paddingX = 'px-[.75rem]'
|
||||
const paddingY = 'py-[.25rem]'
|
||||
const spacing = 'space-x-[.25rem]'
|
||||
|
||||
const mainButtonPaddingX = 'px-[.75rem]'
|
||||
const mainButtonPaddingY = 'py-[.5rem]'
|
||||
---
|
||||
|
||||
<section class="fixed z-navbar w-full backdrop-blur-sm">
|
||||
<div
|
||||
class={`max-h-navbar-h bg-background-light ${paddingX} ${paddingY} opacity-80`}
|
||||
>
|
||||
<nav
|
||||
class={`flex w-full items-center whitespace-nowrap font-normal ${spacing}`}
|
||||
>
|
||||
<a
|
||||
href="/"
|
||||
rel="prefetch"
|
||||
class=`mr-auto text-[1.25rem] font-bold hover:underline focus-visible:underline focus-visible:outline-none ${mainButtonPaddingX} ${mainButtonPaddingY}`
|
||||
>{homeText}</a
|
||||
>
|
||||
<div class={`sm:flex items-center hidden ${spacing}`}>
|
||||
{
|
||||
links.map((link) => (
|
||||
<ButtonSecondary link={link.link}>
|
||||
{link.text}
|
||||
</ButtonSecondary>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
layout: './Section.astro'
|
||||
title: 'About Me'
|
||||
slug: 'about'
|
||||
background: true
|
||||
---
|
||||
|
||||
I am a sophomore at the University of Texas at Dallas who loves programming and tech in general. Although web development and web hosting are my main interests, I enjoy branching out and exploring other ways to work with technology and create things.
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
layout: './Section.astro'
|
||||
title: 'Contact Me'
|
||||
slug: 'contact'
|
||||
background: true
|
||||
---
|
||||
|
||||
import Socials from './Socials.astro'
|
||||
|
||||
Are you looking for help on a web project, or interested in learning more about myself or my projects?
|
||||
|
||||
Feel free to contact or follow me at any time using these links:
|
||||
|
||||
<Socials />
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
layout: './Section.astro'
|
||||
title: 'Experience & Skills'
|
||||
slug: 'experience'
|
||||
background: true
|
||||
---
|
||||
|
||||
import Subsection from './Subsection.astro'
|
||||
|
||||
Although I am most familiar with JavaScript and C#, I have used several other programming languages [i.e. Java, Python, C++, Kotlin] and can quickly learn new ones.
|
||||
|
||||
I have also used multiple web frameworks. I am most familiar with Express.js and React, but I have also used ASP.NET, Astro, and a bit of Django and Next.js. I plan to become more familiar with Django, Rails, and Laravel for backend along with Svelte and SolidJS for frontend.
|
||||
|
||||
I am also familiar with Linux since I use Arch Linux on my laptop and work with Linux Debian VMs on my server. Along with that, I have worked with PostgreSQL and MS SQL in my projects and at work, and I have also touched Google Cloud and Azure, using them for my self-hosted cloud, a cybersecurity course, and for work-related tasks.
|
||||
|
||||
Although I have learned most of these skills in my spare time, I have also used them in my internships:
|
||||
|
||||
<Subsection title="Scrumfish" subtitle="Nampa, ID" url="https://scrumfish.com/">
|
||||
Worked on backend and frontend for multiple projects using ASP.NET, MS SQL,
|
||||
and React to create APIs, a login system with MFA, and functional frontend
|
||||
components. Rewrote part of a web app to open EPUB documents, enable users
|
||||
to highlight text and comment, and position those comments relative to the
|
||||
highlighted text.
|
||||
</Subsection>
|
||||
|
||||
<Subsection
|
||||
title="R-Technics"
|
||||
subtitle="Boise, ID"
|
||||
url="https://www.rtechnics.com/"
|
||||
>
|
||||
Performed Windows server maintenance tasks and created a prototype in C# to
|
||||
pull song data using the SongSelect API, which was later integrated into the
|
||||
company's main product.
|
||||
</Subsection>
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
// @ts-ignore
|
||||
import { Picture } from 'astro-imagetools/components'
|
||||
|
||||
const viewHeight = 'md:h-[30rem] sm:h-[15rem] h-[17rem]'
|
||||
const padding = 'p-[1rem] pb-[3.5rem] md:p-[2rem] sm:pb-[2rem]'
|
||||
|
||||
const textMainSize = 'text-[2rem]'
|
||||
const textEmphasisSize = 'md:text-[4rem] text-[3rem]'
|
||||
const textEmphasisLineHeight = 'leading-[3.5rem]'
|
||||
|
||||
const text = ['Hello', ', I am', 'Ethan Reece', 'and I do ', 'web stuff', '.']
|
||||
const image = '../public/images/index/myself.png'
|
||||
const imageAlt = 'black and white portrait of myself'
|
||||
---
|
||||
|
||||
<section class={`w-full ${viewHeight} ${padding}`}>
|
||||
<header
|
||||
class="mx-auto flex h-full max-w-page-w items-center text-center sm:max-w-[32rem] sm:text-left md:max-w-page-w"
|
||||
>
|
||||
<h1 class={`font-medium ${textMainSize} w-full sm:w-auto`}>
|
||||
<i
|
||||
><b><u>{text[0]}</u></b><span class="text-font-light"
|
||||
>{text[1]}</span
|
||||
></i
|
||||
>
|
||||
<br />
|
||||
<b
|
||||
class={`font-extrabold ${textEmphasisSize} ${textEmphasisLineHeight}`}
|
||||
>{text[2]}</b
|
||||
>
|
||||
<br />
|
||||
<i
|
||||
><span class="text-font-light">{text[3]}</span>
|
||||
<b class="font-extrabold"><u>{text[4]}</u></b></i
|
||||
><span class="text-font-light">{text[5]}</span>
|
||||
</h1>
|
||||
<div class="ml-auto hidden h-full sm:flex">
|
||||
<Picture
|
||||
src={image}
|
||||
alt={imageAlt}
|
||||
fit="cover"
|
||||
layout="fill"
|
||||
placeholder="none"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
</section>
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
layout: './Section.astro'
|
||||
title: 'Hobbies'
|
||||
slug: 'hobbies'
|
||||
background: false
|
||||
---
|
||||
|
||||
Besides programming and working with technology, I enjoy biking and playing Minecraft modpacks. Biking gives me a way to explore the outdoors while traveling to where I need to go, and it is also less costly and more environmentally sustainable than travelling via car.
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
layout: './Section.astro'
|
||||
title: 'Projects'
|
||||
slug: 'projects'
|
||||
background: false
|
||||
---
|
||||
|
||||
import Subsection from './Subsection.astro'
|
||||
|
||||
My biggest personal project so far is building a server to self-host my cloud ecosystem. My server uses a Proxmox hypervisor along with Linux Debian VMs and Docker containers running services such as TrueNAS, Syncthing, Nextcloud, Plex, Forejo, Pterodactyl, GoToSocial, Lemmy, and others. To get around carrier-grade NAT limitations, I host a VPN using Google Cloud and use nftables to route traffic so I can make services publicly accessible.
|
||||
|
||||
I also have other projects viewable on multiple git platforms, most of which came from hackathons, game jams, and school projects. Of these, the most interesting are:
|
||||
|
||||
<Subsection
|
||||
title="FFmpeg automated build system"
|
||||
subtitle="R-Technics 2023"
|
||||
url="https://github.com/rtechnics/ffmpeg"
|
||||
>
|
||||
GitHub Actions workflow that automatically checks for the latest FFmpeg
|
||||
release tag, cross-compiles it for Windows using automatically compiled
|
||||
OpenSSL libraries from another workflow, and generates a WiX installer and
|
||||
NuGet package for internal business use.
|
||||
</Subsection>
|
||||
|
||||
<Subsection
|
||||
title="GroceRead"
|
||||
subtitle="HackAI, UTD 2023"
|
||||
url="https://devpost.com/software/groceread"
|
||||
>
|
||||
Android app intended to help visually-impaired people navigate grocery
|
||||
stores by telling the user the product the phone camera is pointed at. Uses
|
||||
Chooch API for object detection.
|
||||
</Subsection>
|
||||
|
||||
<Subsection
|
||||
title="Med-Terms"
|
||||
subtitle="Axxess Hackathon, UTD 2023"
|
||||
url="https://devpost.com/software/med-terms"
|
||||
>
|
||||
Educational chatbot that teaches anatomical terminology and suggests others
|
||||
for the user to learn about. Wrote the API layer of the backend using
|
||||
Django, and created most of the frontend using React.
|
||||
</Subsection>
|
||||
|
||||
<Subsection
|
||||
title="Score Tracker"
|
||||
subtitle="HS senior project"
|
||||
url="https://git.sudoer.ch/sudoer777/score-tracker"
|
||||
>
|
||||
Web app that allows coaches of HS sports teams to submit game scores. Scores
|
||||
would then be publicly viewable on the app's homepage and could be managed
|
||||
through an admin interface. Created with Express.js, PostgreSQL, and vanilla
|
||||
JavaScript for frontend. This was my first personal web project developed
|
||||
from scratch.
|
||||
</Subsection>
|
||||
|
||||
Some other projects and activities I have worked on include [Med Check [MetroHacks 2022]](https://devpost.com/software/medcheck-sopt84), [WitchBlade [UTD SGDA 2023 Spring Jam]](https://itch.io/jam/spring23-jam/rate/1968464), [Dungeons & Debuffs [UTD SGDA 2022 Thanksgiving Jam]](https://itch.io/jam/sgda-thanksgiving-jam/rate/1810971), [ZipVersionControl [FLL #1790, yrs 2017-2019]](https://github.com/TheEthan8or/ZipVersionControl), FIRST Tech Challenge robotics [2021-2022], and FIRST Lego League robotics [2013-2019, #1790].
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
import type { MDXLayoutProps } from 'astro'
|
||||
type Props = MDXLayoutProps<{
|
||||
title: string
|
||||
slug: string
|
||||
background: boolean
|
||||
}>
|
||||
|
||||
const { title, slug, background } = Astro.props.frontmatter
|
||||
|
||||
const fontSize = 'text-[1rem]'
|
||||
const fontSizeH2 = 'text-[1.5rem]'
|
||||
|
||||
const paddingT = 'pt-[1.5rem]'
|
||||
const paddingB = 'pb-[2.5rem]'
|
||||
const anchorDistance = 'mt-[-5rem] pt-[5rem]'
|
||||
---
|
||||
|
||||
<section
|
||||
class={`px-screen-x ${paddingT} ${paddingB} ${fontSize} ${
|
||||
background ? 'bg-background-dark' : ''
|
||||
}`}
|
||||
>
|
||||
<div class={anchorDistance} id={slug}>
|
||||
<div class={`mx-auto max-w-page-w space-y-paragraph prose`}>
|
||||
<h2 class={`font-bold ${fontSizeH2}`}>
|
||||
{title}
|
||||
</h2>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
import ButtonSecondary from '../../ButtonSecondary.astro'
|
||||
interface SocialExtra {
|
||||
name: string
|
||||
url: string
|
||||
external?: boolean
|
||||
}
|
||||
interface Social {
|
||||
name?: string
|
||||
profile: string
|
||||
url: string
|
||||
extra?: SocialExtra[]
|
||||
icon?: string
|
||||
}
|
||||
|
||||
const socials: Social[] = [
|
||||
{
|
||||
name: 'Email',
|
||||
profile: 'contact@ethanreece.com',
|
||||
url: 'mailto:contact@ethanreece.com',
|
||||
icon: 'heroicons:envelope-solid',
|
||||
extra: [
|
||||
{
|
||||
name: 'PGP key',
|
||||
url: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'GitHub (Personal)',
|
||||
profile: 'Sudoer777',
|
||||
url: 'https://gh.sudoer.ch/sudoer777',
|
||||
icon: 'fa-brands:github',
|
||||
},
|
||||
{
|
||||
name: 'GitHub (R-Technics)',
|
||||
profile: 'ethanreece-rtechnics',
|
||||
url: 'https://gh.sudoer.ch/ethanreece-rtechnics',
|
||||
icon: 'heroicons:briefcase-solid',
|
||||
},
|
||||
{
|
||||
name: 'GitHub (Scrumfish)',
|
||||
profile: 'ethanreece-scrumfish',
|
||||
url: 'https://gh.sudoer.ch/ethanreece-scrumfish',
|
||||
icon: 'heroicons:briefcase-solid',
|
||||
},
|
||||
{
|
||||
name: 'Forgejo',
|
||||
profile: 'me@git.sudoer.ch',
|
||||
url: 'https://git.sudoer.ch/me',
|
||||
icon: 'simple-icons:forgejo',
|
||||
},
|
||||
{
|
||||
name: 'Mastodon (GoToSocial)',
|
||||
profile: '@me@gts.sudoer.ch',
|
||||
url: 'https://elk.sudoer.ch/@me@gts.sudoer.ch',
|
||||
icon: 'fa-brands:mastodon',
|
||||
},
|
||||
{
|
||||
name: 'Devpost',
|
||||
profile: 'sudoer777',
|
||||
url: 'https://devpost.com/sudoer777',
|
||||
icon: 'simple-icons:devpost',
|
||||
},
|
||||
{
|
||||
name: 'LinkedIn',
|
||||
profile: '-ethanreece-',
|
||||
url: 'https://www.linkedin.com/in/-ethanreece-/',
|
||||
icon: 'fa-brands:linkedin',
|
||||
},
|
||||
]
|
||||
---
|
||||
|
||||
<div class="not-prose flex flex-col">
|
||||
{
|
||||
socials.map((s) => (
|
||||
<div class="flex">
|
||||
<ButtonSecondary link={s.url} external={true} icon={s.icon}>
|
||||
<b class="font-extrabold">{s.name}:</b>
|
||||
{s.profile}
|
||||
</ButtonSecondary>
|
||||
{s.extra &&
|
||||
s.extra.map((e) => (
|
||||
<ButtonSecondary link={e.url} external={e.external}>
|
||||
[{e.name}]
|
||||
</ButtonSecondary>
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
export interface Props {
|
||||
title: string
|
||||
subtitle: string
|
||||
url: string | null
|
||||
}
|
||||
|
||||
const { title, subtitle, url } = Astro.props
|
||||
|
||||
const fontSize = 'text-[.9rem]'
|
||||
const fontSizeH3 = 'text-[1.4rem]'
|
||||
const fontSpacing = 'leading-[1.5rem]'
|
||||
|
||||
const margin = 'm-[-1rem] p-[1rem]'
|
||||
const background = 'bg-[#00000020]'
|
||||
---
|
||||
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class={`block md:rounded ${
|
||||
url &&
|
||||
`hover:bg-background-hover hover:text-font-hover focus-visible:bg-background-hover focus-visible:text-font-hover focus-visible:outline-none not-prose no-underline font-extralight`
|
||||
} ${fontSize} ${margin} ${background}`}
|
||||
>
|
||||
<div class={`mx-auto w-full md:w-auto max-w-page-w space-y-paragraph`}>
|
||||
<span class={`flex flex-col`}>
|
||||
<h3 class={`font-bold ${fontSizeH3}`}>{title}</h3>
|
||||
{
|
||||
subtitle && (
|
||||
<span class={`font-thin italic ${fontSpacing}`}>
|
||||
{subtitle}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
<slot />
|
||||
</div>
|
||||
</a>
|
|
@ -1 +1,3 @@
|
|||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
||||
/// <reference types="astro-imagetools" />
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
import type { MDXLayoutProps } from 'astro'
|
||||
import Base from './Base.astro'
|
||||
|
||||
type Props = MDXLayoutProps<{
|
||||
title: string
|
||||
description: string
|
||||
}>
|
||||
|
||||
const { title, description } = Astro.props.frontmatter
|
||||
---
|
||||
|
||||
<Base title={`${title} - Ethan Reece`} description={description}>
|
||||
<article class="prose mx-auto px-screen-x">
|
||||
<div class="mx-auto max-w-page-w">
|
||||
<section>
|
||||
<header>
|
||||
<h1>
|
||||
{title}
|
||||
</h1>
|
||||
</header>
|
||||
</section>
|
||||
<section>
|
||||
<slot />
|
||||
</section>
|
||||
</div>
|
||||
</article>
|
||||
</Base>
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
import { SEO } from 'astro-seo'
|
||||
|
||||
import Footer from '../components/content/global/Footer.astro'
|
||||
import Navbar from '../components/content/global/Navbar.astro'
|
||||
import Background from './properties/Background.astro'
|
||||
import Head from './properties/Head.astro'
|
||||
|
||||
export interface Props {
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const { title, description } = Astro.props
|
||||
|
||||
import '@fontsource-variable/jetbrains-mono'
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<Head title={title} description={description}>
|
||||
<SEO title={title} description={description} />
|
||||
</Head>
|
||||
<body class="min-h-screen w-screen font-main font-light text-font">
|
||||
<Background>
|
||||
<Navbar />
|
||||
<main class="mb-auto pt-navbar-h">
|
||||
<slot />
|
||||
</main>
|
||||
<Footer class="mb-auto" />
|
||||
</Background>
|
||||
</body>
|
||||
</html>
|
|
@ -1,36 +0,0 @@
|
|||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="Astro description">
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
<style is:global>
|
||||
:root {
|
||||
--accent: 124, 58, 237;
|
||||
--accent-gradient: linear-gradient(45deg, rgb(var(--accent)), #da62c4 30%, white 60%);
|
||||
}
|
||||
html {
|
||||
font-family: system-ui, sans-serif;
|
||||
background-color: #F6F6F6;
|
||||
}
|
||||
code {
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
const pattern = 'heropattern-circuitboard-pattern'
|
||||
---
|
||||
|
||||
<div class="min-h-screen bg-background">
|
||||
<div
|
||||
class={`flex min-h-screen w-full bg-circuitboard bg-repeat flex-col ${pattern}`}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
<head>
|
||||
<slot />
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
</head>
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
layout: '../layouts/Article.astro'
|
||||
title: 'Site Credits'
|
||||
---
|
||||
|
||||
This site was built using the following tools:
|
||||
|
||||
| | |
|
||||
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Framework:** | Astro with TypeScript and MDX |
|
||||
| **Runtime:** | Node.js |
|
||||
| **Version management:** | rtx |
|
||||
| **Package management:** | pnpm |
|
||||
| **Layout:** | Tailwind CSS (with Astro extension) |
|
||||
| **Font:** | JetBrains Mono (using Fontsource) |
|
||||
| **Icons:** | Hero Icons, Font Awesome Brands, Simple Icons (using Astro Icon) |
|
||||
| **Circuitboard Pattern:** | Hero Patterns |
|
||||
| **IDE:** | VSCodium |
|
||||
| **Code formatting:** | Prettier, ESLint, Astro MDX |
|
||||
| **Optimization:** | Astro Prefetch, Astro Sitemap, Astro Imagetools (using Sharp), Astro Robots.txt, Astro SEO, Astro Compress, Autoprefixer, CSSNano |
|
||||
| **Contact Frontends** | Elk (Mastodon/GoToSocial), GotHub (GitHub) |
|
||||
| **Source code:** | [On Forgejo](https://git.sudoer.ch/me/ethanreece.com) |
|
|
@ -1,81 +1,24 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Card from '../components/Card.astro';
|
||||
import Hero from '../components/content/index/Hero.astro'
|
||||
import About from '../components/content/index/About.mdx'
|
||||
import Projects from '../components/content/index/Projects.mdx'
|
||||
import Experience from '../components/content/index/Experience.mdx'
|
||||
import Hobbies from '../components/content/index/Hobbies.mdx'
|
||||
import Contact from '../components/content/index/Contact.mdx'
|
||||
|
||||
import Base from '../layouts/Base.astro'
|
||||
|
||||
const title = 'Ethan Reece'
|
||||
const description = ''
|
||||
---
|
||||
|
||||
<Layout title="Welcome to Astro.">
|
||||
<main>
|
||||
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
|
||||
<p class="instructions">
|
||||
To get started, open the directory <code>src/pages</code> in your project.<br />
|
||||
<strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
|
||||
</p>
|
||||
<ul role="list" class="link-card-grid">
|
||||
<Card
|
||||
href="https://docs.astro.build/"
|
||||
title="Documentation"
|
||||
body="Learn how Astro works and explore the official API docs."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/integrations/"
|
||||
title="Integrations"
|
||||
body="Supercharge your project with new frameworks and libraries."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/themes/"
|
||||
title="Themes"
|
||||
body="Explore a galaxy of community-built starter themes."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/chat/"
|
||||
title="Community"
|
||||
body="Come say hi to our amazing Discord community. ❤️"
|
||||
/>
|
||||
</ul>
|
||||
</main>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
main {
|
||||
margin: auto;
|
||||
padding: 1.5rem;
|
||||
max-width: 60ch;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
margin: 0;
|
||||
}
|
||||
.text-gradient {
|
||||
background-image: var(--accent-gradient);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-size: 400%;
|
||||
background-position: 0%;
|
||||
}
|
||||
.instructions {
|
||||
line-height: 1.6;
|
||||
margin: 1rem 0;
|
||||
border: 1px solid rgba(var(--accent), 25%);
|
||||
background-color: white;
|
||||
padding: 1rem;
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
.instructions code {
|
||||
font-size: 0.875em;
|
||||
font-weight: bold;
|
||||
background: rgba(var(--accent), 12%);
|
||||
color: rgb(var(--accent));
|
||||
border-radius: 4px;
|
||||
padding: 0.3em 0.45em;
|
||||
}
|
||||
.instructions strong {
|
||||
color: rgb(var(--accent));
|
||||
}
|
||||
.link-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
|
||||
gap: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<Base title={title} description={description}>
|
||||
<article>
|
||||
<Hero />
|
||||
<About />
|
||||
<Projects />
|
||||
<Experience />
|
||||
<Hobbies />
|
||||
<Contact />
|
||||
</article>
|
||||
</Base>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
const heropatterns = require('tailwindcss-hero-patterns/src/patterns')
|
||||
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
||||
theme: {
|
||||
extend: {
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
color: 'black',
|
||||
fontWeight: '200',
|
||||
h1: {
|
||||
paddingTop: '1rem',
|
||||
},
|
||||
'a:hover': {
|
||||
background: 'black',
|
||||
color: 'white',
|
||||
textDecoration: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
heroPatterns: {
|
||||
circuitboard: heropatterns.circuitboard,
|
||||
},
|
||||
heroPatternsOpacities: ['100'],
|
||||
heroPatternColors: ['font'],
|
||||
colors: {
|
||||
background: '#dbf6db',
|
||||
'background-dark': '#bbd8bb',
|
||||
'background-light': '#ffffff',
|
||||
'background-hover': '#000000cc',
|
||||
font: '#000000',
|
||||
'font-light': '#004f07',
|
||||
'font-hover': '#ffffff',
|
||||
pattern: 'rgba(0,0,0,.02)',
|
||||
},
|
||||
fontFamily: {
|
||||
main: ['JetBrains Mono Variable', 'monospace'],
|
||||
},
|
||||
backgroundSize: {
|
||||
contain: 'contain',
|
||||
circuitboard: '40rem',
|
||||
},
|
||||
spacing: {
|
||||
'screen-x': '1rem',
|
||||
'screen-y': '1.5rem',
|
||||
'navbar-h': '4rem',
|
||||
paragraph: '1rem',
|
||||
},
|
||||
maxWidth: {
|
||||
'page-w': '45rem',
|
||||
},
|
||||
zIndex: {
|
||||
navbar: '100',
|
||||
},
|
||||
screens: {
|
||||
sm: '640px',
|
||||
md: '768px',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('tailwindcss-hero-patterns'),
|
||||
require('@tailwindcss/typography'),
|
||||
],
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/strict"
|
||||
}
|
||||
"extends": "astro/tsconfigs/strict"
|
||||
}
|
||||
|
|
Reference in New Issue