Compare commits

...

5 Commits

56 changed files with 4235 additions and 913 deletions

View File

@ -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

32
.gitignore vendored
View File

@ -1,22 +1,24 @@
# build output
dist/
.output/
# dependencies
node_modules/
# logs
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# environment variables
.env
.env.production
# macOS-specific files
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
package-lock.json
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -1,4 +1,3 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
"recommendations": ["svelte.svelte-vscode"]
}

11
.vscode/launch.json vendored
View File

@ -1,11 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View File

@ -1,9 +0,0 @@
MIT License
Copyright (c) <year> <copyright holders>
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,47 @@
# Svelte + TS + Vite
This template should help get you started developing with Svelte and TypeScript in Vite.
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
## Need an official Svelte framework?
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
## Technical considerations
**Why use this over SvelteKit?**
- It brings its own routing solution which might not be preferable for some users.
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
**Why include `.vscode/extensions.json`?**
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
**Why enable `allowJs` in the TS template?**
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
**Why is HMR not preserving my local component state?**
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
```ts
// store.ts
// An extremely simple external store
import { writable } from 'svelte/store'
export default writable(0)
```

View File

@ -1,15 +0,0 @@
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: [tailwind(), svelte(), mdx()]
});

24
index.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>thomaspcole.com</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=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&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" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

3410
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,31 @@
{
"name": "@example/basics",
"type": "module",
"version": "0.0.1",
"name": "temppersonal",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.3",
"@tsconfig/svelte": "^3.0.0",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"svelte": "^3.55.1",
"svelte-check": "^2.10.3",
"tailwindcss": "^3.2.7",
"tslib": "^2.5.0",
"typescript": "^4.9.3",
"vite": "^4.2.0"
},
"dependencies": {
"@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",
"@types/node": "^18.15.7",
"@types/three": "^0.149.0",
"p5-svelte": "^3.1.2",
"svelte": "^3.55.0",
"tailwindcss": "^3.2.4",
"theme-change": "^2.2.0"
"svelte-cubed": "^0.2.1",
"three": "^0.150.1"
}
}

6
postcss.config.cjs Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -1,13 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 873 B

View File

Before

Width:  |  Height:  |  Size: 607 KiB

After

Width:  |  Height:  |  Size: 607 KiB

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

43
src/App.svelte Normal file
View File

@ -0,0 +1,43 @@
<script lang="ts">
import { onMount } from "svelte";
import Background from "./components/Background.svelte";
import Bootup from "./components/Bootup.svelte";
import Footer from "./components/Footer.svelte";
import NameSplash from "./components/NameSplash.svelte";
import About from "./components/About.svelte";
import Projects from "./components/Projects.svelte";
let bootDone = false;
let mainContent;
async function finishBoot() {
bootDone = true;
await new Promise((r) => setTimeout(r, 300));
mainContent.classList.add("opacity-100");
}
onMount(() => {
if ("skip_boot" in localStorage) {
console.log("Skipping boot sequence");
finishBoot();
}
});
</script>
<Background />
<section class="h-screen relative p-4" class:hidden={bootDone}>
<Bootup on:boot_done={finishBoot} />
</section>
<div
bind:this={mainContent}
class="trasnition-all ease-in duration-300 opacity-0"
class:hidden={!bootDone}
>
<NameSplash />
<div class="py-6" />
<About />
<div class="py-6" />
<Projects />
<Footer />
</div>

14
src/app.css Normal file
View File

@ -0,0 +1,14 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body::-webkit-scrollbar {
display: none;
}
body {
-ms-overflow-style: none;
scrollbar-width: none;
background-color: black;
user-select: none;
}

View File

@ -0,0 +1,17 @@
<script lang="ts">
</script>
<section
id="projects"
class="w-5/6 lg:xl:w-4/5 mx-auto font-mono text-neutral-50 rounded-xl drop-shadow-screen p-4 border-2 border-solid border-green-700"
>
<p class="font-bold text-2xl pb-6 ">&gt; 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>
</section>

View File

