feat: Initialize m5rcel portfolio project

This commit sets up the foundational structure for the m5rcel portfolio website. It includes:

- Basic Vite project configuration with React and TypeScript.
- Essential dependencies like React, Framer Motion, and Lucide React.
- Initial HTML structure with meta tags and Tailwind CSS setup.
- Placeholder data structures for projects and timeline.
- A basic loading screen component.
- Updated README with local run instructions.
This commit is contained in:
m5rcel { Marcel }
2025-12-09 09:04:50 +01:00
parent 901808b5e6
commit 1fe4716728
17 changed files with 689 additions and 8 deletions

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

29
App.tsx Normal file
View File

@@ -0,0 +1,29 @@
import React, { useState } from 'react';
import Navbar from './components/Navbar';
import Hero from './components/Hero';
import Projects from './components/Projects';
import About from './components/About';
import Footer from './components/Footer';
import LoadingScreen from './components/LoadingScreen';
const App: React.FC = () => {
const [isLoading, setIsLoading] = useState(true);
return (
<>
{isLoading && <LoadingScreen onComplete={() => setIsLoading(false)} />}
<div className={`min-h-screen bg-[#f2f2f2] ${isLoading ? 'hidden' : 'block'}`}>
<Navbar toggleTheme={() => {}} isDark={false} />
<main>
<Hero />
<Projects />
<About />
</main>
<Footer />
</div>
</>
);
};
export default App;

View File

@@ -1,11 +1,20 @@
<div align="center"> <div align="center">
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" /> <img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
<h1>Built with AI Studio</h2>
<p>The fastest path from prompt to production with Gemini.</p>
<a href="https://aistudio.google.com/apps">Start building</a>
</div> </div>
# Run and deploy your AI Studio app
This contains everything you need to run your app locally.
View your app in AI Studio: https://ai.studio/apps/drive/1TiMFoAG3MlNJvKqVz68y71I_K1ca--la
## Run Locally
**Prerequisites:** Node.js
1. Install dependencies:
`npm install`
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
3. Run the app:
`npm run dev`

84
components/About.tsx Normal file
View File

@@ -0,0 +1,84 @@
import React from 'react';
import { FileCode2, Terminal, Database, Server, GitBranch, Layout } from 'lucide-react';
const TECH_STACKS = [
{
name: 'TypeScript',
desc: 'Strict syntactical superset of JavaScript',
icon: FileCode2
},
{
name: 'Python',
desc: 'Data structures, scripting & automation',
icon: Terminal
},
{
name: 'MySQL',
desc: 'Relational database management',
icon: Database
},
{
name: 'Node.js',
desc: 'Server-side JavaScript runtime',
icon: Server
},
{
name: 'Git',
desc: 'Version control & collaboration',
icon: GitBranch
},
{
name: 'HTML5',
desc: 'Semantic web markup & accessibility',
icon: Layout
}
];
const About: React.FC = () => {
return (
<section id="about" className="bg-[#f2f2f2] py-16">
<div className="max-w-[980px] mx-auto px-4 grid grid-cols-1 md:grid-cols-3 gap-12 items-center">
{/* Left Column: Bio - Vertically Centered */}
<div className="md:col-span-2 space-y-6">
<h3 className="text-2xl font-display font-medium text-[#333] border-b border-[#ccc] pb-2">
Why m5rcel?
</h3>
<div className="flex gap-6 items-center">
<img src="https://github.com/m4rcel-lol.png" alt="m4rcel-lol Avatar" className="w-[120px] h-[120px] rounded shadow-md border border-white" />
<div className="space-y-4">
<p className="text-sm text-[#444] font-sans leading-relaxed">
Its not just about writing code. Its about creating an experience that feels magical. From the moment the page loads, everything should feel responsive, fluid, and intuitive.
</p>
<p className="text-sm text-[#444] font-sans leading-relaxed">
I specialize in creating pixel-perfect interfaces that pay homage to the golden era of design while utilizing the raw power of modern web technologies.
</p>
</div>
</div>
</div>
{/* Right Column: Skills (Sidebar style) */}
<div className="md:col-span-1 bg-white border border-[#d6d6d6] rounded-md shadow-sm p-5 h-fit">
<h3 className="text-lg font-bold text-[#333] mb-4 font-sans text-center border-b border-[#eee] pb-2">Tech Stacks</h3>
<ul className="space-y-4">
{TECH_STACKS.map((tech, i) => (
<li key={i} className="flex items-start gap-3">
<div className="mt-0.5 text-[#555]">
<tech.icon size={18} strokeWidth={2} />
</div>
<div>
<div className="text-sm font-bold text-[#333]">{tech.name}</div>
<div className="text-[11px] text-[#666] leading-tight mt-0.5">{tech.desc}</div>
</div>
</li>
))}
</ul>
</div>
</div>
</section>
);
};
export default About;

