Compare commits
No commits in common. "main" and "clean-branch" have entirely different histories.
main
...
clean-bran
8
.env
8
.env
|
|
@ -1,8 +0,0 @@
|
|||
VITE_CG_API_KEY=CG-6tRLtAyJ9t5YdthDmRPhwKRD
|
||||
VITE_CG_API_URL=https://pro-api.coingecko.com/api/v3
|
||||
|
||||
# VPS Configuration
|
||||
SERVER_IP="66.179.188.130"
|
||||
SERVER_USER="root"
|
||||
# Replace with your actual password
|
||||
SERVER_PASSWORD="4Kk7r8bi"
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
SERVER_USER=your_server_username
|
||||
SERVER_PASSWORD=your_server_password
|
||||
SERVER_IP=your_server_ip
|
||||
# VPS Configuration
|
||||
SERVER_IP="your_server_ip"
|
||||
SERVER_USER="root"
|
||||
SERVER_PASSWORD="your_server_password"
|
||||
|
|
@ -1,39 +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*
|
||||
|
||||
# Dependencies and build artifacts
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.parcel-cache
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
*.env.local
|
||||
*.env.development.local
|
||||
*.env.test.local
|
||||
*.env.production.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Misc
|
||||
.codegpt
|
||||
.cache
|
||||
.temp
|
||||
.tmp
|
||||
# Parcel
|
||||
.cache/
|
||||
.parcel-cache/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
182
README.md
182
README.md
|
|
@ -1,143 +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://git.boilerhaus.org/boiler/Web3CV.git
|
||||
cd Web3CV
|
||||
```
|
||||
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
|
||||
|
||||
## 🚀 Deployment
|
||||
2. Deploy only to VPS (same as above):
|
||||
```bash
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
This website is deployed on a VPS at [boilerhaus.org](https://boilerhaus.org) using a custom deployment script.
|
||||
## Repository Management
|
||||
|
||||
### Deployment Setup
|
||||
This repository is hosted on a self-hosted Gitea instance at git.boilerhaus.org.
|
||||
|
||||
1. **Environment Configuration**
|
||||
- Create a `.env` file based on `.env.example`
|
||||
- Add your server credentials:
|
||||
```
|
||||
SERVER_USER=your_server_username
|
||||
SERVER_PASSWORD=your_server_password
|
||||
SERVER_IP=your_server_ip
|
||||
```
|
||||
### Remote Setup
|
||||
|
||||
2. **Deploy to VPS**
|
||||
- Run the deployment script:
|
||||
```bash
|
||||
./deploy-website.sh
|
||||
```
|
||||
- This script will:
|
||||
- Build the project
|
||||
- Create a tarball of the built files
|
||||
- Upload to the server
|
||||
- Extract files to the web directory
|
||||
- Set proper permissions
|
||||
|
||||
3. **Check Website Status**
|
||||
- Run the status check script:
|
||||
```bash
|
||||
./check-website.sh
|
||||
```
|
||||
- This will check the status of all services:
|
||||
- Portfolio website (boilerhaus.org)
|
||||
- Gitea (git.boilerhaus.org)
|
||||
- Vaultwarden (bw.boilerhaus.org)
|
||||
- Nextcloud (cloud.boilerhaus.org)
|
||||
|
||||
### Maintenance Scripts
|
||||
|
||||
The repository includes several utility scripts to help with maintenance:
|
||||
|
||||
1. **update-from-gitea.sh**
|
||||
- Updates the local repository from Gitea
|
||||
- Preserves any local changes using git stash
|
||||
|
||||
2. **fix-nginx-config.sh**
|
||||
- Fixes the Nginx configuration on the server
|
||||
- Properly configures all subdomains
|
||||
|
||||
3. **fix-nextcloud-occ.sh**
|
||||
- Adds cloud.boilerhaus.org to Nextcloud's trusted domains
|
||||
- Uses the Nextcloud occ command-line tool
|
||||
|
||||
4. **cleanup.sh**
|
||||
- Removes unnecessary files from the repository
|
||||
- Updates .gitignore to exclude build artifacts
|
||||
|
||||
### Server Configuration
|
||||
|
||||
The website is served by Nginx with SSL certificates from Let's Encrypt. The configuration files are located on the server at `/etc/nginx/sites-available/`.
|
||||
|
||||
## 🌐 Domain Information
|
||||
|
||||
### Current Setup
|
||||
|
||||
- Domain: [boilerhaus.org](https://boilerhaus.org)
|
||||
- Subdomains:
|
||||
- [git.boilerhaus.org](https://git.boilerhaus.org) - Gitea instance
|
||||
- [bw.boilerhaus.org](https://bw.boilerhaus.org) - Vaultwarden instance
|
||||
- [cloud.boilerhaus.org](https://cloud.boilerhaus.org) - Nextcloud dashboard
|
||||
|
||||
### Previous Setup (Archived)
|
||||
|
||||
- Domains: [www.chriswylde.xyz](https://www.chriswylde.xyz) and [www.boilerrat.xyz](https://www.boilerra.xyz)
|
||||
- Registrar: Namecheap
|
||||
- Previous Host: Netlify/Vercel
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://git.boilerhaus.org/boiler/Web3CV/issues).
|
||||
|
||||
## 📝 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## 👤 Contact
|
||||
|
||||
- Website: [boilerhaus.org](https://boilerhaus.org)
|
||||
- Twitter: [@boilerrat](https://twitter.com/boilerrat)
|
||||
- Farcaster: [@boiler](https://warpcast.com/boiler)
|
||||
- Email: [128boilerrat@gmail.com](mailto:128boilerrat@gmail.com)
|
||||
Your remote is configured as:
|
||||
- `origin`: Your Gitea repository (https://git.boilerhaus.org/boiler/Web3CV.git)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script checks the status of all services on boilerhaus.org
|
||||
|
||||
echo "=== Checking Services Status ==="
|
||||
|
||||
echo -e "\n1. Portfolio Website (boilerhaus.org)"
|
||||
echo "--------------------------------"
|
||||
curl -I https://boilerhaus.org
|
||||
echo -e "\nStatus: $(curl -s -o /dev/null -w "%{http_code}" https://boilerhaus.org)"
|
||||
|
||||
echo -e "\n2. Gitea (git.boilerhaus.org)"
|
||||
echo "--------------------------------"
|
||||
curl -I https://git.boilerhaus.org
|
||||
echo -e "\nStatus: $(curl -s -o /dev/null -w "%{http_code}" https://git.boilerhaus.org)"
|
||||
|
||||
echo -e "\n3. Nextcloud (cloud.boilerhaus.org)"
|
||||
echo "--------------------------------"
|
||||
curl -I https://cloud.boilerhaus.org
|
||||
echo -e "\nStatus: $(curl -s -o /dev/null -w "%{http_code}" https://cloud.boilerhaus.org)"
|
||||
|
||||
echo -e "\n4. Vaultwarden (bw.boilerhaus.org)"
|
||||
echo "--------------------------------"
|
||||
curl -I https://bw.boilerhaus.org
|
||||
echo -e "\nStatus: $(curl -s -o /dev/null -w "%{http_code}" https://bw.boilerhaus.org)"
|
||||
|
||||
echo -e "\n=== Service Configuration Summary ==="
|
||||
echo "Portfolio: Port 443 (HTTPS) → /var/www/boilerhaus.org"
|
||||
echo "Gitea: Port 8080 → http://localhost:8080"
|
||||
echo "Nextcloud: Port 8081 → http://localhost:8081"
|
||||
echo "Vaultwarden: Port 8222 → http://localhost:8222"
|
||||
|
||||
echo -e "\n=== All checks completed ==="
|
||||
39
cleanup.sh
39
cleanup.sh
|
|
@ -1,39 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script cleans up unnecessary files from the repository
|
||||
|
||||
echo "=== Cleaning up repository ==="
|
||||
|
||||
# Remove redundant Nextcloud fix scripts
|
||||
echo "Removing redundant Nextcloud fix scripts..."
|
||||
rm -f fix-nextcloud-trusted-domains.sh
|
||||
rm -f fix-nextcloud-domains.sh
|
||||
rm -f fix-nextcloud.sh
|
||||
|
||||
# Remove build cache
|
||||
echo "Removing build cache..."
|
||||
rm -rf .parcel-cache
|
||||
|
||||
# Remove unused configuration files
|
||||
echo "Removing unused configuration files..."
|
||||
rm -f boilerhaus.org.conf.new
|
||||
|
||||
# Clean up build artifacts
|
||||
echo "Cleaning up build artifacts..."
|
||||
rm -rf dist
|
||||
|
||||
# Add the changes to git
|
||||
echo "Adding changes to git..."
|
||||
git add -A
|
||||
|
||||
echo "=== Cleanup complete ==="
|
||||
echo "The following files have been removed:"
|
||||
echo "- fix-nextcloud-trusted-domains.sh"
|
||||
echo "- fix-nextcloud-domains.sh"
|
||||
echo "- fix-nextcloud.sh"
|
||||
echo "- .parcel-cache/ directory"
|
||||
echo "- boilerhaus.org.conf.new"
|
||||
echo "- dist/ directory"
|
||||
echo ""
|
||||
echo "To commit these changes, run:"
|
||||
echo "git commit -m \"Clean up repository by removing unnecessary files\""
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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!"
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script deploys the portfolio website from Gitea to the VPS
|
||||
# This script deploys the portfolio website from GitHub to the VPS
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
|
|
@ -12,54 +12,52 @@ fi
|
|||
|
||||
# Check if sshpass is installed
|
||||
if ! command -v sshpass &> /dev/null; then
|
||||
echo "Error: sshpass is not installed. Please run 'sudo apt-get install sshpass' first."
|
||||
echo "Error: sshpass is not installed. Please run ./install-sshpass.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set SSH and SCP commands with password
|
||||
SSH_CMD="sshpass -p $SERVER_PASSWORD ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP"
|
||||
SCP_CMD="sshpass -p $SERVER_PASSWORD scp -o StrictHostKeyChecking=no"
|
||||
SSH_CMD="sshpass -p \"$SERVER_PASSWORD\" ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP"
|
||||
SCP_CMD="sshpass -p \"$SERVER_PASSWORD\" scp -o StrictHostKeyChecking=no"
|
||||
|
||||
# Set variables
|
||||
REMOTE_DIR="/var/www/boilerhaus.org"
|
||||
GITHUB_REPO="https://github.com/boilerrat/portfolio.git"
|
||||
TEMP_DIR="./portfolio-temp"
|
||||
|
||||
# Create a temporary directory for the website files
|
||||
echo "Creating temporary directory..."
|
||||
mkdir -p $TEMP_DIR
|
||||
cd $TEMP_DIR
|
||||
|
||||
# Check if TypeScript is installed
|
||||
if ! command -v tsc &> /dev/null; then
|
||||
echo "Installing TypeScript globally..."
|
||||
npm install -g typescript
|
||||
fi
|
||||
# Clone the repository
|
||||
echo "Cloning portfolio repository from GitHub..."
|
||||
git clone $GITHUB_REPO .
|
||||
|
||||
# Install dependencies
|
||||
echo "Installing dependencies..."
|
||||
npm install
|
||||
|
||||
# Build the project
|
||||
echo "Building the project..."
|
||||
npm run build
|
||||
|
||||
# Check if dist directory exists
|
||||
if [ ! -d "dist" ]; then
|
||||
echo "Error: Build failed. The dist directory does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create a tarball of the built files
|
||||
echo "Creating tarball of the built files..."
|
||||
cd dist
|
||||
tar -czf ../portfolio-dist.tar.gz .
|
||||
cd ..
|
||||
tar -czf ../../portfolio-dist.tar.gz .
|
||||
cd ../..
|
||||
|
||||
# Upload the tarball to the server
|
||||
echo "Uploading to server..."
|
||||
$SCP_CMD portfolio-dist.tar.gz $SERVER_USER@$SERVER_IP:/tmp/
|
||||
eval "$SCP_CMD portfolio-dist.tar.gz $SERVER_USER@$SERVER_IP:/tmp/"
|
||||
|
||||
# Extract the tarball on the server
|
||||
echo "Extracting files on server..."
|
||||
$SSH_CMD "mkdir -p $REMOTE_DIR && \
|
||||
tar -xzf /tmp/portfolio-dist.tar.gz -C $REMOTE_DIR && \
|
||||
chown -R www-data:www-data $REMOTE_DIR && \
|
||||
rm /tmp/portfolio-dist.tar.gz"
|
||||
eval "$SSH_CMD \"mkdir -p $REMOTE_DIR && \
|
||||
tar -xzf /tmp/portfolio-dist.tar.gz -C $REMOTE_DIR && \
|
||||
chown -R www-data:www-data $REMOTE_DIR && \
|
||||
rm /tmp/portfolio-dist.tar.gz\""
|
||||
|
||||
# Clean up local files
|
||||
echo "Cleaning up..."
|
||||
|
|
|
|||
|
|
@ -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!"
|
||||
|
|
@ -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 },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
@ -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"
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script adds cloud.boilerhaus.org to Nextcloud's trusted domains using the occ command
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
source .env
|
||||
else
|
||||
echo "Error: .env file not found. Please create it based on .env.example."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if sshpass is installed
|
||||
if ! command -v sshpass &> /dev/null; then
|
||||
echo "Error: sshpass is not installed. Please run 'sudo apt-get install sshpass' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set SSH command with password
|
||||
SSH_CMD="sshpass -p $SERVER_PASSWORD ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP"
|
||||
|
||||
echo "Adding cloud.boilerhaus.org to Nextcloud's trusted domains using occ command..."
|
||||
|
||||
# Find the Nextcloud directory
|
||||
$SSH_CMD "sudo find /var/www -name occ 2>/dev/null" > nextcloud_occ_path.txt
|
||||
OCC_PATH=$(cat nextcloud_occ_path.txt)
|
||||
|
||||
if [ -z "$OCC_PATH" ]; then
|
||||
echo "Error: Could not find Nextcloud occ command."
|
||||
echo "Trying alternative approach..."
|
||||
|
||||
# Try to directly edit the config.php file
|
||||
$SSH_CMD "sudo bash -c 'echo \"<?php \\\$CONFIG['trusted_domains'][] = \\\"cloud.boilerhaus.org\\\"; \" > /var/www/nextcloud/config/config.php.additional'"
|
||||
$SSH_CMD "sudo bash -c 'cat /var/www/nextcloud/config/config.php.additional >> /var/www/nextcloud/config/config.php'"
|
||||
$SSH_CMD "sudo rm /var/www/nextcloud/config/config.php.additional"
|
||||
else
|
||||
echo "Found Nextcloud occ at: $OCC_PATH"
|
||||
|
||||
# Get the directory containing occ
|
||||
NEXTCLOUD_DIR=$(dirname "$OCC_PATH")
|
||||
|
||||
# Use the occ command to add the trusted domain
|
||||
$SSH_CMD "cd $NEXTCLOUD_DIR && sudo -u www-data php occ config:system:set trusted_domains 3 --value=cloud.boilerhaus.org"
|
||||
fi
|
||||
|
||||
# Restart Apache to apply changes
|
||||
$SSH_CMD "sudo systemctl restart apache2"
|
||||
|
||||
# Clean up
|
||||
rm -f nextcloud_occ_path.txt
|
||||
|
||||
echo "Trusted domains configuration updated. You should now be able to access your Nextcloud instance at https://cloud.boilerhaus.org"
|
||||
echo "If you still see the 'untrusted domain' error, try clearing your browser cache or using a private/incognito window."
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script fixes the Nginx configuration
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
source .env
|
||||
else
|
||||
echo "Error: .env file not found. Please create it based on .env.example."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if sshpass is installed
|
||||
if ! command -v sshpass &> /dev/null; then
|
||||
echo "Error: sshpass is not installed. Please run 'sudo apt-get install sshpass' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set SSH command with password
|
||||
SSH_CMD="sshpass -p $SERVER_PASSWORD ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP"
|
||||
|
||||
echo "Fixing Nginx configuration..."
|
||||
|
||||
# Create a temporary file with the complete configuration
|
||||
cat > nginx-fix.conf << 'EOF'
|
||||
server {
|
||||
server_name boilerhaus.org;
|
||||
|
||||
root /var/www/boilerhaus.org;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# Managed by Certbot
|
||||
listen [::]:443 ssl ipv6only=on; # managed by Certbot
|
||||
listen 443 ssl; # managed by Certbot
|
||||
ssl_certificate /etc/letsencrypt/live/boilerhaus.org-0001/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/boilerhaus.org-0001/privkey.pem; # managed by Certbot
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
}
|
||||
|
||||
# Nextcloud configuration
|
||||
server {
|
||||
server_name cloud.boilerhaus.org;
|
||||
|
||||
# Proxy to Nextcloud
|
||||
location / {
|
||||
proxy_pass http://localhost:8081;
|
||||
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;
|
||||
|
||||
# Increase timeout for long-running operations
|
||||
proxy_connect_timeout 600;
|
||||
proxy_send_timeout 600;
|
||||
proxy_read_timeout 600;
|
||||
send_timeout 600;
|
||||
|
||||
# WebDAV support
|
||||
client_max_body_size 512M;
|
||||
|
||||
# Enable WebSocket support
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# Managed by Certbot
|
||||
listen [::]:443 ssl; # managed by Certbot
|
||||
listen 443 ssl; # managed by Certbot
|
||||
ssl_certificate /etc/letsencrypt/live/boilerhaus.org-0001/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/boilerhaus.org-0001/privkey.pem; # managed by Certbot
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
}
|
||||
|
||||
server {
|
||||
if ($host = boilerhaus.org) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name boilerhaus.org;
|
||||
return 404; # managed by Certbot
|
||||
}
|
||||
|
||||
server {
|
||||
if ($host = cloud.boilerhaus.org) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name cloud.boilerhaus.org;
|
||||
return 404; # managed by Certbot
|
||||
}
|
||||
EOF
|
||||
|
||||
# Upload the temporary file to the server
|
||||
sshpass -p "$SERVER_PASSWORD" scp -o StrictHostKeyChecking=no nginx-fix.conf $SERVER_USER@$SERVER_IP:/tmp/
|
||||
|
||||
# Backup the current configuration
|
||||
$SSH_CMD "sudo cp /etc/nginx/sites-available/boilerhaus.org.conf /etc/nginx/sites-available/boilerhaus.org.conf.bak.$(date +%s)"
|
||||
|
||||
# Replace the configuration
|
||||
$SSH_CMD "sudo cp /tmp/nginx-fix.conf /etc/nginx/sites-available/boilerhaus.org.conf"
|
||||
|
||||
# Test the Nginx configuration
|
||||
$SSH_CMD "sudo nginx -t"
|
||||
|
||||
# Reload Nginx if the configuration is valid
|
||||
$SSH_CMD "sudo systemctl reload nginx || sudo systemctl restart nginx"
|
||||
|
||||
# Clean up
|
||||
rm nginx-fix.conf
|
||||
|
||||
echo "Nginx configuration fixed! cloud.boilerhaus.org should now point to your Nextcloud instance."
|
||||
238
index.html
238
index.html
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script installs sshpass, which is needed for password automation
|
||||
|
||||
# Check if sshpass is already installed
|
||||
if command -v sshpass &> /dev/null; then
|
||||
echo "sshpass is already installed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Install sshpass based on the detected package manager
|
||||
if command -v apt-get &> /dev/null; then
|
||||
echo "Installing sshpass using apt..."
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y sshpass
|
||||
elif command -v dnf &> /dev/null; then
|
||||
echo "Installing sshpass using dnf..."
|
||||
sudo dnf install -y sshpass
|
||||
elif command -v yum &> /dev/null; then
|
||||
echo "Installing sshpass using yum..."
|
||||
sudo yum install -y sshpass
|
||||
elif command -v pacman &> /dev/null; then
|
||||
echo "Installing sshpass using pacman..."
|
||||
sudo pacman -S --noconfirm sshpass
|
||||
elif command -v brew &> /dev/null; then
|
||||
echo "Installing sshpass using Homebrew..."
|
||||
brew install hudochenkov/sshpass/sshpass
|
||||
else
|
||||
echo "Error: Could not detect package manager. Please install sshpass manually."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "sshpass has been installed successfully."
|
||||
|
|
@ -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"
|
||||
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script sets up the VPS with the correct Nginx configuration and deploys the website
|
||||
|
||||
# Check if .env file exists
|
||||
if [ ! -f .env ]; then
|
||||
echo "Error: .env file not found. Please create it based on .env.example."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source the .env file
|
||||
source .env
|
||||
|
||||
# Check if sshpass is installed
|
||||
if ! command -v sshpass &> /dev/null; then
|
||||
echo "sshpass is not installed. Installing it now..."
|
||||
./install-sshpass.sh
|
||||
|
||||
# Check if installation was successful
|
||||
if ! command -v sshpass &> /dev/null; then
|
||||
echo "Error: Failed to install sshpass. Please install it manually."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "=== Setting up VPS for boilerhaus.org (IP: $SERVER_IP) ==="
|
||||
echo ""
|
||||
|
||||
# Step 1: Update Nginx configuration
|
||||
echo "Step 1: Updating Nginx configuration..."
|
||||
./update-nginx-config.sh
|
||||
|
||||
# Step 2: Deploy website
|
||||
echo ""
|
||||
echo "Step 2: Deploying website..."
|
||||
./deploy-website.sh
|
||||
|
||||
echo ""
|
||||
echo "=== Setup complete! ==="
|
||||
echo "Your domains should now be configured as follows:"
|
||||
echo "- boilerhaus.org → Main website"
|
||||
echo "- bw.boilerhaus.org → Vaultwarden instance"
|
||||
echo "- cloud.boilerhaus.org → Nextcloud dashboard"
|
||||
echo "- git.boilerhaus.org → Gitea instance"
|
||||
echo ""
|
||||
echo "Please allow a few minutes for DNS changes to propagate."
|
||||
42
src/App.css
42
src/App.css
|
|
@ -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;
|
||||
}
|
||||
63
src/App.tsx
63
src/App.tsx
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
10
src/main.tsx
10
src/main.tsx
|
|
@ -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>,
|
||||
)
|
||||
|
|
@ -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[];
|
||||
}
|
||||
|
|
@ -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 +0,0 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
|
@ -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: [],
|
||||
}
|
||||
|
|
@ -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"]
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
|
|
@ -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"]
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script updates the local repository from Gitea
|
||||
|
||||
echo "Updating local repository from Gitea..."
|
||||
|
||||
# Fetch the latest changes from Gitea
|
||||
git fetch origin
|
||||
|
||||
# Check if there are any changes
|
||||
if git diff --quiet HEAD origin/main; then
|
||||
echo "Local repository is already up to date."
|
||||
else
|
||||
echo "New changes detected. Updating local repository..."
|
||||
|
||||
# Stash any local changes
|
||||
git stash
|
||||
|
||||
# Pull the latest changes
|
||||
git pull origin main
|
||||
|
||||
# Apply stashed changes if any
|
||||
if git stash list | grep -q "stash@{0}"; then
|
||||
echo "Applying stashed changes..."
|
||||
git stash pop
|
||||
fi
|
||||
|
||||
echo "Local repository updated successfully!"
|
||||
fi
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script updates the Nginx configuration for boilerhaus.org and its subdomains
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
source .env
|
||||
else
|
||||
echo "Error: .env file not found. Please create it based on .env.example."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if sshpass is installed
|
||||
if ! command -v sshpass &> /dev/null; then
|
||||
echo "Error: sshpass is not installed. Please run ./install-sshpass.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set SSH and SCP commands with password
|
||||
SSH_CMD="sshpass -p \"$SERVER_PASSWORD\" ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP"
|
||||
SCP_CMD="sshpass -p \"$SERVER_PASSWORD\" scp -o StrictHostKeyChecking=no"
|
||||
|
||||
# Create main website configuration file
|
||||
echo "Creating main website configuration file..."
|
||||
cat > boilerhaus.org.conf.new << 'EOL'
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name 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
|
||||
}
|
||||
|
||||
# Nextcloud configuration
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name cloud.boilerhaus.org;
|
||||
|
||||
# Proxy to Nextcloud
|
||||
location / {
|
||||
proxy_pass http://localhost:8080;
|
||||
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
|
||||
}
|
||||
EOL
|
||||
|
||||
# Upload the new configuration
|
||||
echo "Uploading main website configuration..."
|
||||
eval "$SCP_CMD boilerhaus.org.conf.new $SERVER_USER@$SERVER_IP:/tmp/boilerhaus.org.conf.new"
|
||||
eval "$SSH_CMD \"mv /tmp/boilerhaus.org.conf.new /etc/nginx/sites-available/boilerhaus.org.conf\""
|
||||
|
||||
# Make sure the site is enabled
|
||||
echo "Ensuring site is enabled..."
|
||||
eval "$SSH_CMD \"ln -sf /etc/nginx/sites-available/boilerhaus.org.conf /etc/nginx/sites-enabled/boilerhaus.org.conf\""
|
||||
|
||||
# Test Nginx configuration
|
||||
echo "Testing Nginx configuration..."
|
||||
NGINX_TEST=$(eval "$SSH_CMD \"nginx -t 2>&1\"")
|
||||
NGINX_TEST_EXIT_CODE=$?
|
||||
|
||||
# If the test is successful, reload Nginx
|
||||
if [ $NGINX_TEST_EXIT_CODE -eq 0 ]; then
|
||||
echo "Reloading Nginx..."
|
||||
eval "$SSH_CMD \"systemctl reload nginx\""
|
||||
echo "Configuration updated successfully!"
|
||||
else
|
||||
echo "Nginx configuration test failed:"
|
||||
echo "$NGINX_TEST"
|
||||
echo "Please check the configuration and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run Certbot to ensure SSL certificates are set up for main domain
|
||||
echo "Running Certbot to ensure SSL certificates are set up..."
|
||||
eval "$SSH_CMD \"certbot --nginx -d boilerhaus.org -d cloud.boilerhaus.org --non-interactive --agree-tos --email admin@boilerhaus.org\""
|
||||
|
||||
echo "Done!"
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
Loading…
Reference in New Issue