Initial commit

This commit is contained in:
2ManyProjects 2025-03-26 00:01:28 -05:00
commit 65ed1548a7
13 changed files with 13292 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules

12685
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

33
package.json Normal file
View file

@ -0,0 +1,33 @@
{
"name": "comet-technologies",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

18
public/index.html Normal file
View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#1a1a2e" />
<meta
name="description"
content="Comet Technologies - Innovative Solutions for Complex Problems"
/>
<title>Comet Technologies | Freelance Portfolio</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

248
src/App.css Normal file
View file

@ -0,0 +1,248 @@
/* Base Styles */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f8f9fa;
}
.app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* Header Styles */
.app-header {
background-color: #1a1a2e;
color: white;
padding: 2rem 1rem;
text-align: center;
}
.header-content {
max-width: 1200px;
margin: 0 auto;
position: relative;
}
.back-button {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: white;
font-size: 1rem;
cursor: pointer;
padding: 0.5rem;
}
.back-button:hover {
text-decoration: underline;
}
.app-header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
.tagline {
font-size: 1.1rem;
font-weight: 300;
}
/* Main Content */
.app-content {
flex: 1;
padding: 2rem 1rem;
max-width: 1200px;
margin: 0 auto;
width: 100%;
}
/* Project List */
.projects-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
}
.intro {
grid-column: 1 / -1;
margin-bottom: 1rem;
}
.intro h2 {
font-size: 2rem;
margin-bottom: 1rem;
}
.intro p {
font-size: 1.1rem;
color: #555;
}
/* Project Card */
.project-card {
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
transition: transform 0.3s ease, box-shadow 0.3s ease;
cursor: pointer;
}
.project-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
.project-card h3 {
margin-bottom: 1rem;
color: #1a1a2e;
}
.project-summary {
margin-bottom: 1.5rem;
color: #555;
}
.view-details {
text-align: right;
color: #16213e;
font-weight: 500;
}
/* Project Details */
.project-details {
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 2rem;
}
.project-details h2 {
color: #1a1a2e;
margin-bottom: 1.5rem;
font-size: 1.8rem;
}
.project-carousel {
margin-bottom: 2rem;
}
.project-description p {
margin-bottom: 1rem;
font-size: 1.05rem;
}
/* Carousel Styles */
.carousel {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
background-color: #f1f1f1;
border-radius: 4px;
overflow: hidden;
}
.carousel-image-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
position: relative;
height: 400px;
}
.carousel-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.carousel-button {
background-color: rgba(0, 0, 0, 0.5);
border: none;
color: white;
font-size: 1.5rem;
padding: 0.5rem 1rem;
cursor: pointer;
height: 100%;
z-index: 2;
}
.carousel-button:hover {
background-color: rgba(0, 0, 0, 0.7);
}
.carousel-counter {
position: absolute;
bottom: 10px;
right: 10px;
background-color: rgba(0, 0, 0, 0.6);
color: white;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
}
.carousel-loading, .carousel-empty {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
background-color: #f1f1f1;
color: #555;
font-style: italic;
}
/* Footer */
.app-footer {
background-color: #1a1a2e;
color: white;
text-align: center;
padding: 1.5rem;
margin-top: 2rem;
}
/* Responsive styles */
@media (max-width: 768px) {
.header-content {
padding: 0 1rem;
}
.back-button {
position: static;
transform: none;
margin-bottom: 1rem;
display: inline-block;
}
.project-details {
padding: 1.5rem;
}
.carousel-image-container {
height: 300px;
}
}
@media (max-width: 480px) {
.app-header h1 {
font-size: 2rem;
}
.carousel-image-container {
height: 200px;
}
}

62
src/App.js Normal file
View file