17
components/Footer.tsx Normal file
View File

@@ -0,0 +1,17 @@
import React from 'react';
const Footer: React.FC = () => {
return (
<footer className="bg-[#f2f2f2] border-t border-[#d6d6d6] pt-8 pb-12 text-[11px] font-sans text-[#666]">
<div className="max-w-[980px] mx-auto px-4">
<div className="flex justify-center text-center">
<p>Copyright © 2025 m5rcel. All rights reserved.</p>
</div>
</div>
</footer>
);
};
export default Footer;

77
components/Hero.tsx Normal file
View File

@@ -0,0 +1,77 @@
import React from 'react';
import { motion } from 'framer-motion';
const Hero: React.FC = () => {
return (
<section className="bg-[#f2f2f2] border-b border-[#d6d6d6] overflow-hidden">
{/* 2009 Layout: Fixed width centered */}
<div className="max-w-[980px] mx-auto pt-12 pb-16 px-4 text-center relative">
{/* Main Headline - Myriad Pro style */}
<motion.h1
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.8 }}
className="text-5xl md:text-6xl font-display font-semibold text-[#333] tracking-tight mb-2"
>
m5rcel.
</motion.h1>
{/* Subheadline */}
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3, duration: 0.8 }}
className="text-2xl md:text-3xl font-light text-[#666] mb-8 font-sans"
>
The developer that likes to do stuff out of boredom.
</motion.p>
{/* Product Hero Image with Reflection */}
<motion.div
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.5, duration: 0.8 }}
className="relative z-10 mb-8"
>
<div className="reflection">
<img
src="https://picsum.photos/id/48/900/400"
alt="Hero Product"
className="mx-auto rounded-md shadow-2xl border border-white"
/>
</div>
</motion.div>
{/* The Classic "Aqua" Buttons */}
<div className="flex justify-center gap-6 mt-12 relative z-20">
<a href="mailto:m5r@kitties.email" className="group relative inline-flex items-center justify-center">
{/* Glossy Button Container */}
<div className="px-8 py-2 rounded-full bg-glossy-blue shadow-md border border-[#0450a3] active:brightness-90">
<span className="text-white text-sm font-bold font-sans drop-shadow-md">
Contact Me
</span>
</div>
</a>
<a href="https://github.com/m4rcel-lol" className="group relative inline-flex items-center justify-center">
{/* Silver Button Container */}
<div className="px-8 py-2 rounded-full bg-glossy-silver shadow-md border border-[#a6a6a6] hover:bg-white active:brightness-95">
<span className="text-[#333] text-sm font-bold font-sans text-emboss">
See GitHub
</span>
</div>
</a>
</div>
{/* Small legal text typical of ads */}
<p className="mt-8 text-[10px] text-gray-400 font-sans">
Requires an internet connection. Battery life varies by use and configuration.
</p>
</div>
</section>
);
};
export default Hero;

View File