@ -0,0 +1,46 @@
<div
class="fixed w-full h-screen top-0 left-0 bg-green-700/20 rounded-3xl border-solid border-2 border-green-300/50 -z-10 "
>
<div class="absolute screendim" />
<div class="absolute scanlines" />
</div>
<style>
.scanlines {
background: linear-gradient(
rgb(134, 239, 172, 0.15),
rgb(134, 239, 172, 0.15) 3px,
transparent 3px,
transparent 9px
);
background-size: 100% 9px;
border-radius: 1.5rem;
height: 100%;
width: 100%;
overflow: hidden;
animation: scan 120s infinite linear;
}
.screendim {
background: radial-gradient(
circle,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0) 70%,
rgba(0, 0, 0, 1) 100%
);
border-radius: 1.5rem;
height: 100%;
width: 100%;
overflow: hidden;
}
@keyframes scan {
from {
background-position: 0% 0%;
}
to {
background-position: 0% -100%;
}
}
</style>

View File

@ -0,0 +1,82 @@
<script lang="ts">
import { onMount, createEventDispatcher } from "svelte";
import Progress from "../utils/Progress.svelte";
const dispatch = createEventDispatcher();
let bootStage1;
let bootStage2;
let hexDumpContainer;
let percentage = 0;
onMount(async ()=>{
await bootPart1();
await bootPart2();
})
function makeConsoleString(text:string){
const e = document.createElement('p');
e.className = baseTextClass;
e.innerText = text;
return e;
}
function generateHexString(){
return "0x" + (Math.random()*4096).toString(16).replace('.','').slice(0,8).toUpperCase();
}
async function bootPart1() {
bootStage1.insertBefore(makeConsoleString("> System Boot Start..."), hexDumpContainer);
await sleep(100);
for (let i = 0; i <4 ; i++) {
var hexCol = document.createElement('div');
hexCol.className = "flex flex-col mr-4"
hexDumpContainer.appendChild(hexCol);
for (let j = 0; j<16; j++){
hexCol.appendChild(makeConsoleString(generateHexString()));
await sleep(50);
}
}
await sleep(500);
bootStage1.appendChild(makeConsoleString("> Boot ROM Initialized"));
await sleep(750);
bootStage1.appendChild(makeConsoleString("> Checking Integrity"));
await sleep(750);
bootStage1.appendChild(makeConsoleString("> Checksums OK"));
await sleep(750);
bootStage1.appendChild(makeConsoleString("> Starting DisplayOS"));
await sleep(2000);
bootStage1.classList.add("hidden");
}
async function bootPart2(){
bootStage2.classList.remove("hidden");
await sleep(500)
while(percentage < 100){
percentage += 10;
await sleep(Math.random()*500);
}
bootStage2.classList.add("opacity-0");
await sleep(300);
dispatch("boot_done");
}
async function sleep(delay:number){
await new Promise(r => setTimeout(r, delay));
}
const baseTextClass = "text-neutral-50 drop-shadow-screen text-xl font-mono";
</script>
<div bind:this={bootStage1}>
<div bind:this={hexDumpContainer} class="flex flex-row"/>
</div>
<div bind:this={bootStage2} class="hidden w-full h-full transition-all duration-300 ease-out">
<div class="relative top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center">
<img class="w-1/5 mx-auto" src="/vite.svg" alt="" draggable="false">
<div class="h-2 w-4/5 mx-auto mt-8 relative drop-shadow-screen bg-green-700 rounded-md">
<Progress color="#fafafa" percentage={percentage}/>
</div>
</div>
</div>

View File

@ -1,3 +0,0 @@
<footer class="p-4">
<p class="text-center text-sm">&copy; 2023 Thomas Cole</p>
</footer>

View File