@ -0,0 +1,62 @@
import React, { useState } from 'react';
import './App.css';
import Header from './components/Header';
import ProjectCard from './components/ProjectCard';
import ProjectDetails from './components/ProjectDetails';
import projects from './data/projects';
function App() {
const [selectedProjectId, setSelectedProjectId] = useState(null);
const handleProjectClick = (projectId) => {
setSelectedProjectId(projectId);
window.scrollTo(0, 0);
};
const handleBackClick = () => {
setSelectedProjectId(null);
};
const selectedProject = selectedProjectId
? projects.find(p => p.id === selectedProjectId)
: null;
return (
<div className="app">
<Header
onBackClick={handleBackClick}
showBackButton={selectedProjectId !== null}
/>
<main className="app-content">
{selectedProject ? (
<ProjectDetails project={selectedProject} />
) : (
<div className="projects-list">
<div className="intro">
<h2>Freelance Experience</h2>
<p>
Below is a portfolio of my freelance projects.
Each project represents solutions to complex problems across various domains. If you've been given this site please feel free to contact me for more details that can't be made publicly available.
</p>
</div>
{projects.map(project => (
<ProjectCard
key={project.id}
project={project}
onClick={handleProjectClick}
/>
))}
</div>
)}
</main>
<footer className="app-footer">
<p>&copy; {new Date().getFullYear()} Comet Technologies | All Rights Reserved</p>
</footer>
</div>
);
}
export default App;

19
src/components/Header.js Normal file
View file

@ -0,0 +1,19 @@
import React from 'react';
const Header = ({ onBackClick, showBackButton }) => {
return (
<header className="app-header">
<div className="header-content">
{showBackButton && (
<button className="back-button" onClick={onBackClick}>
&larr; Back to Projects
</button>
)}
<h1>Comet Technologies</h1>
<p className="tagline">You need I'll build it</p>
</div>
</header>
);
};
export default Header;

View file

@ -0,0 +1,106 @@
import React, { useState, useEffect } from 'react';
const ImageCarousel = ({ projectId }) => {
const [images, setImages] = useState([]);
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadImages = async () => {
try {
const importAll = (r) => r.keys().map(r);
let imageContext = null;
try {
switch(projectId){
case "mycology-lab":
imageContext = require.context(`../assets/mycology-lab`, false, /\.(png|jpe?g|svg)$/);
break
case "loanterra":
imageContext = require.context(`../assets/loanterra`, false, /\.(png|jpe?g|svg)$/);
break
case "orchard-market":
imageContext = require.context(`../assets/orchard-market`, false, /\.(png|jpe?g|svg)$/);
break
case "fecal-vision-model":
imageContext = require.context(`../assets/fecal-vision-model`, false, /\.(png|jpe?g|svg)$/);
break
default:
imageContext = require.context(`../assets/mycology-lab`, false, /\.(png|jpe?g|svg)$/);
}
} catch (e) {
console.log("Error", e);
}
if(imageContext){
const imageFiles = importAll(imageContext);
setImages(imageFiles);
}
} catch (error) {
console.error("Error loading images:", error);
setImages([]);
} finally {
setLoading(false);
}
};
loadImages();
setCurrentImageIndex(0);
}, [projectId]);
const nextImage = () => {
setCurrentImageIndex((prevIndex) =>
prevIndex === images.length - 1 ? 0 : prevIndex + 1
);
};
const prevImage = () => {
setCurrentImageIndex((prevIndex) =>
prevIndex === 0 ? images.length - 1 : prevIndex - 1
);
};
if (loading) {
return <div className="carousel-loading">Loading images...</div>;
}
if (images.length === 0) {
return <div className="carousel-empty">No images available</div>;
}
return (
<div className="carousel">
<button
className="carousel-button prev"
onClick={prevImage}
aria-label="Previous image"
>
&lt;
</button>
<div className="carousel-image-container">
<img
src={images[currentImageIndex]}
alt={`Project ${projectId} - ${currentImageIndex + 1}`}
className="carousel-image"
/>
<div className="carousel-counter">
{currentImageIndex + 1} / {images.length}
</div>
</div>
<button
className="carousel-button next"
onClick={nextImage}
aria-label="Next image"
>
&gt;
</button>
</div>
);
};
export default ImageCarousel;

View file

@ -0,0 +1,17 @@
import React from 'react';
const ProjectCard = ({ project, onClick }) => {
const summary = project.description.trim().split('\n\n')[0].trim();
const summaryLength = 120;
return (
<div className="project-card" onClick={() => onClick(project.id)}>
<h3>{project.title}</h3>
<p className="project-summary">
{summary.length > summaryLength ? `${summary.substring(0, summaryLength)}...` : summary}
</p>
<div className="view-details">View Details</div>
</div>
);
};
export default ProjectCard;

View file

