diff --git a/cloudflare-worker.js b/cloudflare-worker.js
new file mode 100644
index 0000000..35500d4
--- /dev/null
+++ b/cloudflare-worker.js
@@ -0,0 +1,50 @@
+
+import { Router } from 'itty-router';
+
+const router = Router();
+
+const CLIENT_ID = 'YOUR_SPOTIFY_CLIENT_ID';
+const CLIENT_SECRET = 'YOUR_SPOTIFY_CLIENT_SECRET';
+const REDIRECT_URI = 'YOUR_REDIRECT_URI';
+let accessToken = null;
+
+// Function to refresh Spotify Access Token
+async function refreshAccessToken() {
+ const authResponse = await fetch('https://accounts.spotify.com/api/token', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Authorization': 'Basic ' + btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)
+ },
+ body: 'grant_type=client_credentials'
+ });
+
+ const data = await authResponse.json();
+ accessToken = data.access_token;
+}
+
+// Spotify Data Fetch Endpoint
+router.get('/spotify-data', async (request) => {
+ if (!accessToken) {
+ await refreshAccessToken();
+ }
+
+ const spotifyResponse = await fetch('https://api.spotify.com/v1/me/top/tracks?limit=10', {
+ headers: {
+ 'Authorization': `Bearer ${accessToken}`
+ }
+ });
+
+ const spotifyData = await spotifyResponse.json();
+ return new Response(JSON.stringify(spotifyData), {
+ headers: { 'Content-Type': 'application/json' }
+ });
+});
+
+// Default Route
+router.all('*', () => new Response('Not Found', { status: 404 }));
+
+// Event Listener for Worker Requests
+addEventListener('fetch', (event) => {
+ event.respondWith(router.handle(event.request));
+});
diff --git a/deploy.ps1 b/deploy.ps1
index 7fac0c4..21c2611 100644
--- a/deploy.ps1
+++ b/deploy.ps1
@@ -8,7 +8,7 @@ npm run build
# Step 3: Deploy Frontend to Cloudflare Pages
Write-Output "Deploying frontend to Cloudflare Pages..."
-wrangler pages deploy ./build --project-name personal-site
+wrangler pages deploy ./build --project-name personal-site-test
# Step 4: Deploy Backend to Cloudflare Workers
Write-Output "Deploying backend worker..."
diff --git a/package.json b/package.json
index 6240241..29eca1e 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,22 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "@babel/plugin-transform-class-properties": "^7.25.9",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9",
+ "@babel/plugin-transform-numeric-separator": "^7.25.9",
+ "@babel/plugin-transform-optional-chaining": "^7.25.9",
+ "@babel/plugin-transform-private-methods": "^7.25.9",
+ "@eslint/eslintrc": "^1.4.1",
+ "@eslint/js": "^8.57.1",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@babel/plugin-transform-class-properties": "^7.25.9",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9",
+ "@babel/plugin-transform-numeric-separator": "^7.25.9",
+ "@babel/plugin-transform-optional-chaining": "^7.25.9",
+ "@babel/plugin-transform-private-methods": "^7.25.9",
+ "@eslint/eslintrc": "^1.4.1",
+ "@eslint/js": "^8.57.1",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
"cra-template": "1.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
diff --git a/public/manifest.json b/public/manifest.json
index 080d6c7..e6c7b95 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -1,6 +1,6 @@
{
- "short_name": "React App",
- "name": "Create React App Sample",
+ "short_name": "Personal Website",
+ "name": "My Personal Website",
"icons": [
{
"src": "favicon.ico",
@@ -8,12 +8,12 @@
"type": "image/x-icon"
},
{
- "src": "logo192.png",
+ "src": "logo.png",
"type": "image/png",
"sizes": "192x192"
},
{
- "src": "logo512.png",
+ "src": "logo.png",
"type": "image/png",
"sizes": "512x512"
}
diff --git a/spotify-worker.js b/spotify-worker.js
new file mode 100644
index 0000000..24b7020
--- /dev/null
+++ b/spotify-worker.js
@@ -0,0 +1,73 @@
+
+import { Router } from 'itty-router';
+
+const router = Router();
+
+const CLIENT_ID = 'YOUR_SPOTIFY_CLIENT_ID';
+const CLIENT_SECRET = 'YOUR_SPOTIFY_CLIENT_SECRET';
+
+let accessToken = null;
+let tokenExpiry = null;
+
+// Helper function to fetch a new access token
+async function fetchAccessToken() {
+ const response = await fetch('https://accounts.spotify.com/api/token', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Authorization': 'Basic ' + btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)
+ },
+ body: 'grant_type=client_credentials'
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch Spotify token: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ accessToken = data.access_token;
+ tokenExpiry = Date.now() + (data.expires_in * 1000);
+ console.log('New access token fetched');
+}
+
+// Middleware to ensure a valid access token
+async function ensureAccessToken() {
+ if (!accessToken || Date.now() >= tokenExpiry) {
+ await fetchAccessToken();
+ }
+}
+
+// Route to fetch Spotify data (Top Tracks)
+router.get('/spotify-data', async () => {
+ try {
+ await ensureAccessToken();
+
+ const response = await fetch('https://api.spotify.com/v1/me/top/tracks?limit=10', {
+ headers: { 'Authorization': `Bearer ${accessToken}` }
+ });
+
+ if (!response.ok) {
+ return new Response(JSON.stringify({ error: 'Failed to fetch Spotify data' }), { status: response.status });
+ }
+
+ const data = await response.json();
+ return new Response(JSON.stringify({
+ topTracks: data.items.map(track => ({
+ name: track.name,
+ artist: track.artists.map(artist => artist.name).join(', ')
+ }))
+ }), { headers: { 'Content-Type': 'application/json' } });
+
+ } catch (error) {
+ console.error('Error fetching Spotify data:', error);
+ return new Response(JSON.stringify({ error: 'Internal server error' }), { status: 500 });
+ }
+});
+
+// Default route for unmatched paths
+router.all('*', () => new Response('Not Found', { status: 404 }));
+
+// Event listener for handling requests
+addEventListener('fetch', (event) => {
+ event.respondWith(router.handle(event.request));
+});
diff --git a/src/App.css b/src/App.css
index 74b5e05..8871b1b 100644
--- a/src/App.css
+++ b/src/App.css
@@ -5,6 +5,9 @@
.App-logo {
height: 40vmin;
pointer-events: none;
+ background-image: url('/logo.png');
+ background-size: contain;
+ background-repeat: no-repeat;
}
@media (prefers-reduced-motion: no-preference) {
diff --git a/src/App.js b/src/App.js
index 164fba9..c90e6ed 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,21 +1,42 @@
-import React from 'react';
-import Navbar from './components/Navbar';
-import SpotifyList from './components/SpotifyList';
-import GitHubRepos from './components/GitHubRepos';
-import './index.css';
+import React, { useEffect } from 'react';
+import './rolling-code.css';
+import logo from './logo.png';
-function App() {
- return (
-
-
-
- Welcome to my resume website
- This is the homepage. It is currently a WIP.
-
-
-
-
- );
-}
+const App = () => {
+ useEffect(() => {
+ // Generate rolling code lines
+ const container = document.querySelector('.rolling-code-container');
+ for (let i = 0; i < 30; i++) {
+ const line = document.createElement('div');
+ line.className = 'code-line';
+ line.style.animationDelay = `${Math.random() * 5}s`;
+ line.textContent = Math.random().toString(36).substr(2, 80);
+ container.appendChild(line);
+ }
+
+ // Generate particles
+ const particleContainer = document.querySelector('.particle-container');
+ for (let i = 0; i < 50; i++) {
+ const particle = document.createElement('div');
+ particle.className = 'particle';
+ particle.style.left = `${Math.random() * 100}vw`;
+ particle.style.animationDelay = `${Math.random() * 10}s`;
+ particleContainer.appendChild(particle);
+ }
+ }, []);
+
+ return (
+
+

+
+
+
+
+
Welcome to My Website
+
Enhanced Background with Lighting Effects
+
+
+ );
+};
export default App;
diff --git a/src/components/GithubRepos.css b/src/components/GithubRepos.css
new file mode 100644
index 0000000..985e6cf
--- /dev/null
+++ b/src/components/GithubRepos.css
@@ -0,0 +1,54 @@
+
+.github-repos-container {
+ padding: 2rem;
+ background-color: #f9f9f9;
+ font-family: Arial, sans-serif;
+ text-align: center;
+}
+
+.repos-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 1.5rem;
+ margin-top: 2rem;
+}
+
+.repo-card {
+ background: #fff;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ padding: 1rem;
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+}
+
+.repo-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
+}
+
+.repo-name {
+ font-size: 1.2rem;
+ font-weight: bold;
+ color: #0077cc;
+ text-decoration: none;
+}
+
+.repo-name:hover {
+ text-decoration: underline;
+}
+
+.repo-description {
+ color: #555;
+ margin-top: 0.5rem;
+}
+
+.repo-language {
+ display: inline-block;
+ margin-top: 1rem;
+ padding: 0.3rem 0.6rem;
+ background-color: #eee;
+ border-radius: 4px;
+ font-size: 0.9rem;
+ color: #333;
+}
diff --git a/src/components/GithubRepos.js b/src/components/GithubRepos.js
index 89a8049..8371f12 100644
--- a/src/components/GithubRepos.js
+++ b/src/components/GithubRepos.js
@@ -1,29 +1,44 @@
+
import React, { useEffect, useState } from 'react';
+import './GithubRepos.css';
-function GitHubRepos() {
- const [repos, setRepos] = useState([]);
+const GithubRepos = () => {
+ const [repos, setRepos] = useState([]);
- useEffect(() => {
- fetch('/github-repos')
- .then(response => response.json())
- .then(data => {
- setRepos(data);
- });
- }, []);
+ useEffect(() => {
+ const fetchRepos = async () => {
+ try {
+ const response = await fetch('https://api.github.com/users/EndofTimee/repos');
+ const data = await response.json();
+ setRepos(data);
+ } catch (error) {
+ console.error('Error fetching GitHub repos:', error);
+ }
+ };
- return (
-
-
My GitHub Repositories
-
- {repos.map(repo => (
- -
-
{repo.name}
- {repo.description}
-
- ))}
-
-
- );
-}
+ fetchRepos();
+ }, []);
-export default GitHubRepos;
\ No newline at end of file
+ return (
+
+
My GitHub Repositories
+
+ {repos.map((repo) => (
+
+
+ {repo.name}
+
+
+ {repo.description || 'No description provided.'}
+
+ {repo.language && (
+
{repo.language}
+ )}
+
+ ))}
+
+
+ );
+};
+
+export default GithubRepos;
diff --git a/src/components/LoadingScreen.js b/src/components/LoadingScreen.js
new file mode 100644
index 0000000..d4a94c7
--- /dev/null
+++ b/src/components/LoadingScreen.js
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import './LoadingAnimation.css';
+
+const LoadingAnimation = () => {
+ return (
+
+ );
+};
+
+export default LoadingAnimation;
diff --git a/src/components/ParallaxEffect.js b/src/components/ParallaxEffect.js
new file mode 100644
index 0000000..6f5e386
--- /dev/null
+++ b/src/components/ParallaxEffect.js
@@ -0,0 +1,20 @@
+
+import React from 'react';
+import './parallax.css';
+
+const ParallaxEffect = () => {
+ return (
+
+
+
+
+
+
Welcome to the Parallax Effect
+
Scroll down to see the magic happen!
+
+
+
+ );
+};
+
+export default ParallaxEffect;
diff --git a/src/components/loading-screen.css b/src/components/loading-screen.css
new file mode 100644
index 0000000..d363b25
--- /dev/null
+++ b/src/components/loading-screen.css
@@ -0,0 +1,24 @@
+
+.loading-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ background: black;
+ color: white;
+}
+
+.loading-spinner {
+ width: 50px;
+ height: 50px;
+ border: 5px solid rgba(255, 255, 255, 0.3);
+ border-top: 5px solid white;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
diff --git a/src/parallax-effect.css b/src/parallax-effect.css
new file mode 100644
index 0000000..a9077b5
--- /dev/null
+++ b/src/parallax-effect.css
@@ -0,0 +1,52 @@
+
+body, html {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ overflow-x: hidden;
+ font-family: Arial, sans-serif;
+}
+
+.parallax-container {
+ position: relative;
+ width: 100%;
+ height: 100vh;
+ overflow: hidden;
+ perspective: 2px; /* Creates the parallax effect */
+}
+
+.parallax-layer {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-size: cover;
+ background-position: center;
+ transform: translateZ(0);
+ z-index: -1;
+}
+
+.layer-1 {
+ background-image: url('https://source.unsplash.com/1600x900/?nature');
+ transform: translateZ(-1px) scale(1.5);
+}
+
+.layer-2 {
+ background-image: url('https://source.unsplash.com/1600x900/?forest');
+ transform: translateZ(-0.5px) scale(1.2);
+}
+
+.content-layer {
+ position: relative;
+ z-index: 1;
+ text-align: center;
+ color: white;
+ font-size: 2rem;
+ padding-top: 40vh;
+}
+
+.parallax-text {
+ background: rgba(0, 0, 0, 0.5);
+ padding: 1rem 2rem;
+ display: inline-block;
+ border-radius: 8px;
+}
diff --git a/src/rolling-effects.css b/src/rolling-effects.css
new file mode 100644
index 0000000..b04e553
--- /dev/null
+++ b/src/rolling-effects.css
@@ -0,0 +1,88 @@
+
+@keyframes rollingCode {
+ 0% { transform: translateY(0); opacity: 1; }
+ 100% { transform: translateY(100vh); opacity: 0; }
+}
+
+@keyframes particleMove {
+ 0% { transform: translateY(0) translateX(0); opacity: 0.5; }
+ 50% { opacity: 1; }
+ 100% { transform: translateY(100vh) translateX(10px); opacity: 0; }
+}
+
+@keyframes lightAnimation {
+ 0% {
+ background-position: 0% 0%;
+ }
+ 50% {
+ background-position: 100% 100%;
+ }
+ 100% {
+ background-position: 0% 0%;
+ }
+}
+
+body {
+ margin: 0;
+ overflow: hidden;
+ background: black;
+ font-family: monospace;
+ font-size: 1rem;
+ color: limegreen;
+ position: relative;
+ height: 100vh;
+}
+
+.animated-lighting {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(circle, rgba(0, 255, 0, 0.3), transparent 60%);
+ background-size: 300% 300%;
+ animation: lightAnimation 12s infinite linear;
+ z-index: 0;
+}
+
+.code-line {
+ position: absolute;
+ top: -10%;
+ left: 0;
+ right: 0;
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ animation: rollingCode 10s linear infinite;
+ opacity: 0.7;
+ z-index: 1;
+ color: #00ff00;
+}
+
+.particle {
+ position: absolute;
+ width: 5px;
+ height: 5px;
+ background: rgba(255, 255, 255, 0.5);
+ border-radius: 50%;
+ animation: particleMove 8s linear infinite;
+ z-index: 2;
+}
+
+.particle:nth-child(odd) {
+ animation-duration: 12s;
+}
+
+.particle:nth-child(even) {
+ animation-duration: 10s;
+ width: 4px;
+ height: 4px;
+}
+
+.content {
+ position: relative;
+ z-index: 3;
+ color: white;
+ text-align: center;
+ margin-top: 20vh;
+}