@ -0,0 +1,37 @@
<script lang="ts">
import { onMount } from "svelte";
let showAnimationCheck;
function updateBootPrefrence(){
console.log(showAnimationCheck)
if(!showAnimationCheck){
localStorage.clear();
} else {
localStorage.setItem("skip_boot", "");
}
}
onMount(()=>{
if ("skip_boot" in localStorage) {
showAnimationCheck = false;
} else {
showAnimationCheck = true;
}
})
</script>
<footer
class="w-5/6 xl:lg:w-4/5 mx-auto text-neutral-50 drop-shadow-screen font-mono font-light m-2 p-4"
>
<div class="flex gap-4 place-content-center underline">
<a href="mailto:thomas.patrick.cole@gmail.com">Contact</a>
<a href="https://github.com/thomaspcole">Github</a>
<a href="https://git.thomaspcole.com/thomascole">Gitea</a>
<a href="https://www.linkedin.com/in/thomaspcole/">Linkedin</a>
<span>'Boot' animation:</span>
<input type="checkbox" bind:checked={showAnimationCheck} on:click={updateBootPrefrence}>
</div>
<br />
<p class="text-sm text-center font-sans">&copy; 2023 Thomas Cole</p>
</footer>

View File

@ -0,0 +1,33 @@
<script>
import GlitchText from "../utils/GlitchText.svelte";
</script>
<section id="top" class="h-screen">
<div class="relative top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<img
class="w-1/2 lg:xl:w-1/4 mx-auto rounded-full aspect-square object-cover mb-6"
src="/profile.jpg"
alt=""
/>
<h1
class="text-center font-mono text-4xl lg:xl:text-6xl text-neutral-50 drop-shadow-screen"
>
Thomas Cole
</h1>
<GlitchText
strings={[
"system administrator",
"linux enthusiast",
"web developer",
"network engineer",
"electronics hobbyist",
]}
classVars="text-neutral-50 drop-shadow-screen text-center text-xl lg:xl:text-2xl font-mono w-fit mx-auto capitalize"
/>
</div>
<span
class="material-symbols-outlined absolute bottom-[10px] left-1/2 -translate-x-1/2 text-neutral-50 drop-shadow-screen text-4xl lg:xl:text-6xl animate-[pulse_4s_cubic-bezier(0.4,0,0.6,1)_infinite] transition-all duration-500"
>
expand_more
</span>
</section>

10
src/components/Nav.svelte Normal file
View File

@ -0,0 +1,10 @@
<script lang="ts">
</script>
<section class="h-[10vh] w-5/6 lg:xl:w-4/5 mx-auto text-neutral-50 drop-shadow-screen font-mono">
<div class="flex flex-col text-2xl">
<p>&gt; <a href="#projects" class="underline">Latest Projects</a></p>
<p>&gt; <a href="#about" class="underline">About Me</a></p>
</div>
</section>

View File

@ -0,0 +1,44 @@
<script lang="ts">
import ProjectCard from "../utils/ProjectCard.svelte";
import LocalDemo from "../utils/LocalDemo.svelte";
import Quicksort from "../demos/quicksort.svelte";
</script>
<section
id="projects"
class="w-5/6 lg:xl:w-4/5 mx-auto font-mono text-neutral-50 rounded-xl drop-shadow-screen p-4 border-2 border-solid border-green-700"
>
<p class="font-bold text-2xl pb-6 ">&gt; Projects and Demos</p>
<ProjectCard
title="Pedal-pi"
subtitle="A custom effects processor using MODEP"
image="https://images.unsplash.com/photo-1511203438670-49f8ea8441c6?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80"
link="#"
callback={undefined}
/>
<LocalDemo
title="Quicksort Demo"
subtitle="A simple visualization of the quicksort algorithm using p5.js"
image="https://images.unsplash.com/photo-1669399213378-2853e748f217?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1632&q=80"
>
<p class="text-xl underline">Quicksort</p>
<p>
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: <a
class="underline"
href="https://www.youtube.com/watch?v=kPRA0W1kECg"
>15 Sorting Algorithms in 6 Minutes</a
>. The source code for those interested can be found on my
<a
class="underline"
href="https://github.com/thomaspcole/p5QuicksortVisualization"
>Github</a
>.
</p>
<br />
<Quicksort />
</LocalDemo>
</section>

View File