@ -0,0 +1,28 @@
import React from 'react';
import ImageCarousel from './ImageCarousel';
const ProjectDetails = ({ project }) => {
if (!project) {
return <div className="project-not-found">Project not found</div>;
}
const paragraphs = project.description.trim().split('\n\n');
return (
<div className="project-details">
<h2>{project.title}</h2>
<div className="project-carousel">
<ImageCarousel projectId={project.id} />
</div>
<div className="project-description">
{paragraphs.map((paragraph, index) => (
<p key={index}>{paragraph.trim()}</p>
))}
</div>
</div>
);
};
export default ProjectDetails;

64
src/data/projects.js Normal file
View file

@ -0,0 +1,64 @@
const projects = [
{
id: "loanterra",
title: "DevOps for Loanterra (2023-2025)",
imagesFolder: "/assets/loanterra",
description: `
- Setup Loanterra Cloud environment with Okta for authentication and resource provisioning.\n\n
- Ensured SOC2 Compliance through implementation of security controls and documentation.\n\n
- DNS setup for mail server and custom vanity urls.\n\n
- Integrated with Turnkey Lender for Loan management.\n\n
- Built an automated backup SQL ingester and tester on AWS to ingest SQL backups from TurnKey's Azure environment.\n\n
- Multiple Email tenancy migrations.\n\n
- Assessed old site for cost optimizations (identified payments being charged for free wordpress addons).
`
},
{
id: "fecal-vision-model",
title: "Fecal Vision Model for North of 55 Veterinary Services (2022)",
imagesFolder: "/assets/fecal-vision-model",
description: `
- Over 1 year I developed a vision model using TensorFlow that was ~90% accurate (sufficient for medical diagnoses) in fecal egg counts using standard McMaster method fecal slides. This reduced slide analysis time from 20 minutes down to 5 minutes per sample.\n\n
- Commercial alternatives like WSI systems start around $90,000USD, making this a cost-effective solution.\n\n
- I modified a microscope to automate slide scanning and created data pipeline for high-quality slide scans for training purposes.\n\n
- Using QuPath's scripting capabilities and sample annotated slides, I created a pipeline where the veterinarian only needed to take slide scans using a custom modified microscope, place the scans in a network folder, and receive analysis results in Excel format.\n\n
`
},
{
id: "orchard-market",
title: "Order Picking Software for Orchard Market (2021)",
imagesFolder: "/assets/orchard-market",
description: `
I built several pieces of software for Orchard Market, a collaboration of 5 farms sharing warehousing and storefront space.\n\n
The order picking software aggregated inventory across all 5 disparate inventory systems, allowing orders to be efficiently processed based on availability and delivery time. This reduced order fulfillment time by 15%.\n\n
Depending on the inventory system, integrations were written in either JavaScript (for Excel-based systems) or C# (Produce Pro) to send data to a central server running Croptracker.\n\n
Implemented phone image text recognition for handwritten order forms to quickly and accurately update the inventory system, reducing data entry errors by 50%. Python scripts extract handwriting from order forms, using QR codes to determine form types and apply appropriate analysis.\n\n
`
},
{
id: "mycology-lab",
title: "Mycology Lab Technical Lead (2019-2022)",
imagesFolder: "/assets/mycology-lab",
description: `
Fungal Stimuli Tester:\n\n
Built a 3-axis gantry system that moves a central platform along an x,y plane with a winch that raises/lowers the end piece.\n\n
The system features modular end effectors with multiple stimuli options including temperature probes, liquid dispensers, laser pointers, and more.\n\n
Used 8 32-bit ADCs to record signal propagation within mycelial networks, with Fast Fourier Transform for spike detection and wavelet processing methods. \n\n
Data logged to PostgreSQL for analysis. System built in C++.\n\n\n\n\n\n
Mycelium Bioreactor:\n\n
Automated bioreactor for growing liquid mycelium culture with automated temperature, oxygen, stirring, and harvesting control. Implemented sterilization systems and contamination detection using OpenCV computer vision. System built in C++.\n\n\n\n\n\n
Growth Chamber:\n\n
9-cell growth chamber with individual temperature control for each level, optimized for mycelial remediation experiments on contaminated cardboard. The system logged environmental variables and growth progress, allowing for optimization of growing conditions. System built in C++.
`
}
];
export default projects;

11
src/index.js Normal file
View file

@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './App.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);