Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
14c3460c11 | |||
ab16219f3b | |||
7ffc4f518b | |||
1f00bfa07b | |||
93050928d8 | |||
4668473293 | |||
0ad815d516 | |||
abf4575151 | |||
383148a3ac | |||
9241171e1b | |||
294956f8e7 | |||
cc848bf06f | |||
8fa85ed6ac | |||
8c7cec7e7c | |||
664fe17610 | |||
b76100ad06 | |||
db794ff582 | |||
002e1aa234 | |||
922f2b4e0e | |||
9ee8e25090 | |||
78e980df41 | |||
a1dfdfb8c5 | |||
94f114bb5c |
28
.drone.yml
28
.drone.yml
@ -1,28 +0,0 @@
|
|||||||
kind: pipeline
|
|
||||||
name: buildsite
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: node
|
|
||||||
commands:
|
|
||||||
- npm install
|
|
||||||
- npm run build
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: copy
|
|
||||||
image: appleboy/drone-scp
|
|
||||||
settings:
|
|
||||||
host: 192.168.10.5
|
|
||||||
username: thomas
|
|
||||||
password:
|
|
||||||
from_secret: ssh_password
|
|
||||||
port: 22
|
|
||||||
target: /home/thomas/swag/www
|
|
||||||
source: ./dist/*
|
|
||||||
strip_components: 1
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
28
.github/workflows/build.yml
vendored
Normal file
28
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: Build Site
|
||||||
|
run-name: ${{ github.actor }} is building Personal Site
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
Build-Site:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [18.x]
|
||||||
|
steps:
|
||||||
|
- run: echo "Starting build job"
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: npm install
|
||||||
|
- run: npm run build
|
||||||
|
- name: copy file via ssh password
|
||||||
|
uses: appleboy/scp-action@v0.1.4
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.HOST }}
|
||||||
|
username: ${{ secrets.USERNAME }}
|
||||||
|
password: ${{ secrets.PASSWORD }}
|
||||||
|
port: ${{ secrets.PORT }}
|
||||||
|
source: "dist/*"
|
||||||
|
target: /home/thomas/testwww
|
||||||
|
strip_components: 1
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) <year> <copyright holders>
|
Copyright (c) 2023 Thomas Cole
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
// https://astro.build/config
|
|
||||||
import tailwind from "@astrojs/tailwind";
|
import tailwind from "@astrojs/tailwind";
|
||||||
|
|
||||||
// https://astro.build/config
|
|
||||||
import svelte from "@astrojs/svelte";
|
import svelte from "@astrojs/svelte";
|
||||||
|
|
||||||
// https://astro.build/config
|
|
||||||
import mdx from "@astrojs/mdx";
|
import mdx from "@astrojs/mdx";
|
||||||
|
import sitemap from "@astrojs/sitemap";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [tailwind(), svelte(), mdx()]
|
site: 'https://thomaspcole.com',
|
||||||
|
integrations: [tailwind(), svelte(), mdx(), sitemap()]
|
||||||
});
|
});
|
22
package.json
22
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@example/basics",
|
"name": "@thomaspcole/website",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.1",
|
"version": "0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
@ -11,15 +11,19 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/mdx": "^0.13.0",
|
"@astrojs/mdx": "^2.0.1",
|
||||||
"@astrojs/svelte": "^1.0.2",
|
"@astrojs/sitemap": "^3.0.3",
|
||||||
"@astrojs/tailwind": "^2.1.3",
|
"@astrojs/svelte": "^5.0.1",
|
||||||
"astro": "^1.6.15",
|
"@astrojs/tailwind": "^5.0.3",
|
||||||
|
"astro": "^4.0.4",
|
||||||
"cytoscape": "^3.23.0",
|
"cytoscape": "^3.23.0",
|
||||||
"daisyui": "^2.45.0",
|
"daisyui": "^4.4.19",
|
||||||
"p5-svelte": "^3.1.2",
|
"p5-svelte": "^3.1.2",
|
||||||
"svelte": "^3.55.0",
|
"svelte": "^4.2.8",
|
||||||
"tailwindcss": "^3.2.4",
|
"tailwindcss": "^3.3.1",
|
||||||
"theme-change": "^2.2.0"
|
"theme-change": "^2.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/typography": "^0.5.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
public/icon-512-maskable.png
Normal file
BIN
public/icon-512-maskable.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 104 KiB |
BIN
public/icon-512.png
Normal file
BIN
public/icon-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
9
public/particles.min.js
vendored
Normal file
9
public/particles.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
102
public/particlesjs-config-dark.json
Normal file
102
public/particlesjs-config-dark.json
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"particles": {
|
||||||
|
"number": {
|
||||||
|
"value": 100,
|
||||||
|
"density": {
|
||||||
|
"enable": true,
|
||||||
|
"value_area": 800
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"value": "#ffffff"
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"type": "circle",
|
||||||
|
"stroke": {
|
||||||
|
"width": 0,
|
||||||
|
"color": "#000000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"value": 0.5,
|
||||||
|
"random": false,
|
||||||
|
"anim": {
|
||||||
|
"enable": false,
|
||||||
|
"speed": 1,
|
||||||
|
"opacity_min": 0.1,
|
||||||
|
"sync": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"value": 3,
|
||||||
|
"random": true,
|
||||||
|
"anim": {
|
||||||
|
"enable": false,
|
||||||
|
"speed": 40,
|
||||||
|
"size_min": 0.1,
|
||||||
|
"sync": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"line_linked": {
|
||||||
|
"enable": true,
|
||||||
|
"distance": 150,
|
||||||
|
"color": "#ffffff",
|
||||||
|
"opacity": 0.4,
|
||||||
|
"width": 1
|
||||||
|
},
|
||||||
|
"move": {
|
||||||
|
"enable": true,
|
||||||
|
"speed": 1,
|
||||||
|
"direction": "none",
|
||||||
|
"random": false,
|
||||||
|
"straight": false,
|
||||||
|
"out_mode": "out",
|
||||||
|
"bounce": false,
|
||||||
|
"attract": {
|
||||||
|
"enable": false,
|
||||||
|
"rotateX": 600,
|
||||||
|
"rotateY": 1200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interactivity": {
|
||||||
|
"detect_on": "canvas",
|
||||||
|
"events": {
|
||||||
|
"onhover": {
|
||||||
|
"enable": false,
|
||||||
|
"mode": "repulse"
|
||||||
|
},
|
||||||
|
"onclick": {
|
||||||
|
"enable": false,
|
||||||
|
"mode": "push"
|
||||||
|
},
|
||||||
|
"resize": true
|
||||||
|
},
|
||||||
|
"modes": {
|
||||||
|
"grab": {
|
||||||
|
"distance": 400,
|
||||||
|
"line_linked": {
|
||||||
|
"opacity": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bubble": {
|
||||||
|
"distance": 400,
|
||||||
|
"size": 40,
|
||||||
|
"duration": 2,
|
||||||
|
"opacity": 8,
|
||||||
|
"speed": 3
|
||||||
|
},
|
||||||
|
"repulse": {
|
||||||
|
"distance": 200,
|
||||||
|
"duration": 0.4
|
||||||
|
},
|
||||||
|
"push": {
|
||||||
|
"particles_nb": 4
|
||||||
|
},
|
||||||
|
"remove": {
|
||||||
|
"particles_nb": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"retina_detect": true
|
||||||
|
}
|
@ -1,3 +1,11 @@
|
|||||||
<footer class="p-4">
|
---
|
||||||
<p class="text-center text-sm">© 2023 Thomas Cole</p>
|
import SocialLinks from "./nav/SocialLinks.astro";
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<footer class="z-10 text-gray-600 dark:text-gray-400 body-font">
|
||||||
|
<div class="container flex flex-col items-center px-5 pt-8 pb-2 mx-auto place-content-center sm:flex-row">
|
||||||
|
<p class="mt-4 text-sm text-gray-500 align-middle dark:text-gray-400">© 2024
|
||||||
|
Thomas Cole</p>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
24
src/components/Particles.astro
Normal file
24
src/components/Particles.astro
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<style lang="scss">
|
||||||
|
#pjs {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 0;
|
||||||
|
animation: fadein .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadein {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script is:inline src="/particles.min.js" />
|
||||||
|
<div id="pjs"></div>
|
||||||
|
<script>
|
||||||
|
particlesJS.load('pjs', '/particlesjs-config-dark.json');
|
||||||
|
</script>
|
@ -4,20 +4,30 @@ export interface Props {
|
|||||||
subtitle: string;
|
subtitle: string;
|
||||||
image: string;
|
image: string;
|
||||||
link: string;
|
link: string;
|
||||||
|
category: string;
|
||||||
|
date: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, subtitle, image, link } = Astro.props;
|
const { title, subtitle, image, link, category, date } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<div class="p-4 md:w-1/3">
|
||||||
<div class="rounded-lg transition ease-in-out hover:scale-105">
|
<div class="w-full h-full overflow-hidden bg-gray-500 rounded-lg sm:pt-0 rounded-xl bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-10">
|
||||||
<a href={link}>
|
<img class="object-cover object-center w-full lg:h-48 md:h-36" src={image} alt="blog" draggable="false">
|
||||||
<div class="ml-2 my-2 flex flex-col sm:flex-col md:flex-col lg:flex-row xl:flex-row 2xl:flex-row w-11/12 mx-auto">
|
<div class="p-6">
|
||||||
<img class="h-28 my-2 aspect-video object-cover rounded-lg" src={image} alt="">
|
<h2 class="mb-1 text-xs font-medium tracking-widest text-gray-300">{category}</h2>
|
||||||
<div class="lg:ml-4 xl:ml-4 2xl:ml-4 grow">
|
<h1 class="mb-3 text-lg font-medium text-white title-font">{title}</h1>
|
||||||
<p class="mt-2 font-semibold text-2xl py-2 sm:text-2xl md:text-2xl lg:text-3xl xl:text-3xl 2xl:text-3xl">{title}</p>
|
<p class="mb-3 leading-relaxed">{subtitle}</p>
|
||||||
<p>{subtitle}</p>
|
<div class="flex flex-wrap items-center ">
|
||||||
</div>
|
<a href={link} class="inline-flex items-center text-indigo-200 md:mb-2 lg:mb-0">Read
|
||||||
</div>
|
<svg class="w-4 h-4 ml-2" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M5 12h14"></path>
|
||||||
|
<path d="M12 5l7 7-7 7"></path>
|
||||||
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
<span class="text-sm leading-none text-right text-gray-300 grow">{date}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
13
src/components/misc/Button.astro
Normal file
13
src/components/misc/Button.astro
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
text: string;
|
||||||
|
link: string;
|
||||||
|
width?: string;
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { text, link, width, icon } = Astro.props;
|
||||||
|
let baseClass = "flex items-center p-2 mx-auto font-bold text-center text-white transition-all border-2 border-white rounded-md place-content-center hover:text-black hover:bg-white " + width;
|
||||||
|
---
|
||||||
|
|
||||||
|
<a class={baseClass} href={link}>{text}{icon && <span class="ml-2 material-symbols-outlined">{icon}</span>}</a>
|
@ -1,44 +1,29 @@
|
|||||||
<!-- Could be remade as svelte component.
|
<label class="transition ease-in-out swap swap-rotate hover:scale-125">
|
||||||
For the 3 lines of javascript needed I think its okay to use a script tag -->
|
<!-- this hidden checkbox controls the state -->
|
||||||
|
<input id="themetoggle" type="checkbox" onchange="updateTheme()" />
|
||||||
<style>
|
<span class="swap-off material-symbols-outlined">
|
||||||
#themetoggle~.dot {
|
dark_mode
|
||||||
background-color: #e2e2e2;
|
</span>
|
||||||
}
|
<span class="swap-on material-symbols-outlined">
|
||||||
|
|
||||||
#themetoggle:checked~.dot {
|
|
||||||
transform: translateX(100%);
|
|
||||||
background-color: #202020;
|
|
||||||
}
|
|
||||||
|
|
||||||
#themetoggle~.capsule {
|
|
||||||
background-color: #181a2a;
|
|
||||||
}
|
|
||||||
|
|
||||||
#themetoggle:checked~.capsule {
|
|
||||||
background-color: #cecece;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let theme = localStorage.getItem("theme");
|
|
||||||
if (theme == "business") {
|
|
||||||
document.getElementById("themetoggle").checked = true;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-center w-full">
|
|
||||||
<span class="material-symbols-outlined mx-2">
|
|
||||||
light_mode
|
light_mode
|
||||||
</span>
|
</span>
|
||||||
<label for="themetoggle" class="flex items-center cursor-pointer">
|
|
||||||
<div class="relative">
|
|
||||||
<input type="checkbox" id="themetoggle" class="sr-only" data-toggle-theme="business,corporate">
|
|
||||||
<div class="capsule block w-14 h-8 rounded-full"></div>
|
|
||||||
<div class="dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition"></div>
|
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
<span class="material-symbols-outlined mx-2">
|
|
||||||
nightlight
|
<script is:inline>
|
||||||
</span>
|
let toggle = document.getElementById("themetoggle");
|
||||||
</div>
|
|
||||||
|
if (localStorage.theme === 'light') {
|
||||||
|
toggle.checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTheme() {
|
||||||
|
if (toggle.checked) {
|
||||||
|
document.documentElement.classList.remove('dark')
|
||||||
|
localStorage.setItem("theme", "light");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.add('dark')
|
||||||
|
localStorage.setItem("theme", "dark");
|
||||||
|
};
|
||||||
|
window.dispatchEvent(new Event("themeupdate"));
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,86 +1,22 @@
|
|||||||
---
|
---
|
||||||
import Divider from "../misc/Divider.astro";
|
export interface Props {
|
||||||
import SocialLinks from "./SocialLinks.astro";
|
pageName?: string;
|
||||||
import ThemeToggle from "../misc/ThemeToggle.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<style>
|
|
||||||
[data-shown="true"] {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
|
||||||
<div id="menu" data-shown=""
|
const { pageName } = Astro.props;
|
||||||
class="fixed top-0 left-0 w-72 h-screen p-4 flex flex-col bg-base-200 -translate-x-full sm:-translate-x-full md:-translate-x-full lg:translate-x-0 xl:translate-x-0 2xl: transition-all duration-100 z-50">
|
let displayedName = "";
|
||||||
|
if (pageName) {
|
||||||
|
displayedName = pageName.replace("|", "").trim();
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
<button id="menu-close" class="absolute top-0 right-0 p-2 lg:hidden 2xl:hidden">
|
<header class="z-10 text-white">
|
||||||
<span class="material-symbols-outlined">
|
<div class="container flex flex-col flex-wrap items-center p-5 mx-auto md:flex-row">
|
||||||
close
|
<nav class="flex flex-wrap items-center justify-center text-base md:ml-auto">
|
||||||
</span>
|
<a href="/" class="mr-5">Home</a>
|
||||||
</button>
|
<a href="/projects/1" class="mr-5">Projects</a>
|
||||||
|
<a href="/about" class="mr-5">About</a>
|
||||||
<a href="/">
|
<a href="/contact" class="mr-5">Contact</a>
|
||||||
<div class="transition ease-in-out w-1/2 hover:scale-105 block mx-auto mt-3 mb-6">
|
</nav>
|
||||||
<img class="rounded-full aspect-square w-full object-cover" src="/img/profile.jpg" alt="">
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</header>
|
||||||
|
|
||||||
<p class="text-2xl">Thomas Cole</p>
|
|
||||||
<p class="font-thin text-sm">Developer, Linux Enthusiast, System Administrator</p>
|
|
||||||
|
|
||||||
<SocialLinks />
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li class="flex flex-col">
|
|
||||||
<a href="/" class="w-full hover:bg-base-100 rounded transation ease-in-out duration-200">
|
|
||||||
<button class="p-2">Home</button>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="flex">
|
|
||||||
<a href="/projects/1" class="w-full hover:bg-base-100 rounded transation ease-in-out duration-200">
|
|
||||||
<button class="p-2">Projects</button>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="flex">
|
|
||||||
<a href="/resume" class="w-full hover:bg-base-100 rounded transation ease-in-out duration-200">
|
|
||||||
<button class="p-2">Resume/CV</button>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="flex">
|
|
||||||
<a href="mailto:thomas.patrick.cole@gmail.com"
|
|
||||||
class="w-full hover:bg-base-100 rounded transation ease-in-out duration-200">
|
|
||||||
<button class="p-2">Contact</button>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
<div class="grow"></div>
|
|
||||||
<ThemeToggle />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="h-12 w-full sm:block md:block lg:hidden xl:hidden 2xl:hidden block bg-base-200 text-left transition-all duration-100">
|
|
||||||
|
|
||||||
<button id="menubtn" class="w-12 h-12 m-auto">
|
|
||||||
<span class="material-symbols-outlined mt-1">
|
|
||||||
menu
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script is:inline>
|
|
||||||
const btn = document.getElementById("menubtn");
|
|
||||||
const menu = document.getElementById("menu");
|
|
||||||
const closebtn = document.getElementById("menu-close")
|
|
||||||
|
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
menu.dataset.shown = "true";
|
|
||||||
});
|
|
||||||
|
|
||||||
closebtn.addEventListener('click', () => {
|
|
||||||
menu.dataset.shown = "false";
|
|
||||||
})
|
|
||||||
</script>
|
|
@ -1,7 +1,6 @@
|
|||||||
<div class="flex place-content-center mt-4">
|
<span class="flex justify-center gap-4 mt-2">
|
||||||
<a class="mx-2" href="https://git.thomaspcole.com/thomascole">
|
<a class="text-white" href="https://git.thomaspcole.com/thomascole">
|
||||||
<svg class="inline-block w-7 h-7 transition ease-in-out hover:scale-125" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
<svg class="w-5 h-5" id="main_outline" viewBox="0 0 640 550" xml:space="preserve" stroke="currentColor">
|
||||||
version="1.1" id="main_outline" viewBox="0 0 640 640" xml:space="preserve">
|
|
||||||
<g>
|
<g>
|
||||||
<path id="teabag" class="fill-none"
|
<path id="teabag" class="fill-none"
|
||||||
d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8 c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4 c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z" />
|
d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8 c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4 c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z" />
|
||||||
@ -16,22 +15,19 @@
|
|||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a class="mx-2" href="https://github.com/thomaspcole">
|
<a class="text-white" href="https://github.com/thomaspcole">
|
||||||
<svg class="inline-block w-6 h-6 fill-current transition ease-in-out hover:scale-125" viewBox="0 0 96 98" xmlns="http://www.w3.org/2000/svg">
|
<svg class="w-5 h-5" viewBox="0 0 98 96" fill="currentColor" stroke="currentColor" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" />
|
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a class="mx-2" href="https://www.linkedin.com/in/thomaspcole">
|
<a class="text-white" href="https://www.linkedin.com/in/thomaspcole">
|
||||||
<svg class="inline-block w-6 h-6 fill-current transition ease-in-out hover:scale-125" xmlns="http://www.w3.org/2000/svg"
|
<svg fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="0"
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 455 455"
|
class="w-5 h-5" viewBox="0 0 24 24">
|
||||||
style="enable-background:new 0 0 455 455;" xml:space="preserve">
|
<path stroke="none"
|
||||||
<g>
|
d="M16 8a6 6 0 016 6v7h-4v-7a2 2 0 00-2-2 2 2 0 00-2 2v7h-4v-7a6 6 0 016-6zM2 9h4v12H2z"></path>
|
||||||
<path style="fill-rule:evenodd;clip-rule:evenodd;"
|
<circle cx="4" cy="4" r="2" stroke="none"></circle>
|
||||||
d="M246.4,204.35v-0.665c-0.136,0.223-0.324,0.446-0.442,0.665H246.4z" />
|
|
||||||
<path style="fill-rule:evenodd;clip-rule:evenodd;"
|
|
||||||
d="M0,0v455h455V0H0z M141.522,378.002H74.016V174.906h67.506V378.002z M107.769,147.186h-0.446C84.678,147.186,70,131.585,70,112.085c0-19.928,15.107-35.087,38.211-35.087 c23.109,0,37.31,15.159,37.752,35.087C145.963,131.585,131.32,147.186,107.769,147.186z M385,378.002h-67.524V269.345 c0-27.291-9.756-45.92-34.195-45.92c-18.664,0-29.755,12.543-34.641,24.693c-1.776,4.34-2.24,10.373-2.24,16.459v113.426h-67.537 c0,0,0.905-184.043,0-203.096H246.4v28.779c8.973-13.807,24.986-33.547,60.856-33.547c44.437,0,77.744,29.02,77.744,91.398V378.002 z" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</span>
|
@ -9,11 +9,11 @@ const { title, subtitle } = Astro.props;
|
|||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div>
|
<div>
|
||||||
<span class="w-4 h-4 bg-primary block rounded-full mt-2"></span>
|
<span class="block w-4 h-4 mt-2 bg-white rounded-full"></span>
|
||||||
<span class="bg-primary block h-full w-[2px] translate-x-[7px]"></span>
|
<span class="bg-white block h-full w-[2px] translate-x-[7px]"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-5">
|
<div class="px-5">
|
||||||
<p class="font-semibold text-xl">{title}</p>
|
<p class="text-xl font-semibold">{title}</p>
|
||||||
<span class="text-sm font-light">{subtitle}</span>
|
<span class="text-sm font-light">{subtitle}</span>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
import Divider from "../components/misc/Divider.astro";
|
|
||||||
import Layout from "./Layout.astro";
|
|
||||||
|
|
||||||
const { frontmatter } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<Layout>
|
|
||||||
<main
|
|
||||||
class="w-1/2 sm:w-5/6 md:w-4/5 lg:w-4/5 xl:w-4/5 2xl:w-1/2 mx-auto mt-4 mb-6 flex flex-col justify-center">
|
|
||||||
<div class="relative mb-4">
|
|
||||||
<img class="aspect-video object-cover" src={frontmatter.image} alt=""/>
|
|
||||||
<a href={frontmatter.imageattr} class="absolute bottom-0 right-0 h-[24px]">
|
|
||||||
<span class="material-symbols-outlined opacity-25 mr-[4px]">link</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<p class="my-2 font-bold text-5xl">{frontmatter.title}</p>
|
|
||||||
<p>{frontmatter.date}</p>
|
|
||||||
<Divider />
|
|
||||||
<slot />
|
|
||||||
</main>
|
|
||||||
</Layout>
|
|
@ -1,19 +1,22 @@
|
|||||||
---
|
---
|
||||||
import Divider from "../components/misc/Divider.astro";
|
import Divider from "../components/misc/Divider.astro";
|
||||||
import Layout from "./Layout.astro";
|
import Layout from "./Layout.astro";
|
||||||
|
import Nav from "../components/nav/Nav.astro";
|
||||||
const { frontmatter } = Astro.props;
|
const { frontmatter } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<main class="w-5/6 sm:w-5/6 md:w-4/5 lg:w-4/5 xl:w-4/5 2xl:w-1/2 mx-auto mt-4 mb-6 flex flex-col justify-center">
|
<Nav/>
|
||||||
|
<main class="z-10 flex flex-col justify-center w-5/6 mx-auto mt-4 mb-6 sm:w-5/6 md:w-4/5 lg:w-4/5 xl:w-4/5 2xl:w-1/2 overflow-hidden bg-gray-500 rounded-lg sm:pt-0 rounded-xl bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-10">
|
||||||
<div class="relative mb-4">
|
<div class="relative mb-4">
|
||||||
<img class="aspect-video object-cover rounded-xl" src={frontmatter.image} alt="" />
|
<img class="object-cover aspect-video rounded-xl max-h-[50vh] w-full" src={frontmatter.image} alt=""
|
||||||
|
draggable="false" />
|
||||||
<a href={frontmatter.imageattr} class="absolute bottom-0 right-0 h-[24px]">
|
<a href={frontmatter.imageattr} class="absolute bottom-0 right-0 h-[24px]">
|
||||||
<span class="material-symbols-outlined opacity-25 mr-[4px]">link</span>
|
<span class="material-symbols-outlined opacity-25 mr-[4px]">link</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="my-2 font-bold text-5xl">{frontmatter.title}</p>
|
<p class="my-2 text-5xl px-2 font-bold text-white">{frontmatter.title}</p>
|
||||||
<p>{frontmatter.date}</p>
|
<p class="text-gray-200 px-2">{frontmatter.date}</p>
|
||||||
<Divider />
|
<Divider />
|
||||||
<!-- Render our markdown with the normal header styling and some extra padding. -->
|
<!-- Render our markdown with the normal header styling and some extra padding. -->
|
||||||
<style is:inline>
|
<style is:inline>
|
||||||
@ -22,40 +25,12 @@
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
.prose p>img{
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
p,
|
|
||||||
hr {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 3rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 2.25rem;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.875rem;
|
|
||||||
line-height: 2.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
<article id="article" class="max-w-full prose text-white px-2 pb-2">
|
||||||
<slot />
|
<slot />
|
||||||
|
</article>
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
@ -1,21 +1,22 @@
|
|||||||
---
|
---
|
||||||
|
import Nav from '../components/nav/Nav.astro';
|
||||||
|
import Footer from '../components/Footer.astro';
|
||||||
|
import Particles from '../components/Particles.astro';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
import Nav from '../components/nav/Nav.astro';
|
|
||||||
import Footer from '../components/Footer.astro';
|
|
||||||
|
|
||||||
const { title } = Astro.props;
|
const { title } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" class="light">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/icon-512.png" />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
<title>thomaspcole.com {title}</title>
|
<title>thomaspcole.com {title}</title>
|
||||||
|
|
||||||
@ -34,27 +35,25 @@
|
|||||||
'GRAD' 0,
|
'GRAD' 0,
|
||||||
'opsz' 48
|
'opsz' 48
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
<script is:inline>
|
.bg-gradient {
|
||||||
if (localStorage.getItem("theme") === null) {
|
background: hsla(205, 46%, 10%, 1);
|
||||||
document.documentElement.setAttribute("data-theme", "business");
|
background: linear-gradient(90deg, hsla(205, 46%, 10%, 1) 0%, hsla(191, 28%, 23%, 1) 50%, hsla(207, 41%, 27%, 1) 100%);
|
||||||
} else {
|
background: -moz-linear-gradient(90deg, hsla(205, 46%, 10%, 1) 0%, hsla(191, 28%, 23%, 1) 50%, hsla(207, 41%, 27%, 1) 100%);
|
||||||
document.documentElement.setAttribute("data-theme", localStorage.getItem("theme"));
|
background: -webkit-linear-gradient(90deg, hsla(205, 46%, 10%, 1) 0%, hsla(191, 28%, 23%, 1) 50%, hsla(207, 41%, 27%, 1) 100%);
|
||||||
|
filter: progid: DXImageTransform.Microsoft.gradient(startColorstr="#0e1c26", endColorstr="#2a454b", GradientType=1);
|
||||||
}
|
}
|
||||||
</script>
|
</style>
|
||||||
<script>
|
|
||||||
import { themeChange } from 'theme-change';
|
|
||||||
themeChange();
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="">
|
<body class="flex flex-col w-full h-full min-h-screen">
|
||||||
<Nav />
|
<div class="fixed top-0 left-0 w-full h-full min-h-screen bg-gradient"/>
|
||||||
<div class="ml-0 sm:ml-0 md:ml-0 lg:ml-72 xl:ml-72 2xl:ml-72 flex flex-col min-h-[calc(100vh-48px)] lg:min-h-screen xl:min-h-screen">
|
<Particles />
|
||||||
|
<!-- <Nav pageName={title} /> -->
|
||||||
<slot />
|
<slot />
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
12
src/pages/404.astro
Normal file
12
src/pages/404.astro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title=" | 404">
|
||||||
|
<main class="z-10 flex flex-col w-full text-center text-white mt-36 place-content-center">
|
||||||
|
<h1 class="text-5xl">Uh Oh!</h1>
|
||||||
|
<p class="text-2xl">Sorry about that I can't seem to find what you are looking for.</p>
|
||||||
|
<br>
|
||||||
|
<a href="/" class="underline">Go Home</a>
|
||||||
|
</main>
|
||||||
|
</Layout>
|
81
src/pages/about.astro
Normal file
81
src/pages/about.astro
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import Timeline from "../components/resume/Timeline.astro";
|
||||||
|
import Nav from '../components/nav/Nav.astro';
|
||||||
|
import Button from "../components/misc/Button.astro";
|
||||||
|
|
||||||
|
|
||||||
|
const resumeRaw = await fetch('https://git.thomaspcole.com/thomascole/Resume/raw/branch/master/resume.json');
|
||||||
|
const resume = await resumeRaw.json();
|
||||||
|
|
||||||
|
function parseDate(date: string) {
|
||||||
|
const retVal = new Date(date).toLocaleString('default', { month: 'long', year: "numeric" })
|
||||||
|
if (retVal === "Invalid Date") {
|
||||||
|
return "Present";
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={" | Resume"}>
|
||||||
|
<Nav/>
|
||||||
|
|
||||||
|
<style is:inline>
|
||||||
|
.time-line-container>div:last-child>div:nth-child(1)>span:nth-child(2) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<main class="z-10 w-full mx-auto text-white sm:w-5/6 md:w-4/5 lg:w-4/5 xl:w-4/5 2xl:w-1/2">
|
||||||
|
<section class="w-full h-full px-4 pt-2 bg-gray-500 sm:pt-0 rounded-xl bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-10">
|
||||||
|
<div class="container pb-6 mx-auto sm:pb-0">
|
||||||
|
<div class="flex flex-col-reverse items-start justify-between mx-auto sm:flex-row sm:items-center">
|
||||||
|
<p class="py-0 mr-auto text-2xl font-bold sm:py-6">About Me</p>
|
||||||
|
<div class="hidden sm:block">
|
||||||
|
<Button link="https://git.thomaspcole.com/attachments/9027185b-9841-4687-addd-0e5ba5eb0d1f" text="Download as PDF" icon="download"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>{resume.basics.summary}</p>
|
||||||
|
|
||||||
|
<div class="divider" />
|
||||||
|
<p class="pb-6 text-2xl font-bold">Work Experience</p>
|
||||||
|
<div class="time-line-container">
|
||||||
|
{resume.work.map((item:any)=>
|
||||||
|
<Timeline title={item.position} subtitle={item.name + " | " + parseDate(item.startDate) + " - " +
|
||||||
|
parseDate(item.endDate)}>
|
||||||
|
<ul class="text-sm ml-3.5 list-disc mt-2 mb-4">
|
||||||
|
{item.highlights.map((highlight:any)=>
|
||||||
|
<li>{highlight}</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</Timeline>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p class="py-6 text-2xl font-bold">Education</p>
|
||||||
|
<div class="time-line-container">
|
||||||
|
{resume.education.map((item:any)=>
|
||||||
|
<Timeline title={item.institution} subtitle={item.area + " | " + parseDate(item.endDate)} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p class="py-6 text-2xl font-bold">Professional Certificates</p>
|
||||||
|
<div class="time-line-container">
|
||||||
|
{resume.certificates.map((item:any)=>
|
||||||
|
<Timeline title={item.name} subtitle={item.issuer + " | " + parseDate(item.date)} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p class="py-6 text-2xl font-bold">Skills</p>
|
||||||
|
{resume.skills.map((item:any)=>
|
||||||
|
<div class="divider">{item.name}</div>
|
||||||
|
<div class="flex flex-wrap gap-2 pb-4 mx-auto my-2">
|
||||||
|
{item.keywords.map((skill:any)=>
|
||||||
|
<div
|
||||||
|
class="text-black bg-white border-gray-200 badge badge-lg">
|
||||||
|
{skill}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</Layout>
|
44
src/pages/contact.astro
Normal file
44
src/pages/contact.astro
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import Button from "../components/misc/Button.astro";
|
||||||
|
import Nav from "../components/nav/Nav.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={" | Contact"}>
|
||||||
|
<Nav/>
|
||||||
|
<section class="relative z-10 text-white">
|
||||||
|
<div class="container w-full h-full px-5 py-12 pt-0 mx-auto sm:pt-24 rounded-xl">
|
||||||
|
<div class="flex flex-col w-full text-center">
|
||||||
|
<h1 class="mb-4 text-2xl font-medium sm:text-3xl">Contact Me</h1>
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto lg:w-1/2 md:w-2/3">
|
||||||
|
<div class="flex flex-wrap -m-2">
|
||||||
|
<div class="w-1/2 p-2">
|
||||||
|
<div class="relative">
|
||||||
|
<label for="name" class="text-sm leading-7">Name</label>
|
||||||
|
<input type="text" id="name" name="name"
|
||||||
|
class="w-full px-3 py-1 text-base leading-6 text-white transition-colors duration-200 ease-in-out bg-gray-500 border border-white rounded outline-none resize-none backdrop-blur-md bg-opacity-10 focus:text-black focus:bg-white">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-1/2 p-2">
|
||||||
|
<div class="relative">
|
||||||
|
<label for="email" class="text-sm leading-7">Email</label>
|
||||||
|
<input type="email" id="email" name="email"
|
||||||
|
class="w-full px-3 py-1 text-base leading-6 text-white transition-colors duration-200 ease-in-out bg-gray-500 border border-white rounded outline-none resize-none backdrop-blur-md bg-opacity-10 focus:text-black focus:bg-white">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full p-2">
|
||||||
|
<div class="relative">
|
||||||
|
<label for="message" class="text-sm leading-7">Message</label>
|
||||||
|
<textarea id="message" name="message"
|
||||||
|
class="w-full h-32 px-3 py-1 text-base leading-6 text-white transition-colors duration-200 ease-in-out bg-gray-500 border border-white rounded outline-none resize-none backdrop-blur-md bg-opacity-10 focus:text-black focus:bg-white"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full p-2">
|
||||||
|
<Button text="Submit">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Layout>
|
@ -1,41 +1,22 @@
|
|||||||
---
|
---
|
||||||
import BlogProjectCard from "../components/misc/BlogProjectCard.astro";
|
|
||||||
import Divider from "../components/misc/Divider.astro";
|
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import Button from "../components/misc/Button.astro";
|
||||||
const projects = (await Astro.glob("./projects/**/*.{md,mdx}")).sort(
|
import SocialLinks from "../components/nav/SocialLinks.astro";
|
||||||
(a,b) =>
|
|
||||||
new Date(b.frontmatter.date).valueOf()-
|
|
||||||
new Date(a.frontmatter.date).valueOf()
|
|
||||||
).slice(0,5);
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<main class="flex flex-col m-6 mx-auto w-5/6 sm:w-5/6 md:w-4/5 lg:w-4/5 xl:w-4/5 2xl:w-1/2">
|
<main class="z-10">
|
||||||
<section>
|
<div class="flex flex-col w-[90%] mx-auto mt-24 text-center text-white lg:w-1/4">
|
||||||
<div class="flex flex-row">
|
<img class="object-cover w-40 mx-auto mb-2 rounded-full aspect-square drop-shadow-md" src="/img/profile.jpg" alt="profile_img">
|
||||||
<p class="text-5xl sm:text-5xl md:text-5xl lg:text-6xl py-1 font-extrabold text-transparent bg-clip-text bg-gradient-to-br from-[#1f89db] to-[#f42a8b]">Hello World!</p>
|
<h1 class="mb-2 text-3xl font-bold drop-shadow-md title-font sm:text-4xl">Thomas Cole</h1>
|
||||||
<!-- <p class="text-5xl ml-1 animate-wiggle hover:animate-wiggle-infinite">👋</p> -->
|
<p class="mb-8 font-thin drop-shadow-md">Web Developer, System Administrator,<br/> and Network Engineer</p>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<Button text="Projects" link="/projects/1" width="w-1/2"/>
|
||||||
|
<Button text="About" link="/about" width="w-1/2"/>
|
||||||
|
<Button text="Contact" link="/contact" width="w-1/2"/>
|
||||||
|
<SocialLinks/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-3xl font-semibold">I'm glad you are here!</p>
|
|
||||||
<p>
|
|
||||||
Feel free to stay as long as you like. There are cool projects to explore and more to come. Stay awesome and thanks for visiting!
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<Divider/>
|
|
||||||
<section>
|
|
||||||
<p class="text-2xl font-bold py-2 sm:text-2xl md:text-2xl lg:text-4xl xl:text-4xl 2xl:text-4xl">Latest Projects</p>
|
|
||||||
{
|
|
||||||
projects.map((post) => (
|
|
||||||
<BlogProjectCard
|
|
||||||
title={post.frontmatter.title}
|
|
||||||
subtitle={post.frontmatter.tagline}
|
|
||||||
image={post.frontmatter.image}
|
|
||||||
link={post.url}
|
|
||||||
/>
|
|
||||||
<Divider/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</section>
|
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
@ -1,36 +1,43 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../../layouts/Layout.astro";
|
import Layout from "../../layouts/Layout.astro";
|
||||||
import Divider from "../../components/misc/Divider.astro";
|
|
||||||
import BlogProjectCard from "../../components/misc/BlogProjectCard.astro";
|
import BlogProjectCard from "../../components/misc/BlogProjectCard.astro";
|
||||||
|
import Nav from "../../components/nav/Nav.astro";
|
||||||
|
|
||||||
export async function getStaticPaths({ paginate }: { paginate: any }) {
|
export async function getStaticPaths({ paginate }: { paginate: any }) {
|
||||||
const posts = (await Astro.glob("./**/*.{md,mdx}")).sort(
|
const posts = (await Astro.glob("./**/*.{md,mdx}")).sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
new Date(b.frontmatter.date).valueOf() -
|
new Date(b.frontmatter.date).valueOf() -
|
||||||
new Date(a.frontmatter.date).valueOf()
|
new Date(a.frontmatter.date).valueOf()
|
||||||
);
|
).filter((record)=> record.frontmatter.active == true);
|
||||||
|
|
||||||
return paginate(posts, { pageSize: 10 });
|
return paginate(posts, { pageSize: 6 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { page } = Astro.props;
|
const { page } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title=" | Blog">
|
<Layout title=" | Projects">
|
||||||
<main class="w-1/2 sm:w-5/6 md:w-4/5 lg:w-4/5 xl:w-4/5 2xl:w-1/2 mx-auto mt-4">
|
<Nav/>
|
||||||
<div class="flex flex-col">
|
<main class="z-10 w-full mx-auto mt-4 md:w-5/6 lg:w-4/5 xl:w-4/5">
|
||||||
|
<section class="text-white">
|
||||||
|
<p class="px-5 text-2xl font-bold drop-shadow-md sm:text-2xl md:text-2xl lg:text-4xl xl:text-4xl 2xl:text-4xl">Latest
|
||||||
|
Projects</p>
|
||||||
|
<div class="container px-5 py-6 mx-auto">
|
||||||
|
<div class="flex flex-wrap -m-4">
|
||||||
{
|
{
|
||||||
page.data.map((post: any) => (
|
page.data.map((post: any) => (
|
||||||
<BlogProjectCard title={post.frontmatter.title} subtitle={post.frontmatter.tagline}
|
<BlogProjectCard title={post.frontmatter.title} subtitle={post.frontmatter.tagline}
|
||||||
image={post.frontmatter.image} link={post.url}/>
|
image={post.frontmatter.image} link={post.url} category={post.frontmatter.category} date={post.frontmatter.date}/>
|
||||||
<Divider />
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/2 mx-auto flex justify-between">
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="flex justify-between w-1/2 mx-auto">
|
||||||
{
|
{
|
||||||
page.url.prev ? (
|
page.url.prev ? (
|
||||||
<a class="w-40 flex" href={page.url.prev}>
|
<a class="flex w-40" href={page.url.prev}>
|
||||||
<span class="material-symbols-outlined">
|
<span class="material-symbols-outlined">
|
||||||
chevron_left
|
chevron_left
|
||||||
</span>
|
</span>
|
||||||
@ -41,11 +48,11 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
<p class="grow text-center whitespace-nowrap">Page {page.currentPage}</p>
|
<p class="text-center grow whitespace-nowrap">Page {page.currentPage}</p>
|
||||||
{
|
{
|
||||||
page.url.next ? (
|
page.url.next ? (
|
||||||
<a class="w-40 flex" href={page.url.next}>
|
<a class="flex w-40" href={page.url.next}>
|
||||||
<p class="grow text-right">Next Page</p>
|
<p class="text-right grow">Next Page</p>
|
||||||
<span class="material-symbols-outlined">
|
<span class="material-symbols-outlined">
|
||||||
chevron_right
|
chevron_right
|
||||||
</span>
|
</span>
|
||||||
|
38
src/pages/projects/digital-signage.mdx
Normal file
38
src/pages/projects/digital-signage.mdx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
layout: "../../layouts/InteractiveDemoLayout.astro"
|
||||||
|
title: "Adventures in Digital Signage"
|
||||||
|
tagline: "Using slideshow.digital for cheap reliable signs"
|
||||||
|
date: "April 17, 2023"
|
||||||
|
category: "Software"
|
||||||
|
image: "https://images.unsplash.com/photo-1496442226666-8d4d0e62e6e9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80"
|
||||||
|
imageattr: "https://unsplash.com/photos/TaCk3NspYe0"
|
||||||
|
active: false
|
||||||
|
---
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
A recent project at work gave me the opportunity to explore the fascinating world of digital signage. I was tasked with making use of a few tvs that we had laying around to display the current events and advertisements for our organization.
|
||||||
|
|
||||||
|
After a quick google search I found no shortage of off the shelf solutions. However, I couldn't find a product that really suited our needs and that was reasonably priced. So, being a tinkerer and not having any form of budget to purchase an off the shelf solution I decided to take a stab at creating my own signage solution.
|
||||||
|
|
||||||
|
### What didn't work
|
||||||
|
|
||||||
|
My first attempt to tackle this was to create a web first approach. I thought if the web server did all the heavy lifting that it would be trivial to attach any old device that could display a web page to a tv and be done. For the most part this worked but it had some drawbacks, the biggest among them being the signage could not operate without an internet connection. I also had issues with the Raspberry pi 4 and gpu acceleration, so displaying a large collection of pictures was a bit choppy at times (*To be fair that might be my fault*).
|
||||||
|
|
||||||
|
The second attempt was to have each sign rsync with a master folder of images and display them locally using a python script. This was functional but not visually appealing. We can do better.
|
||||||
|
|
||||||
|
### Enter Slideshow
|
||||||
|
|
||||||
|
I don't remember how I managed to stumble across Slideshow but I sure am glad that I found it.
|
||||||
|
|
||||||
|
[Slideshow](https://slideshow.digital/) bills itself as a free digital signage app for android devices. AND. IT. DELIVERS.
|
||||||
|
|
||||||
|
The app is configured through a web interface that runs locally on device. It has a simple yet powerful screen builder and allows for content to be synced from remote sources.
|
||||||
|
|
||||||
|
The initial setup is a bit cumbersome but after getting everything configured on a test device the built in backup and restore feature allows for easy export of a configuration file that can be dropped onto a new device. Thanks to the built in file sync server, it is able to drop into our existing workflow for marketing content. Our marketing team does not have to do any additional work to make sure the advertisements make it to the signage.
|
||||||
|
|
||||||
|
I chose to pair slideshow with a new (but extremely cheap) [Chromecast with Google TV](https://store.google.com/us/product/chromecast_google_tv?hl=en-US) and it works flawlessly. The worst part of the setup is the rather forceful insertion of ads onto the Chromecast home screen. Putting the device in app only mode mostly resolved that issue though and makes it simple enough to restart slideshow if the chromecast reboots for any reason.
|
||||||
|
|
||||||
|
The app does offer the ability to restart itself after a device reboot however I personally have not gotten that working yet. I don't think it is the apps fault; I have a feeling the Chromecast does not like having its launcher overridden.
|
||||||
|
|
||||||
|
Slideshow has way more features than we are using in our setup but it scratches that itch that other solutions could not and the end product is a very clean and professional looking sign.
|
@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
layout: "../../layouts/InteractiveDemoLayout.astro"
|
|
||||||
title: "Pedal-pi"
|
|
||||||
tagline: "A custom effects processor using MODEP"
|
|
||||||
date: "February 10, 2023"
|
|
||||||
image: "https://images.unsplash.com/photo-1511203438670-49f8ea8441c6?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80"
|
|
||||||
imageattr: "https://unsplash.com/photos/lEwyj4FiHHU"
|
|
||||||
---
|
|
14
src/pages/projects/pedalpi/audio.svelte
Normal file
14
src/pages/projects/pedalpi/audio.svelte
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script>
|
||||||
|
import silence from "./silence.mp3";
|
||||||
|
export let title = "Title me";
|
||||||
|
export let file = silence;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-full flex my-4">
|
||||||
|
<p class="my-auto mr-4 w-1/6 text-center font-bold">{title}</p>
|
||||||
|
<audio controls preload="metadata" style="width: 100% !important">
|
||||||
|
<source src={file} type="audio/mpeg" />
|
||||||
|
Audio not supported
|
||||||
|
</audio>
|
||||||
|
<div class="w-1/6"/>
|
||||||
|
</div>
|
47
src/pages/projects/pedalpi/pedal-pi.mdx
Normal file
47
src/pages/projects/pedalpi/pedal-pi.mdx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
layout: "../../../layouts/InteractiveDemoLayout.astro"
|
||||||
|
title: "Pedal-pi"
|
||||||
|
tagline: "A bass guitar custom effects processor using MODEP"
|
||||||
|
date: "May 15, 2023"
|
||||||
|
category: "Hardware"
|
||||||
|
image: "https://images.unsplash.com/photo-1511203438670-49f8ea8441c6?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80"
|
||||||
|
imageattr: "https://unsplash.com/photos/lEwyj4FiHHU"
|
||||||
|
active: false
|
||||||
|
---
|
||||||
|
|
||||||
|
import Audio from './audio.svelte';
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
For once I have a post about something other than a project that was put on my desk at work. I have the opportunity to play bass guitar with a local rock band. Often when I am playing with them I can't help but notice the dizzying array of pedals the guitar players have at their disposal and how drastically they can change the sound.
|
||||||
|
|
||||||
|
While bass guitar pedals do exist, they are often hundreds of dollars a piece and while I enjoy playing, I would rather spend that kind of money elsewhere. So I wondered, is there a way I can have the functionality of a massive pedalboard without actually breaking the bank? Enter MODEP.
|
||||||
|
|
||||||
|
### What is MODEP
|
||||||
|
|
||||||
|
[MODEP](https://blokas.io/modep/) is a virtual guitar effects pedalboard powered by the Raspberry Pi. It's user interface is built on top of the work done by the team over at [MOD](https://mod.audio/) and is powered by RaspberryPi OS and Jack audio under the hood.
|
||||||
|
|
||||||
|
### Building a pedalboard
|
||||||
|
|
||||||
|
[Blokas](https://blokas.io/), the team behind MODEP, have a official hat for the Raspberry Pi called the [PiSound](https://blokas.io/pisound/) if you are looking for a compact all in one solution to run MODEP, but I decided to build my own. Why? Because it's more fun that way.
|
||||||
|
|
||||||
|
I already had a pedalboard and a spare Raspberry Pi so the biggest piece I needed was a audio interface. The parts list is below.
|
||||||
|
|
||||||
|
- Pedaltrain Nano+
|
||||||
|
- Korg Pitchblack Advance
|
||||||
|
- Raspberry Pi 4 4gb model
|
||||||
|
- Behringer U-Phoria UM2
|
||||||
|
- Amazon Basics 1/4in patch cable.
|
||||||
|
|
||||||
|
If you are ok using 2 power bricks you can stop with the list above. One 5v brick to power the Pi and a 9v brick for any pedals. I wanted a more all in one solution so I also got a beefy 12v power brick and used a few voltage step down converters to power everything from one plug. However, that is totally optional.
|
||||||
|
### Pictures
|
||||||
|
|
||||||
|
### Demo
|
||||||
|
|
||||||
|
{/* Import audio file before passing it to element */}
|
||||||
|
<Audio title="Test" client:idle/>
|
||||||
|
<Audio title="Test 2" client:idle/>
|
||||||
|
|
||||||
|
### Conclusion
|
||||||
|
|
||||||
|
Overall I'm very pleased with how this project came together. It doesn't make me a better player but at least I can now make cool sounds. 🙃
|
BIN
src/pages/projects/pedalpi/silence.mp3
Normal file
BIN
src/pages/projects/pedalpi/silence.mp3
Normal file
Binary file not shown.
@ -3,14 +3,15 @@ layout: "../../../layouts/InteractiveDemoLayout.astro"
|
|||||||
title: "Quicksort Demo"
|
title: "Quicksort Demo"
|
||||||
tagline: "A simple visualization of the quicksort algorithm using p5.js"
|
tagline: "A simple visualization of the quicksort algorithm using p5.js"
|
||||||
date: "December 23, 2022"
|
date: "December 23, 2022"
|
||||||
|
category: "Demo"
|
||||||
image: "https://images.unsplash.com/photo-1669399213378-2853e748f217?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1632&q=80"
|
image: "https://images.unsplash.com/photo-1669399213378-2853e748f217?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1632&q=80"
|
||||||
imageattr: "https://unsplash.com/photos/5dgXQJ7ezuU"
|
imageattr: "https://unsplash.com/photos/5dgXQJ7ezuU"
|
||||||
|
active: true
|
||||||
---
|
---
|
||||||
import Quicksort from './quicksort.svelte';
|
import Quicksort from './quicksort.svelte';
|
||||||
|
|
||||||
<Quicksort client:load/>
|
<Quicksort client:load/>
|
||||||
---
|
---
|
||||||
|
|
||||||
This demo was originally written as part of a job application to work for the IT department at my university. It was the first project I wrote with the aid of the p5.js library.
|
This demo was originally written as part of a job application to work for the IT department at my university. It was the first project I wrote with the aid of the p5.js library.
|
||||||
I used this demo as a way to learn how to visualize the quicksort algorithm.\
|
I used this demo as a way to learn how to visualize the quicksort algorithm.\
|
||||||
\
|
\
|
||||||
|
@ -23,17 +23,17 @@
|
|||||||
sketch = (p5) => {
|
sketch = (p5) => {
|
||||||
p5.setup = () => {
|
p5.setup = () => {
|
||||||
for (let i = 0; i < valuesToGenerate; i++) {
|
for (let i = 0; i < valuesToGenerate; i++) {
|
||||||
data[i]=(i%700);
|
data[i] = i % 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
p5.shuffle(data, true)
|
p5.shuffle(data, true);
|
||||||
quicksort(data, 0, data.length - 1);
|
quicksort(data, 0, data.length - 1);
|
||||||
animArray.push(new arrayFrame(data.toString(), null, null, null));
|
animArray.push(new arrayFrame(data.toString(), null, null, null));
|
||||||
|
|
||||||
//setup the canvas
|
//setup the canvas
|
||||||
p5.createCanvas(sketchWidth,400);
|
p5.createCanvas(sketchWidth, 250);
|
||||||
p5.frameRate(30);
|
p5.frameRate(30);
|
||||||
}
|
};
|
||||||
|
|
||||||
p5.draw = () => {
|
p5.draw = () => {
|
||||||
if (playpause) {
|
if (playpause) {
|
||||||
@ -43,30 +43,27 @@
|
|||||||
drawFrame(frame);
|
drawFrame(frame);
|
||||||
frame++;
|
frame++;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
drawFrame(frame);
|
drawFrame(frame);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function drawFrame(index) {
|
function drawFrame(index) {
|
||||||
let dataFrame = animArray[index];
|
let dataFrame = animArray[index];
|
||||||
p5.background(28);
|
p5.clear();
|
||||||
for (var i = 0; i < valuesToGenerate; i++) {
|
for (var i = 0; i < valuesToGenerate; i++) {
|
||||||
p5.fill(255)
|
p5.fill(255);
|
||||||
if (dataFrame.midIndex == i) {
|
if (dataFrame.midIndex == i) {
|
||||||
p5.fill(255, 0, 0);
|
p5.fill(255, 0, 0);
|
||||||
}
|
}
|
||||||
if (dataFrame.lowVal == i || dataFrame.highVal == i) {
|
if (dataFrame.lowVal == i || dataFrame.highVal == i) {
|
||||||
p5.fill(0, 255, 0);
|
p5.fill(0, 255, 0);
|
||||||
}
|
}
|
||||||
p5.rect(barWidth*i+10,350, barWidth,-dataFrame.getArray()[i]);
|
p5.rect(barWidth * i + 10, 230, barWidth, -dataFrame.getArray()[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copied directly from original code
|
* Copied directly from original code
|
||||||
@ -82,7 +79,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Pick a pivot point in the middle of the passed array.
|
//Pick a pivot point in the middle of the passed array.
|
||||||
let mid = Math.floor(low + ((high-low) / 2));
|
let mid = Math.floor(low + (high - low) / 2);
|
||||||
let pivot = array[mid];
|
let pivot = array[mid];
|
||||||
|
|
||||||
let l = low;
|
let l = low;
|
||||||
@ -135,7 +132,7 @@
|
|||||||
|
|
||||||
this.getArray = function () {
|
this.getArray = function () {
|
||||||
return this.arrayState.split(",");
|
return this.arrayState.split(",");
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function prevFrame() {
|
function prevFrame() {
|
||||||
@ -157,39 +154,33 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={parentContainer} class="w-full">
|
<div bind:this={parentContainer} class="w-[90%] mx-auto rounded-xl border border-white">
|
||||||
<P5 {sketch} />
|
<P5 {sketch} />
|
||||||
</div>
|
|
||||||
<div class="flex justify-evenly mt-6 mb-4">
|
<div class="flex items-center justify-center gap-8 rounded-xl pb-4">
|
||||||
<button on:click={prevFrame}>
|
<button on:click={prevFrame} class="h-[24px]">
|
||||||
<span class="material-symbols-outlined">
|
<span class="material-symbols-outlined">skip_previous</span>
|
||||||
skip_previous
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
<button on:click={()=>{
|
<button
|
||||||
|
on:click={() => {
|
||||||
if (frame == animArray.length - 1) {
|
if (frame == animArray.length - 1) {
|
||||||
reset();
|
reset();
|
||||||
} else {
|
} else {
|
||||||
playpause = !playpause;
|
playpause = !playpause;
|
||||||
}
|
}
|
||||||
}}>
|
}}
|
||||||
|
class="h-[24px]"
|
||||||
|
>
|
||||||
{#if playpause}
|
{#if playpause}
|
||||||
<span class="material-symbols-outlined">
|
<span class="material-symbols-outlined">pause</span>
|
||||||
pause
|
|
||||||
</span>
|
|
||||||
{:else if frame == animArray.length - 1}
|
{:else if frame == animArray.length - 1}
|
||||||
<span class="material-symbols-outlined">
|
<span class="material-symbols-outlined">restart_alt</span>
|
||||||
restart_alt
|
|
||||||
</span>
|
|
||||||
{:else}
|
{:else}
|
||||||
<span class="material-symbols-outlined">
|
<span class="material-symbols-outlined">play_arrow</span>
|
||||||
play_arrow
|
|
||||||
</span>
|
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
<button on:click={nextFrame}>
|
<button on:click={nextFrame} class="h-[24px]">
|
||||||
<span class="material-symbols-outlined">
|
<span class="material-symbols-outlined">skip_next</span>
|
||||||
skip_next
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
10
src/pages/projects/resume-json.mdx
Normal file
10
src/pages/projects/resume-json.mdx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
layout: "../../layouts/InteractiveDemoLayout.astro"
|
||||||
|
title: "Building a Better Resume"
|
||||||
|
tagline: "Using JSON Resume to make a resume"
|
||||||
|
date: "August 06, 2023"
|
||||||
|
category: "Software"
|
||||||
|
image: "https://images.unsplash.com/photo-1602407294553-6ac9170b3ed0?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1548&q=80"
|
||||||
|
imageattr: "https://unsplash.com/photos/4YzrcDNcRVg"
|
||||||
|
active: false
|
||||||
|
---
|
@ -3,8 +3,10 @@ layout: "../../../layouts/InteractiveDemoLayout.astro"
|
|||||||
title: "Visual Sitemap"
|
title: "Visual Sitemap"
|
||||||
tagline: "Using Cytoscape.js to visualize all the pages of a website"
|
tagline: "Using Cytoscape.js to visualize all the pages of a website"
|
||||||
date: "January 6, 2023"
|
date: "January 6, 2023"
|
||||||
|
category: "Demo"
|
||||||
image: "https://images.unsplash.com/photo-1639322537228-f710d846310a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1632&q=80"
|
image: "https://images.unsplash.com/photo-1639322537228-f710d846310a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1632&q=80"
|
||||||
imageattr: "https://unsplash.com/photos/T9rKvI3N0NM"
|
imageattr: "https://unsplash.com/photos/T9rKvI3N0NM"
|
||||||
|
active: true
|
||||||
---
|
---
|
||||||
import Sitemapgraph from './sitemapgraph.svelte'
|
import Sitemapgraph from './sitemapgraph.svelte'
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
'text-valign' : 'center',
|
'text-valign' : 'center',
|
||||||
'shape': 'round-rectangle',
|
'shape': 'round-rectangle',
|
||||||
'width': '500px',
|
'width': '500px',
|
||||||
'height': '50px',
|
'height': '100px',
|
||||||
'background-color' : '#F8F9FA',
|
'background-color' : '#F8F9FA',
|
||||||
'border-color': '#000',
|
'border-color': '#000',
|
||||||
},
|
},
|
||||||
@ -60,11 +60,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
userZoomingEnabled: false,
|
||||||
|
userPanningEnabled: false,
|
||||||
layout: {
|
layout: {
|
||||||
name: "breadthfirst",
|
name: "breadthfirst",
|
||||||
fit: true, // whether to fit the viewport to the graph
|
fit: true, // whether to fit the viewport to the graph
|
||||||
directed: true, // whether the tree is directed downwards (or edges can point in any direction if false)
|
directed: true, // whether the tree is directed downwards (or edges can point in any direction if false)
|
||||||
padding: 10, // padding on fit
|
padding: 5, // padding on fit
|
||||||
circle: false, // put depths in concentric circles if true, put depths top down if false
|
circle: false, // put depths in concentric circles if true, put depths top down if false
|
||||||
grid: false, // whether to create an even grid into which the DAG is placed (circle:false only)
|
grid: false, // whether to create an even grid into which the DAG is placed (circle:false only)
|
||||||
spacingFactor: 1, // positive spacing factor, larger => more space between nodes (N.B. n/a if causes overlap)
|
spacingFactor: 1, // positive spacing factor, larger => more space between nodes (N.B. n/a if causes overlap)
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
---
|
|
||||||
import Layout from "../layouts/Layout.astro";
|
|
||||||
import Timeline from "../components/resume/Timeline.astro";
|
|
||||||
import Divider from "../components/misc/Divider.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<Layout title={" | Resume"}>
|
|
||||||
|
|
||||||
<style is:inline>
|
|
||||||
.time-line-container > div:last-child > div:nth-child(1) > span:nth-child(2){
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<main class="w-5/6 mx-auto sm:w-5/6 md:w-4/5 lg:w-4/5 xl:w-4/5 2xl:w-1/2">
|
|
||||||
<p class="font-bold text-2xl py-6">About Me</p>
|
|
||||||
<p>Detail oriented IT professional with 5+ years in systems and network administration.
|
|
||||||
Excellent problem-solving skills and ability to perform well in a team.
|
|
||||||
Responsible for operation and maintenance of a multicampus enterprise network with 500+ average daily users.
|
|
||||||
Demonstrated experience in reducing operating expenses by implementing open-source solutions and services.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<Divider/>
|
|
||||||
<p class="font-bold text-2xl py-6">Work Experience</p>
|
|
||||||
<div class="time-line-container">
|
|
||||||
<Timeline title="Director Of Information Technology" subtitle="Christ Lutheran Church | December 2020 - Present">
|
|
||||||
<ul class="text-sm ml-3.5 list-disc mt-2 mb-4">
|
|
||||||
<li>Maintain and upgrade critical network infrascture for a multi-campus environment.</li>
|
|
||||||
<li>Facilitate backups and ensure their integrity.</li>
|
|
||||||
<li>Integrate with action teams to develop technology plans and solutions.</li>
|
|
||||||
<li>Implement new software and hardware solutions with increased functionality while reducing costs by $50,000/yr.</li>
|
|
||||||
</ul>
|
|
||||||
</Timeline>
|
|
||||||
<Timeline title="Tech Assosciate" subtitle="Christ Lutheran Church | August 2019 - December 2020 (Part Time)">
|
|
||||||
<ul class="text-sm ml-3 5 list-disc mt-2 mb-4">
|
|
||||||
<li>Develop and implement new strategies in collaboration with the Christ Providence Tech team to improve live stream services and reach a broader audience.</li>
|
|
||||||
<li>Facilitated building and installation of new computer systems to improve recording and streaming capabilities of worship services.</li>
|
|
||||||
<li>Run graphics for in house worship and live stream services.</li>
|
|
||||||
</ul>
|
|
||||||
</Timeline>
|
|
||||||
<Timeline title="Tier II Managed Services Technician " subtitle="ScanOnline | June 2019 - December 2020">
|
|
||||||
<ul class="text-sm ml-3 5 list-disc mt-2 mb-4">
|
|
||||||
<li>Administer Office 365 and Windows Active Directory infrastructure.</li>
|
|
||||||
<li>Manage company VOIP phone system and extension listings.</li>
|
|
||||||
<li>Deploy and configure virtual machines to align with business needs.</li>
|
|
||||||
<li>Develop new Android applications to suit the business needs of customers operating in the logistics industry.</li>
|
|
||||||
<li>Maintain legacy Windows Mobile applications for existing customers.</li>
|
|
||||||
<li>Preform configuration and maintenance of customer hardware and software.</li>
|
|
||||||
</ul>
|
|
||||||
</Timeline>
|
|
||||||
<Timeline title="Student Network Analyst" subtitle="University of North Carolina at Greensboro | February 2018 - May 2019">
|
|
||||||
<ul class="text-sm ml-3 5 list-disc mt-2 mb-4">
|
|
||||||
<li>Assisted in maintenance and troubleshooting of enterprise network systems.</li>
|
|
||||||
<li>Preformed on-boarding of new network devices at the physical level.</li>
|
|
||||||
</ul>
|
|
||||||
</Timeline>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="font-bold text-2xl py-6">Education</p>
|
|
||||||
<div class="time-line-container">
|
|
||||||
<Timeline title="The University of North Carolina at Greensboro" subtitle="Bachelor Of Science Information Systems and Supply Chain Management, Computer Science Minor"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="font-bold text-2xl py-6">Professional Certificates</p>
|
|
||||||
<div class="time-line-container">
|
|
||||||
<Timeline title="Dante Level 3" subtitle="Audianate - 2021"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="font-bold text-2xl py-6">Skills</p>
|
|
||||||
<ul class="list-disc sm:columns-2 md:columns-2 lg:columns-5 columns-2 mx-auto">
|
|
||||||
<li>HTML</li>
|
|
||||||
<li>CSS</li>
|
|
||||||
<li>JavaScript</li>
|
|
||||||
<li>Astro</li>
|
|
||||||
<li>Svelte</li>
|
|
||||||
<li>Java</li>
|
|
||||||
<li>C#</li>
|
|
||||||
<li>Tailwind</li>
|
|
||||||
<li>Python</li>
|
|
||||||
<li>MySQL</li>
|
|
||||||
<li>Linux</li>
|
|
||||||
<li>Bash</li>
|
|
||||||
<li>Docker</li>
|
|
||||||
<li>Mosyle MDM</li>
|
|
||||||
<li>Windows Server</li>
|
|
||||||
<li>Active Directory</li>
|
|
||||||
<li>Cisco IOS</li>
|
|
||||||
<li>Ubiquiti Unifi</li>
|
|
||||||
<li>3CX</li>
|
|
||||||
</ul>
|
|
||||||
<Divider/>
|
|
||||||
</main>
|
|
||||||
</Layout>
|
|
7
src/pages/submitContact.js
Normal file
7
src/pages/submitContact.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export async function GET({ params, request }) {
|
||||||
|
const response = await fetch("https://docs.astro.build/assets/full-logo-light.png");
|
||||||
|
const buffer = Buffer.from(await response.arrayBuffer());
|
||||||
|
return new Response(buffer, {
|
||||||
|
headers: { "Content-Type": "image/png" },
|
||||||
|
});
|
||||||
|
}
|
5
svelte.config.js
Normal file
5
svelte.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { vitePreprocess } from '@astrojs/svelte';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
preprocess: vitePreprocess(),
|
||||||
|
};
|
@ -1,34 +1,42 @@
|
|||||||
|
const defaultTheme = require('tailwindcss/defaultTheme');
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
||||||
darkMode: ["class", "[data-theme]=business"],
|
darkMode: 'class',
|
||||||
theme: {
|
theme: {
|
||||||
fontFamily: {
|
|
||||||
'sans': ['Open Sans', 'ui-sans-serif, system-ui', '-apple-system', 'BlinkMacSystemFont', "Segoe UI", 'Roboto', "Helvetica Neue", 'Arial', "Noto Sans", 'sans-serif', "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"],
|
|
||||||
'serif': ['ui-serif', 'Georgia', 'Cambria', "Times New Roman", 'Times', 'serif'],
|
|
||||||
'mono': ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', "Liberation Mono", "Courier New", 'monospace'],
|
|
||||||
'code': [],
|
|
||||||
},
|
|
||||||
extend: {
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: '#294861',
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
'sans': ['Open Sans', defaultTheme.fontFamily.sans],
|
||||||
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
wiggle: {
|
wiggle: {
|
||||||
'0%, 100%': { transform: 'rotate(-5deg)' },
|
'0%, 100%': { transform: 'rotate(-5deg)' },
|
||||||
'50%': { transform: 'rotate(5deg)' },
|
'50%': { transform: 'rotate(5deg)' },
|
||||||
},
|
},
|
||||||
'bg-animate':{
|
text: {
|
||||||
'0%,100%': {'background-position': '25% 50%'},
|
'0%, 100%': {
|
||||||
'50%': {'background-position': '75% 50%'},
|
'background-size': '200% 200%',
|
||||||
}
|
'background-position': 'left center',
|
||||||
|
},
|
||||||
|
'50%': {
|
||||||
|
'background-size': '200% 200%',
|
||||||
|
'background-position': 'right center',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
'wiggle-infinite': 'wiggle 1s ease-in-out infinite',
|
'wiggle-infinite': 'wiggle 1s ease-in-out infinite',
|
||||||
wiggle: 'wiggle 1s ease-in-out',
|
wiggle: 'wiggle 1s ease-in-out',
|
||||||
'bg-animate': 'bg-animate 5s ease-in-out infinite',
|
'text': 'text 15s ease infinite'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("daisyui")],
|
plugins: [
|
||||||
daisyui: {
|
require("daisyui"),
|
||||||
themes: ["corporate", "business"],
|
require('@tailwindcss/typography')
|
||||||
},
|
],
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user