@ -0,0 +1,119 @@
<script lang="ts">
import ResumeItem from "../utils/ResumeItem.svelte";
</script>
<section
id="about"
class="w-5/6 lg:xl:w-4/5 mx-auto font-mono text-neutral-50 rounded-xl drop-shadow-screen p-4 border-2 border-solid border-green-700"
>
<a href="#top" class="text-sm font-thin underline">Back to Top</a>
<!-- <a href="#top" class="text-sm font-thin underline">Download as PDF</a> -->
<p class="font-bold text-2xl py-6 ">&gt; Work Experience</p>
<ResumeItem
title="Director Of Information Technology"
subtitle="Christ Lutheran Church | December 2020 - Present"
>
<ul class="mt-2 mb-4">
<li>
&gt; Maintain and upgrade critical network infrastructure for a
multi-campus environment.
</li>
<li>&gt; Facilitate backups and ensure their integrity.</li>
<li>
&gt; Integrate with action teams to develop technology plans and
solutions.
</li>
<li>
&gt; Implement new software and hardware solutions with
increased functionality while reducing costs by $50,000/yr.
</li>
</ul>
</ResumeItem>
<ResumeItem
title="Tech Associate"
subtitle="Christ Lutheran Church | August 2019 - December 2020 (Part Time)"
>
<ul class="mt-2 mb-4">
<li>
&gt; 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>
&gt; Facilitated building and installation of new computer
systems to improve recording and streaming capabilities of
worship services.
</li>
<li>
&gt; Run graphics for in house worship and live stream services.
</li>
</ul>
</ResumeItem>
<ResumeItem
title="Tier II Managed Services Technician"
subtitle="ScanOnline | June 2019 - December 2020"
>
<ul class="mt-2 mb-4">
<li>
&gt; Administer Office 365 and Windows Active Directory
infrastructure.
</li>
<li>
&gt; Manage company VOIP phone system and extension listings.
</li>
<li>
&gt; Deploy and configure virtual machines to align with
business needs.
</li>
<li>
&gt; Develop new Android applications to suit the business needs
of customers operating in the logistics industry.
</li>
<li>
&gt; Maintain legacy Windows Mobile applications for existing
customers.
</li>
<li>
&gt; Preform configuration and maintenance of customer hardware
and software.
</li>
</ul>
</ResumeItem>
<ResumeItem
title="Student Network Analyst"
subtitle="University of North Carolina at Greensboro | February 2018 - May 2019"
>
<ul class="mt-2 mb-4">
<li>
&gt; Assisted in maintenance and troubleshooting of enterprise
network systems.
</li>
<li>
&gt; Preformed on-boarding of new network devices at the
physical level.
</li>
</ul>
</ResumeItem>
<p class="font-bold text-2xl py-6 ">&gt; Education</p>
<ResumeItem
title="The University of North Carolina at Greensboro"
subtitle=""
>
<ul class="mt-2 mb-4">
<li>
&gt; Bachelor Of Science Information Systems and Supply Chain
Management
</li>
<li>&gt; Minor in Computer Science</li>
</ul>
</ResumeItem>
<p class="font-bold text-2xl py-6 ">&gt; Professional Certifications</p>
<ResumeItem title="Dante Level 3" subtitle="">
<ul class="mt-2 mb-4">
<li>&gt; Audinate | Certificate earned March 2021</li>
</ul>
</ResumeItem>
</section>

View File

@ -1 +0,0 @@
<div class="w-full h-0.5 rounded divider"></div>

View File

@ -1,44 +0,0 @@
<!-- 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>

View File

@ -1,86 +0,0 @@
---
import Divider from "../misc/Divider.astro";
import SocialLinks from "./SocialLinks.astro";
import ThemeToggle from "../misc/ThemeToggle.astro";
---
<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 sm:-translate-x-full md:-translate-x-full lg:translate-x-0 xl:translate-x-0 2xl: transition-all duration-100 z-50">
<button id="menu-close" class="absolute top-0 right-0 p-2 lg:hidden 2xl: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-3 mb-6">
<img class="rounded-full aspect-square w-full object-cover" src="/img/profile.jpg" alt="">
</div>
</a>
<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>

