Compare commits

...

No commits in common. "ece54771b786d68677b6b3f6c3d6af9926c64e62" and "cee7e0f0167a5c072535657f4e500b3d39c8ab61" have entirely different histories.

46 changed files with 823 additions and 6638 deletions

2
.env
View File

@ -1,2 +0,0 @@
VITE_CG_API_KEY=CG-6tRLtAyJ9t5YdthDmRPhwKRD
VITE_CG_API_URL=https://pro-api.coingecko.com/api/v3

45
.gitignore vendored
View File

@ -1,26 +1,43 @@
# Dependencies
node_modules/
/.pnp
.pnp.js
# Testing
/coverage
# Production
/build
/dist
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.env
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
.idea/
.vscode/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.codegpt
# Parcel
.cache/
.parcel-cache/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache

121
LICENSE Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

141
README.md
View File

@ -1,102 +1,77 @@
# Personal Website - Chris Wylde
# Web3 CV
A modern, responsive personal website built with React, TypeScript, and Vite. This project serves as a professional landing page and link aggregator, featuring sections for professional experience, Web3 achievements, NFT collections, and social connections.
A simple web page for my Web3 CV.
## 🚀 Features
## Deployment
- Modern React with TypeScript and Vite
- Responsive design with Tailwind CSS
- Animated sections and transitions
- Dark theme with custom gradients
- Social media integration
- NFT gallery
- Achievement showcase
- Professional experience timeline
This website is deployed to:
- [boilerhaus.org](https://boilerhaus.org) - Primary domain on VPS
- [chriswylde.xyz](https://chriswylde.xyz) - Secondary domain on Vercel (manually deployed)
## 🛠 Tech Stack
## Setup Instructions
- React 18
- TypeScript
- Vite
- Tailwind CSS
- Lucide Icons
- Recharts for data visualization
### Initial Setup
## 📦 Installation
1. Clone the repository:
```bash
git clone https://git.boilerhaus.org/boiler/Web3CV.git
cd Web3CV
```
**Clone the repository:**
2. Install dependencies:
```bash
npm install
```
```bash
git clone https://github.com/boilerrat/personal-website
cd personal-website
```
3. Set up the VPS:
```bash
./setup-vps.sh
```
This script will:
- Check if Nginx and Certbot are installed on your VPS
- Create the necessary directories
- Configure Nginx for boilerhaus.org, cloud.boilerhaus.org, and git.boilerhaus.org
- Set up SSL certificates using Let's Encrypt
- Configure Nextcloud to use cloud.boilerhaus.org (if installed)
**Install dependencies:**
4. Migrate to Gitea:
```bash
./migrate-to-gitea.sh
```
This script will:
- Create a new repository on your Gitea instance
- Push your code to Gitea
```bash
npm install
```
### Development
**Start the development server:**
1. Run the development server:
```bash
npx parcel index.html
```
```bash
npm run dev
```
2. Open [http://localhost:1234](http://localhost:1234) in your browser.
**Build for production:**
### Deployment
```bash
npm run build
```
1. Deploy to VPS:
```bash
./deploy-all.sh
```
This script will:
- Build the site
- Deploy to your VPS
- Push changes to Gitea
## 🌐 Domain Transfer to Vercel
2. Deploy only to VPS (same as above):
```bash
./deploy.sh
```
### Current Setup
## Repository Management
- Domains: [www.chriswylde.xyz](https://www.chriswylde.xyz) and [www.boilerrat.xyz](https://www.boilerra.xyz)
- Registrar: Namecheap
- Current Host: Netlify
- Target Host: Vercel
This repository is hosted on a self-hosted Gitea instance at git.boilerhaus.org.
### Steps to Transfer Domain to Vercel
### Remote Setup
1. **Add Domain to Vercel Project**
- Go to your Vercel project dashboard
- Navigate to "Settings" > "Domains"
- Add your domains: `chriswylde.xyz` and `boilerrat.xyz`
- Vercel will provide nameserver information
2. **Update Namecheap DNS Settings**
- Log in to Namecheap
- Go to "Domain List" and select your domain
- Click "Manage"
- Select "Custom DNS" under "Nameservers"
- Add Vercel's nameservers:
```bash
ns1.vercel-dns.com
ns2.vercel-dns.com
```
3. **Wait for Propagation**
- DNS changes can take up to 48 hours to propagate
- You can check propagation status in Vercel's dashboard
4. **SSL/HTTPS Setup**
- Vercel automatically provisions SSL certificates
- No additional configuration needed
## 🤝 Contributing
Contributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://github.com/boilerrat/personal-website/issues).
## 📝 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 👤 Contact
- Website: [chriswylde.xyz](https://www.chriswylde.xyz)
- Twitter: [@boilerrat](https://twitter.com/boilerrat)
- Farcaster: [@boiler](https://warpcast.com/boiler)
- Email: mailto [128boilerrat@gmail.com](mailto:128boilerrat@gmail.com)
Your remote is configured as:
- `origin`: Your Gitea repository (https://git.boilerhaus.org/boiler/Web3CV.git)

49
boilerhaus.org.conf Normal file
View File

@ -0,0 +1,49 @@
server {
listen 80;
listen [::]:80;
server_name boilerhaus.org www.boilerhaus.org;
root /var/www/boilerhaus.org;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Managed by Certbot
# This section will be updated by Certbot automatically
}
server {
listen 80;
listen [::]:80;
server_name cloud.boilerhaus.org;
# Managed by Certbot
# This section will be updated by Certbot automatically
# Proxy to Nextcloud (will be active after SSL is set up)
location / {
return 503; # Temporary unavailable until SSL is configured
}
}
server {
listen 80;
listen [::]:80;
server_name git.boilerhaus.org;
# Proxy to Gitea
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Managed by Certbot
# This section will be updated by Certbot automatically
}
# SSL configurations will be added by Certbot automatically

108
css/style.css Normal file
View File

@ -0,0 +1,108 @@
body {
background-color: #1d1d1d;
border: none;
color: #444;
font-family: "Inter",sans-serif;
font-size: 20px;
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
}
a:hover {
text-decoration: none;
}
.btn {
background: #333;
padding: 20px; /* Increase Button Height */
font-size: 1.2em; /* Increase button font size */
}
.btn:hover {
background-color: #567730;
}
.header-inner {
position: relative;
text-align: center;
}
.site-title {
font-size: 2.75em;
font-family: "Gloock",cursive; /* Updated to Gloock font */
font-style: normal;
font-weight: 400;
text-align: center;
text-shadow: 1px 1px 2px rgba(0,0,0,.25);
}
.site-title a {
color: #fff;
}
.site-title a:hover {
color: #567730;
}
.site-description {
color: #999;
font-size: 1.1em;
font-family: "Inter", sans-serif;
font-weight: 400;
line-height: 110%;
margin: 20px 0 0;
text-align: center;
text-shadow: 1px 1px 1px rgba(0,0,0,.25);
}
.image-border {
border: 10px solid #567730;
box-sizing: border-box;
}
.section-header {
color: #fff;
font-size: 2em;
font-family: "Gloock", cursive; /* Updated to Gloock font */
font-style: normal;
font-weight: 400;
text-align: center;
margin-top: .5em;
margin-bottom: .5em;
border: 1px solid #fff; /* Add a thin white border */
padding: 10px; /* Add some padding */
box-sizing: border-box; /* Include border and padding in the element's total width and height */
}
.figure img {
height: 400px; /* Set a maximum height for the images */
max-width: 100%;
object-fit: cover; /* Resize the images to cover the entire area while maintaining the aspect ratio */
}
.figure figcaption {
text-align: center; /* Center the caption */
color: #fff; /* Set the text color to white */
font-family: "Inter",sans-serif; /* Match the font with the rest of the webpage */
font-size: 1em; /* Set the font size */
}
/* Media query for screens smaller than 600px */
@media (max-width: 600px) {
body {
font-size: 18px;
}
.btn {
padding: 15px;
font-size: 1em;
}
}
/* Media query for screens smaller than 400px */
@media (max-width: 400px) {
body {
font-size: 16px;
}
.btn {
padding: 10px;
font-size: 0.8em;
}
}

19
deploy-all.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# This script deploys your website to your VPS
# Build the site
echo "Building the site..."
npx parcel build index.html
# Deploy to VPS
echo "Deploying to VPS..."
rsync -avz --delete dist/ root@boilerhaus.org:/var/www/boilerhaus.org/
# Push changes to Gitea
echo "Pushing changes to Gitea..."
git add .
git commit -m "Update website content" || echo "No changes to commit"
git push origin main
echo "Deployment complete!"

19
deploy.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# This script deploys your website to your VPS
# Build the site
echo "Building the site..."
npx parcel build index.html
# Deploy to VPS
echo "Deploying to VPS..."
rsync -avz --delete dist/ root@boilerhaus.org:/var/www/boilerhaus.org/
# Push changes to Gitea
echo "Pushing changes to Gitea..."
git add .
git commit -m "Update website content" || echo "No changes to commit"
git push origin main
echo "Deployment complete!"

View File

@ -1,28 +0,0 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)

25
fix-git-subdomain.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# This script fixes the git.boilerhaus.org subdomain configuration
# Check if we can connect to the VPS
echo "Testing connection to VPS..."
if ! ssh root@66.179.188.130 "echo 'Connection successful'"; then
echo "Failed to connect to VPS. Please check your SSH configuration."
exit 1
fi
# Upload the updated Nginx configuration
echo "Uploading updated Nginx configuration..."
scp boilerhaus.org.conf root@66.179.188.130:/etc/nginx/sites-available/boilerhaus.org
# Add git.boilerhaus.org to the SSL certificate
echo "Adding git.boilerhaus.org to the SSL certificate..."
ssh root@66.179.188.130 "certbot --nginx -d git.boilerhaus.org"
# Restart Nginx
echo "Restarting Nginx..."
ssh root@66.179.188.130 "systemctl restart nginx"
echo "Git subdomain fix complete!"
echo "You should now be able to access git.boilerhaus.org"

View File

@ -1,68 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<! DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- Preload critical fonts -->
<link
rel="preload"
href="https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&display=swap"
as="style"
/>
<link
rel="preload"
href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap"
as="style"
/>
<!-- Load fonts -->
<link
href="https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&family=Space+Mono:wght@400;700&display=swap"
rel="stylesheet"
/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css" rel="stylesheet" />
<link href="css/style.css" rel="stylesheet" /> <!-- Link to your custom CSS file -->
<script src="https://kit.fontawesome.com/56ab309fdd.js" crossorigin="anonymous"></script>
<link rel="icon" href="https://i.imgur.com/Clv3Y1dt.png" type="image/png">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Gloock&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Handjet&family=Inter&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Primary favicon -->
<link rel="icon" type="image/png" href="https://i.imgur.com/Clv3Y1d.png" />
<!-- iOS/macOS icons -->
<link rel="apple-touch-icon" href="https://i.imgur.com/Clv3Y1d.png" />
<link rel="mask-icon" href="https://i.imgur.com/Clv3Y1d.png" color="#000000" />
<!-- Windows/IE -->
<meta name="msapplication-TileImage" content="https://i.imgur.com/Clv3Y1d.png" />
<meta name="msapplication-TileColor" content="#000000" />
<!-- Viewport and theme -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />
<!-- SEO meta tags -->
<title>Chris Wylde | Web3 Futurist</title>
<meta name="description" content="Web3 Futurist, Nuclear Professional, and DAO Designer" />
<meta name="keywords" content="Web3, Nuclear, DAO, Blockchain, Radiation Protection, Boilermaker" />
<!-- Open Graph / Social Media -->
<meta property="og:type" content="website" />
<meta property="og:title" content="Chris Wylde | Web3 Futurist" />
<meta property="og:description" content="Web3 Futurist, Nuclear Professional, and DAO Designer" />
<meta property="og:image" content="https://i.imgur.com/Clv3Y1d.png" />
<meta property="og:url" content="https://www.chriswylde.xyz" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Chris Wylde | Web3 Futurist" />
<meta name="twitter:description" content="Web3 Futurist, Nuclear Professional, and DAO Designer" />
<meta name="twitter:image" content="https://i.imgur.com/Clv3Y1d.png" />
<!-- Font display optimization -->
<style>
.font-courier-prime {
font-family: 'Courier Prime', monospace;
}
.font-space-mono {
font-family: 'Space Mono', monospace;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
<div class="header-inner section-inner my-5">
<h1 class="site-title">
<a href="https://www.chriswylde.xyz/" rel="home">
<img src="https://en.gravatar.com/userimage/28803791/eb0d590383693b2cbaefb2379c9afe7a.jpeg?size=256" class="rounded-circle mx-auto d-block mb-3 image-border" />
Boiler(Chris)
</a>
</h1>
<p class="site-description">Web3 Futurist - DAO Design, Governance and Operations</p>
</div>
<div class="container">
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://rxresu.me/boilerrat/victorious-present-lungfish" target="_blank" role="button">
<i class="fa-solid fa-calendar-plus"></i> Professional Resume
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://calendly.com/boiler-chris/call-with-boilerrat" target="_blank" role="button">
<i class="fa-solid fa-calendar-plus"></i> Book Time - Calendly
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://app.ens.domains/boilerhaus.eth" target="_blank" role="button">
<i class="fa-brands fa-ethereum"></i> ENS - boilerhaus.eth
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://etherscan.io/address/0x6FeD46ed75C1165b6bf5bA21f7F507702A2691cB" target="_blank" role="button">
<i class="fa-solid fa-chart-simple"></i> Etherscan
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://www.metokens.com/profile/0x6FeD46ed75C1165b6bf5bA21f7F507702A2691cB/" target="_blank" role="button">
<i class="fa-solid fa-coins"></i> meToken
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://quest.philand.xyz/account/0x6FeD46ed75C1165b6bf5bA21f7F507702A2691cB" target="_blank" role="button">
<i class="fa-solid fa-cube"></i> PhiLand
</a>
</div>
<div class="row mb-4">
<div class="col-12">
<h2 class="section-header">Social Media and Contacts</h2>
</div>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://twitter.com/boilerrat" target="_blank" role="button">
<i class="fa-brands fa-twitter"></i> Twitter
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://warpcast.com/boiler" target="_blank" role="button">
<i class="fa-solid fa-archway"></i> Farcaster
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://lenster.xyz/u/boilerrat" target="_blank" role="button">
<i class="fa-solid fa-binoculars"></i> Lens
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://mastodon.world/@boilerrat" target="_blank" role="button">
<i class="fa-brands fa-mastodon"></i> Mastadon
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://github.com/boilerrat" target="_blank" role="button">
<i class="fa-brands fa-github"></i> Github
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://www.linkedin.com/in/christopherwylde/" target="_blank" role="button">
<i class="fa-brands fa-linkedin"></i> Linkedin
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://t.me/boilerrat" target="_blank" role="button">
<i class="fa-brands fa-telegram"></i> Telegram
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://discordapp.com/users/boiler_chris" target="_blank" role="button">
<i class="fa-brands fa-discord"></i> Discord
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="mailto:128boilerrat@gmail.com" target="_blank" role="button">
<i class="fa-solid fa-envelope"></i> Email
</a>
</div>
<div class="row mb-4">
<div class="col-12">
<h2 class="section-header">DAO Memberships</h2>
</div>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://admin.daohaus.fun/#/molochv3/0x1/0x54e1826d462570a06a6bc44a78fdfbf0568aa2f3" target="_blank" role="button">
<i class="fa-solid fa-people-roof"></i> Boiler Family DAO
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://www.daomasons.com/" target="_blank" role="button">
<i class="fa fa-chess-rook"></i> DAO Masons - Core Team, DAO Design and Operations
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://publichaus.club/" target="_blank" role="button">
<i class="fa-brands fa-fort-awesome"></i> Public HAUS - Champion (Delegate)
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://www.youtube.com/channel/UC2UTfuf0xkIrLcmdnd96bGA" target="_blank" role="button">
<i class="fa-solid fa-house"></i> DAOhaus - Warcamp (Core Contributor)
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://publicnouns.wtf/" target="_blank" role="button">
<i class="fa-solid fa-glasses"></i> Public Nouns - NFT Holder and Contributor
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://www.metacartel.org/" target="_blank" role="button">
<i class="fa-solid fa-pepper-hot"></i> Metacartel - Member and Contributor
</a>
</div>
<div class="row mb-4">
<div class="col-12">
<h2 class="section-header">Personal Profile Pictures</h2>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-lg-6 col-sm-12 mb-4">
<figure class="figure">
<img src="https://i.imgur.com/Clv3Y1d.png" class="img-fluid rounded mx-auto d-block" alt="Public Noun 237">
<figcaption class="figure-caption text-center"><a href="https://etherscan.io/nft/0x93ecac71499147627dfec6d0e494d50fcfff10ee/237">Etherscan: Public Noun 237</a></figcaption>
</figure>
</div>
<div class="col-lg-6 col-sm-12 mb-4">
<figure class="figure">
<img src="https://i.imgur.com/qvJL6Rd.png" class="img-fluid rounded mx-auto d-block" alt="Public Noun 111">
<figcaption class="figure-caption text-center"><a href="">Etherscan: Public Noun 111</a></figcaption>
</figure>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-sm-12 mb-4">
<figure class="figure">
<img src="https://i.imgur.com/BRXIjWr.png" class="img-fluid rounded mx-auto d-block" alt="Public Noun 65">
<figcaption class="figure-caption text-center"><a href="https://etherscan.io/nft/0x93ecac71499147627dfec6d0e494d50fcfff10ee/65">Etherscan: Public Noun 65</a></figcaption>
</figure>
</div>
<div class="col-lg-6 col-sm-12 mb-4">
<figure class="figure">
<img src="https://i.imgur.com/adQSaCJ.png" class="img-fluid rounded mx-auto d-block" alt="Boiler's Jerry">
<figcaption class="figure-caption text-center"><a href="https://etherscan.io/nft/0x42c4a46bd0f9c642e852a848a5b730b6e3086ccf/32">Etherscan: Boiler's Jerry</a></figcaption>
</figure>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fontawesome-iconpicker/3.2.0/js/fontawesome-iconpicker.min.js"></script>
</body>
</html>

52
migrate-to-gitea.sh Executable file
View File

@ -0,0 +1,52 @@
#!/bin/bash
# This script helps migrate your repository to your self-hosted Gitea
# First, check if we can connect to the Gitea server
echo "Testing connection to Gitea server..."
if ! curl -s https://git.boilerhaus.org > /dev/null; then
echo "Failed to connect to Gitea server. Please check your network connection."
exit 1
fi
# Create a new repository on Gitea using the API
echo "Creating a new repository on Gitea..."
# You'll need to generate an API token from your Gitea instance
# Generate one at https://git.boilerhaus.org/user/settings/applications
read -p "Enter your Gitea API token: " API_TOKEN
if [ -z "$API_TOKEN" ]; then
echo "API token is required. Please generate one at https://git.boilerhaus.org/user/settings/applications"
exit 1
fi
curl -X POST "https://git.boilerhaus.org/api/v1/user/repos" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: token $API_TOKEN" \
-d "{\"name\":\"Web3CV\",\"description\":\"My Web3 CV website\",\"private\":false,\"auto_init\":false}"
# Wait a moment for the repository to be created
sleep 2
# Reset remotes
echo "Resetting remotes..."
git remote remove origin 2>/dev/null || true
git remote remove github 2>/dev/null || true
git remote remove gitea 2>/dev/null || true
# Add Gitea as the only remote
echo "Adding Gitea as the remote..."
git remote add origin https://git.boilerhaus.org/boiler/Web3CV.git
# Verify the remote was added
echo "Verifying remote..."
git remote -v
# Push all branches and tags to Gitea
echo "Pushing to Gitea..."
git push -u origin main
git push --tags origin
echo "Migration to Gitea complete!"
echo "Your repository is now available at: https://git.boilerhaus.org/boiler/Web3CV"

4663
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
{
"name": "portfolio",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"lucide-react": "^0.469.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.4.0",
"recharts": "^2.15.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.17.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",
"vite": "^6.0.5"
}
}

View File

@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

60
push-in-chunks.sh Executable file
View File

@ -0,0 +1,60 @@
#!/bin/bash
# This script pushes your repository in smaller chunks to avoid HTTP 413 errors
# Set the remote and branch
REMOTE="origin"
BRANCH="main"
# Get the list of commits in reverse order (oldest first)
COMMITS=$(git rev-list --reverse HEAD)
# Initialize variables
LAST_PUSHED=""
CHUNK_SIZE=10
TOTAL_COMMITS=$(echo "$COMMITS" | wc -l)
CURRENT=0
echo "Total commits to push: $TOTAL_COMMITS"
# Process commits in chunks
for COMMIT in $COMMITS; do
CURRENT=$((CURRENT + 1))
# If this is the first commit or we've reached the chunk size
if [ -z "$LAST_PUSHED" ] || [ $((CURRENT % CHUNK_SIZE)) -eq 0 ] || [ $CURRENT -eq $TOTAL_COMMITS ]; then
echo "Pushing commit $CURRENT of $TOTAL_COMMITS: $COMMIT"
if [ -z "$LAST_PUSHED" ]; then
# For the first commit, create a new branch at this commit and push it
git checkout -b temp_branch $COMMIT
git push -f $REMOTE temp_branch:$BRANCH
LAST_PUSHED=$COMMIT
git checkout $BRANCH
git branch -D temp_branch
else
# For subsequent chunks, push the range from last pushed to current
git checkout -b temp_branch $COMMIT
git push -f $REMOTE temp_branch:$BRANCH
LAST_PUSHED=$COMMIT
git checkout $BRANCH
git branch -D temp_branch
fi
# Check if the push was successful
if [ $? -ne 0 ]; then
echo "Error pushing commit $COMMIT. Exiting."
exit 1
fi
echo "Successfully pushed up to commit $CURRENT of $TOTAL_COMMITS"
sleep 2 # Give the server a short break
fi
done
echo "All commits pushed successfully!"
git push -f $REMOTE $BRANCH
# Return to the original branch
git checkout $BRANCH
echo "Done! Your repository should now be fully pushed to $REMOTE/$BRANCH"

107
setup-vps.sh Executable file
View File

@ -0,0 +1,107 @@
#!/bin/bash
# This script helps set up your VPS for hosting your website and Nextcloud
# Check if we can connect to the VPS
echo "Testing connection to VPS..."
if ! ssh root@66.179.188.130 "echo 'Connection successful'"; then
echo "Failed to connect to VPS. Please check your SSH configuration."
exit 1
fi
# Check if Nginx is installed
echo "Checking if Nginx is installed..."
if ! ssh root@66.179.188.130 "which nginx > /dev/null"; then
echo "Nginx is not installed. Installing..."
ssh root@66.179.188.130 "apt update && apt install -y nginx"
else
echo "Nginx is already installed."
fi
# Check if Certbot is installed
echo "Checking if Certbot is installed..."
if ! ssh root@66.179.188.130 "which certbot > /dev/null"; then
echo "Certbot is not installed. Installing..."
ssh root@66.179.188.130 "apt update && apt install -y certbot python3-certbot-nginx"
else
echo "Certbot is already installed."
fi
# Create directory for the website
echo "Creating directory for the website..."
ssh root@66.179.188.130 "mkdir -p /var/www/boilerhaus.org"
# Create a temporary Nginx configuration without SSL
echo "Creating temporary Nginx configuration..."
cat > temp-boilerhaus.org.conf << EOF
server {
listen 80;
listen [::]:80;
server_name boilerhaus.org www.boilerhaus.org cloud.boilerhaus.org git.boilerhaus.org;
root /var/www/boilerhaus.org;
index index.html;
location / {
try_files \$uri \$uri/ =404;
}
}
EOF
# Upload temporary Nginx configuration
echo "Uploading temporary Nginx configuration..."
scp temp-boilerhaus.org.conf root@66.179.188.130:/etc/nginx/sites-available/boilerhaus.org
# Enable the site
echo "Enabling the site..."
ssh root@66.179.188.130 "ln -sf /etc/nginx/sites-available/boilerhaus.org /etc/nginx/sites-enabled/boilerhaus.org"
# Restart Nginx with temporary configuration
echo "Restarting Nginx with temporary configuration..."
ssh root@66.179.188.130 "systemctl restart nginx"
# Check for existing Nextcloud configuration
echo "Checking for existing Nextcloud configuration..."
if ssh root@66.179.188.130 "[ -d /var/www/nextcloud ]"; then
echo "Nextcloud directory found. Assuming Nextcloud is already installed."
# Ask if user wants to move Nextcloud to cloud subdomain
read -p "Do you want to move Nextcloud to cloud.boilerhaus.org? (y/n): " move_nextcloud
if [[ $move_nextcloud == "y" ]]; then
echo "Updating Nextcloud configuration..."
ssh root@66.179.188.130 "sed -i 's/\"trusted_domains\".*$/\"trusted_domains\" => [\"cloud.boilerhaus.org\"],/' /var/www/nextcloud/config/config.php"
fi
else
echo "Nextcloud directory not found. Please install Nextcloud manually after setting up the domains."
fi
# Check for Gitea port
echo "Checking if Gitea is running..."
if ssh root@66.179.188.130 "netstat -tuln | grep -q ':3000'"; then
echo "Gitea appears to be running on port 3000."
else
echo "Warning: Gitea doesn't seem to be running on the expected port (3000)."
echo "Please make sure Gitea is installed and running before proceeding."
read -p "Continue anyway? (y/n): " continue_anyway
if [[ $continue_anyway != "y" ]]; then
echo "Setup aborted. Please install and configure Gitea first."
exit 1
fi
fi
# Set up SSL certificates
echo "Setting up SSL certificates..."
ssh root@66.179.188.130 "certbot --nginx -d boilerhaus.org -d www.boilerhaus.org -d cloud.boilerhaus.org -d git.boilerhaus.org"
# Now upload the final configuration with SSL
echo "Uploading final Nginx configuration with SSL..."
scp boilerhaus.org.conf root@66.179.188.130:/etc/nginx/sites-available/boilerhaus.org
# Restart Nginx with final configuration
echo "Restarting Nginx with final configuration..."
ssh root@66.179.188.130 "systemctl restart nginx"
# Clean up temporary file
rm temp-boilerhaus.org.conf
echo "VPS setup complete!"

View File

@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@ -1,63 +0,0 @@
import type { FC } from 'react';
import { Hero } from './components/Hero';
import { Navigation } from './components/Navigation';
import { Expertise } from './components/Expertise';
import { Achievements } from './components/Achievements';
import { Skills } from './components/Skills';
import { NFTGrid } from './components/NFTGrid';
import { Contact } from './components/Contact';
import ReactorStatus from './components/ReactorStatus';
const App: FC = () => {
return (
<div className="bg-black font-['Space_Mono']">
<Navigation />
<main className="relative">
{/* Hero keeps min-h-screen as it's the landing section */}
<section id="home" className="relative z-10 min-h-screen font-['Courier_Prime']">
<Hero />
</section>
{/* Content sections with reduced spacing */}
<div className="space-y-16 relative z-10"> {/* Main content wrapper with consistent spacing */}
<section className="py-12">
<Expertise />
</section>
<section className="py-12">
<ReactorStatus />
</section>
<section className="py-12">
<Achievements />
</section>
<section className="py-12">
<Skills />
</section>
<section className="py-12">
<NFTGrid />
</section>
<section className="py-12">
<Contact />
</section>
</div>
{/* Background Elements */}
<div className="fixed inset-0 pointer-events-none">
{/* Gradient Orbs */}
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-blue-500 rounded-full mix-blend-multiply filter blur-xl opacity-10 animate-float" />
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-purple-500 rounded-full mix-blend-multiply filter blur-xl opacity-10 animate-float-delayed" />
{/* Grid Pattern */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#1a1a1a_1px,transparent_1px),linear-gradient(to_bottom,#1a1a1a_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
</div>
</main>
</div>
);
};
export default App;

View File

@ -1,115 +0,0 @@
import React from 'react';
import { BarChart3, Award } from 'lucide-react';
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import { AchievementsProps, GrantChartProps, AchievementCardProps, TooltipProps } from './types';
import { Grant, Achievement } from '../../types';
const defaultGrants: Grant[] = [
{ name: 'DAOMasons (Plurality Labs)', amount: 150000, color: '#2aae91' },
{ name: 'DAOHaus (Moloch DAO)', amount: 90000, color: '#6366f1' },
{ name: 'DAOhaus (Optimism)', amount: 90000, color: '#ec4899' },
{ name: 'Farcastle (Public Nouns)', amount: 17000, color: '#06b6d4' },
];
const defaultAchievements: Achievement[] = [
{ title: "Grant Ships Launch", description: "Distributed $100K ARB tokens to fund Web3 game development on Arbitrum", date: "2024" },
{ title: "DAO Masons", description: "Co-founded DAO Masons, a product and services DAO, designed to make DAOs better", date: "2023" },
{ title: "Public HAUS Champion", description: "Led governance activities and secured multiple major grants", date: "2022" },
{ title: "Metaguide", description: "Guided Web3 projects in securing grants, building communities, and facilitating funding for blockchain initiatives.", date: "2021" },
{ title: "Radiation Protection Technician: EPRI Certification", description: "Qualified as Radiation Protection Technician at OPG and later at Bruce Power", date: "2016/2019" },
{ title: "Boilermaker Red Seal", description: "Completed Boilermaker apprenticeship and passed Red Seal Exam", date: "2006" },
];
const CustomTooltip: React.FC<TooltipProps> = ({ active, payload }) => {
if (active && payload && payload.length) {
return (
<div className="bg-gray-800 p-4 rounded-lg border border-gray-700">
<p className="text-white font-medium">{payload[0].payload.name}</p>
<p className="text-blue-400">${payload[0].value.toLocaleString()}</p>
</div>
);
}
return null;
};
interface CustomYAxisTickProps {
x: number;
y: number;
payload: { value: string };
}
const CustomYAxisTick: React.FC<CustomYAxisTickProps> = ({ x, y, payload }) => {
const truncatedText = payload.value.length > 20 ? `${payload.value.slice(0, 20)}...` : payload.value;
return (
<text x={x - 10} y={y} fill="#FFFFFF" fontSize={12} dy={5} textAnchor="end">
{truncatedText}
</text>
);
};
const GrantChart: React.FC<GrantChartProps> = ({ data, className = '' }) => (
<div className={`bg-gray-900 rounded-lg p-6 h-80 ${className}`}>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data} layout="vertical">
<XAxis
type="number"
domain={[0, 150000]}
tickFormatter={(val) => `$${val.toLocaleString()}`}
axisLine={false}
tickLine={false}
/>
<YAxis
dataKey="name"
type="category"
width={200}
tick={(props) => <CustomYAxisTick {...props} />}
/>
<Tooltip content={<CustomTooltip />} />
<Bar dataKey="amount" fill="#3b82f6" radius={[0, 4, 4, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
);
const AchievementCard: React.FC<AchievementCardProps> = ({ title, description, date, className = '' }) => (
<div className={`group bg-gray-900 rounded-lg p-6 hover:bg-gray-800 transition-colors duration-300 ${className}`}>
<div className="flex flex-col h-full">
<div className="flex justify-between items-start mb-4">
<h3 className="text-xl font-semibold text-white">{title}</h3>
<span className="text-sm text-gray-500">{date}</span>
</div>
<p className="text-gray-400 flex-grow">{description}</p>
</div>
</div>
);
export const Achievements: React.FC<AchievementsProps> = ({ className = '', grants = defaultGrants, achievements = defaultAchievements }) => {
return (
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto space-y-12">
<div className="space-y-6">
<div className="flex items-center gap-4">
<BarChart3 size={32} className="text-green-500" />
<h2 className="text-3xl font-bold bg-gradient-to-r from-green-500 to-blue-500 bg-clip-text text-transparent">
Grants Secured
</h2>
</div>
<GrantChart data={grants} />
</div>
<div className="space-y-6">
<div className="flex items-center gap-4">
<Award size={32} className="text-yellow-500" />
<h2 className="text-3xl font-bold bg-gradient-to-r from-yellow-500 to-orange-500 bg-clip-text text-transparent">
Key Achievements
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{achievements.map((achievement, index) => (
<AchievementCard key={index} {...achievement} />
))}
</div>
</div>
</div>
</div>
);
};

View File

@ -1,18 +0,0 @@
import type { Grant, Achievement, TooltipProps as SharedTooltipProps } from '../../types';
export interface AchievementsProps {
className?: string;
grants?: Grant[];
achievements?: Achievement[];
}
export interface GrantChartProps {
data: Grant[];
className?: string;
}
export interface AchievementCardProps extends Achievement {
className?: string;
}
export type TooltipProps = SharedTooltipProps;

View File

@ -1,152 +0,0 @@
import type { FC } from 'react';
import {
MdEmail,
MdCalendarToday,
MdLanguage
} from 'react-icons/md';
import {
FaGithub,
FaLinkedin,
FaTelegram
} from 'react-icons/fa';
import { FaXTwitter } from "react-icons/fa6";
import { SiFarcaster } from "react-icons/si";
import { BiSearch } from "react-icons/bi";
import type { ContactProps, SocialLinkCardProps, CategorySectionProps } from './types';
const SocialLinkCard: FC<SocialLinkCardProps> = ({
name,
icon: Icon,
url,
username,
className = ''
}) => (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className={`group bg-gray-900 rounded-lg p-4 hover:bg-gray-800 transition-all duration-300 ${className}`}
>
<div className="flex items-center gap-4">
<div className="p-2 bg-gray-800 rounded-lg group-hover:bg-gray-700 transition-colors">
<Icon size={20} className="text-blue-400" />
</div>
<div>
<div className="text-sm text-gray-400">{name}</div>
<div className="text-white">{username}</div>
</div>
</div>
</a>
);
const CategorySection: FC<CategorySectionProps> = ({
category,
links,
className = ''
}) => (
<div className={`space-y-4 ${className}`}>
<h3 className="text-xl font-medium text-gray-400">
{category}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{links.map((link, index) => (
<SocialLinkCard key={index} {...link} />
))}
</div>
</div>
);
export const Contact: FC<ContactProps> = ({
className = '',
socialLinks = defaultSocialLinks
}) => {
return (
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto">
<div className="flex items-center gap-4 mb-6">
<h2 className="text-3xl font-bold bg-gradient-to-r from-green-500 to-blue-500 bg-clip-text text-transparent">
Connect With Me
</h2>
</div>
<div className="grid gap-8">
{socialLinks.map((category, index) => (
<CategorySection
key={index}
category={category.category}
links={category.links}
/>
))}
</div>
</div>
</div>
);
};
const defaultSocialLinks = [
{
category: "Professional",
links: [
{
name: "Email",
icon: MdEmail,
url: "mailto:128boilerrat@gmail.com",
username: "128boilerrat@gmail.com"
},
{
name: "Calendar",
icon: MdCalendarToday,
url: "https://calendly.com/boiler-chris/call-with-boilerrat",
username: "Schedule a call"
},
{
name: "Resume",
icon: MdLanguage,
url: "https://rxresu.me/boilerrat/victorious-present-lungfish",
username: "Download PDF"
}
]
},
{
category: "Social",
links: [
{
name: "X",
icon: FaXTwitter,
url: "https://twitter.com/boilerrat",
username: "@boilerrat"
},
{
name: "GitHub",
icon: FaGithub,
url: "https://github.com/boilerrat",
username: "boilerrat"
},
{
name: "LinkedIn",
icon: FaLinkedin,
url: "https://www.linkedin.com/in/christopherwylde/",
username: "christopherwylde"
},
{
name: "Telegram",
icon: FaTelegram,
url: "https://t.me/boilerrat",
username: "@boilerrat"
},
{
name: "Farcaster",
icon: SiFarcaster,
url: "https://warpcast.com/boiler",
username: "boiler"
},
{
name: "Lens",
icon: BiSearch,
url: "https://lenster.xyz/u/boilerrat",
username: "boilerrat"
}
]
}
];
export default Contact;

View File

@ -1,33 +0,0 @@
import type { IconType } from 'react-icons';
// Define SocialLink type
export interface SocialLink {
name: string;
icon: IconType; // Correct type for react-icons components
url: string;
username?: string; // Make this optional
}
// Define LinkCategory type
export interface LinkCategory {
category: string;
links: SocialLink[];
}
// Update ContactProps to use LinkCategory[]
export interface ContactProps {
className?: string;
socialLinks?: LinkCategory[];
}
// Update SocialLinkCardProps to extend SocialLink
export interface SocialLinkCardProps extends SocialLink {
className?: string;
}
// Update CategorySectionProps to use the updated SocialLink type
export interface CategorySectionProps {
category: string;
links: SocialLink[];
className?: string;
}

View File

@ -1,143 +0,0 @@
import React from 'react';
import { Atom, Database, Shield, Users, Wrench, Factory, Coins, BatteryCharging } from 'lucide-react';
import { ExpertiseProps, ExperienceCardProps, SectionProps } from './types';
import { ExperienceItem } from '../../types';
const ExperienceCard: React.FC<ExperienceCardProps> = ({
icon: Icon,
title,
company,
period,
description,
className = '',
gradientFrom
}) => (
<div className={`bg-gray-900 rounded-lg p-6 hover:bg-gray-800 transition-colors duration-300 ${className}`}>
<div className="flex items-start gap-4">
<div className={`p-3 bg-${gradientFrom}/10 rounded-lg`}>
<Icon size={24} className={`text-${gradientFrom}`} />
</div>
<div>
<h3 className="text-xl font-semibold text-white">{title}</h3>
<p className="text-gray-400 mt-1">{company}</p>
<p className="text-sm text-gray-500 mt-1">{period}</p>
<p className="text-gray-300 mt-3">{description}</p>
</div>
</div>
</div>
);
const Section: React.FC<SectionProps> = ({
title,
icon: Icon,
items,
gradientFrom,
gradientTo,
id
}) => (
<section id={id} className="space-y-6">
<div className="flex items-center gap-4">
<Icon size={32} className={`text-${gradientFrom}`} />
<h2 className={`text-3xl font-bold bg-gradient-to-r from-${gradientFrom} to-${gradientTo} bg-clip-text text-transparent`}>
{title}
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{items.map((exp, index) => (
<ExperienceCard key={index} {...exp} gradientFrom={gradientFrom} />
))}
</div>
</section>
);
const defaultIndustryExperience: ExperienceItem[] = [
{
title: "Journeyman Boilermaker",
company: "International Brotherhood of Boilermakers",
period: "2003 - Present",
description: "Two decades of experience across industrial sectors, specializing in pressure vessels, heat exchangers, and critical infrastructure. Led teams in major projects nationwide.",
icon: Wrench
},
{
title: "Industrial Maintenance and Construction",
company: "Multiple Sectors",
period: "2003 - Present",
description: "Extensive experience in oil refineries, steel mills, and power generation facilities. Specialized in maintenance, repairs, and large-scale equipment installations.",
icon: Factory
}
];
const defaultNuclearExperience: ExperienceItem[] = [
{
title: "Radiation Protection",
company: "Makwa-Cahill/Nuvia",
period: "2019 - Present",
description: "Radiation Protection Technician for Bruce Nuclear's Major Component Replacement Project. Managing radiological safety for one of North America's largest nuclear refurbishment projects.",
icon: Shield
},
{
title: "Nuclear Maintenance and Refurbishment",
company: "Multiple Nuclear Facilities",
period: "2003 - Present",
description: "20 years of nuclear experience including Pickering, Darlington, and Bruce Power. Specialized focus on reactor refurbishment and radiation protection for the last 8 years.",
icon: BatteryCharging
}
];
const defaultWeb3Experience: ExperienceItem[] = [
{
title: "DAO Governance and Operations",
company: "Public HAUS",
period: "2022 - Present",
description: "Secured $197K in grants through strategic partnerships with Moloch DAO, Optimism, and Public Nouns. Pioneering governance frameworks and community development initiatives.",
icon: Users
},
{
title: "Web3 Contributor",
company: "DAO Masons & MetaCartel",
period: "2022 - 2024",
description: "Founded DAO Masons, led Grant Ships program distributing $100K ARB tokens. Active contributor to MetaCartel, driving Web3 adoption and innovation in DAOs.",
icon: Coins
}
];
export const Expertise: React.FC<ExpertiseProps> = ({
className = '',
nuclearExperience = defaultNuclearExperience,
web3Experience = defaultWeb3Experience
}) => {
return (
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto space-y-12">
<Section
id="industry"
title="Industrial Expertise"
icon={Factory}
items={defaultIndustryExperience}
gradientFrom="cyan-500"
gradientTo="indigo-500"
/>
<Section
id="nuclear"
title="Nuclear Expertise"
icon={Atom}
items={nuclearExperience}
gradientFrom="emerald-400"
gradientTo="blue-500"
/>
<Section
id="web3"
title="Web3 Innovation"
icon={Database}
items={web3Experience}
gradientFrom="purple-500"
gradientTo="pink-500"
/>
</div>
</div>
);
};
export default Expertise;

View File

@ -1,22 +0,0 @@
import type { ExperienceItem } from '../../types';
import type { LucideIcon } from 'lucide-react';
export interface ExpertiseProps {
className?: string;
nuclearExperience?: ExperienceItem[];
web3Experience?: ExperienceItem[];
}
export interface ExperienceCardProps extends ExperienceItem {
className?: string;
gradientFrom?: string; // Add this for icon color customization
}
export interface SectionProps {
id: string;
title: string;
icon: LucideIcon;
items: ExperienceItem[];
gradientFrom: string;
gradientTo: string;
}

View File

@ -1,129 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Terminal, ExternalLink, AtSign, Calendar } from 'lucide-react';
import { SiFarcaster } from "react-icons/si";
import { HeroProps, QuickLink, AnimationState } from './types';
const defaultIntroText = `> const profile = {
name: "Christopher Wylde",
alias: "boiler, boilerrat",
roles: [
"Radiation Protection Specialist",
"Journeyman Boilermaker",
"Web3 Innovator",
"DAO Designer"
],
expertise: {
boilermaker: "20+ years across industrial sectors, nationwide experience",
nuclear: "20 years total, 8 years specialized in radiation protection",
web3: "Lead multiple DAOs, $300K+ in grants secured and distributed"
}
};`;
const quickLinks: QuickLink[] = [
{
icon: ExternalLink,
label: "Full Resume",
href: "https://rxresu.me/boilerrat/victorious-present-lungfish",
external: true
},
{
icon: AtSign,
label: "email",
href: "mailto:128boilerrat@gmail.com"
},
{
icon: Calendar,
label: "Schedule a Call",
href: "https://calendly.com/boiler-chris/call-with-boilerrat",
external: true
},
{
icon: SiFarcaster,
label: "DM on Warpcast",
href: "https://www.warpcast.com/boiler",
external: true
}
];
export const Hero: React.FC<HeroProps> = ({
className = '',
customIntroText
}) => {
const [animationState, setAnimationState] = useState<AnimationState>({
text: '',
showContent: false
});
const introText = customIntroText || defaultIntroText;
useEffect(() => {
let currentIndex = 0;
const typeWriter = setInterval(() => {
if (currentIndex < introText.length) {
setAnimationState(prev => ({
...prev,
text: prev.text + introText[currentIndex]
}));
currentIndex++;
} else {
clearInterval(typeWriter);
setTimeout(() => setAnimationState(prev => ({
...prev,
showContent: true
})), 500);
}
}, 50);
return () => clearInterval(typeWriter);
}, [introText]);
return (
<div className={`min-h-screen bg-black text-white flex flex-col items-center justify-center ${className}`}>
<div className="w-full max-w-3xl px-4">
{/* Terminal Window */}
<div className="bg-gray-900 rounded-lg overflow-hidden shadow-xl mb-6">
<div className="bg-gray-800 px-4 py-2 flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-500"></div>
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
<div className="w-3 h-3 rounded-full bg-green-500"></div>
<Terminal className="ml-4" size={14} />
</div>
<div className="p-4 font-mono text-sm">
<pre className="whitespace-pre-wrap">{animationState.text}</pre>
</div>
</div>
{/* Quick Links Section */}
{animationState.showContent && (
<div className="space-y-6 opacity-0 animate-fade-in">
<div className="text-center">
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent mb-3">
Bridging Industry & Web3
</h1>
<p className="text-gray-400 text-lg">
Bringing decades of industrial expertise to decentralized innovation
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{quickLinks.map((link, index) => (
<a
key={index}
href={link.href}
target={link.external ? "_blank" : undefined}
rel={link.external ? "noopener noreferrer" : undefined}
className="flex items-center gap-3 p-4 bg-gray-800 rounded-lg hover:bg-gray-700 transition-colors"
>
<link.icon size={20} />
<span>{link.label}</span>
</a>
))}
</div>
</div>
)}
</div>
</div>
);
};
export default Hero;

View File

@ -1,19 +0,0 @@
import { LucideIcon } from 'lucide-react';
import { IconType } from 'react-icons';
export interface HeroProps {
className?: string;
customIntroText?: string;
}
export interface QuickLink {
icon: LucideIcon | IconType; // Allow both Lucide and React icons
label: string;
href: string;
external?: boolean;
}
export interface AnimationState {
text: string;
showContent: boolean;
}

View File

@ -1,111 +0,0 @@
import React, { useState } from 'react';
import { ExternalLink } from 'lucide-react';
import { NFTGridProps, NFTCardProps } from './types';
import { NFT } from '../../types';
const NFTCard: React.FC<NFTCardProps> = ({
image,
name,
link,
isHovered,
onHover,
id,
className = ''
}) => (
<div
className={`relative group ${className}`}
onMouseEnter={() => onHover(id)}
onMouseLeave={() => onHover(null)}
>
{/* NFT Card */}
<div className="relative overflow-hidden rounded-lg bg-gray-900 aspect-square">
{/* Image */}
<img
src={image}
alt={name}
className="w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-500"
/>
{/* Overlay */}
<div
className={`absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent
transition-opacity duration-300 flex items-end justify-between p-6
${isHovered ? 'opacity-100' : 'opacity-0'}`}
>
<h3 className="text-white text-xl font-medium">{name}</h3>
{link && (
<a
href={link}
target="_blank"
rel="noopener noreferrer"
className="text-white hover:text-blue-400 transition-colors"
>
<ExternalLink size={24} />
</a>
)}
</div>
</div>
{/* Glow Effect */}
<div className={`absolute inset-0 rounded-lg bg-blue-500 opacity-0 group-hover:opacity-20
blur-xl transition-opacity duration-500 -z-10`} />
</div>
);
const defaultNFTs: NFT[] = [
{
id: 1,
image: "https://i.imgur.com/Clv3Y1d.png",
name: "Public Noun 237",
link: "https://etherscan.io/nft/0x93ecac71499147627dfec6d0e494d50fcfff10ee/237"
},
{
id: 2,
image: "https://i.imgur.com/qvJL6Rd.png",
name: "Public Noun 111",
link: ""
},
{
id: 3,
image: "https://i.imgur.com/BRXIjWr.png",
name: "Public Noun 65",
link: "https://etherscan.io/nft/0x93ecac71499147627dfec6d0e494d50fcfff10ee/65"
},
{
id: 4,
image: "https://i.imgur.com/adQSaCJ.png",
name: "Boiler's Jerry",
link: "https://etherscan.io/nft/0x42c4a46bd0f9c642e852a848a5b730b6e3086ccf/32"
}
];
export const NFTGrid: React.FC<NFTGridProps> = ({
className = '',
nfts = defaultNFTs
}) => {
const [hoveredNFT, setHoveredNFT] = useState<number | null>(null);
return (
<div className={`bg-black ${className}`}>
<div className="max-w-6xl w-full mx-auto">
<div className="mb-6">
<div className="flex items-center gap-4">
<h2 className="text-3xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
PFP's
</h2>
</div>
<p className="text-gray-400 text-lg mt-2">My Online Appearances</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{nfts.map((nft) => (
<NFTCard
key={nft.id}
{...nft}
isHovered={hoveredNFT === nft.id}
onHover={setHoveredNFT}
/>
))}
</div>
</div>
</div>
);
};

View File

@ -1,12 +0,0 @@
import { NFT } from '../../types';
export interface NFTGridProps {
className?: string;
nfts?: NFT[];
}
export interface NFTCardProps extends NFT {
isHovered: boolean;
onHover: (id: number | null) => void;
className?: string;
}

View File

@ -1,99 +0,0 @@
import React, { useState, useEffect, useCallback } from 'react';
import { Home, Atom, Database, Award, Image, User, Factory, Briefcase } from 'lucide-react';
import { NavItem } from '../../types';
import { NavigationProps, ScrollState } from './types';
export const Navigation: React.FC<NavigationProps> = ({
className = '',
initialActiveSection = 'home',
navItems: customNavItems
}) => {
const [scrollState, setScrollState] = useState<ScrollState>({
progress: 0,
activeSection: initialActiveSection
});
const defaultNavItems: NavItem[] = [
{ id: 'home', icon: Home, label: 'Home' },
{ id: 'industry', icon: Factory, label: 'Industry' },
{ id: 'nuclear', icon: Atom, label: 'Nuclear' },
{ id: 'web3', icon: Database, label: 'Web3' },
{ id: 'achievements', icon: Award, label: 'Achievements' },
{ id: 'skills', icon: Briefcase, label: 'Skills' },
{ id: 'nfts', icon: Image, label: 'NFTs' },
{ id: 'contact', icon: User, label: 'Contact' }
];
const navItems = customNavItems || defaultNavItems;
const handleScroll = useCallback(() => {
// Calculate scroll progress
const totalHeight = document.documentElement.scrollHeight - window.innerHeight;
const progress = (window.scrollY / totalHeight) * 100;
// Update active section based on scroll position
const sections = document.querySelectorAll('section');
let newActiveSection = scrollState.activeSection;
sections.forEach(section => {
const rect = section.getBoundingClientRect();
if (rect.top >= 0 && rect.top <= window.innerHeight / 2) {
newActiveSection = section.id;
}
});
setScrollState({ progress, activeSection: newActiveSection });
}, [scrollState.activeSection]);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);
const scrollToSection = (id: string) => {
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
};
return (
<nav className={`fixed right-4 top-1/2 transform -translate-y-1/2 z-50 ${className}`}>
{/* Progress Bar */}
<div className="absolute left-0 top-0 w-0.5 h-full bg-gray-800">
<div
className="bg-blue-500"
style={{ height: `${scrollState.progress}%`, transition: 'height 0.1s' }}
/>
</div>
{/* Navigation Items */}
<div className="relative flex flex-col gap-6">
{navItems.map(({ id, icon: Icon, label }) => (
<button
key={id}
onClick={() => scrollToSection(id)}
className={`group relative flex items-center ${
scrollState.activeSection === id ? 'text-blue-500' : 'text-gray-400'
}`}
>
<div className={`p-2 rounded-full transition-all duration-300
${scrollState.activeSection === id
? 'bg-gray-800 text-blue-500'
: 'hover:bg-gray-800 hover:text-blue-500'}`}>
<Icon size={20} />
</div>
<span className="absolute right-full mr-4 px-2 py-1 bg-gray-800
rounded-md text-sm opacity-0 group-hover:opacity-100
transition-opacity duration-300 whitespace-nowrap">
{label}
</span>
</button>
))}
</div>
</nav>
);
};
export default Navigation;

View File

@ -1,13 +0,0 @@
import { NavItem } from '../../types';
export interface NavigationProps {
className?: string; // Optional: for additional styling
initialActiveSection?: string; // Optional: to set initial active section
navItems?: NavItem[]; // Optional: to override default nav items
}
// Local component-specific types
export interface ScrollState {
progress: number;
activeSection: string;
}

View File

@ -1,298 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Activity, Power, Atom, TrendingUp, Users } from 'lucide-react';
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import type { ReactorStatusProps } from './types';
const statusMessages = [
'Consensus protocols maintaining criticality',
'Smart contracts properly shielded',
'DAOs operating at optimal temperature',
'Governance tokens well-contained',
'Web3 radiation levels nominal',
'Blockchain fully operational',
'Decentralized cooling systems engaged',
'Token emissions within parameters',
'NFT containment field stable',
'Reactor governance optimized'
];
// Simulated data for the career timeline
// Generate simulated stability data
const generateStabilityData = () => {
const points = 20;
const data = [];
for (let i = 0; i < points; i++) {
data.push({
time: i,
value: 50 + Math.sin(i / 2) * 20 + (Math.random() * 10 - 5)
});
}
return data;
};
// Generate simulated neutron flux data
const generateFluxData = () => {
const points = 20;
const data = [];
for (let i = 0; i < points; i++) {
data.push({
time: i,
value: 70 + Math.cos(i / 3) * 15 + (Math.random() * 8 - 4)
});
}
return data;
};
const generateTimelineData = () => {
const data = [];
const startYear = 2003;
const currentYear = new Date().getFullYear();
for (let year = startYear; year <= currentYear; year++) {
let value = 30; // Base value
// Add significant career events
if (year === 2003) value += 20; // Started as Boilermaker
if (year === 2005) value += 15; // Red Seal certification
if (year === 2016) value += 25; // Radiation Protection certification
if (year === 2019) value += 20; // EPRI certification
if (year === 2022) value += 30; // Entered Web3 space
data.push({
year: year.toString(),
value: value + Math.random() * 10
});
}
return data;
};
const CustomTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
const careerEvents: { [key: string]: string } = {
'2003': 'Started as Boilermaker',
'2005': 'Achieved Red Seal certification',
'2016': 'Obtained Radiation Protection certification',
'2019': 'Earned EPRI certification',
'2022': 'Entered Web3 space and founded DAO Masons'
};
return (
<div className="bg-gray-800 p-3 rounded-lg border border-gray-700">
<p className="text-white font-medium">{label}</p>
<p className="text-blue-400">{careerEvents[label] || 'Career progression'}</p>
</div>
);
}
return null;
};
export const ReactorStatus: React.FC<ReactorStatusProps> = ({
}) => {
const [power, setPower] = useState(85);
const [coreTemp, setCoreTemp] = useState(1350);
const [blockHeight, setBlockHeight] = useState(18_500_000);
const [radLevel, setRadLevel] = useState(5.5e7);
const [status, setStatus] = useState('Initializing systems...');
const [isAnimating, setIsAnimating] = useState(false);
const [currentTime, setCurrentTime] = useState(new Date());
const [timelineData] = useState(generateTimelineData());
const careerStats = [
{ label: 'Years Experience', value: '20+' },
{ label: 'Major Projects', value: '50+' },
{ label: 'Grants Secured', value: '$197K' }
];
useEffect(() => {
const intervals = [
setInterval(() => setCurrentTime(new Date()), 1000),
setInterval(() => {
setPower(prev => {
const fluctuation = Math.random() * 10 - 5;
return Math.min(Math.max(prev + fluctuation, 60), 100);
});
}, 3000),
setInterval(() => {
setCoreTemp(() => {
const fluctuation = Math.random() * 20 - 10;
return 1300 + (fluctuation % 100);
});
}, 2000),
setInterval(() => {
setBlockHeight(prev => prev + Math.floor(Math.random() * 3));
}, 5000),
setInterval(() => {
setRadLevel(() => {
const base = 5e7;
const fluctuation = Math.random() * 5e7;
return base + fluctuation;
});
}, 4000),
setInterval(() => {
const randomStatus = statusMessages[Math.floor(Math.random() * statusMessages.length)];
setStatus(randomStatus);
setIsAnimating(true);
setTimeout(() => setIsAnimating(false), 500);
}, 5000)
];
return () => intervals.forEach(clearInterval);
}, []);
const formatRadLevel = (level: number) => {
return `${(level / 1e7).toFixed(2)}E7 Gy/hr`;
};
return (
<div className="max-w-6xl mx-auto">
<div className="flex items-center gap-4 mb-6">
<Atom size={32} className="text-blue-500" />
<h2 className="text-3xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
System Status
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Reactor Status Panel */}
<div className="bg-gray-900 rounded-lg p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<Activity className="text-blue-500" />
<span className="text-sm text-gray-400 font-mono">
{currentTime.toLocaleTimeString()}
</span>
</div>
<div className="flex items-center gap-2">
<Power className="text-green-500" />
<span className="text-sm text-gray-400 font-mono">
{power.toFixed(1)}% Capacity
</span>
</div>
</div>
{/* Status Message */}
<div className="bg-gray-800/50 rounded-lg p-4 mb-6">
<div className={`text-lg font-mono text-blue-400 ${isAnimating ? 'animate-pulse' : ''}`}>
{status}
</div>
</div>
{/* Reactor Metrics */}
<div className="grid grid-cols-3 gap-4 mb-6">
<div className="bg-gray-800/50 rounded-lg p-4 text-center">
<div className="text-sm text-gray-400 mb-2">Core Temp</div>
<div className="text-blue-400 font-mono font-medium">
{coreTemp.toFixed(1)}°C
</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-4 text-center">
<div className="text-sm text-gray-400 mb-2">Block Height</div>
<div className="text-blue-400 font-mono font-medium">
#{blockHeight.toLocaleString()}
</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-4 text-center">
<div className="text-sm text-gray-400 mb-2">Rad Level</div>
<div className="text-blue-400 font-mono font-medium">
{formatRadLevel(radLevel)}
</div>
</div>
</div>
{/* Systems Monitor */}
<div className="grid grid-cols-2 gap-4">
<div className="bg-gray-800/50 rounded-lg p-4">
<div className="text-sm text-gray-400 mb-2">Core Stability</div>
<div className="h-[100px]">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={generateStabilityData()}>
<Line
type="natural"
dataKey="value"
stroke="#3B82F6"
strokeWidth={2}
dot={false}
isAnimationActive={true}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-4">
<div className="text-sm text-gray-400 mb-2">Neutron Flux</div>
<div className="h-[100px]">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={generateFluxData()}>
<Line
type="natural"
dataKey="value"
stroke="#8B5CF6"
strokeWidth={2}
dot={false}
isAnimationActive={true}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
{/* Career Timeline Panel */}
<div className="bg-gray-900 rounded-lg p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<TrendingUp className="text-purple-500" />
<h3 className="text-lg font-bold text-white">Career Timeline</h3>
</div>
<div className="flex items-center gap-2">
<Users className="text-purple-500" />
<span className="text-sm text-gray-400">Career Progression</span>
</div>
</div>
{/* Career Stats */}
<div className="grid grid-cols-3 gap-4 mb-6">
{careerStats.map((stat, index) => (
<div key={index} className="bg-gray-800/50 rounded-lg p-4 text-center">
<div className="text-sm text-gray-400">{stat.label}</div>
<div className="text-purple-400 font-medium mt-1">{stat.value}</div>
</div>
))}
</div>
{/* Timeline Chart */}
<div className="bg-gray-800/50 rounded-lg p-4 h-[200px]">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={timelineData}>
<XAxis
dataKey="year"
stroke="#4B5563"
tick={{ fill: '#9CA3AF', fontSize: 12 }}
/>
<YAxis
stroke="#4B5563"
tick={{ fill: '#9CA3AF', fontSize: 12 }}
domain={['auto', 'auto']}
hide
/>
<Tooltip content={<CustomTooltip />} />
<Line
type="monotone"
dataKey="value"
stroke="#8B5CF6"
strokeWidth={2}
dot={false}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
);
};
export default ReactorStatus;

View File

@ -1,24 +0,0 @@
// src/components/ReactorStatus/types.ts
export interface ReactorStatusProps {
className?: string;
}
export interface TooltipProps {
active?: boolean;
payload?: Array<{
value: number;
dataKey?: string;
}>;
label?: string;
}
export interface PriceStat {
label: string;
value: string;
}
export interface PriceDataPoint {
time: number;
price: number;
}

View File

@ -1,138 +0,0 @@
import type { FC } from 'react';
import { ChevronRight } from 'lucide-react';
import type { SkillsProps, SkillCardProps, SkillBarProps, Skill } from './types';
const SkillBar: FC<SkillBarProps> = ({
level,
maxLevel = 5,
className = ''
}) => {
const percentage = (level / maxLevel) * 100;
return (
<div className={`h-2 w-32 bg-gray-800 rounded-full overflow-hidden ${className}`}>
<div
className="h-full bg-gradient-to-r from-emerald-400 to-cyan-500 rounded-full transform origin-left transition-transform duration-1000 ease-out"
style={{ transform: `scaleX(${percentage / 100})` }}
/>
</div>
);
};
const SkillCard: FC<SkillCardProps> = ({
skill,
className = ''
}) => (
<div className={`bg-gray-900 rounded-lg p-6 hover:bg-gray-800 transition-all duration-300 ${className}`}>
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-medium text-white">{skill.name}</h3>
<p className="text-sm text-gray-400">{skill.description}</p>
</div>
<SkillBar level={skill.level} />
</div>
<div className="space-y-2">
{skill.keywords.map((keyword, index) => (
<div key={index} className="flex items-center text-sm text-gray-400">
<ChevronRight size={16} className="text-blue-500 mr-2" />
<span>{keyword}</span>
</div>
))}
</div>
</div>
);
const defaultSkills: Skill[] = [
{
name: "Radiation Protection",
description: "Expert",
level: 5,
keywords: [
"Radiation Monitoring",
"Contamination Control",
"Radiological Safety",
"Documentation and Procedure"
]
},
{
name: "Rigging",
description: "Advanced",
level: 4,
keywords: [
"Heavy Machinery",
"Safety Protocols",
"Equipment Setup",
"Planning"
]
},
{
name: "Project Management",
description: "Intermediate",
level: 3,
keywords: [
"Task Prioritization",
"Time Management",
"Team Collaboration",
"Project Scheduling",
"Milestone Tracking",
"Problem Solving"
]
},
{
name: "Grant Writing",
description: "Expert",
level: 4,
keywords: [
"Governance",
"Operations",
"Community Building",
"Grants"
]
},
{
name: "Community Building",
description: "Expert",
level: 3,
keywords: [
"Onboarding",
"Content Creation",
"Networking"
]
},
{
name: "Web Development",
description: "Novice",
level: 2,
keywords: [
"Python",
"Javascript",
"Typescript",
"HTML",
"CSS",
"REACT"
]
}
];
export const Skills: FC<SkillsProps> = ({
className = '',
skills = defaultSkills
}) => {
return (
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto">
<div className="flex items-center gap-4">
<h2 className="text-3xl font-bold bg-gradient-to-r from-emerald-400 to-cyan-500 bg-clip-text text-transparent">
Professional Skills
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
{skills.map((skill, index) => (
<SkillCard key={index} skill={skill} />
))}
</div>
</div>
</div>
);
};
export default Skills;

View File

@ -1,24 +0,0 @@
export interface Skill {
id?: string;
visible?: boolean;
name: string;
description: string;
level: number;
keywords: string[];
}
export interface SkillsProps {
className?: string;
skills?: Skill[];
}
export interface SkillCardProps {
skill: Skill;
className?: string;
}
export interface SkillBarProps {
level: number;
maxLevel?: number;
className?: string;
}

View File

@ -1,24 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Custom animations */
@keyframes float {
0% { transform: translate(0, 0); }
50% { transform: translate(-10px, 10px); }
100% { transform: translate(0, 0); }
}
@keyframes floatDelayed {
0% { transform: translate(0, 0); }
50% { transform: translate(10px, -10px); }
100% { transform: translate(0, 0); }
}
.animate-float {
animation: float 20s ease-in-out infinite;
}
.animate-float-delayed {
animation: floatDelayed 15s ease-in-out infinite;
}

View File

@ -1,10 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

View File

@ -1,60 +0,0 @@
import type { LucideIcon } from 'lucide-react';
export interface NavItem {
id: string;
icon: LucideIcon;
label: string;
}
export interface ExperienceItem {
title: string;
company: string;
period: string;
description: string;
icon: LucideIcon;
}
export interface Grant {
name: string;
amount: number;
color: string;
}
export interface Achievement {
title: string;
description: string;
date: string;
}
export interface TooltipProps {
active?: boolean;
payload?: Array<{
value: number;
payload: Grant;
}>;
}
export interface Skills {
title: string;
description: string;
date: string;
}
export interface NFT {
id: number;
image: string;
name: string;
link: string;
}
export interface SocialLink {
name: string;
icon: LucideIcon;
url: string;
username: string;
}
export interface LinkCategory {
category: string;
links: SocialLink[];
}

View File

@ -1,76 +0,0 @@
// src/utils/api.ts
const API_KEY = import.meta.env.VITE_CG_API_KEY;
export interface PriceData {
prices: [number, number][];
market_caps: [number, number][];
total_volumes: [number, number][];
}
export async function getEthereumData(): Promise<PriceData> {
try {
console.log('Fetching ETH price data...');
const response = await fetch(
`https://pro-api.coingecko.com/api/v3/coins/ethereum/market_chart?vs_currency=usd&days=1&interval=hourly&x_cg_pro_api_key=${API_KEY}`
);
if (!response.ok) {
const errorText = await response.text();
console.error('API Error Response:', errorText);
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
console.log('Raw API response:', data);
return data;
} catch (error) {
console.error('Error in getEthereumData:', error);
// Return mock data in case of API failure
const now = Date.now();
const mockData: PriceData = {
prices: Array.from({ length: 24 }, (_, i) => {
const timestamp = now - (23 - i) * 3600000;
const basePrice = 2300;
const randomChange = Math.random() * 100 - 50;
return [timestamp, basePrice + randomChange] as [number, number];
}),
market_caps: Array.from({ length: 24 }, (_, i) => {
const timestamp = now - (23 - i) * 3600000;
return [timestamp, 2300000000] as [number, number];
}),
total_volumes: Array.from({ length: 24 }, (_, i) => {
const timestamp = now - (23 - i) * 3600000;
return [timestamp, 500000000] as [number, number];
})
};
return mockData;
}
}
export function formatPrice(price: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(price);
}
export function getHighLowPrices(prices: [number, number][]): { high: number; low: number } {
const priceValues = prices.map(([, price]) => price);
return {
high: Math.max(...priceValues),
low: Math.min(...priceValues)
};
}
export function formatVolume(volume: number): string {
if (volume >= 1e9) {
return `$${(volume / 1e9).toFixed(1)}B`;
}
if (volume >= 1e6) {
return `$${(volume / 1e6).toFixed(1)}M`;
}
return `$${volume.toFixed(0)}`;
}

1
src/vite-env.d.ts vendored
View File

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

View File

@ -1,55 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
safelist: [
{
pattern: /from-(cyan|emerald|purple|blue|indigo|pink)-(400|500)/,
variants: ['hover', 'focus'],
},
{
pattern: /to-(indigo|blue|pink)-(400|500)/,
variants: ['hover', 'focus'],
},
{
pattern: /bg-(cyan|emerald|purple|blue|indigo|pink)-(400|500)\/10/,
variants: ['hover', 'focus'],
},
{
pattern: /text-(cyan|emerald|purple|blue|indigo|pink)-(400|500)/,
variants: ['hover', 'focus'],
},
],
theme: {
extend: {
fontFamily: {
'courier-prime': ['Courier Prime', 'monospace'],
'space-mono': ['Space Mono', 'monospace'],
},
animation: {
'fade-in': 'fadeIn 0.5s ease-in forwards',
'float': 'float 20s ease-in-out infinite',
'float-delayed': 'floatDelayed 15s ease-in-out infinite',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
float: {
'0%': { transform: 'translate(0, 0)' },
'50%': { transform: 'translate(-10px, 10px)' },
'100%': { transform: 'translate(0, 0)' },
},
floatDelayed: {
'0%': { transform: 'translate(0, 0)' },
'50%': { transform: 'translate(10px, -10px)' },
'100%': { transform: 'translate(0, 0)' },
},
},
},
},
plugins: [],
}

View File

@ -1,24 +0,0 @@
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

View File

@ -1,7 +0,0 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

View File

@ -1,24 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

View File

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