@@ -0,0 +1,32 @@
import React, { useEffect } from 'react';
import { Apple } from 'lucide-react';
interface LoadingScreenProps {
onComplete: () => void;
}
const LoadingScreen: React.FC<LoadingScreenProps> = ({ onComplete }) => {
useEffect(() => {
// Simulate classic boot time
const timer = setTimeout(() => {
onComplete();
}, 2000);
return () => clearTimeout(timer);
}, [onComplete]);
return (
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center bg-[#bfbfbf]">
{/* Mac OS X Boot Logo */}
<div className="mb-12">
<Apple size={96} className="text-[#555555] fill-[#555555]" />
</div>
{/* Classic Mac Spinner */}
<div className="relative w-8 h-8">
<div className="absolute inset-0 border-4 border-[#888888] border-t-[#444444] rounded-full animate-spin"></div>
</div>
</div>
);
};
export default LoadingScreen;

27
components/Navbar.tsx Normal file
View File

@@ -0,0 +1,27 @@
import React from 'react';
import { Apple } from 'lucide-react';
interface NavbarProps {
toggleTheme: () => void;
isDark: boolean;
}
const Navbar: React.FC<NavbarProps> = () => {
return (
<nav className="w-full bg-glossy-nav border-b border-[#333] shadow-md h-[44px] relative z-50 font-sans antialiased">
<div className="max-w-[980px] mx-auto h-full flex items-center justify-center md:justify-start px-2">
{/* Nav Items */}
<ul className="flex items-center h-full">
{/* Apple Logo */}
<li className="h-full flex items-center px-4">
<Apple className="text-[#e6e6e6] drop-shadow-md filter" size={18} fill="#e6e6e6" />
</li>
</ul>
</div>
</nav>
);
};
export default Navbar;

84
components/Projects.tsx Normal file
View File

@@ -0,0 +1,84 @@
import React from 'react';
import { PROJECTS } from '../constants';
const Projects: React.FC = () => {
return (
<section id="projects" className="bg-[#fff] border-b border-[#d6d6d6] py-12">
<div className="max-w-[980px] mx-auto px-4">
{/* Section Header */}
<div className="text-center mb-12">
<h2 className="text-3xl font-display font-medium text-[#333] mb-2">
Built for performance.
</h2>
<p className="text-[#666] text-lg font-sans">
Thousands of lines of code. Zero compromise.
</p>
</div>
{/* 2009 Grid Layout */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{PROJECTS.map((project) => (
<div key={project.id} className="flex flex-col group">
{/* Image Box */}
<div className="h-[240px] w-full overflow-hidden rounded-md border border-gray-300 shadow-md mb-4 bg-gray-100 relative">
<img
src={project.image}
alt={project.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
{/* Gloss Overlay */}
<div className="absolute inset-0 bg-gradient-to-b from-white/20 to-transparent pointer-events-none" />
</div>
{/* Content */}
<h3 className="text-xl font-bold text-[#333] font-sans mb-1 group-hover:text-[#0083e6] transition-colors cursor-pointer flex items-center gap-2">
{project.title}
{/* Fedora SVG */}
{project.id === 'fedora-bytebeat' && (
<svg viewBox="0 0 14 14" className="w-5 h-5 mb-0.5" xmlns="http://www.w3.org/2000/svg">
<path d="m 6.8848754,1.0000711 c -3.2587005,0.0618 -5.8826129,2.72355 -5.8848705,5.99671 l 0,4.6430899 c 0.00181,0.75194 0.6125562,1.3602 1.3651321,1.3602 2.682e-4,0 0.00137,0 0.00164,0 8.23e-4,-10e-6 0.00249,0 0.00329,0 l 0.00164,0 4.6315803,0 c 3.3124987,-10e-4 5.9967077,-2.68707 5.9967077,-5.9999999 0,-3.31392 -2.686309,-6 -5.9999997,-6 -0.02588,0 -0.0515,-3.1999e-4 -0.0773,0 -0.0128,1.6e-4 -0.02505,-2.3999e-4 -0.03783,0 z m 1.730263,1.82073 c 0.02126,-5.4e-4 0.04275,0 0.06414,0 0.209167,0 0.341292,0.0205 0.539474,0.0724 0.226849,0.0595 0.394658,0.24499 0.394735,0.41447 1.01e-4,0.24819 -0.136939,0.39309 -0.422696,0.39309 -0.154071,0 -0.22822,-0.0345 -0.511513,-0.0345 -0.895596,0 -1.626782,0.73112 -1.628287,1.62664 l 0,1.22369 c 0,0.22408 0.18709,0.41283 0.411183,0.41283 l 0.93092,0 c 0.23447,0 0.41758,0.17967 0.417764,0.41447 0,0.23473 -0.183215,0.41447 -0.417764,0.41447 l -1.129933,0 -0.21217,0 0,0.21054 0,1.42762 c 0,1.3691599 -1.104569,2.4753299 -2.473686,2.4753299 -0.209215,0 -0.3413107,-0.0205 -0.5394734,-0.0724 -0.2267786,-0.0594 -0.3946594,-0.24681 -0.394737,-0.41611 0,-0.24819 0.1367469,-0.39145 0.4226979,-0.39145 0.1538263,0 0.2283257,0.0345 0.5115125,0.0345 0.895603,0 1.626888,-0.7313 1.628291,-1.6266399 -3e-6,0 0,-1.2316 0,-1.23191 -3e-6,-0.22438 -0.187095,-0.40954 -0.411187,-0.40954 -6.36e-4,0 -0.0016,0 -0.0016,0 l -0.930918,0 c -0.234955,0 -0.416121,-0.17954 -0.416121,-0.41447 -6.1e-5,-0.23627 0.184085,-0.41447 0.424343,-0.41447 l 1.125,0 0.210527,0 0,-0.21217 0,-1.42106 c -3e-6,-1.34791 1.069895,-2.44152 2.409538,-2.47532 z m 1.30921,0.0954 c 0.8562886,0.44851 1.4407896,1.34457 1.4407896,2.37829 0,1.3868 -1.052185,2.51856 -2.4013146,2.65954 0.164957,-0.15256 0.268091,-0.36887 0.268091,-0.6102 -2.04e-4,-0.26094 -0.121941,-0.49472 -0.310853,-0.64802 0.668547,-0.1158 1.1825636,-0.70111 1.1825636,-1.40132 1e-6,-0.50763 -0.2695986,-0.9535 -0.6726976,-1.20395 0.353577,-0.0982 0.6037636,-0.41032 0.6036196,-0.78289 -6.9e-5,-0.14405 -0.04107,-0.27657 -0.1101986,-0.39145 z m -5.6282893,3.81415 c -0.1658665,0.15217 -0.2697985,0.37124 -0.2697371,0.61348 0,0.26114 0.120642,0.49314 0.309212,0.64638 -0.6683184,0.11707 -1.1809229,0.70752 -1.1809229,1.4079 0,0.50754 0.2684383,0.9523099 0.671053,1.2022999 -0.3531752,0.0987 -0.6036184,0.411 -0.6036184,0.7829 6.89e-5,0.14404 0.039434,0.2763 0.1085531,0.39144 -0.8552602,-0.44881 -1.4391449,-1.34359 -1.4391449,-2.3766399 0,-1.38801 1.0536597,-2.52765 2.4046052,-2.66776 z" fill="#000000"/>
</svg>
)}
{/* Python SVG */}
{['m5rcode', 'bytebeat-win', 'fedora-bytebeat'].includes(project.id) && (
<svg viewBox="0 0 512 512" className="w-4 h-4 mb-0.5" xmlns="http://www.w3.org/2000/svg">
<g>
<path d="M194.734,246.879h121.768c33.9,0,60.956-27.908,60.956-61.95V68.846c0-33.035-27.87-57.855-60.956-63.371 c-20.943-3.484-42.673-5.069-63.51-4.971c-20.845,0.097-40.74,1.874-58.258,4.971c-51.586,9.117-60.952,28.191-60.952,63.371 v46.463H255.69v15.486H133.782h-45.75c-35.434,0-66.459,21.295-76.158,61.808c-11.192,46.435-11.694,75.409,0,123.898 c8.666,36.088,29.359,61.807,64.79,61.807h41.917v-55.699C118.581,282.37,153.39,246.879,194.734,246.879z M187.063,84.333 c-12.636,0-22.877-10.355-22.877-23.161c0-12.849,10.241-23.3,22.877-23.3c12.594,0,22.873,10.451,22.873,23.3 C209.936,73.979,199.658,84.333,187.063,84.333z M499.37,192.603c-8.761-35.27-25.484-61.808-60.96-61.808h-45.75v54.134 c0,41.972-35.582,77.292-76.158,77.292H194.734c-33.349,0-60.952,28.547-60.952,61.954v116.079 c0,33.037,28.726,52.476,60.952,61.943c38.589,11.353,75.59,13.409,121.768,0c30.688-8.876,60.956-26.764,60.956-61.943v-46.461 H255.69v-15.486h121.768h60.952c35.431,0,48.638-24.715,60.96-61.807C512.092,278.314,511.549,241.589,499.37,192.603z M324.178,424.766c12.64,0,22.873,10.356,22.873,23.156c0,12.85-10.233,23.305-22.873,23.305 c-12.595,0-22.877-10.455-22.877-23.305C301.301,435.122,311.583,424.766,324.178,424.766z" fill="#000000" />
</g>
</svg>
)}
{/* Vercel SVG */}
{project.id === 'geminicord' && (
<svg viewBox="0 0 15 15" className="w-4 h-4 mb-0.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.49998 1L6.92321 2.00307L1.17498 12L0.599976 13H1.7535H13.2464H14.4L13.825 12L8.07674 2.00307L7.49998 1ZM7.49998 3.00613L2.3285 12H12.6714L7.49998 3.00613Z"
fill="#333333"
/>
</svg>
)}
</h3>
<p className="text-sm text-[#666] leading-relaxed font-sans mb-3">
{project.description}
</p>
<a
href={project.githubUrl}
className="text-[12px] font-bold text-[#0083e6] hover:underline flex items-center gap-1 font-sans"
>
Learn more <span className="text-[10px]"></span>
</a>
</div>
))}
</div>
</div>
</section>
);
};
export default Projects;

84
constants.ts Normal file
View File

@@ -0,0 +1,84 @@
import { Project, TimelineItem } from './types';
// Apple's "Emphasized" easing
export const EASE_APPLE = [0.2, 0, 0, 1];
export const PROJECTS: Project[] = [
{
id: 'm5rcode',
title: 'm5rcode',
category: 'Language Design',
description: 'Experimental polyglot programming language written with a blend of Python, JavaScript, PHP, C#, and C++.',
image: 'https://picsum.photos/seed/m5rcode/800/600',
tags: ['C++', 'Python', 'Compiler'],
githubUrl: 'https://github.com/m4rcel-lol/m5rcode',
featured: true,
},
{
id: 'geminicord',
title: 'Geminicord',
category: 'AI Web Application',
description: "Pixel-perfect recreation of the modern Discord UI, powered by Google's Gemini AI.",
image: 'https://picsum.photos/seed/geminicord/800/600',
tags: ['React', 'Gemini API', 'UI Clone'],
githubUrl: 'https://github.com/m4rcel-lol/custom-discord-ai-chatbot-site',
featured: true,
},
{
id: 'bytebeat-win',
title: 'Python Bytebeat Player',
category: 'Audio Software',
description: 'Play Bytebeat sequences on PC using Python.',
image: 'https://picsum.photos/seed/bytebeat1/800/600',
tags: ['Python', 'Audio Synthesis'],
githubUrl: 'https://github.com/m4rcel-lol/python-bytebeat-player',
},
{
id: 'fedora-bytebeat',
title: 'Python Bytebeat Player',
category: 'Linux Audio',
description: 'Play Bytebeat sequences on PC using Python, but on linux.',
image: 'https://picsum.photos/seed/fedora/800/600',
tags: ['Python', 'Linux', 'Fedora'],
githubUrl: 'https://github.com/m4rcel-lol/fedora-bytebeat-player',
},
];
export const TIMELINE: TimelineItem[] = [
{
year: '2024',
title: 'Senior Frontend Engineer',
description: 'Leading UI architecture for next-gen consumer products.',
},
{
year: '2022',
title: 'Creative Developer',
description: 'Bridging the gap between design and engineering with motion-heavy interfaces.',
},
{
year: '2020',
title: 'Open Source Contributor',
description: 'Started journey contributing to major UI libraries.',
},
];
// Animation Variants
export const fadeInUp = {
hidden: { opacity: 0, y: 40 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.8, ease: EASE_APPLE }
}
};
export const staggerContainer = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2
}
}
};