View File

@ -1,37 +0,0 @@
<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>

View File

@ -1,20 +0,0 @@
---
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>

View File

@ -1,7 +1,7 @@
<script>
import P5 from "p5-svelte/P5.svelte";
<script lang="ts">
import P5, { type Sketch } from "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
@ -11,14 +11,17 @@
let valuesToGenerate = 200;
let frame = 0;
let parentContainer;
let sketch;
let width;
let sketch:Sketch;
//Added controls for play/pause and stepping frame by frame.
let playpause = false;
onMount(()=>{
const sketchWidth = parentContainer.offsetWidth;
if(width < 1000){
valuesToGenerate = 75;
}
const sketchWidth = Math.floor(width*.8);
const barWidth = (sketchWidth-20)/valuesToGenerate;
sketch = (p5) => {
p5.setup = () => {
@ -31,7 +34,7 @@
animArray.push(new arrayFrame(data.toString(), null, null, null));
//setup the canvas
p5.createCanvas(sketchWidth,400);
p5.createCanvas(sketchWidth,valuesToGenerate*2);
p5.frameRate(30);
}
@ -60,7 +63,7 @@
if(dataFrame.lowVal == i || dataFrame.highVal == i){
p5.fill(0,255,0);
}
p5.rect(barWidth*i+10,350, barWidth,-dataFrame.getArray()[i]);
p5.rect(barWidth*i+10,valuesToGenerate*2-10, barWidth,-dataFrame.getArray()[i]);
}
}
}
@ -157,7 +160,8 @@
}
</script>
<div bind:this={parentContainer} class="w-full">
<svelte:window bind:innerWidth={width}/>
<div class="w-fit mx-auto">
<P5 {sketch}/>
</div>
<div class="flex justify-evenly mt-6 mb-4">

1
src/env.d.ts vendored
View File

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

View File

@ -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>

View File

@ -1,61 +0,0 @@
---
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>

View File

@ -1,60 +0,0 @@
---
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 />
<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>

8
src/main.ts Normal file
View File

@ -0,0 +1,8 @@
import './app.css'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app'),
})
export default app

View File

@ -1,41 +0,0 @@
---
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);
---
<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>
<!-- <p class="text-5xl ml-1 animate-wiggle hover:animate-wiggle-infinite">👋</p> -->
</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>
</Layout>

View File

@ -1,59 +0,0 @@
---
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=" | Blog">
<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">
<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>

View File

@ -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"
---

View File

@ -1,20 +0,0 @@
---
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.

View File

@ -1,86 +0,0 @@
---
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/>

View File

@ -1,107 +0,0 @@
<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"
}
}
],
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>

View File

@ -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>

1
src/utils/Divider.svelte Normal file
View File

@ -0,0 +1 @@
<div class="my-6 h-0.5 mx-auto bg-neutral-50 drop-shadow-screen rounded"/>

View File

@ -0,0 +1,54 @@
<script lang="ts">
import { onMount } from "svelte";
let text:string;
export let strings:string[];
export let classVars:string;
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()_+[]{},.<>/?";
let stringIndex = 0;
function pickNextWord(){
shuffleLetters(strings[stringIndex]);
if(stringIndex == strings.length-1){
stringIndex = 0;
} else {
stringIndex ++;
}
}
//https://www.youtube.com/watch?v=W5oawMJaXbU
//Hpyerplexed FTW
function shuffleLetters(targetWord:string){
let itterations = 0;
const interval = setInterval(()=>{
text = targetWord.split("").map((letter, index) => {
if(index < itterations){
return targetWord[index];
}
return letters[Math.floor(Math.random()*letters.length)];
}).join("");
if(itterations >= targetWord.length){
clearInterval(interval);
}
itterations ++;
}, 40);
}
onMount(()=>{
stringIndex = Math.floor(Math.random()*strings.length)
pickNextWord();
setInterval(pickNextWord,10000);
})
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<!-- <p class={classVars} on:mouseover={pickNextWord}> -->
<p class={classVars}>
{text}
</p>

