Merge pull request 'Merge Next into Master' (#1) from next into master
Reviewed-on: #1
This commit is contained in:
commit
a1dfdfb8c5
47
.drone.yml
47
.drone.yml
@ -1,47 +0,0 @@
|
||||
kind: pipeline
|
||||
name: buildsite
|
||||
|
||||
steps:
|
||||
- name: restore_cache
|
||||
image: appleboy/drone-sftp-cache
|
||||
settings:
|
||||
server: 192.168.10.5
|
||||
port: 22
|
||||
username: thomas
|
||||
password:
|
||||
from_secret: ssh_password
|
||||
path: /tmp/drone
|
||||
restore: true
|
||||
mount:
|
||||
- node_modules
|
||||
|
||||
- name: build
|
||||
image: node
|
||||
commands:
|
||||
- npm install
|
||||
- npm run build
|
||||
|
||||
- name: rebuild_cache
|
||||
image: appleboy/drone-sftp-cache
|
||||
settings:
|
||||
server: 192.168.10.5
|
||||
port: 22
|
||||
username: thomas
|
||||
password:
|
||||
from_secret: ssh_password
|
||||
path: /tmp/drone
|
||||
rebuild: true
|
||||
mount:
|
||||
- node_modules
|
||||
|
||||
- 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
|
7
.gitea/workflows/build.yaml
Normal file
7
.gitea/workflows/build.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
name: Build Site
|
||||
run-name: ${{ github.actor }} is building Personal Site
|
||||
jobs:
|
||||
Build-Site:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "Starting build job"
|
123
.gitignore
vendored
123
.gitignore
vendored
@ -1,119 +1,22 @@
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
# build output
|
||||
dist/
|
||||
.output/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
# environment variables
|
||||
.env
|
||||
.env.test
|
||||
.env.production
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
package-lock.json
|
4
.vscode/extensions.json
vendored
Normal file
4
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
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:
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
# Personal-Site
|
||||
|
||||
Repo for my personal website. Updates to README on the way.
|
@ -1,9 +1,15 @@
|
||||
import { defineConfig } from 'astro/config'
|
||||
import svelte from '@astrojs/svelte';
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
|
||||
// https://astro.build/config
|
||||
import svelte from "@astrojs/svelte";
|
||||
|
||||
// https://astro.build/config
|
||||
import mdx from "@astrojs/mdx";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [
|
||||
svelte(),
|
||||
],
|
||||
|
||||
integrations: [tailwind(), svelte(), mdx()]
|
||||
});
|
45
package.json
45
package.json
@ -1,34 +1,25 @@
|
||||
{
|
||||
"name": "personal-site",
|
||||
"version": "1.0.0",
|
||||
"description": "Repo for my personal website",
|
||||
"main": "index.js",
|
||||
"name": "@example/basics",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build"
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.thomaspcole.com/thomascole/Personal-Site.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@astrojs/renderer-svelte": "^0.1.1",
|
||||
"@astrojs/svelte": "^0.1.4",
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
"@mdi/font": "^5.9.55",
|
||||
"@mdi/js": "^5.9.55",
|
||||
"astro": "^1.0.0-beta.38",
|
||||
"bootstrap-icons": "^1.5.0",
|
||||
"bulma": "^0.9.3",
|
||||
"bulmaswatch": "^0.8.1",
|
||||
"sass": "1.52.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-react": "^0.1.5",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"@astrojs/mdx": "^0.13.0",
|
||||
"@astrojs/svelte": "^1.0.2",
|
||||
"@astrojs/tailwind": "^2.1.3",
|
||||
"astro": "^1.6.15",
|
||||
"cytoscape": "^3.23.0",
|
||||
"daisyui": "^2.45.0",
|
||||
"p5-svelte": "^3.1.2",
|
||||
"svelte": "^3.55.0",
|
||||
"tailwindcss": "^3.3.1",
|
||||
"theme-change": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
13
public/favicon.svg
Normal file
13
public/favicon.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 36 36">
|
||||
<path fill="#000" d="M22.25 4h-8.5a1 1 0 0 0-.96.73l-5.54 19.4a.5.5 0 0 0 .62.62l5.05-1.44a2 2 0 0 0 1.38-1.4l3.22-11.66a.5.5 0 0 1 .96 0l3.22 11.67a2 2 0 0 0 1.38 1.39l5.05 1.44a.5.5 0 0 0 .62-.62l-5.54-19.4a1 1 0 0 0-.96-.73Z"/>
|
||||
<path fill="url(#gradient)" d="M18 28a7.63 7.63 0 0 1-5-2c-1.4 2.1-.35 4.35.6 5.55.14.17.41.07.47-.15.44-1.8 2.93-1.22 2.93.6 0 2.28.87 3.4 1.72 3.81.34.16.59-.2.49-.56-.31-1.05-.29-2.46 1.29-3.25 3-1.5 3.17-4.83 2.5-6-.67.67-2.6 2-5 2Z"/>
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="16" x2="16" y1="32" y2="24" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#000"/>
|
||||
<stop offset="1" stop-color="#000" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<style>
|
||||
@media (prefers-color-scheme:dark){:root{filter:invert(100%)}}
|
||||
</style>
|
||||
</svg>
|
After Width: | Height: | Size: 873 B |
Binary file not shown.
Before Width: | Height: | Size: 37 KiB |
Binary file not shown.
Before Width: | Height: | Size: 38 KiB |
Binary file not shown.
Before Width: | Height: | Size: 176 KiB |
5
public/js/fa-all.min.js
vendored
5
public/js/fa-all.min.js
vendored
File diff suppressed because one or more lines are too long
9
public/js/particles.min.js
vendored
9
public/js/particles.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,102 +0,0 @@
|
||||
{
|
||||
"particles": {
|
||||
"number": {
|
||||
"value": 80,
|
||||
"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,2 +0,0 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
@ -1,8 +0,0 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
@ -1,20 +1,3 @@
|
||||
<style lang="scss">
|
||||
@import 'bulmaswatch/darkly/_variables.scss';
|
||||
@import 'bulma/bulma.sass';
|
||||
@import 'bulmaswatch/darkly/_overrides.scss';
|
||||
|
||||
.footer{
|
||||
padding: 1em !important;
|
||||
max-height: 10vh;
|
||||
background-color: #00000000;
|
||||
font-size: .7em !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="content has-text-centered">
|
||||
<p>Copyright © 2022 Thomas Cole. All source code is licensed
|
||||
<a href="http://opensource.org/licenses/mit-license.php">MIT</a>.
|
||||
</p>
|
||||
</div>
|
||||
<footer class="p-4">
|
||||
<p class="text-center text-sm">© 2023 Thomas Cole.</p>
|
||||
</footer>
|
@ -1,46 +0,0 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<style lang="scss">
|
||||
@import 'bulmaswatch/darkly/_variables.scss';
|
||||
@import 'bulma/bulma.sass';
|
||||
@import 'bulmaswatch/darkly/_overrides.scss';
|
||||
|
||||
.content{
|
||||
height: 80%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hello{
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.name-plate{
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
font-weight: bold;
|
||||
font-size: 4em !important;
|
||||
}
|
||||
|
||||
.tag-line-element{
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
font-size: 1.15em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="content">
|
||||
<h2 class="hello">Hello, World.</h2>
|
||||
<h1 class="name-plate has-text-link">I'm Thomas Cole</h1>
|
||||
<div class="tag-line">
|
||||
<span class="tag-line-element">Developer,</span>
|
||||
<span class="tag-line-element">Linux Enthusiast,</span>
|
||||
<span class="tag-line-element">System Administrator</span>
|
||||
</div>
|
||||
</div>
|
@ -1,85 +0,0 @@
|
||||
<style lang="scss">
|
||||
@import 'bulmaswatch/darkly/_variables.scss';
|
||||
@import 'bulma/bulma.sass';
|
||||
@import 'bulmaswatch/darkly/_overrides.scss';
|
||||
</style>
|
||||
|
||||
<nav class="navbar is-transparent" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<span class="navbar-item brand-text">
|
||||
thomaspcole.com
|
||||
</span>
|
||||
|
||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="navbarBasicExample" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item" href="/">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fas fa-home"></i>
|
||||
</span>
|
||||
<span>Home</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<!-- <a class="navbar-item" href="/projects">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fas fa-project-diagram"></i>
|
||||
</span>
|
||||
<span>Projects</span>
|
||||
</span>
|
||||
</a> -->
|
||||
|
||||
<a class="navbar-item" href="/about">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fas fa-user"></i>
|
||||
</span>
|
||||
<span>About</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="https://github.com/thomaspcole">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fab fa-github"></i>
|
||||
</span>
|
||||
<span>Github</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="https://git.thomaspcole.com/thomascole">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fab fa-git-alt"></i>
|
||||
</span>
|
||||
<span>Gitea</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
if ($navbarBurgers.length > 0) {
|
||||
$navbarBurgers.forEach( el => {
|
||||
el.addEventListener('click', () => {
|
||||
const target = el.dataset.target;
|
||||
const $target = document.getElementById(target);
|
||||
el.classList.toggle('is-active');
|
||||
$target.classList.toggle('is-active');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -1,19 +0,0 @@
|
||||
<style lang="scss">
|
||||
#pjs{
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
z-index: -1;
|
||||
animation: fadein .5s;
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
<script is:inline src="/js/particles.min.js"/>
|
||||
<div id="pjs"></div>
|
||||
<script>
|
||||
particlesJS.load('pjs', '/particlesjs-config.json');
|
||||
</script>
|
@ -1,20 +0,0 @@
|
||||
<style lang="scss">
|
||||
@import 'bulmaswatch/darkly/_variables.scss';
|
||||
@import 'bulma/bulma.sass';
|
||||
@import 'bulmaswatch/darkly/_overrides.scss';
|
||||
</style>
|
||||
|
||||
<nav class="navbar is-transparent" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/projects">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</span>
|
||||
<span>Back</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
@ -1,56 +0,0 @@
|
||||
---
|
||||
export interface Props {
|
||||
post: any;
|
||||
}
|
||||
|
||||
const { post } = Astro.props;
|
||||
---
|
||||
|
||||
<style lang="scss">
|
||||
@import 'bulmaswatch/darkly/_variables.scss';
|
||||
@import 'bulma/bulma.sass';
|
||||
@import 'bulmaswatch/darkly/_overrides.scss';
|
||||
|
||||
.card{
|
||||
width: 300px !important;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: .5em !important;
|
||||
}
|
||||
|
||||
.tagline{
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.posted-date{
|
||||
font-size: .75em;
|
||||
}
|
||||
|
||||
.post-img{
|
||||
margin: auto;
|
||||
padding-top: 1em;
|
||||
max-width: 280px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-image">
|
||||
<figure class="image">
|
||||
<img src={post.coverImage} alt="CoverImage" class="post-img">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h1 class="title has-text-link">{post.title}</h1>
|
||||
<div class="content">
|
||||
<p class="tagline">{post.description}</p>
|
||||
<a href={post.url}>Read More</a>
|
||||
<hr class="dropdown-divider">
|
||||
<span class="posted-date">Posted: {post.publishDate}</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
23
src/components/misc/BlogProjectCard.astro
Normal file
23
src/components/misc/BlogProjectCard.astro
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
image: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
const { title, subtitle, image, link } = Astro.props;
|
||||
---
|
||||
|
||||
|
||||
<div class="rounded-lg transition ease-in-out hover:scale-105">
|
||||
<a href={link}>
|
||||
<div class="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">
|
||||
<img class="h-44 lg:h-28 xl:h-28 my-2 aspect-video object-cover rounded-lg" src={image} alt="">
|
||||
<div class="lg:ml-4 xl:ml-4 2xl:ml-4 grow">
|
||||
<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>{subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
1
src/components/misc/Divider.astro
Normal file
1
src/components/misc/Divider.astro
Normal file
@ -0,0 +1 @@
|
||||
<div class="w-full h-0.5 rounded divider"></div>
|
44
src/components/misc/ThemeToggle.astro
Normal file
44
src/components/misc/ThemeToggle.astro
Normal file
@ -0,0 +1,44 @@
|
||||
<!-- Could be remade as svelte component.
|
||||
For the 3 lines of javascript needed I think its okay to use a script tag -->
|
||||
|
||||
<style>
|
||||
#themetoggle~.dot {
|
||||
background-color: #e2e2e2;
|
||||
}
|
||||
|
||||
#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
|
||||
</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>
|
||||
<span class="material-symbols-outlined mx-2">
|
||||
nightlight
|
||||
</span>
|
||||
</div>
|
100
src/components/nav/Nav.astro
Normal file
100
src/components/nav/Nav.astro
Normal file
@ -0,0 +1,100 @@
|
||||
---
|
||||
export interface Props {
|
||||
pageName?: string;
|
||||
}
|
||||
|
||||
import Divider from "../misc/Divider.astro";
|
||||
import SocialLinks from "./SocialLinks.astro";
|
||||
import ThemeToggle from "../misc/ThemeToggle.astro";
|
||||
|
||||
const { pageName } = Astro.props;
|
||||
let displayedName = "";
|
||||
if(pageName){
|
||||
displayedName = pageName.replace("|","").trim();
|
||||
}
|
||||
---
|
||||
|
||||
<style>
|
||||
[data-shown="true"] {
|
||||
transform: translateX(0);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="menu" data-shown=""
|
||||
class="fixed top-0 left-0 w-72 h-screen p-4 flex flex-col bg-base-200 -translate-x-full lg:translate-x-0 xl:translate-x-0 transition-all duration-100 z-50">
|
||||
|
||||
<button id="menu-close" class="absolute top-0 right-0 p-2 lg:hidden">
|
||||
<span class="material-symbols-outlined">
|
||||
close
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<a href="/">
|
||||
<div class="transition ease-in-out w-1/2 hover:scale-105 block mx-auto mt-2 mb-2">
|
||||
<img class="rounded-full aspect-square w-full object-cover" src="/img/profile.jpg" alt="">
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<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 flex lg:hidden xl:hidden bg-base-200 text-left transition-all duration-100">
|
||||
<button id="menubtn" class="w-12 h-12">
|
||||
<span class="material-symbols-outlined mt-1">
|
||||
menu
|
||||
</span>
|
||||
</button>
|
||||
<p class="my-auto mx-auto -translate-x-1/2 font-semibold text-xl">{displayedName}</p>
|
||||
</div>
|
||||
|
||||
<div id="draweroverlay" class="hidden fixed top-0 left-0 w-full h-[100vh] z-10 bg-black/50"/>
|
||||
|
||||
<script is:inline>
|
||||
const btn = document.getElementById("menubtn");
|
||||
const menu = document.getElementById("menu");
|
||||
const closebtn = document.getElementById("menu-close")
|
||||
const draweroverlay = document.getElementById("draweroverlay")
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
menu.dataset.shown = "true";
|
||||
draweroverlay.classList.remove("hidden");
|
||||
});
|
||||
|
||||
closebtn.addEventListener('click', closeMenu);
|
||||
draweroverlay.addEventListener('click', closeMenu);
|
||||
|
||||
function closeMenu(){
|
||||
menu.dataset.shown = "false";
|
||||
draweroverlay.classList.add("hidden");
|
||||
}
|
||||
</script>
|
37
src/components/nav/SocialLinks.astro
Normal file
37
src/components/nav/SocialLinks.astro
Normal file
@ -0,0 +1,37 @@
|
||||
<div class="flex place-content-center mt-4">
|
||||
<a class="mx-2" 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"
|
||||
version="1.1" id="main_outline" viewBox="0 0 640 640" xml:space="preserve">
|
||||
<g>
|
||||
<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" />
|
||||
<g>
|
||||
<g>
|
||||
<path class="fill-current"
|
||||
d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2 c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5 c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5 c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3 c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1 C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4 c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7 S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55 c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8 l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z" />
|
||||
<path class="fill-current"
|
||||
d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4 c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1 c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9 c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3 c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3 c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29 c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8 C343.2,346.5,335,363.3,326.8,380.1z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
<a class="mx-2" 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">
|
||||
<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" />
|
||||
</svg>
|
||||
</a>
|
||||
<a class="mx-2" 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"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 455 455"
|
||||
style="enable-background:new 0 0 455 455;" xml:space="preserve">
|
||||
<g>
|
||||
<path style="fill-rule:evenodd;clip-rule:evenodd;"
|
||||
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>
|
||||
</a>
|
||||
</div>
|
20
src/components/resume/Timeline.astro
Normal file
20
src/components/resume/Timeline.astro
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
const { title, subtitle } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="flex">
|
||||
<div>
|
||||
<span class="w-4 h-4 bg-primary block rounded-full mt-2"></span>
|
||||
<span class="bg-primary block h-full w-[2px] translate-x-[7px]"></span>
|
||||
</div>
|
||||
<div class="px-5">
|
||||
<p class="font-semibold text-xl">{title}</p>
|
||||
<span class="text-sm font-light">{subtitle}</span>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
1
src/env.d.ts
vendored
Normal file
1
src/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="astro/client" />
|
@ -1,50 +0,0 @@
|
||||
---
|
||||
import Nav from '../components/Nav.astro';
|
||||
import Particle from '../components/Particle.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
|
||||
const {title} = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{title}</title>
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="/favicon.svg"> -->
|
||||
<link rel="stylesheet" href="/style/global.css">
|
||||
|
||||
<style lang="scss">
|
||||
@import 'bulmaswatch/darkly/_variables.scss';
|
||||
@import 'bulma/bulma.sass';
|
||||
@import 'bulmaswatch/darkly/_overrides.scss';
|
||||
|
||||
html{
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
body{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main{
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Sometimes some js is needed. Load font awesome -->
|
||||
<script is:inline src="/js/fa-all.min.js"/>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<Particle/>
|
||||
<Nav/>
|
||||
<main class="main">
|
||||
<slot/>
|
||||
</main>
|
||||
<Footer/>
|
||||
</body>
|
||||
</html>
|
22
src/layouts/BlogLayout.astro
Normal file
22
src/layouts/BlogLayout.astro
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
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>
|
61
src/layouts/InteractiveDemoLayout.astro
Normal file
61
src/layouts/InteractiveDemoLayout.astro
Normal file
@ -0,0 +1,61 @@
|
||||
---
|
||||
import Divider from "../components/misc/Divider.astro";
|
||||
import Layout from "./Layout.astro";
|
||||
const { frontmatter } = Astro.props;
|
||||
---
|
||||
|
||||
<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">
|
||||
<div class="relative mb-4">
|
||||
<img class="aspect-video object-cover rounded-xl" 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 />
|
||||
<!-- Render our markdown with the normal header styling and some extra padding. -->
|
||||
<style is:inline>
|
||||
.astro-code {
|
||||
margin-bottom: 1rem;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
h1,
|
||||
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;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
<slot />
|
||||
</main>
|
||||
</Layout>
|
60
src/layouts/Layout.astro
Normal file
60
src/layouts/Layout.astro
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
export interface Props {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
import Nav from '../components/nav/Nav.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
|
||||
const { title } = Astro.props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<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} />
|
||||
<title>thomaspcole.com {title}</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
'FILL' 0,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 48
|
||||
}
|
||||
</style>
|
||||
<script is:inline>
|
||||
if (localStorage.getItem("theme") === null) {
|
||||
document.documentElement.setAttribute("data-theme", "business");
|
||||
} else {
|
||||
document.documentElement.setAttribute("data-theme", localStorage.getItem("theme"));
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
import { themeChange } from 'theme-change';
|
||||
themeChange();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="">
|
||||
<Nav pageName={title} />
|
||||
<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">
|
||||
<slot />
|
||||
<div class="grow"></div>
|
||||
<Footer/>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,74 +0,0 @@
|
||||
---
|
||||
import PostNav from '../components/PostNav.astro';
|
||||
import Particle from '../components/Particle.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
|
||||
const { content } = Astro.props;
|
||||
const { title, description, publishDate} = content;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{title} - thomaspcole.com</title>
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="/favicon.svg"> -->
|
||||
<link rel="stylesheet" href="/style/global.css">
|
||||
|
||||
<style lang="scss">
|
||||
@import 'bulmaswatch/darkly/_variables.scss';
|
||||
@import 'bulma/bulma.sass';
|
||||
@import 'bulmaswatch/darkly/_overrides.scss';
|
||||
|
||||
html{
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
body{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main{
|
||||
flex: 1 1 auto;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
place-content: space-between;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Sometimes some js is needed. Load font awesome -->
|
||||
<script is:inline src="/js/fa-all.min.js"/>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<Particle/>
|
||||
<PostNav/>
|
||||
<main class="main">
|
||||
<div class="card">
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1 class="title">{title}</h1>
|
||||
<p class="subtitle">{description}</p>
|
||||
</div>
|
||||
|
||||
<span>{publishDate}</span>
|
||||
</div>
|
||||
|
||||
<hr class="dropdown-divider">
|
||||
|
||||
<slot/>
|
||||
</div>
|
||||
</main>
|
||||
<Footer/>
|
||||
</body>
|
||||
</html>
|
@ -1,112 +0,0 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import { Markdown } from 'astro/components';
|
||||
|
||||
const resumeLink = 'https://git.thomaspcole.com/thomascole/Resume-MD/raw/branch/master/src/Resume.md';
|
||||
|
||||
const response = await fetch(resumeLink);
|
||||
const data = await response.text();
|
||||
---
|
||||
<BaseLayout title="About - thomaspcole.com">
|
||||
<style lang="scss">
|
||||
@import 'bulmaswatch/darkly/_variables.scss';
|
||||
@import 'bulma/bulma.sass';
|
||||
@import 'bulmaswatch/darkly/_overrides.scss';
|
||||
|
||||
html{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.bio-div {
|
||||
display: flex;
|
||||
min-height: 10em;
|
||||
}
|
||||
|
||||
.md-container{
|
||||
width: 80%;
|
||||
padding-top: 1em;
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.no-margin{
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.hidden{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.cert-seal{
|
||||
width: auto;
|
||||
height: 200px;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.cert-grid{
|
||||
display: flex;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
// Hack to adjust some of the markdown content
|
||||
#thomaspatrickcolegmailcom--704-771-2453{
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
#work-experience{
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.md-container{
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="content">
|
||||
<div class="md-container">
|
||||
<!-- Resume -->
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<h2 class="card-header-title no-margin">
|
||||
Resume
|
||||
</h2>
|
||||
<!-- <a class="card-header-icon" download="ThomasCole-Resume.pdf" href="/Resume.pdf">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span>Download</span>
|
||||
</span>
|
||||
</a> -->
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<Markdown content={data}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<h2 class="card-header-title no-margin">
|
||||
Certifications
|
||||
</h2>
|
||||
</header>
|
||||
<div class="cert-grid">
|
||||
<img class="cert-seal" src="/img/dante_certified_logo_level1.png" alt="dante_cert_level3.png">
|
||||
<img class="cert-seal" src="/img/dante_certified_logo_level2.png" alt="dante_cert_level3.png">
|
||||
<img class="cert-seal" src="/img/dante_certified_seal_level3.png" alt="dante_cert_level3.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
@ -1,9 +1,38 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import IndexSplash from '../components/IndexSplash.astro';
|
||||
import BlogProjectCard from "../components/misc/BlogProjectCard.astro";
|
||||
import Divider from "../components/misc/Divider.astro";
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
|
||||
const projects = (await Astro.glob("./projects/**/*.{md,mdx}")).sort(
|
||||
(a,b) =>
|
||||
new Date(b.frontmatter.date).valueOf()-
|
||||
new Date(a.frontmatter.date).valueOf()
|
||||
).slice(0,5);
|
||||
---
|
||||
|
||||
<BaseLayout title="thomaspcole.com">
|
||||
<IndexSplash/>
|
||||
</BaseLayout>
|
||||
|
||||
<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">
|
||||
<section>
|
||||
<div class="flex flex-row">
|
||||
<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>
|
||||
</div>
|
||||
<p class="text-3xl font-semibold">I'm Thomas Cole</p>
|
||||
<p class="font-thin">Web Developer, System Administrator, Network Engineer</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>
|
||||
</Layout>
|
||||
|
@ -1,9 +0,0 @@
|
||||
---
|
||||
title: "Astro.build: It's not Javascript"
|
||||
description: "The new and improved thomaspcole.com. Powered by Astro"
|
||||
publishDate: 'Friday, July 30 2021'
|
||||
layout: '../../layouts/ProjectPost.astro'
|
||||
coverImage: 'https://imgs.xkcd.com/comics/standards.png'
|
||||
---
|
||||
|
||||
# Header
|
@ -1,27 +0,0 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import ProjectPostPreview from '../components/ProjectPostPreview.astro';
|
||||
|
||||
let allPosts = await Astro.glob('./posts/*.md');
|
||||
//allPosts.sort((a,b) => new Date(b.publishDate) - new Date(a.publishDate));
|
||||
|
||||
---
|
||||
|
||||
<BaseLayout title="Projects - thomaspcole.com">
|
||||
<style lang="scss">
|
||||
.project-div{
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
html{
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="project-div">
|
||||
{allPosts.map(p => <ProjectPostPreview post={p}/>)}
|
||||
</div>
|
||||
</BaseLayout>
|
8
src/pages/projects/.pedal-pi.mdx
Normal file
8
src/pages/projects/.pedal-pi.mdx
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
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"
|
||||
---
|
59
src/pages/projects/[page].astro
Normal file
59
src/pages/projects/[page].astro
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import Divider from "../../components/misc/Divider.astro";
|
||||
import BlogProjectCard from "../../components/misc/BlogProjectCard.astro";
|
||||
|
||||
export async function getStaticPaths({ paginate }: { paginate: any }) {
|
||||
const posts = (await Astro.glob("./**/*.{md,mdx}")).sort(
|
||||
(a, b) =>
|
||||
new Date(b.frontmatter.date).valueOf() -
|
||||
new Date(a.frontmatter.date).valueOf()
|
||||
);
|
||||
|
||||
return paginate(posts, { pageSize: 10 });
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
---
|
||||
|
||||
<Layout title=" | Projects">
|
||||
<main class="w-full md:w-5/6 lg:w-4/5 xl:w-4/5 mx-auto mt-4">
|
||||
<div class="flex flex-col">
|
||||
{
|
||||
page.data.map((post: any) => (
|
||||
<BlogProjectCard title={post.frontmatter.title} subtitle={post.frontmatter.tagline}
|
||||
image={post.frontmatter.image} link={post.url}/>
|
||||
<Divider />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div class="w-1/2 mx-auto flex justify-between">
|
||||
{
|
||||
page.url.prev ? (
|
||||
<a class="w-40 flex" href={page.url.prev}>
|
||||
<span class="material-symbols-outlined">
|
||||
chevron_left
|
||||
</span>
|
||||
<p>Previous Page</p>
|
||||
</a>
|
||||
) : (
|
||||
<div class="w-40" />
|
||||
)
|
||||
}
|
||||
|
||||
<p class="grow text-center whitespace-nowrap">Page {page.currentPage}</p>
|
||||
{
|
||||
page.url.next ? (
|
||||
<a class="w-40 flex" href={page.url.next}>
|
||||
<p class="grow text-right">Next Page</p>
|
||||
<span class="material-symbols-outlined">
|
||||
chevron_right
|
||||
</span>
|
||||
</a>
|
||||
) : (
|
||||
<div class="w-40" />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
20
src/pages/projects/quicksort/quicksort.mdx
Normal file
20
src/pages/projects/quicksort/quicksort.mdx
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
layout: "../../../layouts/InteractiveDemoLayout.astro"
|
||||
title: "Quicksort Demo"
|
||||
tagline: "A simple visualization of the quicksort algorithm using p5.js"
|
||||
date: "December 23, 2022"
|
||||
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"
|
||||
---
|
||||
import Quicksort from './quicksort.svelte';
|
||||
|
||||
<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.
|
||||
I used this demo as a way to learn how to visualize the quicksort algorithm.\
|
||||
\
|
||||
I took inspiration from the YouTube video: *[15 Sorting Algorithms in 6 Minutes](https://www.youtube.com/watch?v=kPRA0W1kECg)*. The source code for those interested can be found on my *[Github](https://github.com/thomaspcole/p5QuicksortVisualization)*.\
|
||||
\
|
||||
The demo works by generating an array of 200 values then randomly shuffling the array. We then call the ```quicksort``` function to sort the array. At each iteration of the function, the code takes a 'snapshot' of the data array at that iteration.
|
||||
By iterating through the 'snapshot' array we get our final animation. It also allows for stepping forward and backwards to see each step of the animation.
|
195
src/pages/projects/quicksort/quicksort.svelte
Normal file
195
src/pages/projects/quicksort/quicksort.svelte
Normal file
@ -0,0 +1,195 @@
|
||||
<script>
|
||||
import P5 from "p5-svelte/P5.svelte";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
// Demo modified from a very old p5js sketch written in college.
|
||||
// The original was part of a the application process to work for the university IT department
|
||||
// https://github.com/thomaspcole/p5QuicksortVisualization
|
||||
|
||||
let data = [];
|
||||
let animArray = [];
|
||||
let valuesToGenerate = 200;
|
||||
let frame = 0;
|
||||
|
||||
let parentContainer;
|
||||
let sketch;
|
||||
|
||||
//Added controls for play/pause and stepping frame by frame.
|
||||
let playpause = false;
|
||||
|
||||
onMount(()=>{
|
||||
const sketchWidth = parentContainer.offsetWidth;
|
||||
const barWidth = (sketchWidth-20)/valuesToGenerate;
|
||||
sketch = (p5) => {
|
||||
p5.setup = () => {
|
||||
for (let i = 0; i < valuesToGenerate; i++) {
|
||||
data[i]=(i%700);
|
||||
}
|
||||
|
||||
p5.shuffle(data, true)
|
||||
quicksort(data, 0, data.length-1);
|
||||
animArray.push(new arrayFrame(data.toString(), null, null, null));
|
||||
|
||||
//setup the canvas
|
||||
p5.createCanvas(sketchWidth,400);
|
||||
p5.frameRate(30);
|
||||
}
|
||||
|
||||
p5.draw = () => {
|
||||
if(playpause){
|
||||
if(frame == animArray.length-1){
|
||||
playpause = !playpause;
|
||||
} else {
|
||||
drawFrame(frame);
|
||||
frame++;
|
||||
}
|
||||
|
||||
} else {
|
||||
drawFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
function drawFrame(index){
|
||||
let dataFrame = animArray[index];
|
||||
p5.background(28);
|
||||
for (var i = 0; i < valuesToGenerate; i++){
|
||||
p5.fill(255)
|
||||
if(dataFrame.midIndex == i){
|
||||
p5.fill(255,0,0);
|
||||
}
|
||||
if(dataFrame.lowVal == i || dataFrame.highVal == i){
|
||||
p5.fill(0,255,0);
|
||||
}
|
||||
p5.rect(barWidth*i+10,350, barWidth,-dataFrame.getArray()[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Copied directly from original code
|
||||
* @param array the array of data being worked on
|
||||
* @param low the low values
|
||||
* @param high the high value
|
||||
*/
|
||||
function quicksort(array, low, high){
|
||||
animArray.push(new arrayFrame(array.toString(), 0, array.length, 0));
|
||||
//Do we need to sort?
|
||||
if(low >= high){
|
||||
return;
|
||||
}
|
||||
|
||||
//Pick a pivot point in the middle of the passed array.
|
||||
let mid = Math.floor(low + ((high-low) / 2));
|
||||
let pivot = array[mid];
|
||||
|
||||
let l = low;
|
||||
let h = high;
|
||||
|
||||
while(l <= h){
|
||||
while(array[l] < pivot){
|
||||
l++;
|
||||
}
|
||||
|
||||
while(array[h] > pivot){
|
||||
h--;
|
||||
}
|
||||
|
||||
if(l <= h){
|
||||
//console.log(array, l, h, mid);
|
||||
//console.log("Switch: " + array[l] + ":" + array[h]);
|
||||
animArray.push(new arrayFrame(array.toString(), l, h, mid));
|
||||
|
||||
let tmp = array[l];
|
||||
array[l] = array[h];
|
||||
array[h] = tmp;
|
||||
l++;
|
||||
h--;
|
||||
}
|
||||
}
|
||||
|
||||
if(low < h){
|
||||
quicksort(array, low, h);
|
||||
}
|
||||
|
||||
if(high > l){
|
||||
quicksort(array, l, high);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a arrayFrame object
|
||||
* @param arrayState the array of data at a current point in time
|
||||
* @param lowVal the low index
|
||||
* @param highVal the high midIndex
|
||||
* @param midIndex the pivot index
|
||||
* @param getArray Returns the array at the current time
|
||||
*/
|
||||
function arrayFrame(arrayState, lowVal, highVal, midIndex){
|
||||
this.arrayState = arrayState;
|
||||
this.lowVal = lowVal;
|
||||
this.highVal = highVal;
|
||||
this.midIndex = midIndex;
|
||||
|
||||
this.getArray = function(){
|
||||
return this.arrayState.split(",");
|
||||
}
|
||||
}
|
||||
|
||||
function prevFrame(){
|
||||
if(frame == 0){
|
||||
return;
|
||||
}
|
||||
frame--;
|
||||
}
|
||||
|
||||
function nextFrame(){
|
||||
if(frame == animArray.length-1){
|
||||
return;
|
||||
}
|
||||
frame++;
|
||||
}
|
||||
|
||||
function reset(){
|
||||
frame=0;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={parentContainer} class="w-full">
|
||||
<P5 {sketch}/>
|
||||
</div>
|
||||
<div class="flex justify-evenly mt-6 mb-4">
|
||||
<button on:click={prevFrame}>
|
||||
<span class="material-symbols-outlined">
|
||||
skip_previous
|
||||
</span>
|
||||
</button>
|
||||
<button on:click={()=>{
|
||||
if(frame==animArray.length-1){
|
||||
reset();
|
||||
} else {
|
||||
playpause = !playpause;
|
||||
}
|
||||
}}>
|
||||
{#if playpause}
|
||||
<span class="material-symbols-outlined">
|
||||
pause
|
||||
</span>
|
||||
{:else if frame==animArray.length-1}
|
||||
<span class="material-symbols-outlined">
|
||||
restart_alt
|
||||
</span>
|
||||
{:else}
|
||||
<span class="material-symbols-outlined">
|
||||
play_arrow
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
<button on:click={nextFrame}>
|
||||
<span class="material-symbols-outlined">
|
||||
skip_next
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
86
src/pages/projects/sitemap/sitemapgraph.mdx
Normal file
86
src/pages/projects/sitemap/sitemapgraph.mdx
Normal file
@ -0,0 +1,86 @@
|
||||
---
|
||||
layout: "../../../layouts/InteractiveDemoLayout.astro"
|
||||
title: "Visual Sitemap"
|
||||
tagline: "Using Cytoscape.js to visualize all the pages of a website"
|
||||
date: "January 6, 2023"
|
||||
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"
|
||||
---
|
||||
import Sitemapgraph from './sitemapgraph.svelte'
|
||||
|
||||
### Background
|
||||
|
||||
I was given a project at work to help improve the SEO of our organization's website. We quickly realized that we did not have a great overview of everything on the website. This began the search for a tool to help visualize all the pages on the website. While there are lots of expensive SEO tools we struggled to find something that would just give us a hierarchical view of pages. So if the tool doesn't exist, we need to make the tool.
|
||||
|
||||
### Designing the tool
|
||||
|
||||
![xkcd](https://imgs.xkcd.com/comics/manuals.png)
|
||||
|
||||
I set out to make a generic site mapper that could take a url and crawl all other pages on the site. I wanted it to have a simple command line interface, with the ability to save results for later viewing, and be able to spin up an integrated web server for the visualization. I also took the opportunity to write it all in Typescript, and I mean really use it not just write Javascript in a .ts file.
|
||||
|
||||
The command line interface and saving a file to disk are straight forward enough. The node fs module lets us read and write to the host filesystem and [Yargs](http://yargs.js.org/) can handle the command line stuff for us. Even crawling a website wasn't too difficult. The [sitemap-generator](https://www.npmjs.com/package/sitemap-generator) project handles all the heavy lifting. The generator creates a crawler object that keeps track of every page it visits in a queue. By looking at that queue after the crawler finishes we can get json objects of every page visited and importantly for us the page that linked to it. Finally, [Express](https://expressjs.com/) framework will handle the web server bits for the visualization.
|
||||
|
||||
### Putting it all together
|
||||
|
||||
So we have a great big array of json objects that have a url and a referrer among other things. How do we take this data and turn it into something usable?
|
||||
|
||||
***Graphs***
|
||||
|
||||
My CS200 professor can rest easy, I was paying attention and did learn something. Side note: for the sake of this app, we made the decision to not care about back links. While this doesn't change the implementation of our graph it does simplify displaying the graph as we don't have to worry about loops. On to the code.
|
||||
|
||||
```typescript
|
||||
class Vertex {
|
||||
name: string;
|
||||
ajd: Array<Edge>;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
this.ajd = new Array<Edge>();
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
class Edge {
|
||||
destination: Vertex;
|
||||
|
||||
constructor(vertex: Vertex) {
|
||||
this.destination = vertex;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each Vertex in the graph has a name and an array of edges. Each edge has a destination vertex. Easy enough. Now for the graph. We keep a map of vertex names and its corresponding vertex. If the vertex does not exist in the map we add it to the map and return the vertex. Adding an edge then becomes as simple as passing the name of the two vertices we want to connect. If both vertices don't exist they are created in the graph and we push the new edge to the source vertex adjacency array. Thats really it, there is some code omitted from the example below for printing the graph as well serializing the graph object so it can be saved to disk.
|
||||
|
||||
```typescript
|
||||
class Graph {
|
||||
vertices: Map<string, Vertex>;
|
||||
|
||||
constructor() {
|
||||
this.vertices = new Map<string, Vertex>();
|
||||
}
|
||||
|
||||
getVertex(name: string) {
|
||||
let v = this.vertices.get(name);
|
||||
if (v === undefined) {
|
||||
v = new Vertex(name);
|
||||
this.vertices.set(name, v);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
addEdge(source: string, dest: string) {
|
||||
const v = this.getVertex(source);
|
||||
const w = this.getVertex(dest);
|
||||
v.ajd.push(new Edge(w));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now that we have a graph representation of our site we just need to convert it something a bit more visually appealing. [Cytoscape.js](https://js.cytoscape.org/) will handle that for us. I was tempted to use something like p5.js or D3 to handle the visualization but Cytoscape seemed better built for graph and network data structures. The translation from our graph data structure to Cytoscape is super easy, just pass in an array of vertices and edges.
|
||||
|
||||
Finally after all that crawling and parsing we are left with the desired result. A interactive hierarchical view of every page on a website. Is the tool perfect, no, but it was good enough for what we needed.
|
||||
|
||||
<Sitemapgraph client:load/>
|
109
src/pages/projects/sitemap/sitemapgraph.svelte
Normal file
109
src/pages/projects/sitemap/sitemapgraph.svelte
Normal file
@ -0,0 +1,109 @@
|
||||
<script>
|
||||
import cytoscape from 'cytoscape';
|
||||
import { onMount } from 'svelte';
|
||||
let div;
|
||||
let data = {"map":[{"name":"root","adj":[{"destination":{"name":"http://localhost:8000/","ajd":[{"destination":{"name":"http://localhost:8000/about.html","ajd":[]}},{"destination":{"name":"http://localhost:8000/blog/demo1.html","ajd":[]}},{"destination":{"name":"http://localhost:8000/blog/demo2.html","ajd":[]}}]}}]},{"name":"http://localhost:8000/","adj":[{"destination":{"name":"http://localhost:8000/about.html","ajd":[]}},{"destination":{"name":"http://localhost:8000/blog/demo1.html","ajd":[]}},{"destination":{"name":"http://localhost:8000/blog/demo2.html","ajd":[]}}]},{"name":"http://localhost:8000/about.html","adj":[]},{"name":"http://localhost:8000/blog/demo1.html","adj":[]},{"name":"http://localhost:8000/blog/demo2.html","adj":[]}]};
|
||||
let cyElements = [];
|
||||
|
||||
onMount(()=>{
|
||||
//load all vertexes into cytoscape
|
||||
data.map.forEach((element) => {
|
||||
let shortURL = element.name.replace(/^(?:\/\/|[^/]+)*\//, "/");
|
||||
cyElements.push({
|
||||
data: {
|
||||
id: element.name,
|
||||
name: element.name,
|
||||
short: shortURL,
|
||||
href: element.name,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
data.map.forEach((element) => {
|
||||
if (element.adj.length > 0) {
|
||||
for (let i = 0; i < element.adj.length; i++) {
|
||||
const e = element.adj[i];
|
||||
let cyid = element.name + "," + element.adj[i].destination.name;
|
||||
let cysrc = element.name;
|
||||
let cytgt = element.adj[i].destination.name;
|
||||
cyElements.push({
|
||||
data: {
|
||||
id: cyid,
|
||||
source: cysrc,
|
||||
target: cytgt,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cytoscape({
|
||||
container: div,
|
||||
elements: cyElements,
|
||||
style: [
|
||||
{
|
||||
selector: "node",
|
||||
style: {
|
||||
'content': "data(name)",
|
||||
'text-valign' : 'center',
|
||||
'shape': 'round-rectangle',
|
||||
'width': '500px',
|
||||
'height': '50px',
|
||||
'background-color' : '#F8F9FA',
|
||||
'border-color': '#000',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: "edge",
|
||||
style: {
|
||||
'line-color': "#fff"
|
||||
}
|
||||
}
|
||||
],
|
||||
userZoomingEnabled: false,
|
||||
userPanningEnabled: false,
|
||||
layout: {
|
||||
name: "breadthfirst",
|
||||
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)
|
||||
padding: 10, // padding on fit
|
||||
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)
|
||||
spacingFactor: 1, // positive spacing factor, larger => more space between nodes (N.B. n/a if causes overlap)
|
||||
boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
|
||||
avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space
|
||||
nodeDimensionsIncludeLabels: false, // Excludes the label when calculating node bounding boxes for the layout algorithm
|
||||
roots: cyElements[0], // the roots of the trees
|
||||
maximal: false, // whether to shift nodes down their natural BFS depths in order to avoid upwards edges (DAGS only)
|
||||
depthSort: undefined, // a sorting function to order nodes at equal depth. e.g. function(a, b){ return a.data('weight') - b.data('weight') }
|
||||
animate: false, // whether to transition the node positions
|
||||
animationDuration: 500, // duration of animation in ms if enabled
|
||||
animationEasing: undefined, // easing of animation if enabled,
|
||||
animateFilter: function (node, i) {
|
||||
return true;
|
||||
}, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts
|
||||
ready: undefined, // callback on layoutready
|
||||
stop: undefined, // callback on layoutstop
|
||||
transform: function (node, position) {
|
||||
return position;
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
#cy {
|
||||
width: 100%;
|
||||
height: 75vh;
|
||||
display: block;
|
||||
background-color: #343A40;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div bind:this={div} id="cy">
|
||||
|
||||
</div>
|
92
src/pages/resume.astro
Normal file
92
src/pages/resume.astro
Normal file
@ -0,0 +1,92 @@
|
||||
---
|
||||
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>
|
||||
<div class="flex flex-wrap gap-2 mx-auto">
|
||||
<div class="badge badge-lg">HTML</div>
|
||||
<div class="badge badge-lg">CSS</div>
|
||||
<div class="badge badge-lg">Javascript</div>
|
||||
<div class="badge badge-lg">Astro</div>
|
||||
<div class="badge badge-lg">Svelte</div>
|
||||
<div class="badge badge-lg">Java</div>
|
||||
<div class="badge badge-lg">C#</div>
|
||||
<div class="badge badge-lg">Tailwind</div>
|
||||
<div class="badge badge-lg">Python</div>
|
||||
<div class="badge badge-lg">SQL</div>
|
||||
<div class="badge badge-lg">Linux</div>
|
||||
<div class="badge badge-lg">Bash</div>
|
||||
<div class="badge badge-lg">Docker</div>
|
||||
<div class="badge badge-lg">Active Directory</div>
|
||||
<div class="badge badge-lg">Cisco IOS</div>
|
||||
<div class="badge badge-lg">Unifi Networks</div>
|
||||
<div class="badge badge-lg">3CX</div>
|
||||
<div class="badge badge-lg">Mosyle MDM</div>
|
||||
</div>
|
||||
<Divider/>
|
||||
</main>
|
||||
</Layout>
|
33
tailwind.config.cjs
Normal file
33
tailwind.config.cjs
Normal file
@ -0,0 +1,33 @@
|
||||
const defaultTheme = require('tailwindcss/defaultTheme');
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
||||
darkMode: ["class", "[data-theme]=business"],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
'sans': ['Open Sans', defaultTheme.fontFamily.sans],
|
||||
},
|
||||
keyframes: {
|
||||
wiggle: {
|
||||
'0%, 100%': { transform: 'rotate(-5deg)' },
|
||||
'50%': { transform: 'rotate(5deg)' },
|
||||
},
|
||||
'bg-animate':{
|
||||
'0%,100%': {'background-position': '25% 50%'},
|
||||
'50%': {'background-position': '75% 50%'},
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'wiggle-infinite': 'wiggle 1s ease-in-out infinite',
|
||||
wiggle: 'wiggle 1s ease-in-out',
|
||||
'bg-animate': 'bg-animate 5s ease-in-out infinite',
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [require("daisyui")],
|
||||
daisyui: {
|
||||
themes: ["corporate", "business"],
|
||||
},
|
||||
}
|
3
tsconfig.json
Normal file
3
tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict"
|
||||
}
|
Loading…
Reference in New Issue
Block a user