97
index.html Normal file
View File

@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>m5rcel - Developer & Designer</title>
<meta name="description" content="Portfolio of m5rcel. Designed in California." />
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: [
'"Lucida Grande"',
'"Lucida Sans Unicode"',
'"Helvetica Neue"',
'Helvetica',
'Arial',
'sans-serif',
],
display: [
'"Myriad Pro"',
'"Helvetica Neue"',
'Arial',
'sans-serif',
]
},
colors: {
retro: {
navTop: '#4e4e4e',
navBot: '#383838',
blueTop: '#71b1f8',
blueBot: '#0083e6',
bg: '#f2f2f2',
text: '#333333',
textLight: '#666666',
border: '#d6d6d6',
},
},
boxShadow: {
'retro': '0 1px 4px rgba(0,0,0,0.3)',
'retro-inset': 'inset 0 1px 3px rgba(0,0,0,0.2)',
'gloss': 'inset 0 1px 0 rgba(255,255,255,0.4)',
},
backgroundImage: {
'glossy-nav': 'linear-gradient(to bottom, #5e5e5e 0%, #404040 50%, #353535 51%, #383838 100%)',
'glossy-blue': 'linear-gradient(to bottom, #7cbdfe 0%, #2f8bfd 50%, #0672e7 51%, #167efb 100%)',
'glossy-silver': 'linear-gradient(to bottom, #f2f2f2 0%, #e6e6e6 50%, #d9d9d9 51%, #e6e6e6 100%)',
}
},
},
};
</script>
<style>
body {
-webkit-font-smoothing: antialiased;
background-color: #f2f2f2;
color: #333;
}
/* The iconic wet floor reflection */
.reflection {
-webkit-box-reflect: below 0px -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(70%, transparent), to(rgba(255,255,255,0.4)));
}
/* 2009 Search Bar Inset Shadow */
.search-inset {
box-shadow: inset 0 1px 3px rgba(0,0,0,0.5), 0 1px 0 rgba(255,255,255,0.2);
}
/* Text Emboss */
.text-emboss {
text-shadow: 0 1px 0 rgba(255,255,255,0.5);
}
.text-shadow-nav {
text-shadow: 0 -1px 0 rgba(0,0,0,0.6);
}
</style>
<script type="importmap">
{
"imports": {
"react": "https://aistudiocdn.com/react@^19.2.1",
"react-dom/": "https://aistudiocdn.com/react-dom@^19.2.1/",
"react/": "https://aistudiocdn.com/react@^19.2.1/",
"framer-motion": "https://aistudiocdn.com/framer-motion@^12.23.25",
"lucide-react": "https://aistudiocdn.com/lucide-react@^0.556.0"
}
}
</script>
</head>
<body>
<div id="root"></div>
</body>
</html>