View File

@ -0,0 +1,19 @@
<script lang="ts">
import ProjectCard from "./ProjectCard.svelte";
import Modal from "./Modal.svelte";
export let image:string;
export let title:string;
export let subtitle:string;
let showModal = false;
function callback(){
showModal = true;
}
</script>
<ProjectCard title={title} subtitle={subtitle} image={image} link={undefined} callback={callback}/>
<Modal bind:showModal>
<slot/>
</Modal>

55
src/utils/Modal.svelte Normal file
View File

@ -0,0 +1,55 @@
<script>
//Modal from example on svelte website. https://svelte.dev/examples/modal
export let showModal;
let dialog;
$: if (dialog && showModal) dialog.showModal();
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-autofocus -->
<dialog
bind:this={dialog}
on:close={() => (showModal = false)}
on:click|self={() => dialog.close()}
autofocus={false}
class="w-full lg:xl:w-5/6 rounded-lg bg-green-700/90 border-2 border-solid border-green-300/50 text-white"
>
<div on:click|stopPropagation>
<slot />
<hr />
<div class="flex">
<div class="grow"></div>
<button class="pt-4" on:click={() => dialog.close()}>
Close
</button>
</div>
</div>
</dialog>
<style>
dialog::backdrop {
background: rgba(0, 0, 0, 0.3);
}
dialog[open] {
animation: zoom 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes zoom {
from {
transform: scale(0.95);
}
to {
transform: scale(1);
}
}
dialog[open]::backdrop {
animation: fade 0.2s ease-out;
}
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>

21
src/utils/Progress.svelte Normal file
View File

@ -0,0 +1,21 @@
<script lang="ts">
export let percentage: number | 0;
export let color: string | "#FFFFFF";
</script>
<div
class="progress left rounded-md"
style="background-color: {color}; clip-path: polygon(0% 0%, 0% 100%, {percentage}% 100%, {percentage}% 0%);"
/>
<style>
.progress {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
transition: 0.4s clip-path;
clip-path: polygon(0% 0%, 0% 100%, 0% 100%, 0% 0%);
}
</style>

View File

@ -1,17 +1,24 @@
---
export interface Props {
title: string;
subtitle: string;
image: string;
link: string;
}
<script lang="ts">
export let link:string;
export let image:string;
export let title:string;
export let subtitle:string;
export let callback;
const { title, subtitle, image, link } = Astro.props;
---
let url;
if(typeof link === undefined){
url = "javascript:void(0)"
} else {
url = link;
}
if(typeof callback === undefined){
callback = ()=>{};
}
</script>
<div class="rounded-lg transition ease-in-out hover:scale-105">
<a href={link}>
<div class="rounded-lg transition ease-in-out hover:scale-[101%]">
<a href={url} on:click={callback}>
<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">
<img class="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">

View File

@ -0,0 +1,11 @@
<script lang="ts">
export let title: string;
export let subtitle: string;
</script>
<div class="px-5">
<p class="text-xl font-bold">{title}</p>
<p class="">{subtitle}</p>
<hr />
<slot />
</div>

2
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

7
svelte.config.js Normal file
View File

@ -0,0 +1,7 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
}

View File

@ -1,34 +1,20 @@
/** @type {import('tailwindcss').Config} */
const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
darkMode: ["class", "[data-theme]=business"],
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: {
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"],
},
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx,svelte,html}"
],
theme: {
extend: {
fontFamily:{
sans: ['Montserrat', defaultTheme.fontFamily.sans],
mono: ['Share Tech Mono', defaultTheme.fontFamily.mono]
}
},
dropShadow: {
'screen':'0 0 2px rgb(134,239,172)'
}
},
plugins: [],
}

View File

@ -1,3 +1,20 @@
{
"extends": "astro/tsconfigs/strict"
}
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["./**/*.d.ts", "./**/*.ts", "./**/*.js", "./**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
}

8
tsconfig.node.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node"
},
"include": ["vite.config.ts"]
}

7
vite.config.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()],
})