15
index.tsx Normal file
View File

@@ -0,0 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error("Could not find root element to mount to");
}
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

5
metadata.json Normal file
View File

@@ -0,0 +1,5 @@
{
"name": "m5rcel Portfolio",
"description": "A premium, Apple-styled personal portfolio website for m5rcel, featuring cinematic animations and clean typography.",
"requestFramePermissions": []
}

23
package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "m5rcel-portfolio",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.2.1",
"react-dom": "^19.2.1",
"framer-motion": "^12.23.25",
"lucide-react": "^0.556.0"
},
"devDependencies": {
"@types/node": "^22.14.0",
"@vitejs/plugin-react": "^5.0.0",
"typescript": "~5.8.2",
"vite": "^6.2.0"
}
}

29
tsconfig.json Normal file
View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"module": "ESNext",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
"types": [
"node"
],
"moduleResolution": "bundler",
"isolatedModules": true,
"moduleDetection": "force",
"allowJs": true,
"jsx": "react-jsx",
"paths": {
"@/*": [
"./*"
]
},
"allowImportingTsExtensions": true,
"noEmit": true
}
}

22
types.ts Normal file
View File

@@ -0,0 +1,22 @@
export interface Project {
id: string;
title: string;
category: string;
description: string;
image: string;
tags: string[];
githubUrl: string;
demoUrl?: string;
featured?: boolean;
}
export interface Skill {
name: string;
icon: string;
}
export interface TimelineItem {
year: string;
title: string;
description: string;
}

23
vite.config.ts Normal file
View File

@@ -0,0 +1,23 @@
import path from 'path';
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, '.', '');
return {
server: {
port: 3000,
host: '0.0.0.0',
},
plugins: [react()],
define: {
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
}
}
};
});