🚀 Deploy ROM Mirror Portfolio with Glassmorphism

 Features:
- Enhanced glassmorphism design throughout
- Google Drive ROM mirror integration
- Professional icon system (no emojis)
- GitHub Actions secure deployment
- Responsive design for all devices

🔐 Security:
- API keys protected via GitHub Secrets
- No sensitive data in repository
- Automated security scanning

🎯 ROM Mirror:
- Real Google Drive file browser
- Download tracking and analytics
- Beautiful file management interface
- Mobile-optimized experience
This commit is contained in:
2025-07-07 12:20:46 +02:00
commit a03b00a8da
20 changed files with 6735 additions and 0 deletions

140
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,140 @@
name: Deploy ROM Mirror Portfolio to overspend.cloud
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create secure config file
run: |
echo "Creating secure configuration..."
cat > js/config.js << 'EOF'
// ROM Mirror Configuration - Generated by GitHub Actions
// This file contains secure credentials and is not committed to git
// Deployed to: overspend.cloud
window.ROM_MIRROR_CONFIG = {
DRIVE_API_KEY: "${{ secrets.DRIVE_API_KEY }}",
DRIVE_FOLDER_ID: "${{ secrets.DRIVE_FOLDER_ID }}",
// Domain configuration
DOMAIN: "overspend.cloud",
// Optional configuration
MAX_RETRIES: 3,
CACHE_DURATION: 300000,
// File type icons mapping
FILE_ICONS: {
"zip": "icon-zip",
"img": "icon-image",
"iso": "icon-image",
"apk": "icon-file",
"txt": "icon-file",
"md": "icon-file",
"rar": "icon-zip",
"7z": "icon-zip"
},
// User messages
MESSAGES: {
LOADING: "Loading ROM files from Google Drive...",
ERROR: "Failed to connect to Google Drive. Please check your configuration.",
NO_FILES: "No ROM files found in this folder.",
DOWNLOAD_SUCCESS: "Download started successfully!",
SECURITY_OK: "Secure connection to overspend.cloud verified"
}
};
// Production environment indicator
window.ROM_MIRROR_CONFIG.ENVIRONMENT = "production";
window.ROM_MIRROR_CONFIG.BUILD_TIME = "${{ github.run_number }}";
window.ROM_MIRROR_CONFIG.COMMIT_SHA = "${{ github.sha }}";
window.ROM_MIRROR_CONFIG.DEPLOY_DOMAIN = "overspend.cloud";
EOF
- name: Verify config file creation
run: |
echo "Verifying config file exists..."
if [ -f "js/config.js" ]; then
echo "✅ Config file created successfully"
echo "File size: $(wc -c < js/config.js) bytes"
else
echo "❌ Config file creation failed"
exit 1
fi
- name: Setup Pages
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
uses: actions/configure-pages@v4
- name: Upload artifact
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
uses: actions/upload-pages-artifact@v3
with:
path: "."
deploy:
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
security-check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Security audit
run: |
echo "🔍 Running security checks..."
# Check for exposed secrets in code
echo "Checking for exposed API keys..."
if grep -r "AIzaSy" . --exclude-dir=.git --exclude-dir=.github --exclude="*.md" --exclude="deploy-to-repo.sh"; then
echo "❌ Found potential exposed API keys!"
exit 1
else
echo "✅ No exposed API keys found"
fi
# Check gitignore
echo "Checking .gitignore configuration..."
if grep -q "js/config.js" .gitignore; then
echo "✅ Config file properly ignored"
else
echo "⚠️ Warning: config.js not in .gitignore"
fi
# Check for config template
if [ -f "js/config.example.js" ]; then
echo "✅ Config template exists"
else
echo "⚠️ Warning: config.example.js template missing"
fi
echo "🔐 Security check completed"

57
.gitignore vendored Normal file
View File

@@ -0,0 +1,57 @@
# ROM Mirror Configuration
js/config.js
# Environment variables
.env
.env.local
.env.production
# API Keys and Secrets
**/config.js
**/secrets.js
**/*secret*
**/*key*
**/*token*
# Development files
.DS_Store
Thumbs.db
*.log
*.tmp
*.temp
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Node modules (if using build tools)
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build outputs
dist/
build/
out/
# Cache directories
.cache/
.parcel-cache/
# Backup files
*.backup
*.bak
*.old
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

164
README.md Normal file
View File

@@ -0,0 +1,164 @@
# Wiktor - Tech Enthusiast Portfolio
## 🌟 NEW: Full Glassmorphism Theme + Enhanced Contact System
### ✨ Complete Glassmorphism Redesign
- **Transparent Glass Theme** throughout the entire site
- **Backdrop Blur Effects** on all components
- **Floating Glass Cards** for content sections
- **Enhanced Particle System** with dynamic backgrounds
- **Revamped Contact Section** with animated clickable buttons
### 🎯 Authentic Hobbyist Experience
This portfolio honestly represents my journey as a weekend tech tinkerer - no inflated claims, just genuine curiosity about technology!
## Key Features
### 🔮 Full Glassmorphism Design
- **Glass Cards**: Every section uses transparent, blurred backgrounds
- **Layered Transparency**: Multiple depth levels with backdrop filters
- **Dynamic Lighting**: Animated gradients and glow effects
- **Floating Elements**: 3D-style cards that hover and respond
- **Particle Background**: Animated elements that enhance the glass effect
### 🌟 Enhanced Glass Sidebar
- **Ultra-Transparent Design** with multiple blur layers
- **Smart Navigation** with section tracking
- **Social Integration** with actual contact links
- **Mobile-Optimized** full-screen glass overlay
### 🎨 Revamped Sections
#### **Hero Section**
- **Glass Content Card** with floating particles
- **Enhanced Typewriter**: "Hobby Programmer" → "Home Lab Enthusiast" → "Weekend Tinkerer" → "Tech Explorer" → "ROM Experimenter"
- **Glowing Text Effects** with drop shadows
- **Animated Glass Buttons** with sweep effects
#### **About Section**
- **Transparent Content Cards** with honest bio
- **Interactive Competency List** with hover animations
- **Learning Note Callouts** with special glass styling
- **Authentic Messaging** about hobbyist experience
#### **Experimentation Toolkit**
- **Revamped Tech Section** with casual language:
- "Technologies I've Been Playing Around With"
- "Search my toolkit..."
- Filter buttons: "Everything", "Coding", "Web Stuff", etc.
- **Enhanced Tech Cards** with 3D hover effects
- **Casual Proficiency Labels**: "Dabbling", "Weekend Fun", "Home Lab", etc.
- **Glass Filter Controls** with animated interactions
#### **Interactive Contact Section**
- **Animated Contact Buttons** with your actual links:
- 📧 **Email**: wiktator.olszewski@gmail.com
-**GitHub**: github.com/overspend1
- 💬 **Telegram**: t.me/overspend1
- **Advanced Button Animations**:
- Particle burst effects on hover
- Color-coded themes for each platform
- Ripple click effects
- Success feedback animations
- Staggered entrance animations
### 🎯 Honest Tech Stack
Updated with authentic hobbyist language:
- **"Coding & Scripting"** instead of "Programming & Scripting"
- **"Web & Server Stuff"** instead of formal technical terms
- **Casual proficiency levels**: "Dabbling", "Tinkering", "Weekend Project"
- **Real use cases**: "Daily Gamer", "Password Manager", "Media Server"
## Technical Implementation
### Glassmorphism CSS Features
```css
/* Glass Card Base */
.glass-card {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
/* Enhanced Contact Buttons */
.contact-btn {
backdrop-filter: blur(20px);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
```
### Advanced Contact Animations
- **ContactAnimations Class**: Handles button interactions
- **Particle System**: Creates burst effects on hover
- **Ripple Effects**: Click feedback with expanding circles
- **Color Themes**: Unique colors for each contact method
- **Success Feedback**: Animated tooltips on click
### Enhanced Particle System
- **Multi-layer Particles**: Different sizes and speeds
- **Glassmorphism Integration**: Particles complement transparent theme
- **Performance Optimized**: GPU-accelerated animations
## Project Structure
```
portfolio/
├── index.html # Full glassmorphism HTML
├── css/
│ ├── main.css # Complete glass theme
│ ├── animations.css # Enhanced particle effects
│ ├── tech-stack.css # Glass tech cards
│ ├── sidebar.css # Ultra-transparent sidebar
│ └── responsive.css # Mobile glass optimization
├── js/
│ ├── main.js # Core functionality
│ ├── typewriter.js # Hobbyist typewriter sequence
│ ├── tech-stack.js # Casual tech filtering
│ ├── sidebar.js # Glass sidebar interactions
│ ├── animations.js # Enhanced effects
│ └── contact-animations.js # Advanced contact system
├── assets/
│ └── data/
│ └── tech-stack.json # Honest hobbyist data
└── README.md
```
## Honest Messaging Examples
- **"Experimentation Toolkit"** instead of "Tech Stack"
- **"Technologies I've Been Playing Around With"**
- **"Search my toolkit..."** instead of professional terms
- **Casual filters**: "Everything", "Coding", "Web Stuff"
- **Real proficiency**: "Dabbling", "Weekend Fun", "Daily Use"
## Contact Information (Real Links!)
- **📧 Email**: [wiktator.olszewski@gmail.com](mailto:wiktator.olszewski@gmail.com)
- **⚡ GitHub**: [github.com/overspend1](https://github.com/overspend1)
- **💬 Telegram**: [t.me/overspend1](https://t.me/overspend1)
## Browser Support
- **Chrome 60+**: Full glassmorphism with backdrop-filter
- **Firefox 55+**: Graceful fallback without backdrop-filter
- **Safari 12+**: Full glassmorphism support
- **Edge 79+**: Complete feature support
## Getting Started
1. Open `index.html` in any modern browser
2. Experience the full glassmorphism theme
3. Click the ☰ button for the enhanced glass sidebar
4. Try the animated contact buttons
5. Explore the revamped toolkit section
## Performance Features
- **Hardware Acceleration**: GPU-powered glass effects
- **Optimized Animations**: 60fps smooth interactions
- **Lazy Loading**: Efficient particle systems
- **Mobile Optimized**: Touch-friendly glass interfaces
---
**© 2024 Wiktor. Tech Enthusiast. Always Learning.**
### 🎬 Experience the Glass
*A complete glassmorphism portfolio that honestly showcases my journey as a hobbyist tech explorer. Every element is transparent, every animation is smooth, and every claim is authentic to my actual experience level.*

220
assets/data/tech-stack.json Normal file
View File

@@ -0,0 +1,220 @@
{
"categories": {
"languages": {
"name": "Programming Languages",
"color": "#ff6b6b",
"icon": "icon-code",
"techs": [
{
"name": "Python",
"proficiency": "Hobby Projects",
"experience": "Building small scripts and automation tools"
},
{
"name": "JavaScript",
"proficiency": "Learning",
"experience": "Frontend experiments and this portfolio"
},
{
"name": "Bash",
"proficiency": "Daily Use",
"experience": "System automation and server management"
},
{
"name": "HTML/CSS",
"proficiency": "Experimenting",
"experience": "Web design and portfolio building"
}
]
},
"systems": {
"name": "Operating Systems & Tools",
"color": "#4ecdc4",
"icon": "icon-system",
"techs": [
{
"name": "Linux",
"proficiency": "Home Lab",
"experience": "Ubuntu/Debian servers, command line daily"
},
{
"name": "Windows",
"proficiency": "Daily Driver",
"experience": "Main desktop OS, PowerShell scripting"
},
{
"name": "Android",
"proficiency": "ROM Tinkering",
"experience": "Custom ROMs, kernel modifications"
},
{
"name": "Docker",
"proficiency": "Container Fun",
"experience": "Self-hosted services containerization"
}
]
},
"webtech": {
"name": "Web Technologies",
"color": "#45b7d1",
"icon": "icon-web",
"techs": [
{
"name": "Apache",
"proficiency": "Home Server",
"experience": "Local web server for projects"
},
{
"name": "Nginx",
"proficiency": "Reverse Proxy",
"experience": "Load balancing for self-hosted apps"
},
{
"name": "Git",
"proficiency": "Version Control",
"experience": "GitHub for project management"
},
{
"name": "Vercel",
"proficiency": "Deployment",
"experience": "Static site hosting experiments"
}
]
},
"databases": {
"name": "Data & Databases",
"color": "#96ceb4",
"icon": "icon-database",
"techs": [
{
"name": "SQLite",
"proficiency": "Simple Projects",
"experience": "Lightweight apps and data storage"
},
{
"name": "MariaDB",
"proficiency": "Home Lab",
"experience": "Database for self-hosted applications"
},
{
"name": "JSON",
"proficiency": "Data Format",
"experience": "API work and configuration files"
},
{
"name": "CSV",
"proficiency": "Data Analysis",
"experience": "Spreadsheet automation and imports"
}
]
},
"cloud": {
"name": "Cloud & Hosting",
"color": "#feca57",
"icon": "icon-cloud",
"techs": [
{
"name": "AWS EC2",
"proficiency": "Experimenting",
"experience": "Free tier server experiments"
},
{
"name": "OVH VPS",
"proficiency": "Hosting",
"experience": "Affordable cloud server testing"
},
{
"name": "GitHub Pages",
"proficiency": "Static Hosting",
"experience": "Portfolio and project demos"
},
{
"name": "Cloudflare",
"proficiency": "DNS/CDN",
"experience": "Domain management and caching"
}
]
},
"selfhosted": {
"name": "Self-Hosted Services",
"color": "#ff9ff3",
"icon": "icon-server",
"techs": [
{
"name": "Plex",
"proficiency": "Media Server",
"experience": "Personal streaming setup at home"
},
{
"name": "Jellyfin",
"proficiency": "Open Source",
"experience": "Alternative media server testing"
},
{
"name": "Pi-hole",
"proficiency": "Network Filter",
"experience": "Network-wide ad blocking"
},
{
"name": "Nextcloud",
"proficiency": "File Sync",
"experience": "Personal cloud storage solution"
}
]
},
"hardware": {
"name": "Hardware & Electronics",
"color": "#54a0ff",
"icon": "icon-hardware",
"techs": [
{
"name": "Arduino",
"proficiency": "Weekend Projects",
"experience": "Basic sensors and LED experiments"
},
{
"name": "Raspberry Pi",
"proficiency": "Home Automation",
"experience": "Self-hosting and IoT projects"
},
{
"name": "PC Building",
"proficiency": "Enthusiast",
"experience": "Custom builds for gaming and servers"
},
{
"name": "Network Setup",
"proficiency": "Home Lab",
"experience": "Router configuration and VLANs"
}
]
},
"tools": {
"name": "Development Tools",
"color": "#5f27cd",
"icon": "icon-devtools",
"techs": [
{
"name": "VS Code",
"proficiency": "Daily Editor",
"experience": "Primary code editor with extensions"
},
{
"name": "Terminal",
"proficiency": "Command Line",
"experience": "Daily workflow, multiple shell environments"
},
{
"name": "GitHub",
"proficiency": "Version Control",
"experience": "Repository management and collaboration"
},
{
"name": "Postman",
"proficiency": "API Testing",
"experience": "REST API development and testing"
}
]
}
}
}

248
css/animations.css Normal file
View File

@@ -0,0 +1,248 @@
/* Animation Effects */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
@keyframes float {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-20px);
}
}
/* Apply animations */
.fade-in-up {
animation: fadeInUp 0.6s ease forwards;
}
.slide-in-left {
animation: slideInLeft 0.6s ease forwards;
}
.slide-in-right {
animation: slideInRight 0.6s ease forwards;
}
.pulse {
animation: pulse 2s infinite;
}
.float {
animation: float 3s ease-in-out infinite;
}
/* Hover effects */
.hover-scale {
transition: transform 0.3s ease;
}
.hover-scale:hover {
transform: scale(1.05);
}
.hover-glow {
transition: box-shadow 0.3s ease;
}
.hover-glow:hover {
box-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
}
/* Enhanced Particle Effects */
.hero-particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
z-index: 1;
}
.particle {
position: absolute;
background: #00ff88;
border-radius: 50%;
animation: particleFloat 8s infinite linear;
opacity: 0.7;
box-shadow: 0 0 6px rgba(0, 255, 136, 0.8);
}
.particle:nth-child(1) {
width: 3px;
height: 3px;
left: 10%;
animation-delay: -2s;
animation-duration: 6s;
}
.particle:nth-child(2) {
width: 4px;
height: 4px;
left: 30%;
animation-delay: -4s;
animation-duration: 8s;
}
.particle:nth-child(3) {
width: 2px;
height: 2px;
left: 50%;
animation-delay: -6s;
animation-duration: 7s;
}
.particle:nth-child(4) {
width: 5px;
height: 5px;
left: 70%;
animation-delay: -8s;
animation-duration: 9s;
}
.particle:nth-child(5) {
width: 3px;
height: 3px;
left: 90%;
animation-delay: -1s;
animation-duration: 5s;
}
@keyframes particleFloat {
0% {
transform: translateY(100vh) translateX(0) rotate(0deg);
opacity: 0;
}
10% {
opacity: 0.7;
}
50% {
transform: translateY(50vh) translateX(20px) rotate(180deg);
}
90% {
opacity: 0.7;
}
100% {
transform: translateY(-100px) translateX(-20px) rotate(360deg);
opacity: 0;
}
}
/* Glassmorphism Enhancement */
.glass-effect {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* Additional glass elements */
.hero-content::before {
content: "";
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(0, 255, 136, 0.1) 0%, transparent 70%);
animation: rotateGlow 20s linear infinite;
pointer-events: none;
z-index: -1;
}
@keyframes rotateGlow {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Enhanced button effects */
.btn {
position: relative;
overflow: hidden;
}
.btn::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
}
.btn:hover::before {
left: 100%;
}
/* Sidebar glass enhancement */
.glass-sidebar::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent 30%, rgba(0, 255, 136, 0.05) 50%, transparent 70%);
pointer-events: none;
animation: glassShimmer 3s ease-in-out infinite;
}
@keyframes glassShimmer {
0%, 100% {
opacity: 0;
}
50% {
opacity: 1;
}
}

941
css/main.css Normal file
View File

@@ -0,0 +1,941 @@
/* Enhanced Glassmorphism Theme - Full Site */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Inter", sans-serif;
line-height: 1.6;
color: #fff;
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0f0f0f 100%);
background-attachment: fixed;
overflow-x: hidden;
position: relative;
}
/* Professional Icon Classes */
.icon-home::before {
content: "⌂";
font-size: 1.2em;
font-weight: normal;
}
.icon-user::before {
content: "◈";
font-size: 1.2em;
font-weight: normal;
}
.icon-tools::before {
content: "⚙";
font-size: 1.2em;
font-weight: normal;
}
.icon-mail::before {
content: "✉";
font-size: 1.2em;
font-weight: normal;
}
.icon-github::before {
content: "⚡";
font-size: 1.2em;
font-weight: normal;
}
.icon-telegram::before {
content: "◉";
font-size: 1.2em;
font-weight: normal;
}
.icon-code::before {
content: "{ }";
font-size: 1em;
font-weight: bold;
}
.icon-system::before {
content: "■";
font-size: 1.2em;
font-weight: normal;
}
.icon-web::before {
content: "◯";
font-size: 1.2em;
font-weight: normal;
}
.icon-database::before {
content: "⚏";
font-size: 1.2em;
font-weight: normal;
}
.icon-cloud::before {
content: "☁";
font-size: 1.2em;
font-weight: normal;
}
.icon-server::before {
content: "⚊";
font-size: 1.2em;
font-weight: normal;
}
.icon-hardware::before {
content: "⚡";
font-size: 1.2em;
font-weight: normal;
}
.icon-devtools::before {
content: "⚒";
font-size: 1.2em;
font-weight: normal;
}
.icon-grid::before {
content: "▦";
font-size: 1.2em;
font-weight: normal;
}
.icon-upload::before {
content: "↑";
font-size: 1.2em;
font-weight: normal;
}
.icon-folder::before {
content: "▣";
font-size: 1.2em;
font-weight: normal;
}
.icon-file::before {
content: "▢";
font-size: 1.2em;
font-weight: normal;
}
.icon-zip::before {
content: "◈";
font-size: 1.2em;
font-weight: normal;
}
.icon-image::before {
content: "◐";
font-size: 1.2em;
font-weight: normal;
}
.icon-download::before {
content: "↓";
font-size: 1.2em;
font-weight: normal;
}
.icon-delete::before {
content: "✕";
font-size: 1.2em;
font-weight: normal;
}
.icon-info::before {
content: "ⓘ";
font-size: 1.2em;
font-weight: normal;
}
/* Tech Icon Styles */
.tech-icon {
display: inline-block;
font-size: 1.5em;
margin-bottom: 0.5rem;
color: #00ff88;
transition: all 0.3s ease;
}
.tech-icon::before {
display: block;
text-align: center;
}
.tech-card:hover .tech-icon {
transform: scale(1.1);
filter: drop-shadow(0 0 8px currentColor);
}
/* Modal Icon Styles */
.modal-icon {
font-size: 3rem;
margin-bottom: 1rem;
color: #00ff88;
text-align: center;
}
.modal-icon::before {
display: block;
}
/* Proficiency Bar Styles */
.proficiency-bars {
display: flex;
gap: 2px;
margin: 0.5rem 0;
justify-content: center;
}
.prof-bar {
width: 12px;
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
transition: all 0.3s ease;
}
.prof-bar.active {
background: #00ff88;
box-shadow: 0 0 4px rgba(0, 255, 136, 0.5);
}
.modal-prof-bars {
display: flex;
gap: 4px;
margin: 1rem 0;
}
.modal-prof-bars .prof-bar {
width: 20px;
height: 6px;
}
.prof-text {
font-size: 0.9rem;
color: #cccccc;
font-style: italic;
}
body::before {
content: "";
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(
circle at 20% 20%,
rgba(0, 255, 136, 0.1) 0%,
transparent 50%
),
radial-gradient(
circle at 80% 80%,
rgba(0, 204, 255, 0.1) 0%,
transparent 50%
),
radial-gradient(
circle at 40% 60%,
rgba(0, 255, 136, 0.05) 0%,
transparent 50%
);
pointer-events: none;
z-index: -1;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Glass Card Base Style */
.glass-card {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.glass-card:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(0, 255, 136, 0.3);
box-shadow: 0 12px 40px rgba(0, 255, 136, 0.2);
transform: translateY(-5px);
}
/* Hero Section */
.hero-section {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.hero-content {
text-align: center;
z-index: 2;
position: relative;
padding: 3rem;
background: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(15px);
border-radius: 30px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.4);
}
.hero-text h1 {
font-size: 3.5rem;
font-weight: 700;
color: #fff;
margin-bottom: 1rem;
text-shadow: 0 0 30px rgba(0, 255, 136, 0.3);
}
.highlight {
color: #00ff88;
background: linear-gradient(45deg, #00ff88, #00ccff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
filter: drop-shadow(0 0 10px rgba(0, 255, 136, 0.5));
}
.typewriter-container {
height: 80px;
display: flex;
align-items: center;
justify-content: center;
margin: 2rem 0;
padding: 1rem;
background: rgba(0, 255, 136, 0.05);
backdrop-filter: blur(10px);
border-radius: 15px;
border: 1px solid rgba(0, 255, 136, 0.2);
}
.typewriter-text {
font-size: 2rem;
font-weight: 500;
color: #00ff88;
min-width: 300px;
text-align: center;
text-shadow: 0 0 20px rgba(0, 255, 136, 0.4);
}
.cursor {
color: #00ff88;
animation: blink 1s infinite;
filter: drop-shadow(0 0 5px #00ff88);
}
@keyframes blink {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}
.hero-subtitle {
font-size: 1.2rem;
color: #ccc;
margin-bottom: 2rem;
max-width: 600px;
margin-left: auto;
margin-right: auto;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
.hero-buttons {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.btn {
padding: 15px 35px;
border: none;
border-radius: 25px;
font-size: 1rem;
font-weight: 500;
text-decoration: none;
cursor: pointer;
transition: all 0.3s ease;
display: inline-block;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #fff;
position: relative;
overflow: hidden;
}
.btn::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.2),
transparent
);
transition: left 0.5s ease;
}
.btn:hover::before {
left: 100%;
}
.btn-primary {
background: linear-gradient(
45deg,
rgba(0, 255, 136, 0.3),
rgba(0, 204, 255, 0.3)
);
border-color: rgba(0, 255, 136, 0.5);
color: #fff;
}
.btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 10px 25px rgba(0, 255, 136, 0.4);
background: linear-gradient(
45deg,
rgba(0, 255, 136, 0.5),
rgba(0, 204, 255, 0.5)
);
}
.btn-secondary:hover {
transform: translateY(-3px);
background: rgba(0, 255, 136, 0.2);
border-color: #00ff88;
box-shadow: 0 10px 25px rgba(0, 255, 136, 0.3);
}
/* Sections with Glass Effect */
.about-section,
.tech-stack-section,
.contact-section {
padding: 100px 0;
position: relative;
}
.section-content {
background: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
border-radius: 30px;
padding: 3rem;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);
margin: 2rem 0;
}
.section-header {
text-align: center;
margin-bottom: 4rem;
}
.section-header h2 {
font-size: 2.5rem;
font-weight: 700;
color: #fff;
margin-bottom: 1rem;
text-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
}
.section-subtitle {
font-size: 1.1rem;
color: #00ff88;
font-weight: 500;
text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
}
/* About Section */
.about-content {
display: grid;
grid-template-columns: 1fr;
gap: 3rem;
align-items: center;
}
.about-text {
color: #ccc;
}
.lead {
font-size: 1.3rem;
line-height: 1.8;
margin-bottom: 2rem;
color: #fff;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
.competencies {
background: rgba(255, 255, 255, 0.03);
padding: 2rem;
border-radius: 20px;
border: 1px solid rgba(0, 255, 136, 0.1);
margin-top: 2rem;
}
.competencies h3 {
color: #00ff88;
margin-bottom: 1rem;
font-size: 1.3rem;
text-shadow: 0 0 15px rgba(0, 255, 136, 0.5);
}
.competencies ul {
list-style: none;
}
.competencies li {
margin-bottom: 0.8rem;
padding: 0.8rem 1.5rem;
background: rgba(255, 255, 255, 0.02);
border-radius: 10px;
border-left: 3px solid #00ff88;
transition: all 0.3s ease;
position: relative;
}
.competencies li:hover {
background: rgba(0, 255, 136, 0.05);
transform: translateX(10px);
}
.learning-note {
margin-top: 2rem;
padding: 2rem;
background: rgba(0, 255, 136, 0.05);
backdrop-filter: blur(15px);
border: 1px solid rgba(0, 255, 136, 0.3);
border-radius: 20px;
position: relative;
overflow: hidden;
}
.learning-note::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
45deg,
transparent,
rgba(0, 255, 136, 0.03),
transparent
);
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%,
100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
.learning-note p {
color: #ccc;
font-style: italic;
margin: 0;
position: relative;
z-index: 1;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
.learning-note em {
color: #00ff88;
font-style: normal;
text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
}
/* Enhanced Tech Stack Section */
.tech-controls {
display: flex;
flex-direction: column;
gap: 2rem;
margin-bottom: 3rem;
align-items: center;
}
.tech-search {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(15px);
border-radius: 25px;
padding: 5px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.tech-search input {
padding: 15px 25px;
border: none;
border-radius: 20px;
background: transparent;
color: #fff;
font-size: 1rem;
width: 300px;
text-align: center;
outline: none;
}
.tech-search input::placeholder {
color: #666;
}
.tech-search input:focus {
background: rgba(0, 255, 136, 0.05);
}
.tech-filters {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
background: rgba(0, 0, 0, 0.2);
padding: 1.5rem;
border-radius: 20px;
backdrop-filter: blur(15px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.filter-btn {
padding: 10px 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 15px;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
color: #ccc;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
position: relative;
overflow: hidden;
}
.filter-btn::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(0, 255, 136, 0.2),
transparent
);
transition: left 0.5s ease;
}
.filter-btn:hover::before {
left: 100%;
}
.filter-btn.active,
.filter-btn:hover {
border-color: #00ff88;
color: #00ff88;
background: rgba(0, 255, 136, 0.1);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 255, 136, 0.3);
}
.tech-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 2rem;
margin-top: 3rem;
}
.tech-hexagon {
width: 140px;
height: 120px;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(15px);
margin: 0 auto;
position: relative;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(255, 255, 255, 0.1);
overflow: hidden;
}
.tech-hexagon::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
45deg,
transparent,
rgba(0, 255, 136, 0.1),
transparent
);
opacity: 0;
transition: opacity 0.3s ease;
}
.tech-hexagon:hover::before {
opacity: 1;
}
.tech-hexagon:hover {
transform: scale(1.1) rotateY(10deg);
background: rgba(0, 255, 136, 0.1);
border-color: #00ff88;
box-shadow: 0 10px 30px rgba(0, 255, 136, 0.3);
}
.tech-hexagon .tech-name {
font-size: 0.9rem;
font-weight: 500;
text-align: center;
color: #fff;
position: relative;
z-index: 1;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
/* Contact Section with Animated Buttons */
.contact-content {
max-width: 800px;
margin: 0 auto;
}
.contact-info {
text-align: center;
margin-bottom: 3rem;
}
.contact-info h3 {
color: #00ff88;
margin-bottom: 1rem;
font-size: 1.3rem;
text-shadow: 0 0 15px rgba(0, 255, 136, 0.5);
}
.contact-info p {
color: #ccc;
margin-bottom: 3rem;
font-size: 1.1rem;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
.contact-buttons {
display: flex;
justify-content: center;
gap: 2rem;
flex-wrap: wrap;
}
.contact-btn {
display: flex;
align-items: center;
gap: 15px;
padding: 20px 30px;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
color: #fff;
text-decoration: none;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
min-width: 200px;
justify-content: center;
}
.contact-btn::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.1),
transparent
);
transition: left 0.6s ease;
}
.contact-btn:hover::before {
left: 100%;
}
.contact-btn:hover {
transform: translateY(-5px) scale(1.05);
box-shadow: 0 15px 35px rgba(0, 255, 136, 0.3);
border-color: rgba(0, 255, 136, 0.5);
background: rgba(0, 255, 136, 0.1);
}
.contact-btn .contact-icon {
font-size: 1.8rem;
filter: drop-shadow(0 0 10px currentColor);
}
.contact-btn .contact-text {
display: flex;
flex-direction: column;
gap: 5px;
}
.contact-btn .contact-label {
font-size: 0.9rem;
opacity: 0.8;
font-weight: 400;
}
.contact-btn .contact-value {
font-size: 1rem;
font-weight: 500;
}
/* Footer */
.footer {
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(20px);
text-align: center;
padding: 3rem 0;
color: #666;
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin-top: 2rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.hero-content {
padding: 2rem;
margin: 1rem;
}
.hero-text h1 {
font-size: 2.5rem;
}
.typewriter-text {
font-size: 1.5rem;
min-width: 250px;
}
.hero-subtitle {
font-size: 1rem;
}
.section-content {
padding: 2rem;
margin: 1rem;
}
.section-header h2 {
font-size: 2rem;
}
.tech-grid {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 1.5rem;
}
.tech-hexagon {
width: 120px;
height: 100px;
}
.contact-buttons {
flex-direction: column;
align-items: center;
gap: 1.5rem;
}
.contact-btn {
min-width: 280px;
}
.tech-filters {
gap: 0.8rem;
padding: 1rem;
}
.filter-btn {
padding: 8px 16px;
font-size: 0.8rem;
}
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* Selection colors */
::selection {
background: rgba(0, 255, 136, 0.3);
color: #fff;
}
/* Scrollbar styles */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.3);
}
::-webkit-scrollbar-thumb {
background: rgba(0, 255, 136, 0.5);
border-radius: 4px;
backdrop-filter: blur(10px);
}
::-webkit-scrollbar-thumb:hover {
background: rgba(0, 255, 136, 0.7);
}
/* Focus styles */
button:focus,
input:focus,
a:focus {
outline: 2px solid rgba(0, 255, 136, 0.5);
outline-offset: 2px;
}

167
css/responsive.css Normal file
View File

@@ -0,0 +1,167 @@
/* Responsive Design */
@media (max-width: 1200px) {
.container {
padding: 0 30px;
}
.hero-text h1 {
font-size: 3rem;
}
.typewriter-text {
font-size: 1.8rem;
}
}
@media (max-width: 768px) {
.hero-text h1 {
font-size: 2.5rem;
}
.typewriter-text {
font-size: 1.5rem;
min-width: 250px;
}
.hero-subtitle {
font-size: 1rem;
padding: 0 20px;
}
.hero-buttons {
flex-direction: column;
align-items: center;
}
.btn {
width: 200px;
text-align: center;
}
.nav-menu {
display: none;
}
.section-header h2 {
font-size: 2rem;
}
.tech-grid {
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 1rem;
}
.tech-hexagon {
width: 100px;
height: 87px;
}
.tech-name {
font-size: 0.8rem;
}
.tech-filters {
gap: 0.5rem;
}
.filter-btn {
padding: 6px 15px;
font-size: 0.8rem;
}
.tech-search input {
width: 250px;
}
.about-section,
.tech-stack-section,
.contact-section {
padding: 60px 0;
}
.lead {
font-size: 1.1rem;
}
}
@media (max-width: 480px) {
.hero-text h1 {
font-size: 2rem;
}
.typewriter-text {
font-size: 1.2rem;
min-width: 200px;
}
.hero-subtitle {
font-size: 0.9rem;
}
.container {
padding: 0 15px;
}
.tech-grid {
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
}
.tech-hexagon {
width: 80px;
height: 70px;
}
.tech-name {
font-size: 0.7rem;
}
.tech-search input {
width: 200px;
font-size: 0.9rem;
}
.filter-btn {
padding: 5px 12px;
font-size: 0.7rem;
}
.nav-container {
padding: 0 15px;
}
.nav-logo span {
font-size: 1.2rem;
}
}
/* High DPI displays */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.hero-text h1 {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.typewriter-text {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
/* Already optimized for dark mode */
}
/* Reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
.cursor {
animation: none;
}
}

782
css/rom-mirror.css Normal file
View File

@@ -0,0 +1,782 @@
/* ROM Mirror Section Styles - Full Glassmorphism */
.rom-mirror-section {
padding: 100px 0;
background: linear-gradient(135deg, #0a0a0a 0%, #151515 50%, #0a0a0a 100%);
min-height: 100vh;
position: relative;
}
.rom-mirror-section::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(
circle at 25% 25%,
rgba(0, 255, 136, 0.08) 0%,
transparent 50%
),
radial-gradient(
circle at 75% 75%,
rgba(0, 204, 255, 0.08) 0%,
transparent 50%
),
radial-gradient(
circle at 50% 50%,
rgba(255, 255, 255, 0.02) 0%,
transparent 70%
);
pointer-events: none;
z-index: 1;
}
.rom-mirror-section .container {
position: relative;
z-index: 2;
}
.rom-disclaimer {
background: rgba(255, 193, 7, 0.05);
backdrop-filter: blur(25px);
-webkit-backdrop-filter: blur(25px);
border: 1px solid rgba(255, 193, 7, 0.2);
border-radius: 20px;
padding: 2rem;
margin-bottom: 3rem;
text-align: center;
box-shadow:
0 8px 32px rgba(255, 193, 7, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
position: relative;
overflow: hidden;
}
.rom-disclaimer::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.1),
transparent
);
animation: shimmer 3s ease-in-out infinite;
}
.rom-disclaimer p {
color: #ffc107;
margin: 0;
font-size: 0.9rem;
line-height: 1.5;
}
/* File Browser Container - Enhanced Glassmorphism */
.file-browser {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(30px);
-webkit-backdrop-filter: blur(30px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 25px;
overflow: hidden;
box-shadow:
0 20px 50px rgba(0, 0, 0, 0.5),
0 8px 32px rgba(0, 255, 136, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.1),
inset 0 -1px 0 rgba(255, 255, 255, 0.05);
position: relative;
}
.file-browser::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.1) 0%,
transparent 25%,
transparent 75%,
rgba(0, 255, 136, 0.05) 100%
);
pointer-events: none;
z-index: 1;
}
.file-browser > * {
position: relative;
z-index: 2;
}
/* Browser Header - Glass Design */
.browser-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 2rem;
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(20px);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
gap: 1.5rem;
position: relative;
}
.browser-header::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(
90deg,
transparent,
rgba(0, 255, 136, 0.3),
transparent
);
}
.browser-controls {
display: flex;
gap: 1rem;
align-items: center;
}
.view-toggle,
.upload-btn {
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 15px;
padding: 12px 20px;
color: #fff;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 0.8rem;
font-size: 0.9rem;
position: relative;
overflow: hidden;
box-shadow:
0 4px 15px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
.view-toggle::before,
.upload-btn::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.2),
transparent
);
transition: left 0.5s ease;
}
.view-toggle:hover,
.upload-btn:hover {
background: rgba(0, 255, 136, 0.15);
border-color: rgba(0, 255, 136, 0.4);
transform: translateY(-3px);
box-shadow:
0 8px 25px rgba(0, 255, 136, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
.view-toggle:hover::before,
.upload-btn:hover::before {
left: 100%;
}
.browser-search {
flex-grow: 1;
max-width: 300px;
}
.browser-search input {
width: 100%;
background: rgba(255, 255, 255, 0.06);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 30px;
padding: 14px 24px;
color: #fff;
font-size: 0.9rem;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:
inset 0 2px 4px rgba(0, 0, 0, 0.1),
0 2px 8px rgba(0, 0, 0, 0.1);
}
.browser-search input:focus {
outline: none;
border-color: rgba(0, 255, 136, 0.6);
background: rgba(255, 255, 255, 0.1);
box-shadow:
0 0 20px rgba(0, 255, 136, 0.2),
inset 0 2px 4px rgba(0, 0, 0, 0.1),
0 4px 15px rgba(0, 0, 0, 0.2);
transform: translateY(-1px);
}
.browser-search input::placeholder {
color: rgba(255, 255, 255, 0.6);
}
/* Breadcrumb Navigation - Glass Style */
.breadcrumb-nav {
padding: 1.5rem 2rem;
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(15px);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
display: flex;
align-items: center;
gap: 0.8rem;
position: relative;
}
.breadcrumb-nav::after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 80%;
height: 1px;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.1),
transparent
);
}
.breadcrumb-item {
color: rgba(255, 255, 255, 0.7);
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
padding: 0.5rem 1rem;
border-radius: 12px;
position: relative;
}
.breadcrumb-item::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.05);
border-radius: 12px;
opacity: 0;
transition: opacity 0.3s ease;
}
.breadcrumb-item:hover {
color: #00ff88;
transform: translateY(-1px);
}
.breadcrumb-item:hover::before {
opacity: 1;
}
.breadcrumb-item.active {
color: #00ff88;
font-weight: 500;
background: rgba(0, 255, 136, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 255, 136, 0.2);
}
.breadcrumb-item:not(:last-child)::after {
content: "";
margin-left: 0.5rem;
color: rgba(255, 255, 255, 0.4);
}
/* File Grid */
.file-grid {
padding: 1.5rem;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
min-height: 300px;
}
.file-grid.list-view {
grid-template-columns: 1fr;
gap: 0.5rem;
}
/* File Items - Enhanced Glass Cards */
.file-item {
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 20px;
padding: 2rem;
text-align: center;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
box-shadow:
0 8px 25px rgba(0, 0, 0, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
.file-item::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.1) 0%,
transparent 25%,
transparent 75%,
rgba(255, 255, 255, 0.05) 100%
);
opacity: 0;
transition: opacity 0.3s ease;
}
.file-item:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(0, 255, 136, 0.4);
transform: translateY(-8px) scale(1.02);
box-shadow:
0 15px 40px rgba(0, 255, 136, 0.2),
0 5px 15px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
.file-item:hover::before {
opacity: 1;
}
.file-item.folder {
border-color: rgba(255, 193, 7, 0.2);
background: rgba(255, 193, 7, 0.02);
}
.file-item.folder:hover {
border-color: rgba(255, 193, 7, 0.6);
background: rgba(255, 193, 7, 0.05);
box-shadow:
0 15px 40px rgba(255, 193, 7, 0.2),
0 5px 15px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
/* List View Styling */
.file-grid.list-view .file-item {
display: flex;
align-items: center;
text-align: left;
padding: 1rem 1.5rem;
gap: 1rem;
}
.file-grid.list-view .file-icon {
margin-bottom: 0;
}
.file-grid.list-view .file-info {
flex-grow: 1;
}
.file-grid.list-view .file-actions {
display: flex;
gap: 0.5rem;
}
/* File Icon */
.file-icon {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #00ff88;
transition: all 0.3s ease;
}
.file-item.folder .file-icon {
color: #ffc107;
}
.file-item.zip .file-icon {
color: #17a2b8;
}
.file-item.image .file-icon {
color: #e83e8c;
}
.file-item:hover .file-icon {
transform: scale(1.1);
filter: drop-shadow(0 0 10px currentColor);
}
/* File Info */
.file-name {
font-weight: 600;
color: #fff;
margin-bottom: 0.5rem;
font-size: 0.95rem;
word-break: break-word;
}
.file-meta {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 1rem;
}
.file-size {
color: #00ff88;
font-weight: 500;
}
.file-date {
color: rgba(255, 255, 255, 0.5);
}
/* File Actions */
.file-actions {
display: flex;
justify-content: center;
gap: 0.5rem;
opacity: 0;
transition: opacity 0.3s ease;
}
.file-item:hover .file-actions {
opacity: 1;
}
.action-btn {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 6px 10px;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.8rem;
display: flex;
align-items: center;
gap: 0.3rem;
}
.action-btn:hover {
background: rgba(0, 255, 136, 0.2);
border-color: rgba(0, 255, 136, 0.5);
}
.action-btn.delete:hover {
background: rgba(220, 53, 69, 0.2);
border-color: rgba(220, 53, 69, 0.5);
}
/* Upload Area - Full Glass Overlay */
.upload-area {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(25px);
-webkit-backdrop-filter: blur(25px);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
border-radius: 25px;
}
.upload-area.active {
display: flex;
}
.upload-content {
text-align: center;
color: #fff;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
padding: 3rem;
box-shadow:
0 20px 50px rgba(0, 0, 0, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
.upload-icon {
font-size: 4rem;
color: #00ff88;
margin-bottom: 1rem;
display: block;
}
.upload-content h3 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
color: #fff;
}
.upload-content p {
color: rgba(255, 255, 255, 0.7);
font-size: 1rem;
}
/* Upload Progress - Glass Style */
.upload-progress {
margin-top: 2rem;
display: none;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(15px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 1.5rem;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
}
.upload-progress.active {
display: block;
}
.progress-bar {
width: 300px;
height: 8px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
overflow: hidden;
margin-bottom: 1rem;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, #00ff88, #00ccff);
width: 0%;
transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 10px;
box-shadow:
0 0 10px rgba(0, 255, 136, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
position: relative;
}
.progress-bar-fill::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.3),
transparent
);
animation: progressShimmer 2s ease-in-out infinite;
}
@keyframes progressShimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.progress-text {
color: #00ff88;
font-size: 0.9rem;
font-weight: 500;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 3rem;
color: rgba(255, 255, 255, 0.6);
}
.empty-state .icon-folder {
font-size: 4rem;
color: rgba(255, 255, 255, 0.3);
margin-bottom: 1rem;
display: block;
}
.empty-state h3 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
color: rgba(255, 255, 255, 0.7);
}
.empty-state p {
font-size: 1rem;
color: rgba(255, 255, 255, 0.5);
}
/* Responsive Design */
@media (max-width: 768px) {
.browser-header {
flex-direction: column;
gap: 1rem;
align-items: stretch;
}
.browser-controls {
justify-content: center;
}
.browser-search {
max-width: none;
}
.file-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 0.8rem;
padding: 1rem;
}
.file-item {
padding: 1rem;
}
.file-icon {
font-size: 2rem;
margin-bottom: 0.8rem;
}
.file-name {
font-size: 0.9rem;
}
.file-meta {
font-size: 0.75rem;
}
}
@media (max-width: 480px) {
.file-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.upload-content h3 {
font-size: 1.2rem;
}
.upload-content p {
font-size: 0.9rem;
}
.upload-icon {
font-size: 3rem;
}
}
/* Animations */
@keyframes fileItemEntrance {
from {
opacity: 0;
transform: translateY(20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.file-item {
animation: fileItemEntrance 0.4s ease forwards;
}
.file-item:nth-child(1) {
animation-delay: 0s;
}
.file-item:nth-child(2) {
animation-delay: 0.1s;
}
.file-item:nth-child(3) {
animation-delay: 0.2s;
}
.file-item:nth-child(4) {
animation-delay: 0.3s;
}
.file-item:nth-child(5) {
animation-delay: 0.4s;
}
.file-item:nth-child(6) {
animation-delay: 0.5s;
}
/* Drag and Drop States */
.file-browser.dragover {
border-color: rgba(0, 255, 136, 0.7);
background: rgba(0, 255, 136, 0.05);
}
.file-browser.dragover .upload-area {
display: flex;
animation: glassPulse 1s ease-in-out infinite alternate;
}
@keyframes glassPulse {
0% {
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(25px);
}
100% {
background: rgba(0, 255, 136, 0.1);
backdrop-filter: blur(30px);
}
}
@keyframes shimmer {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
/* File Description Styling */
.file-description {
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.5);
margin-top: 0.5rem;
text-align: center;
line-height: 1.3;
}

453
css/sidebar.css Normal file
View File

@@ -0,0 +1,453 @@
/* Glass Sidebar Styles */
.glass-sidebar {
position: fixed;
top: 0;
left: -300px;
width: 280px;
height: 100vh;
background: rgba(0, 0, 0, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-right: 1px solid rgba(0, 255, 136, 0.1);
z-index: 1000;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
flex-direction: column;
overflow: hidden;
}
.glass-sidebar.active {
left: 0;
box-shadow: 0 0 50px rgba(0, 255, 136, 0.2);
}
.glass-sidebar::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
135deg,
rgba(0, 255, 136, 0.1) 0%,
rgba(0, 204, 255, 0.05) 50%,
rgba(0, 0, 0, 0.1) 100%
);
pointer-events: none;
}
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 999;
}
.sidebar-overlay.active {
opacity: 1;
visibility: visible;
}
/* Sidebar Header */
.sidebar-header {
padding: 30px 25px 25px;
border-bottom: 1px solid rgba(0, 255, 136, 0.1);
position: relative;
z-index: 2;
}
.sidebar-logo {
display: flex;
flex-direction: column;
gap: 5px;
}
.logo-text {
font-size: 1.8rem;
font-weight: 700;
color: #00ff88;
text-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
}
.logo-subtitle {
font-size: 0.9rem;
color: #ccc;
font-weight: 400;
opacity: 0.8;
}
.sidebar-close {
position: absolute;
top: 25px;
right: 25px;
background: none;
border: none;
width: 30px;
height: 30px;
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 3px;
padding: 0;
transition: all 0.3s ease;
}
.sidebar-close span {
width: 20px;
height: 2px;
background: #ccc;
transition: all 0.3s ease;
transform-origin: center;
}
.sidebar-close span:first-child {
transform: rotate(45deg) translate(3px, 3px);
}
.sidebar-close span:last-child {
transform: rotate(-45deg) translate(3px, -3px);
}
.sidebar-close:hover span {
background: #00ff88;
}
/* Sidebar Menu */
.sidebar-menu {
flex: 1;
padding: 30px 0;
display: flex;
flex-direction: column;
gap: 8px;
position: relative;
z-index: 2;
}
.sidebar-link {
display: flex;
align-items: center;
padding: 18px 25px;
color: #ccc;
text-decoration: none;
transition: all 0.3s ease;
position: relative;
margin: 0 15px;
border-radius: 12px;
overflow: hidden;
}
.sidebar-link::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(0, 255, 136, 0.1), transparent);
opacity: 0;
transition: opacity 0.3s ease;
}
.sidebar-link:hover::before {
opacity: 1;
}
.sidebar-link.active {
color: #00ff88;
background: rgba(0, 255, 136, 0.1);
box-shadow: 0 0 20px rgba(0, 255, 136, 0.2);
}
.sidebar-link.active::before {
opacity: 1;
}
.link-icon {
font-size: 1.2rem;
width: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
}
.link-text {
font-size: 1rem;
font-weight: 500;
flex: 1;
}
.link-glow {
position: absolute;
right: 10px;
width: 6px;
height: 6px;
background: #00ff88;
border-radius: 50%;
opacity: 0;
box-shadow: 0 0 10px #00ff88;
transition: opacity 0.3s ease;
}
.sidebar-link.active .link-glow {
opacity: 1;
}
/* Sidebar Footer */
.sidebar-footer {
padding: 25px;
border-top: 1px solid rgba(0, 255, 136, 0.1);
position: relative;
z-index: 2;
}
.social-links {
display: flex;
gap: 15px;
margin-bottom: 20px;
justify-content: center;
}
.social-link {
width: 40px;
height: 40px;
background: rgba(0, 255, 136, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #ccc;
text-decoration: none;
transition: all 0.3s ease;
font-size: 1.1rem;
}
.social-link:hover {
background: rgba(0, 255, 136, 0.2);
color: #00ff88;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 255, 136, 0.3);
}
.sidebar-status {
display: flex;
align-items: center;
gap: 10px;
font-size: 0.85rem;
color: #ccc;
justify-content: center;
}
.status-dot {
width: 8px;
height: 8px;
background: #00ff88;
border-radius: 50%;
animation: statusPulse 2s infinite;
}
@keyframes statusPulse {
0% {
box-shadow: 0 0 0 0 rgba(0, 255, 136, 0.7);
}
70% {
box-shadow: 0 0 0 6px rgba(0, 255, 136, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(0, 255, 136, 0);
}
}
/* Sidebar Toggle Button */
.sidebar-toggle {
position: fixed;
top: 25px;
left: 25px;
width: 50px;
height: 50px;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 255, 136, 0.3);
border-radius: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
cursor: pointer;
z-index: 1001;
transition: all 0.3s ease;
padding: 0;
}
.sidebar-toggle:hover {
background: rgba(0, 255, 136, 0.1);
border-color: #00ff88;
transform: scale(1.05);
}
.toggle-line {
width: 20px;
height: 2px;
background: #00ff88;
transition: all 0.3s ease;
transform-origin: center;
}
.sidebar-toggle.active .toggle-line:nth-child(1) {
transform: rotate(45deg) translate(3px, 3px);
}
.sidebar-toggle.active .toggle-line:nth-child(2) {
opacity: 0;
}
.sidebar-toggle.active .toggle-line:nth-child(3) {
transform: rotate(-45deg) translate(3px, -3px);
}
/* Main Content Adjustment */
.main-content {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.main-content.sidebar-open {
transform: translateX(20px);
filter: blur(2px);
}
/* Hero Section Particles */
.hero-particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
}
.particle {
position: absolute;
width: 4px;
height: 4px;
background: #00ff88;
border-radius: 50%;
animation: particleFloat 8s infinite linear;
opacity: 0.7;
}
.particle:nth-child(1) {
left: 20%;
animation-delay: -2s;
animation-duration: 6s;
}
.particle:nth-child(2) {
left: 40%;
animation-delay: -4s;
animation-duration: 8s;
}
.particle:nth-child(3) {
left: 60%;
animation-delay: -6s;
animation-duration: 7s;
}
.particle:nth-child(4) {
left: 80%;
animation-delay: -8s;
animation-duration: 9s;
}
.particle:nth-child(5) {
left: 10%;
animation-delay: -1s;
animation-duration: 5s;
}
@keyframes particleFloat {
0% {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 0.7;
}
90% {
opacity: 0.7;
}
100% {
transform: translateY(-100px) rotate(360deg);
opacity: 0;
}
}
/* Responsive Design */
@media (max-width: 768px) {
.glass-sidebar {
width: 100%;
left: -100%;
}
.sidebar-toggle {
top: 20px;
left: 20px;
width: 45px;
height: 45px;
}
.toggle-line {
width: 18px;
}
.main-content.sidebar-open {
transform: none;
filter: blur(3px);
}
.sidebar-menu {
padding: 20px 0;
}
.sidebar-link {
padding: 15px 20px;
margin: 0 10px;
}
}
@media (max-width: 480px) {
.sidebar-header {
padding: 25px 20px 20px;
}
.sidebar-footer {
padding: 20px;
}
.logo-text {
font-size: 1.6rem;
}
.social-links {
gap: 12px;
}
.social-link {
width: 36px;
height: 36px;
font-size: 1rem;
}
}

579
css/tech-stack.css Normal file
View File

@@ -0,0 +1,579 @@
/* Reformed Experimentation Toolkit Styles */
.tech-stack-section {
position: relative;
overflow: hidden;
}
.tech-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
margin-top: 3rem;
}
.tech-card {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
padding: 2rem;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
min-height: 220px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.tech-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
135deg,
transparent 30%,
rgba(255, 255, 255, 0.02) 50%,
transparent 70%
);
opacity: 0;
transition: opacity 0.3s ease;
}
.tech-card:hover::before {
opacity: 1;
}
.tech-card-inner {
display: flex;
flex-direction: column;
gap: 1rem;
height: 100%;
position: relative;
z-index: 1;
}
.tech-icon {
font-size: 2.5rem;
text-align: center;
margin-bottom: 0.5rem;
filter: drop-shadow(0 0 10px rgba(0, 255, 136, 0.3));
}
.tech-name {
font-size: 1.2rem;
font-weight: 600;
color: #fff;
text-align: center;
margin-bottom: 0.5rem;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
.tech-proficiency {
font-size: 0.9rem;
color: #00ff88;
text-align: center;
font-weight: 500;
text-shadow: 0 0 10px rgba(0, 255, 136, 0.3);
}
.proficiency-bars {
display: flex;
justify-content: center;
gap: 4px;
margin: 0.5rem 0;
}
.prof-bar {
width: 6px;
height: 20px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
transition: all 0.3s ease;
}
.prof-bar.active {
background: linear-gradient(45deg, #00ff88, #00ccff);
box-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
}
.tech-experience {
font-size: 0.85rem;
color: #ccc;
text-align: center;
line-height: 1.4;
opacity: 0.9;
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.learn-more {
font-size: 0.8rem;
color: #666;
text-align: center;
opacity: 0;
transition: opacity 0.3s ease;
font-style: italic;
}
.tech-card:hover .learn-more {
opacity: 1;
}
/* Enhanced Filter Buttons */
.tech-filters {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
background: rgba(0, 0, 0, 0.2);
padding: 1.5rem;
border-radius: 20px;
backdrop-filter: blur(15px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.filter-btn {
padding: 12px 24px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 15px;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
color: #ccc;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
font-weight: 500;
position: relative;
overflow: hidden;
}
.filter-btn::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(0, 255, 136, 0.2),
transparent
);
transition: left 0.5s ease;
}
.filter-btn:hover::before {
left: 100%;
}
.filter-btn.active,
.filter-btn:hover {
border-color: #00ff88;
color: #00ff88;
background: rgba(0, 255, 136, 0.1);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 255, 136, 0.3);
}
/* Tech Modal Styles */
.tech-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2000;
display: none;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.tech-modal.active {
opacity: 1;
}
.modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
}
.modal-content {
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(30px);
border: 1px solid rgba(0, 255, 136, 0.3);
border-radius: 20px;
padding: 2rem;
max-width: 500px;
width: 90%;
position: relative;
z-index: 1;
transform: scale(0.9);
transition: transform 0.3s ease;
}
.tech-modal.active .modal-content {
transform: scale(1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid rgba(0, 255, 136, 0.2);
}
.modal-title {
color: #00ff88;
font-size: 1.5rem;
margin: 0;
text-shadow: 0 0 15px rgba(0, 255, 136, 0.5);
}
.modal-close {
background: none;
border: none;
color: #ccc;
font-size: 2rem;
cursor: pointer;
transition: color 0.3s ease;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.modal-close:hover {
color: #ff6b6b;
background: rgba(255, 107, 107, 0.1);
}
.modal-body {
text-align: center;
}
.modal-icon {
font-size: 3rem;
margin-bottom: 1rem;
filter: drop-shadow(0 0 15px rgba(0, 255, 136, 0.5));
}
.modal-proficiency,
.modal-experience,
.modal-level {
margin-bottom: 1.5rem;
color: #ccc;
line-height: 1.6;
}
.modal-proficiency strong,
.modal-experience strong,
.modal-level strong {
color: #00ff88;
display: block;
margin-bottom: 0.5rem;
}
.modal-prof-bars {
display: flex;
justify-content: center;
gap: 6px;
margin: 1rem 0;
}
.modal-prof-bars .prof-bar {
width: 8px;
height: 25px;
}
.prof-text {
font-size: 0.9rem;
color: #999;
font-style: italic;
}
/* Enhanced Search Input */
.tech-search {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(15px);
border-radius: 25px;
padding: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.tech-search input {
padding: 15px 25px;
border: none;
border-radius: 20px;
background: transparent;
color: #fff;
font-size: 1rem;
width: 350px;
text-align: center;
outline: none;
transition: all 0.3s ease;
}
.tech-search input::placeholder {
color: #666;
transition: color 0.3s ease;
}
.tech-search input:focus {
background: rgba(0, 255, 136, 0.05);
transform: scale(1.02);
}
.tech-search input:focus::placeholder {
color: #999;
}
/* Responsive Design */
@media (max-width: 768px) {
.tech-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.tech-card {
padding: 1.5rem;
min-height: 200px;
}
.tech-search input {
width: 280px;
font-size: 0.9rem;
}
.tech-filters {
gap: 0.8rem;
padding: 1rem;
}
.filter-btn {
padding: 10px 18px;
font-size: 0.8rem;
}
.modal-content {
padding: 1.5rem;
margin: 1rem;
}
.modal-title {
font-size: 1.3rem;
}
}
@media (max-width: 480px) {
.tech-card {
padding: 1.2rem;
}
.tech-icon {
font-size: 2rem;
}
.tech-name {
font-size: 1.1rem;
}
.tech-search input {
width: 240px;
}
}
/* Animation enhancements */
@keyframes techCardEntrance {
0% {
opacity: 0;
transform: translateY(30px) scale(0.9);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.tech-card {
animation: techCardEntrance 0.6s ease forwards;
}
.tech-card:nth-child(1) {
animation-delay: 0s;
}
.tech-card:nth-child(2) {
animation-delay: 0.1s;
}
.tech-card:nth-child(3) {
animation-delay: 0.2s;
}
.tech-card:nth-child(4) {
animation-delay: 0.3s;
}
.tech-card:nth-child(5) {
animation-delay: 0.4s;
}
.tech-card:nth-child(6) {
animation-delay: 0.5s;
}
/* Enhanced Tech Card Styles */
.tech-card-inner {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
}
.tech-name {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: #fff;
text-align: center;
}
.tech-proficiency {
font-size: 0.9rem;
color: #00ff88;
margin-bottom: 0.5rem;
text-align: center;
}
.tech-experience {
font-size: 0.8rem;
color: #cccccc;
text-align: center;
flex-grow: 1;
margin-bottom: 1rem;
}
.learn-more {
font-size: 0.7rem;
color: #999;
text-align: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.tech-card:hover .learn-more {
opacity: 1;
}
/* Modal Styles */
.tech-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: none;
align-items: center;
justify-content: center;
z-index: 10000;
opacity: 0;
transition: opacity 0.3s ease;
}
.tech-modal.active {
opacity: 1;
}
.modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(5px);
}
.modal-content {
position: relative;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 20px;
padding: 2rem;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.modal-title {
font-size: 1.5rem;
font-weight: 600;
color: #fff;
margin: 0;
}
.modal-close {
background: none;
border: none;
font-size: 2rem;
color: #fff;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s ease;
}
.modal-close:hover {
background: rgba(255, 255, 255, 0.1);
transform: scale(1.1);
}
.modal-body {
text-align: center;
}
.modal-proficiency,
.modal-experience,
.modal-level {
margin-bottom: 1.5rem;
color: #fff;
}
.modal-proficiency strong,
.modal-experience strong,
.modal-level strong {
color: #00ff88;
}

240
deploy-to-repo.sh Executable file
View File

@@ -0,0 +1,240 @@
#!/bin/bash
# 🚀 Safe Repository Deployment Script for overspend.cloud ROM Mirror
# This script will safely update your repository with the new portfolio files
set -e # Exit on any error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Repository configuration
REPO_URL="https://github.com/overspend1/website-thingy.git"
REPO_NAME="website-thingy"
BRANCH="main"
echo -e "${BLUE}🚀 ROM Mirror Portfolio Deployment Script${NC}"
echo -e "${BLUE}Repository: ${REPO_URL}${NC}"
echo ""
# Function to print colored output
print_step() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
# Check if git is installed
if ! command -v git &> /dev/null; then
print_error "Git is not installed. Please install Git first."
exit 1
fi
# Check if we're in the portfolio directory
if [ ! -f "index.html" ]; then
print_error "Please run this script from the portfolio directory"
exit 1
fi
print_step "Starting safe repository deployment..."
# 1. Backup current directory
BACKUP_DIR="../portfolio-backup-$(date +%Y%m%d-%H%M%S)"
print_step "Creating backup at: $BACKUP_DIR"
cp -r . "$BACKUP_DIR"
# 2. Initialize or update git repository
if [ ! -d ".git" ]; then
print_step "Initializing git repository..."
git init
git remote add origin "$REPO_URL"
else
print_step "Git repository already initialized"
fi
# 3. Configure git (if not already configured)
if [ -z "$(git config user.name)" ]; then
print_warning "Git user not configured. Please set your git username:"
read -p "Enter your GitHub username: " username
git config user.name "$username"
fi
if [ -z "$(git config user.email)" ]; then
print_warning "Git email not configured. Please set your git email:"
read -p "Enter your GitHub email: " email
git config user.email "$email"
fi
# 4. Create .gitignore if it doesn't exist
if [ ! -f ".gitignore" ]; then
print_step "Creating .gitignore file..."
cat > .gitignore << 'EOL'
# ROM Mirror Configuration
js/config.js
# Environment variables
.env
.env.local
.env.production
# API Keys and Secrets
**/config.js
**/secrets.js
**/*secret*
**/*key*
**/*token*
# Development files
.DS_Store
Thumbs.db
*.log
*.tmp
*.temp
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Node modules (if using build tools)
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build outputs
dist/
build/
out/
# Cache directories
.cache/
.parcel-cache/
# Backup files
*.backup
*.bak
*.old
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
EOL
fi
# 5. Remove any existing config.js files (security)
if [ -f "js/config.js" ]; then
print_warning "Removing existing config.js for security..."
rm js/config.js
fi
# 6. Check for exposed API keys in JavaScript files only
print_step "Scanning JavaScript files for API keys..."
if grep -r "AIzaSy" js/ 2>/dev/null; then
print_error "Found exposed API keys in JavaScript files! Please remove them manually."
echo "Files containing potential API keys:"
grep -r "AIzaSy" js/ -l 2>/dev/null || true
exit 1
else
print_step "No API keys found in JavaScript files"
fi
# 7. Stage all files
print_step "Staging files for commit..."
git add .
# 8. Check what's being committed
echo ""
echo -e "${BLUE}📋 Files to be committed:${NC}"
git status --porcelain
echo ""
print_warning "Review the files above. Do you want to continue? (y/N)"
read -p "Continue with commit? " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_error "Deployment cancelled by user"
exit 1
fi
# 9. Create commit
COMMIT_MESSAGE="🚀 Deploy ROM Mirror Portfolio with Glassmorphism
✨ Features:
- Enhanced glassmorphism design throughout
- Google Drive ROM mirror integration
- Professional icon system (no emojis)
- GitHub Actions secure deployment
- Responsive design for all devices
🔐 Security:
- API keys protected via GitHub Secrets
- No sensitive data in repository
- Automated security scanning
🎯 ROM Mirror:
- Real Google Drive file browser
- Download tracking and analytics
- Beautiful file management interface
- Mobile-optimized experience"
print_step "Creating commit..."
git commit -m "$COMMIT_MESSAGE"
# 10. Set upstream and push
print_step "Pushing to repository..."
git branch -M main
git remote set-url origin "$REPO_URL"
# First push might need force if repository exists
if git push -u origin main 2>/dev/null; then
print_step "Successfully pushed to repository!"
else
print_warning "Initial push failed, trying with force (this is normal for first deployment)..."
git push -u origin main --force
print_step "Force push completed successfully!"
fi
echo ""
echo -e "${GREEN}🎉 Deployment completed successfully!${NC}"
echo ""
echo -e "${BLUE}📋 Next Steps:${NC}"
echo "1. Go to your repository: $REPO_URL"
echo "2. Navigate to Settings > Secrets and variables > Actions"
echo "3. Add these repository secrets:"
echo " - DRIVE_API_KEY: (your new secure Google Drive API key)"
echo " - DRIVE_FOLDER_ID: 19UPdNOK1K4Rq-SeieF1q1L7WksNm3YJR"
echo "4. Go to Settings > Pages and set Source to 'GitHub Actions'"
echo "5. The site will automatically deploy to overspend.cloud"
echo ""
echo -e "${GREEN}🔐 Security Reminders:${NC}"
echo "- Create a NEW API key (delete the old exposed one)"
echo "- Restrict API key to overspend.cloud domain only"
echo "- Never commit js/config.js to git"
echo ""
echo -e "${BLUE}📊 Monitoring:${NC}"
echo "- Check GitHub Actions tab for deployment status"
echo "- Monitor Google Cloud Console for API usage"
echo "- Test ROM Mirror functionality after deployment"
echo ""
print_step "Backup created at: $BACKUP_DIR"
print_step "Repository URL: $REPO_URL"
print_step "Your portfolio will be live at: https://overspend.cloud"

488
index.html Normal file
View File

@@ -0,0 +1,488 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wiktor - Tech Enthusiast Portfolio</title>
<link rel="stylesheet" href="css/main.css" />
<link rel="stylesheet" href="css/animations.css" />
<link rel="stylesheet" href="css/tech-stack.css" />
<link rel="stylesheet" href="css/responsive.css" />
<link rel="stylesheet" href="css/sidebar.css" />
<link rel="stylesheet" href="css/rom-mirror.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
rel="stylesheet"
/>
</head>
<body>
<!-- Glass Sidebar -->
<div class="sidebar-overlay" id="sidebar-overlay"></div>
<nav class="glass-sidebar" id="glass-sidebar">
<div class="sidebar-header">
<div class="sidebar-logo">
<span class="logo-text">Wiktor</span>
<span class="logo-subtitle">Tech Explorer</span>
</div>
<button class="sidebar-close" id="sidebar-close">
<span></span>
<span></span>
</button>
</div>
<div class="sidebar-menu">
<a href="#hero" class="sidebar-link active" data-section="hero">
<span class="link-icon icon-home"></span>
<span class="link-text">Home</span>
<span class="link-glow"></span>
</a>
<a href="#about" class="sidebar-link" data-section="about">
<span class="link-icon icon-user"></span>
<span class="link-text">About</span>
<span class="link-glow"></span>
</a>
<a href="#toolkit" class="sidebar-link" data-section="toolkit">
<span class="link-icon icon-tools"></span>
<span class="link-text">Toolkit</span>
<span class="link-glow"></span>
</a>
<a
href="#rom-mirror"
class="sidebar-link"
data-section="rom-mirror"
>
<span class="link-icon icon-server"></span>
<span class="link-text">ROM Mirror</span>
<span class="link-glow"></span>
</a>
<a href="#contact" class="sidebar-link" data-section="contact">
<span class="link-icon icon-mail"></span>
<span class="link-text">Contact</span>
<span class="link-glow"></span>
</a>
</div>
<div class="sidebar-footer">
<div class="social-links">
<a
href="https://github.com/overspend1"
class="social-link"
title="GitHub"
target="_blank"
>
<span class="icon-github"></span>
</a>
<a
href="https://t.me/overspend1"
class="social-link"
title="Telegram"
target="_blank"
>
<span class="icon-telegram"></span>
</a>
<a
href="mailto:wiktator.olszewski@gmail.com"
class="social-link"
title="Email"
>
<span class="icon-mail"></span>
</a>
</div>
<div class="sidebar-status">
<span class="status-dot"></span>
<span class="status-text">Always learning</span>
</div>
</div>
</nav>
<!-- Sidebar Toggle Button -->
<button class="sidebar-toggle" id="sidebar-toggle">
<span class="toggle-line"></span>
<span class="toggle-line"></span>
<span class="toggle-line"></span>
</button>
<!-- Main Content -->
<main class="main-content">
<section id="hero" class="hero-section">
<div class="hero-content">
<div class="hero-text">
<h1>Hi, I'm <span class="highlight">Wiktor</span></h1>
<div class="typewriter-container">
<span
class="typewriter-text"
id="typewriter"
></span>
<span class="cursor">|</span>
</div>
<p class="hero-subtitle">
Tech enthusiast who loves experimenting with
different technologies after hours
</p>
<div class="hero-buttons">
<a href="#about" class="btn btn-primary"
>Learn More</a
>
<a href="#contact" class="btn btn-secondary"
>Get In Touch</a
>
</div>
</div>
<div class="hero-particles">
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
</div>
</div>
</section>
<section id="about" class="about-section">
<div class="container">
<div class="section-header">
<h2>About Me</h2>
<p class="section-subtitle">Hobbyist Tech Explorer</p>
</div>
<div class="section-content">
<div class="about-content">
<div class="about-text">
<p class="lead">
I'm someone who enjoys tinkering with
technology in my spare time. Whether it's
setting up a home server, experimenting with
different programming languages, or building
custom Android ROMs, I love exploring what's
possible with tech.
</p>
<p>
This portfolio showcases my after-hours
adventures in the tech world. I'm not
claiming to be an expert, but rather sharing
my journey of continuous learning and
experimentation across various technologies
that catch my interest.
</p>
<div class="competencies">
<h3>What I Like to Tinker With</h3>
<ul>
<li>
<strong>Web Technologies:</strong>
Building simple sites, learning
JavaScript and CSS
</li>
<li>
<strong>Self-Hosting:</strong>
Running Plex, Jellyfin, and other
services at home
</li>
<li>
<strong>System Tinkering:</strong>
Linux setups, automation scripts,
terminal customization
</li>
<li>
<strong>Mobile Modding:</strong>
Custom Android ROMs, trying
different kernels
</li>
<li>
<strong>Hardware Projects:</strong>
Basic Arduino experiments, PC
building
</li>
</ul>
</div>
<div class="learning-note">
<p>
<em
>Always learning, always
experimenting. This portfolio itself
is a learning project!</em
>
</p>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="toolkit" class="tech-stack-section">
<div class="container">
<div class="section-header">
<h2>Experimentation Toolkit</h2>
<p class="section-subtitle">
Technologies I've Been Playing Around With
</p>
</div>
<div class="section-content">
<div class="tech-controls">
<div class="tech-search">
<input
type="text"
id="tech-search"
placeholder="Search my toolkit..."
/>
</div>
<div class="tech-filters">
<button
class="filter-btn active"
data-category="all"
>
Everything
</button>
<button
class="filter-btn"
data-category="languages"
>
Languages
</button>
<button
class="filter-btn"
data-category="systems"
>
Systems
</button>
<button
class="filter-btn"
data-category="webtech"
>
Web Tech
</button>
<button
class="filter-btn"
data-category="databases"
>
Databases
</button>
<button
class="filter-btn"
data-category="cloud"
>
Cloud
</button>
<button
class="filter-btn"
data-category="selfhosted"
>
Self-Hosted
</button>
<button
class="filter-btn"
data-category="hardware"
>
Hardware
</button>
<button
class="filter-btn"
data-category="tools"
>
Dev Tools
</button>
</div>
</div>
<div class="tech-grid" id="tech-grid"></div>
<div class="learning-note" style="margin-top: 3rem">
<p>
<em
>Click on any technology to learn more about
my experience with it!</em
>
</p>
</div>
</div>
</div>
</section>
<section id="rom-mirror" class="rom-mirror-section">
<div class="container">
<div class="section-header">
<h2>ROM Mirror</h2>
<p class="section-subtitle">
Custom Android ROMs & Files Repository
</p>
</div>
<div class="section-content">
<div class="rom-disclaimer">
<p>
<strong>Legal Notice:</strong> All files hosted
here are for educational purposes. Users are
responsible for ensuring they have proper rights
to use these files. ROMs should only be used
with devices you own.
</p>
</div>
<div class="file-browser">
<div class="browser-header">
<div class="browser-controls">
<button
class="view-toggle"
id="view-toggle"
title="Toggle View"
>
<span class="icon-grid"></span>
</button>
<button
class="upload-btn"
id="upload-btn"
title="Upload Files"
>
<span class="icon-upload"></span>
Upload
</button>
</div>
<div class="browser-search">
<input
type="text"
id="file-search"
placeholder="Search files..."
/>
</div>
</div>
<div class="breadcrumb-nav" id="breadcrumb-nav">
<span class="breadcrumb-item active"
>ROM Mirror</span
>
</div>
<div class="file-grid" id="file-grid">
<!-- Files will be populated by JavaScript -->
</div>
<div class="upload-area" id="upload-area">
<div class="upload-content">
<span class="upload-icon icon-cloud"></span>
<h3>Drag & Drop Files Here</h3>
<p>or click to select files</p>
<input
type="file"
id="file-input"
multiple
hidden
/>
</div>
<div
class="upload-progress"
id="upload-progress"
>
<div class="progress-bar">
<div class="progress-bar-fill"></div>
</div>
<span class="progress-text">0%</span>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="contact" class="contact-section">
<div class="container">
<div class="section-header">
<h2>Let's Connect!</h2>
<p class="section-subtitle">
Always up for tech discussions
</p>
</div>
<div class="section-content">
<div class="contact-content">
<div class="contact-info">
<h3>Drop me a message</h3>
<p>
Whether you want to share a cool project,
discuss a tech problem, or just chat about
the latest gadgets and software, feel free
to reach out!
</p>
</div>
<div class="contact-buttons">
<a
href="mailto:wiktator.olszewski@gmail.com"
class="contact-btn"
id="email-btn"
>
<span class="contact-icon icon-mail"></span>
<div class="contact-text">
<span class="contact-label"
>Email me</span
>
<span class="contact-value"
>wiktator.olszewski@gmail.com</span
>
</div>
</a>
<a
href="https://github.com/overspend1"
class="contact-btn"
id="github-btn"
target="_blank"
>
<span
class="contact-icon icon-github"
></span>
<div class="contact-text">
<span class="contact-label"
>Check my code</span
>
<span class="contact-value"
>github.com/overspend1</span
>
</div>
</a>
<a
href="https://t.me/overspend1"
class="contact-btn"
id="telegram-btn"
target="_blank"
>
<span
class="contact-icon icon-telegram"
></span>
<div class="contact-text">
<span class="contact-label"
>Chat on Telegram</span
>
<span class="contact-value"
>t.me/overspend1</span
>
</div>
</a>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<p>&copy; 2024 Wiktor. Tech Enthusiast. Always Learning.</p>
</div>
</footer>
<script src="js/main.js"></script>
<script src="js/typewriter.js"></script>
<script src="js/tech-stack.js"></script>
<script src="js/animations.js"></script>
<script src="js/sidebar.js"></script>
<script src="js/contact-animations.js"></script>
<script src="js/config.js"></script>
<script src="js/rom-mirror.js"></script>
</body>
</html>

68
js/animations.js Normal file
View File

@@ -0,0 +1,68 @@
// Enhanced animations and interactions
document.addEventListener('DOMContentLoaded', function() {
// Boot sequence animation for tech stack
function initBootSequence() {
const techHexagons = document.querySelectorAll('.tech-hexagon');
techHexagons.forEach((hexagon, index) => {
hexagon.style.opacity = '0';
hexagon.style.transform = 'scale(0.8)';
setTimeout(() => {
hexagon.style.transition = 'all 0.3s ease';
hexagon.style.opacity = '1';
hexagon.style.transform = 'scale(1)';
hexagon.classList.add('animate-in');
}, index * 50);
});
}
// Observe tech stack section for boot sequence
const techStackSection = document.getElementById('tech-stack');
if (techStackSection) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
setTimeout(initBootSequence, 300);
observer.disconnect();
}
});
}, { threshold: 0.3 });
observer.observe(techStackSection);
}
// Enhanced hover effects for buttons
const buttons = document.querySelectorAll('.btn');
buttons.forEach(btn => {
btn.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-3px)';
});
btn.addEventListener('mouseleave', function() {
this.style.transform = 'translateY(0)';
});
});
// Parallax effect for hero section
window.addEventListener('scroll', function() {
const scrolled = window.pageYOffset;
const heroSection = document.getElementById('hero');
if (heroSection && scrolled < window.innerHeight) {
heroSection.style.transform = 'translateY(' + scrolled * 0.5 + 'px)';
}
});
// Dynamic cursor for tech hexagons
const techHexagons = document.querySelectorAll('.tech-hexagon');
techHexagons.forEach(hexagon => {
hexagon.addEventListener('mouseenter', function() {
document.body.style.cursor = 'pointer';
});
hexagon.addEventListener('mouseleave', function() {
document.body.style.cursor = 'default';
});
});
});

43
js/config.example.js Normal file
View File

@@ -0,0 +1,43 @@
// ROM Mirror Configuration Template
// IMPORTANT: Do NOT commit your actual API keys to git!
// Step 1: Copy this file to 'config.js' (don't commit config.js)
// Step 2: Replace the placeholder values with your real credentials
// Step 3: Add config.js to your .gitignore file
window.ROM_MIRROR_CONFIG = {
// Your Google Drive API Key
// Get this from: https://console.cloud.google.com/apis/credentials
DRIVE_API_KEY: "YOUR_API_KEY_HERE",
// Your Google Drive Folder ID
// Extract from your share URL: https://drive.google.com/drive/folders/FOLDER_ID
DRIVE_FOLDER_ID: "YOUR_FOLDER_ID_HERE",
// Optional: Custom configuration
MAX_RETRIES: 3,
CACHE_DURATION: 300000, // 5 minutes in milliseconds
// File type icons mapping
FILE_ICONS: {
"zip": "icon-zip",
"img": "icon-image",
"iso": "icon-image",
"apk": "icon-file",
"txt": "icon-file",
"md": "icon-file"
},
// Error messages
MESSAGES: {
LOADING: "Loading ROM files from Google Drive...",
ERROR: "Failed to connect to Google Drive. Please check your configuration.",
NO_FILES: "No ROM files found in this folder.",
DOWNLOAD_SUCCESS: "Download started successfully!"
}
};
// Development mode - uncomment these lines for local testing
// if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
// console.log('Development mode: ROM Mirror config loaded');
// }

340
js/contact-animations.js Normal file
View File

@@ -0,0 +1,340 @@
// Enhanced Contact Button Animations
class ContactAnimations {
constructor() {
this.init();
}
init() {
this.setupContactButtons();
this.setupHoverEffects();
this.setupClickEffects();
}
setupContactButtons() {
const contactButtons = document.querySelectorAll(".contact-btn");
contactButtons.forEach((btn, index) => {
// Staggered entrance animation
btn.style.opacity = "0";
btn.style.transform = "translateY(30px)";
setTimeout(() => {
btn.style.transition = "all 0.6s cubic-bezier(0.4, 0, 0.2, 1)";
btn.style.opacity = "1";
btn.style.transform = "translateY(0)";
}, index * 200);
// Add unique color themes
this.addButtonTheme(btn);
});
}
addButtonTheme(btn) {
const themes = {
"email-btn": {
color: "#ff6b6b",
shadow: "rgba(255, 107, 107, 0.3)"
},
"github-btn": {
color: "#00ff88",
shadow: "rgba(0, 255, 136, 0.3)"
},
"telegram-btn": {
color: "#00ccff",
shadow: "rgba(0, 204, 255, 0.3)"
}
};
const theme = themes[btn.id];
if (theme) {
btn.addEventListener("mouseenter", () => {
btn.style.borderColor = theme.color;
btn.style.boxShadow = `0 15px 35px ${theme.shadow}`;
btn.style.background = `${theme.color}20`;
});
btn.addEventListener("mouseleave", () => {
btn.style.borderColor = "rgba(255, 255, 255, 0.1)";
btn.style.boxShadow = "none";
btn.style.background = "rgba(255, 255, 255, 0.05)";
});
}
}
setupHoverEffects() {
const contactButtons = document.querySelectorAll(".contact-btn");
contactButtons.forEach(btn => {
btn.addEventListener("mouseenter", () => {
this.createParticles(btn);
this.addGlowEffect(btn);
});
btn.addEventListener("mouseleave", () => {
this.removeGlowEffect(btn);
});
});
}
createParticles(button) {
const rect = button.getBoundingClientRect();
const particleCount = 6;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement("div");
particle.className = "contact-particle";
particle.style.cssText = `
position: fixed;
width: 4px;
height: 4px;
background: #00ff88;
border-radius: 50%;
pointer-events: none;
z-index: 1000;
left: ${rect.left + Math.random() * rect.width}px;
top: ${rect.top + Math.random() * rect.height}px;
animation: particleBurst 0.8s ease-out forwards;
`;
document.body.appendChild(particle);
setTimeout(() => {
particle.remove();
}, 800);
}
// Add particle animation
if (!document.getElementById("particle-styles")) {
const style = document.createElement("style");
style.id = "particle-styles";
style.textContent = `
@keyframes particleBurst {
0% {
opacity: 1;
transform: scale(1) translate(0, 0);
}
100% {
opacity: 0;
transform: scale(0) translate(${Math.random() * 100 - 50}px, ${Math.random() * 100 - 50}px);
}
}
`;
document.head.appendChild(style);
}
}
addGlowEffect(button) {
const icon = button.querySelector(".contact-icon");
if (icon) {
icon.style.transform = "scale(1.2) rotate(10deg)";
icon.style.filter = "drop-shadow(0 0 15px currentColor)";
}
const text = button.querySelector(".contact-text");
if (text) {
text.style.transform = "translateX(5px)";
}
}
removeGlowEffect(button) {
const icon = button.querySelector(".contact-icon");
if (icon) {
icon.style.transform = "scale(1) rotate(0deg)";
icon.style.filter = "drop-shadow(0 0 10px currentColor)";
}
const text = button.querySelector(".contact-text");
if (text) {
text.style.transform = "translateX(0)";
}
}
setupClickEffects() {
const contactButtons = document.querySelectorAll(".contact-btn");
contactButtons.forEach(btn => {
btn.addEventListener("click", (e) => {
this.createClickRipple(e, btn);
this.addClickAnimation(btn);
});
});
}
createClickRipple(event, button) {
const rect = button.getBoundingClientRect();
const ripple = document.createElement("div");
const size = Math.max(rect.width, rect.height);
const x = event.clientX - rect.left - size / 2;
const y = event.clientY - rect.top - size / 2;
ripple.style.cssText = `
position: absolute;
width: ${size}px;
height: ${size}px;
left: ${x}px;
top: ${y}px;
background: rgba(0, 255, 136, 0.3);
border-radius: 50%;
pointer-events: none;
animation: rippleEffect 0.6s ease-out;
z-index: 0;
`;
button.style.position = "relative";
button.appendChild(ripple);
// Add ripple animation
if (!document.getElementById("ripple-styles")) {
const style = document.createElement("style");
style.id = "ripple-styles";
style.textContent = `
@keyframes rippleEffect {
0% {
transform: scale(0);
opacity: 1;
}
100% {
transform: scale(2);
opacity: 0;
}
}
`;
document.head.appendChild(style);
}
setTimeout(() => {
ripple.remove();
}, 600);
}
addClickAnimation(button) {
button.style.transform = "scale(0.95)";
setTimeout(() => {
button.style.transform = "";
}, 150);
// Success feedback
this.showSuccessFeedback(button);
}
showSuccessFeedback(button) {
const feedback = document.createElement("div");
feedback.textContent = "✓ Opening...";
feedback.style.cssText = `
position: absolute;
top: -40px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 255, 136, 0.9);
color: #000;
padding: 8px 16px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 500;
white-space: nowrap;
pointer-events: none;
animation: feedbackPop 2s ease-out forwards;
z-index: 1001;
`;
button.style.position = "relative";
button.appendChild(feedback);
// Add feedback animation
if (!document.getElementById("feedback-styles")) {
const style = document.createElement("style");
style.id = "feedback-styles";
style.textContent = `
@keyframes feedbackPop {
0% {
opacity: 0;
transform: translateX(-50%) translateY(10px) scale(0.8);
}
20% {
opacity: 1;
transform: translateX(-50%) translateY(0) scale(1);
}
80% {
opacity: 1;
transform: translateX(-50%) translateY(0) scale(1);
}
100% {
opacity: 0;
transform: translateX(-50%) translateY(-10px) scale(0.8);
}
}
`;
document.head.appendChild(style);
}
setTimeout(() => {
feedback.remove();
}, 2000);
}
}
// Enhanced contact section scroll reveal
class ContactScrollReveal {
constructor() {
this.init();
}
init() {
this.observeContactSection();
}
observeContactSection() {
const contactSection = document.getElementById("contact");
if (!contactSection) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.animateContactReveal();
observer.disconnect();
}
});
}, { threshold: 0.3 });
observer.observe(contactSection);
}
animateContactReveal() {
const contactContent = document.querySelector(".contact-content");
const contactButtons = document.querySelectorAll(".contact-btn");
// Animate content container
if (contactContent) {
contactContent.style.opacity = "0";
contactContent.style.transform = "translateY(30px)";
setTimeout(() => {
contactContent.style.transition = "all 0.8s ease";
contactContent.style.opacity = "1";
contactContent.style.transform = "translateY(0)";
}, 200);
}
// Staggered button animations
contactButtons.forEach((btn, index) => {
btn.style.opacity = "0";
btn.style.transform = "translateY(50px) scale(0.9)";
setTimeout(() => {
btn.style.transition = "all 0.6s cubic-bezier(0.4, 0, 0.2, 1)";
btn.style.opacity = "1";
btn.style.transform = "translateY(0) scale(1)";
}, 400 + index * 150);
});
}
}
// Initialize when DOM is loaded
document.addEventListener("DOMContentLoaded", () => {
new ContactAnimations();
new ContactScrollReveal();
});

89
js/main.js Normal file
View File

@@ -0,0 +1,89 @@
// Main JavaScript functionality
document.addEventListener('DOMContentLoaded', function() {
// Smooth scrolling for navigation links
const navLinks = document.querySelectorAll('nav a[href^="#"]');
navLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// Navbar scroll effect
const navbar = document.getElementById('navbar');
if (navbar) {
window.addEventListener('scroll', function() {
if (window.scrollY > 50) {
navbar.style.background = 'rgba(0, 0, 0, 0.98)';
} else {
navbar.style.background = 'rgba(0, 0, 0, 0.95)';
}
});
}
// Contact form handling
const contactForm = document.getElementById('contact-form');
if (contactForm) {
contactForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = {
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message')
};
// Simple validation
if (!data.name || !data.email || !data.message) {
alert('Please fill in all fields');
return;
}
// Here you would typically send the data to a server
console.log('Form submitted:', data);
alert('Thank you for your message! I will get back to you soon.');
// Reset form
this.reset();
});
}
// Add scroll-based animations
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}
});
}, observerOptions);
// Observe sections for scroll animations
const sections = document.querySelectorAll('section');
sections.forEach(section => {
section.style.opacity = '0';
section.style.transform = 'translateY(20px)';
section.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
observer.observe(section);
});
// Hero section is always visible
const heroSection = document.getElementById('hero');
if (heroSection) {
heroSection.style.opacity = '1';
heroSection.style.transform = 'translateY(0)';
}
});

939
js/rom-mirror.js Normal file
View File

@@ -0,0 +1,939 @@
// ROM Mirror File Browser Functionality
class ROMFileBrowser {
constructor() {
this.currentPath = [];
this.files = [];
this.filteredFiles = [];
this.viewMode = "grid"; // grid or list
this.isUploading = false;
this.init();
this.loadGoogleDriveData();
}
init() {
this.setupEventListeners();
this.renderFiles();
}
// Secure configuration getter - checks multiple sources
getConfig(key) {
// 1. Check if config is defined in a separate config file
if (typeof window.ROM_MIRROR_CONFIG !== "undefined") {
return window.ROM_MIRROR_CONFIG[key];
}
// 2. Check environment variables (if using build tools)
if (typeof process !== "undefined" && process.env) {
return process.env[key];
}
// 3. Check for config in localStorage (for development)
const localConfig = localStorage.getItem(`rom_config_${key}`);
if (localConfig) {
return localConfig;
}
return null;
}
// Google Drive API integration
async loadGoogleDriveData() {
this.showLoading();
try {
// Your Google Drive shared folder ID - replace with your actual folder ID
// Configuration - Replace with your own values
const folderId =
this.getConfig("DRIVE_FOLDER_ID") || "YOUR_FOLDER_ID_HERE";
const apiKey = this.getConfig("DRIVE_API_KEY") || "YOUR_API_KEY_HERE";
await this.loadDriveFiles(folderId, apiKey);
} catch (error) {
console.error("Error loading Google Drive files:", error);
this.showError("Failed to load files from Google Drive");
this.loadOfflineData(); // Fallback to local data
}
}
async loadDriveFiles(folderId, apiKey, parentPath = []) {
const url = `https://www.googleapis.com/drive/v3/files?q='${folderId}'+in+parents&key=${apiKey}&fields=files(id,name,mimeType,size,modifiedTime,webContentLink,webViewLink)`;
try {
const response = await fetch(url);
const data = await response.json();
if (data.error) {
throw new Error(data.error.message);
}
this.files = await this.processDriveFiles(data.files, apiKey);
this.hideLoading();
this.renderFiles();
} catch (error) {
throw error;
}
}
async processDriveFiles(files, apiKey) {
const processedFiles = [];
for (const file of files) {
const isFolder = file.mimeType === "application/vnd.google-apps.folder";
const processedFile = {
id: file.id,
name: file.name,
type: isFolder ? "folder" : "file",
extension: isFolder ? null : this.getFileExtension(file.name),
size: isFolder ? null : this.formatFileSize(parseInt(file.size) || 0),
date: this.formatDate(file.modifiedTime),
downloadUrl: file.webContentLink,
viewUrl: file.webViewLink,
downloads: this.getStoredDownloadCount(file.id),
description: this.getFileDescription(file.name),
};
if (isFolder) {
// Load folder contents
processedFile.children = await this.loadFolderContents(file.id, apiKey);
}
processedFiles.push(processedFile);
}
return processedFiles;
}
async loadFolderContents(folderId, apiKey) {
const url = `https://www.googleapis.com/drive/v3/files?q='${folderId}'+in+parents&key=${apiKey}&fields=files(id,name,mimeType,size,modifiedTime,webContentLink,webViewLink)`;
try {
const response = await fetch(url);
const data = await response.json();
if (data.error) return [];
return await this.processDriveFiles(data.files, apiKey);
} catch (error) {
console.error("Error loading folder contents:", error);
return [];
}
}
getFileExtension(filename) {
return filename.split(".").pop().toLowerCase();
}
formatDate(dateString) {
const date = new Date(dateString);
return date.toISOString().split("T")[0];
}
getFileDescription(filename) {
const descriptions = {
lineage: "LineageOS custom ROM",
aosp: "AOSP Android system image",
twrp: "TWRP custom recovery",
kernel: "Custom kernel modification",
fastboot: "Android fastboot tools",
bootloader: "Device bootloader",
};
const key = Object.keys(descriptions).find((k) =>
filename.toLowerCase().includes(k),
);
return key ? descriptions[key] : `ROM file: ${filename}`;
}
getStoredDownloadCount(fileId) {
const counts = JSON.parse(
localStorage.getItem("romDownloadCounts") || "{}",
);
return counts[fileId] || 0;
}
updateDownloadCount(fileId) {
const counts = JSON.parse(
localStorage.getItem("romDownloadCounts") || "{}",
);
counts[fileId] = (counts[fileId] || 0) + 1;
localStorage.setItem("romDownloadCounts", JSON.stringify(counts));
return counts[fileId];
}
showLoading() {
const grid = document.getElementById("file-grid");
if (grid) {
grid.innerHTML = `
<div class="loading-state">
<div class="loading-spinner"></div>
<h3>Loading ROM files...</h3>
<p>Connecting to Google Drive</p>
</div>
`;
}
}
hideLoading() {
// Loading will be hidden when files render
}
showError(message) {
const grid = document.getElementById("file-grid");
if (grid) {
grid.innerHTML = `
<div class="error-state">
<span class="icon-cloud"></span>
<h3>Connection Error</h3>
<p>${message}</p>
<button class="retry-btn" onclick="romBrowser.loadGoogleDriveData()">Retry</button>
</div>
`;
}
}
loadOfflineData() {
// Fallback data when Google Drive is unavailable
this.files = [
{
id: "offline-1",
name: "Connection Error",
type: "folder",
children: [
{
id: "offline-readme",
name: "README.txt",
type: "file",
extension: "txt",
size: "1 KB",
date: new Date().toISOString().split("T")[0],
downloads: 0,
description:
"Google Drive connection unavailable. Please check your internet connection and API configuration.",
},
],
},
];
this.renderFiles();
}
setupEventListeners() {
// View toggle
const viewToggle = document.getElementById("view-toggle");
if (viewToggle) {
viewToggle.addEventListener("click", () => this.toggleView());
}
// Upload button
const uploadBtn = document.getElementById("upload-btn");
if (uploadBtn) {
uploadBtn.addEventListener("click", () => this.showUploadArea());
}
// Search functionality
const searchInput = document.getElementById("file-search");
if (searchInput) {
searchInput.addEventListener("input", (e) =>
this.handleSearch(e.target.value),
);
}
// Upload area
const uploadArea = document.getElementById("upload-area");
const fileInput = document.getElementById("file-input");
if (uploadArea && fileInput) {
uploadArea.addEventListener("click", () => fileInput.click());
uploadArea.addEventListener("dragover", (e) => this.handleDragOver(e));
uploadArea.addEventListener("dragleave", (e) => this.handleDragLeave(e));
uploadArea.addEventListener("drop", (e) => this.handleDrop(e));
fileInput.addEventListener("change", (e) =>
this.handleFileSelect(e.target.files),
);
}
// Close upload area on escape
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
this.hideUploadArea();
}
});
}
getCurrentFiles() {
let current = this.files;
for (const pathItem of this.currentPath) {
const folder = current.find(
(item) => item.name === pathItem && item.type === "folder",
);
if (folder && folder.children) {
current = folder.children;
}
}
return current;
}
renderFiles() {
const grid = document.getElementById("file-grid");
if (!grid) return;
const currentFiles = this.getCurrentFiles();
this.filteredFiles = currentFiles;
grid.innerHTML = "";
grid.className = `file-grid ${this.viewMode}-view`;
if (currentFiles.length === 0) {
this.renderEmptyState(grid);
return;
}
currentFiles.forEach((file, index) => {
const fileElement = this.createFileElement(file, index);
grid.appendChild(fileElement);
});
this.updateBreadcrumb();
}
createFileElement(file, index) {
const fileItem = document.createElement("div");
fileItem.className = `file-item ${file.type} ${file.extension || ""}`;
fileItem.style.animationDelay = `${index * 0.1}s`;
const iconClass = this.getFileIcon(file);
fileItem.innerHTML = `
<div class="file-icon ${iconClass}"></div>
<div class="file-info">
<div class="file-name">${file.name}</div>
<div class="file-meta">
${file.size ? `<span class="file-size">${file.size}</span> • ` : ""}
<span class="file-date">${file.date || "Unknown"}</span>
${file.downloads ? `${file.downloads} downloads` : ""}
</div>
${file.description ? `<div class="file-description">${file.description}</div>` : ""}
</div>
<div class="file-actions">
${
file.type === "folder"
? '<button class="action-btn open"><span class="icon-folder"></span> Open</button>'
: `<button class="action-btn download"><span class="icon-download"></span> Download</button>
<button class="action-btn info"><span class="icon-info"></span> Info</button>
<button class="action-btn delete"><span class="icon-delete"></span> Delete</button>`
}
</div>
`;
// Add event listeners
if (file.type === "folder") {
fileItem.addEventListener("dblclick", () => this.openFolder(file.name));
fileItem
.querySelector(".action-btn.open")
?.addEventListener("click", (e) => {
e.stopPropagation();
this.openFolder(file.name);
});
} else {
fileItem
.querySelector(".action-btn.download")
?.addEventListener("click", (e) => {
e.stopPropagation();
this.downloadFile(file);
});
fileItem
.querySelector(".action-btn.info")
?.addEventListener("click", (e) => {
e.stopPropagation();
this.showFileInfo(file);
});
fileItem
.querySelector(".action-btn.delete")
?.addEventListener("click", (e) => {
e.stopPropagation();
this.deleteFile(file);
});
}
return fileItem;
}
getFileIcon(file) {
if (file.type === "folder") return "icon-folder";
switch (file.extension) {
case "zip":
case "rar":
case "7z":
return "icon-zip";
case "img":
case "iso":
return "icon-image";
default:
return "icon-file";
}
}
renderEmptyState(container) {
container.innerHTML = `
<div class="empty-state">
<span class="icon-folder"></span>
<h3>No files found</h3>
<p>This folder is empty or no files match your search.</p>
</div>
`;
}
openFolder(folderName) {
this.currentPath.push(folderName);
this.renderFiles();
}
navigateToPath(pathIndex) {
this.currentPath = this.currentPath.slice(0, pathIndex);
this.renderFiles();
}
updateBreadcrumb() {
const breadcrumb = document.getElementById("breadcrumb-nav");
if (!breadcrumb) return;
const items = ["ROM Mirror", ...this.currentPath];
breadcrumb.innerHTML = items
.map((item, index) => {
const isLast = index === items.length - 1;
return `<span class="breadcrumb-item ${isLast ? "active" : ""}"
onclick="romBrowser.navigateToPath(${index})">${item}</span>`;
})
.join("");
}
toggleView() {
this.viewMode = this.viewMode === "grid" ? "list" : "grid";
const viewToggle = document.getElementById("view-toggle");
if (viewToggle) {
const icon = viewToggle.querySelector("span");
icon.className = this.viewMode === "grid" ? "icon-grid" : "icon-server";
}
this.renderFiles();
}
handleSearch(query) {
if (!query.trim()) {
this.renderFiles();
return;
}
const currentFiles = this.getCurrentFiles();
this.filteredFiles = currentFiles.filter(
(file) =>
file.name.toLowerCase().includes(query.toLowerCase()) ||
(file.description &&
file.description.toLowerCase().includes(query.toLowerCase())),
);
const grid = document.getElementById("file-grid");
if (!grid) return;
grid.innerHTML = "";
grid.className = `file-grid ${this.viewMode}-view`;
if (this.filteredFiles.length === 0) {
grid.innerHTML = `
<div class="empty-state">
<span class="icon-folder"></span>
<h3>No results found</h3>
<p>No files match "${query}". Try a different search term.</p>
</div>
`;
return;
}
this.filteredFiles.forEach((file, index) => {
const fileElement = this.createFileElement(file, index);
grid.appendChild(fileElement);
});
}
showUploadArea() {
const uploadArea = document.getElementById("upload-area");
if (uploadArea) {
uploadArea.classList.add("active");
document.body.style.overflow = "hidden";
}
}
hideUploadArea() {
const uploadArea = document.getElementById("upload-area");
if (uploadArea) {
uploadArea.classList.remove("active");
document.body.style.overflow = "";
}
}
handleDragOver(e) {
e.preventDefault();
const browser = document.querySelector(".file-browser");
if (browser) {
browser.classList.add("dragover");
}
}
handleDragLeave(e) {
e.preventDefault();
const browser = document.querySelector(".file-browser");
if (browser) {
browser.classList.remove("dragover");
}
}
handleDrop(e) {
e.preventDefault();
const browser = document.querySelector(".file-browser");
if (browser) {
browser.classList.remove("dragover");
}
const files = e.dataTransfer.files;
this.handleFileSelect(files);
}
handleFileSelect(files) {
if (files.length === 0) return;
this.uploadFiles(files);
}
uploadFiles(files) {
if (this.isUploading) return;
this.isUploading = true;
const progressElement = document.getElementById("upload-progress");
const progressText = progressElement?.querySelector(".progress-text");
if (progressElement) {
progressElement.classList.add("active");
// Create progress bar fill if it doesn't exist
let progressBar = progressElement.querySelector(".progress-bar");
if (progressBar && !progressBar.querySelector(".progress-bar-fill")) {
progressBar.innerHTML = '<div class="progress-bar-fill"></div>';
}
}
// Simulate upload progress
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 15;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
setTimeout(() => {
this.completeUpload(files);
}, 500);
}
const progressBarFill =
progressElement?.querySelector(".progress-bar-fill");
if (progressBarFill) {
progressBarFill.style.width = `${progress}%`;
}
if (progressText) {
progressText.textContent = `${Math.round(progress)}%`;
}
}, 200);
}
completeUpload(files) {
this.isUploading = false;
// Add uploaded files to current folder (simulation)
const currentFiles = this.getCurrentFiles();
Array.from(files).forEach((file) => {
const fileObj = {
id: Date.now() + Math.random(),
name: file.name,
type: "file",
extension: file.name.split(".").pop(),
size: this.formatFileSize(file.size),
date: new Date().toISOString().split("T")[0],
downloads: 0,
description: `Uploaded ${file.name}`,
};
currentFiles.push(fileObj);
});
this.hideUploadArea();
this.renderFiles();
// Reset progress bar
const progressElement = document.getElementById("upload-progress");
if (progressElement) {
progressElement.classList.remove("active");
const progressBarFill =
progressElement.querySelector(".progress-bar-fill");
if (progressBarFill) {
progressBarFill.style.width = "0%";
}
const progressText = progressElement.querySelector(".progress-text");
if (progressText) {
progressText.textContent = "0%";
}
}
// Show success message
this.showNotification(
`Successfully uploaded ${files.length} file(s)`,
"success",
);
}
downloadFile(file) {
if (file.downloadUrl) {
// Open Google Drive download link
window.open(file.downloadUrl, "_blank");
// Update download count
const newCount = this.updateDownloadCount(file.id);
file.downloads = newCount;
this.showNotification(`Downloading ${file.name}...`, "success");
this.renderFiles();
} else {
this.showNotification(
`Download link not available for ${file.name}`,
"error",
);
}
}
showFileInfo(file) {
// Create and show file info modal
const modal = document.createElement("div");
modal.className = "file-info-modal";
modal.innerHTML = `
<div class="modal-overlay"></div>
<div class="modal-content">
<div class="modal-header">
<h3>File Information</h3>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<div class="file-info-item"><strong>Name:</strong> ${file.name}</div>
<div class="file-info-item"><strong>Size:</strong> ${file.size}</div>
<div class="file-info-item"><strong>Date:</strong> ${file.date}</div>
<div class="file-info-item"><strong>Downloads:</strong> ${file.downloads || 0}</div>
${file.description ? `<div class="file-info-item"><strong>Description:</strong> ${file.description}</div>` : ""}
</div>
</div>
`;
document.body.appendChild(modal);
setTimeout(() => modal.classList.add("active"), 10);
// Close modal events
modal
.querySelector(".modal-close")
.addEventListener("click", () => this.closeModal(modal));
modal
.querySelector(".modal-overlay")
.addEventListener("click", () => this.closeModal(modal));
}
deleteFile(file) {
if (confirm(`Are you sure you want to delete "${file.name}"?`)) {
const currentFiles = this.getCurrentFiles();
const index = currentFiles.findIndex((f) => f.id === file.id);
if (index !== -1) {
currentFiles.splice(index, 1);
this.renderFiles();
this.showNotification(`Deleted ${file.name}`, "success");
}
}
}
closeModal(modal) {
modal.classList.remove("active");
setTimeout(() => {
document.body.removeChild(modal);
}, 300);
}
formatFileSize(bytes) {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
}
showNotification(message, type = "info") {
const notification = document.createElement("div");
notification.className = `notification ${type}`;
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 1rem 1.5rem;
border-radius: 10px;
border: 1px solid ${type === "success" ? "#00ff88" : type === "error" ? "#ff4757" : "#00ccff"};
z-index: 10001;
backdrop-filter: blur(10px);
animation: slideIn 0.3s ease;
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = "slideOut 0.3s ease forwards";
setTimeout(() => {
if (document.body.contains(notification)) {
document.body.removeChild(notification);
}
}, 300);
}, 3000);
}
}
// Enhanced scroll reveal for ROM mirror section
class ROMScrollReveal {
constructor() {
this.init();
}
init() {
this.observeROMSection();
}
observeROMSection() {
const romSection = document.getElementById("rom-mirror");
if (!romSection) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
this.animateROMReveal();
observer.disconnect();
}
});
},
{ threshold: 0.2 },
);
observer.observe(romSection);
}
animateROMReveal() {
const fileBrowser = document.querySelector(".file-browser");
const disclaimer = document.querySelector(".rom-disclaimer");
// Animate disclaimer
if (disclaimer) {
disclaimer.style.opacity = "0";
disclaimer.style.transform = "translateY(20px)";
setTimeout(() => {
disclaimer.style.transition = "all 0.8s ease";
disclaimer.style.opacity = "1";
disclaimer.style.transform = "translateY(0)";
}, 200);
}
// Animate file browser
if (fileBrowser) {
fileBrowser.style.opacity = "0";
fileBrowser.style.transform = "translateY(30px) scale(0.98)";
setTimeout(() => {
fileBrowser.style.transition = "all 1s cubic-bezier(0.4, 0, 0.2, 1)";
fileBrowser.style.opacity = "1";
fileBrowser.style.transform = "translateY(0) scale(1)";
}, 400);
}
}
}
// Add notification animations to CSS
const notificationStyles = document.createElement("style");
notificationStyles.textContent = `
.loading-state, .error-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem;
text-align: center;
color: rgba(255, 255, 255, 0.7);
grid-column: 1 / -1;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 3px solid rgba(0, 255, 136, 0.3);
border-top: 3px solid #00ff88;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1.5rem;
}
.error-state .icon-cloud {
font-size: 4rem;
color: rgba(255, 107, 107, 0.5);
margin-bottom: 1rem;
display: block;
}
.retry-btn {
background: rgba(0, 255, 136, 0.2);
border: 1px solid rgba(0, 255, 136, 0.5);
color: #00ff88;
padding: 12px 24px;
border-radius: 25px;
cursor: pointer;
margin-top: 1.5rem;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
}
.retry-btn:hover {
background: rgba(0, 255, 136, 0.3);
transform: translateY(-2px);
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
.file-info-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
opacity: 0;
transition: opacity 0.3s ease;
}
.file-info-modal.active {
opacity: 1;
}
.file-info-modal .modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(5px);
}
.file-info-modal .modal-content {
position: relative;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 20px;
padding: 2rem;
max-width: 500px;
width: 90%;
color: white;
}
.file-info-modal .modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.file-info-modal .modal-close {
background: none;
border: none;
color: white;
font-size: 1.5rem;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
border-radius: 50%;
transition: background 0.3s ease;
}
.file-info-modal .modal-close:hover {
background: rgba(255, 255, 255, 0.1);
}
.file-info-item {
margin-bottom: 1rem;
line-height: 1.5;
}
.file-info-item strong {
color: #00ff88;
}
`;
document.head.appendChild(notificationStyles);
// Initialize when DOM is loaded
let romBrowser;
document.addEventListener("DOMContentLoaded", () => {
// Check if ROM mirror section exists before initializing
const romSection = document.getElementById("rom-mirror");
if (romSection) {
romBrowser = new ROMFileBrowser();
new ROMScrollReveal();
}
});

267
js/sidebar.js Normal file
View File

@@ -0,0 +1,267 @@
// Glass Sidebar Functionality
class GlassSidebar {
constructor() {
this.sidebar = document.getElementById("glass-sidebar");
this.overlay = document.getElementById("sidebar-overlay");
this.toggleBtn = document.getElementById("sidebar-toggle");
this.closeBtn = document.getElementById("sidebar-close");
this.mainContent = document.querySelector(".main-content");
this.sidebarLinks = document.querySelectorAll(".sidebar-link");
this.isOpen = false;
this.init();
}
init() {
this.bindEvents();
this.updateActiveLink();
this.handleScroll();
}
bindEvents() {
// Toggle sidebar
this.toggleBtn?.addEventListener("click", () => this.toggleSidebar());
this.closeBtn?.addEventListener("click", () => this.closeSidebar());
this.overlay?.addEventListener("click", () => this.closeSidebar());
// Sidebar links
this.sidebarLinks.forEach(link => {
link.addEventListener("click", (e) => {
e.preventDefault();
const targetId = link.getAttribute("href");
this.navigateToSection(targetId);
this.closeSidebar();
});
});
// Close sidebar on escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && this.isOpen) {
this.closeSidebar();
}
});
// Handle scroll to update active link
window.addEventListener("scroll", () => {
this.updateActiveLink();
});
// Handle resize
window.addEventListener("resize", () => {
if (window.innerWidth > 768 && this.isOpen) {
this.closeSidebar();
}
});
}
toggleSidebar() {
if (this.isOpen) {
this.closeSidebar();
} else {
this.openSidebar();
}
}
openSidebar() {
this.isOpen = true;
this.sidebar?.classList.add("active");
this.overlay?.classList.add("active");
this.toggleBtn?.classList.add("active");
this.mainContent?.classList.add("sidebar-open");
// Prevent body scroll
document.body.style.overflow = "hidden";
// Animate sidebar links
this.animateLinks();
}
closeSidebar() {
this.isOpen = false;
this.sidebar?.classList.remove("active");
this.overlay?.classList.remove("active");
this.toggleBtn?.classList.remove("active");
this.mainContent?.classList.remove("sidebar-open");
// Restore body scroll
document.body.style.overflow = "";
}
animateLinks() {
this.sidebarLinks.forEach((link, index) => {
link.style.transform = "translateX(-20px)";
link.style.opacity = "0";
setTimeout(() => {
link.style.transition = "all 0.3s ease";
link.style.transform = "translateX(0)";
link.style.opacity = "1";
}, index * 50 + 200);
});
}
navigateToSection(targetId) {
const targetElement = document.querySelector(targetId);
if (targetElement) {
const offsetTop = targetElement.offsetTop;
window.scrollTo({
top: offsetTop,
behavior: "smooth"
});
}
}
updateActiveLink() {
const sections = document.querySelectorAll("section[id]");
const scrollPos = window.scrollY + 100;
sections.forEach((section) => {
const sectionTop = section.offsetTop;
const sectionBottom = sectionTop + section.offsetHeight;
const sectionId = section.getAttribute("id");
if (scrollPos >= sectionTop && scrollPos < sectionBottom) {
// Remove active class from all links
this.sidebarLinks.forEach(link => {
link.classList.remove("active");
});
// Add active class to current section link
const activeLink = document.querySelector(`[data-section="${sectionId}"]`);
if (activeLink) {
activeLink.classList.add("active");
}
}
});
}
handleScroll() {
let ticking = false;
const updateScrollEffects = () => {
const scrollY = window.scrollY;
// Update toggle button opacity based on scroll
if (this.toggleBtn) {
const opacity = Math.max(0.7, 1 - scrollY / 1000);
this.toggleBtn.style.opacity = opacity;
}
ticking = false;
};
window.addEventListener("scroll", () => {
if (!ticking) {
requestAnimationFrame(updateScrollEffects);
ticking = true;
}
});
}
}
// Enhanced sidebar interactions
class SidebarEnhancements {
constructor() {
this.init();
}
init() {
this.addHoverEffects();
this.addGlowEffects();
this.addSoundEffects();
}
addHoverEffects() {
const sidebarLinks = document.querySelectorAll(".sidebar-link");
sidebarLinks.forEach(link => {
link.addEventListener("mouseenter", () => {
this.createRipple(link);
});
});
}
createRipple(element) {
const ripple = document.createElement("span");
ripple.className = "ripple-effect";
ripple.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(0, 255, 136, 0.3);
transform: translate(-50%, -50%);
animation: rippleAnimation 0.6s ease-out;
pointer-events: none;
z-index: 0;
`;
// Add ripple animation
const style = document.createElement("style");
style.textContent = `
@keyframes rippleAnimation {
to {
width: 100px;
height: 100px;
opacity: 0;
}
}
`;
document.head.appendChild(style);
element.appendChild(ripple);
setTimeout(() => {
ripple.remove();
}, 600);
}
addGlowEffects() {
const sidebar = document.getElementById("glass-sidebar");
if (sidebar) {
sidebar.addEventListener("mouseenter", () => {
sidebar.style.borderRight = "1px solid rgba(0, 255, 136, 0.3)";
});
sidebar.addEventListener("mouseleave", () => {
sidebar.style.borderRight = "1px solid rgba(0, 255, 136, 0.1)";
});
}
}
addSoundEffects() {
// Add subtle visual feedback for interactions
const interactiveElements = document.querySelectorAll(".sidebar-link, .sidebar-toggle, .social-link");
interactiveElements.forEach(element => {
element.addEventListener("click", () => {
element.style.transform = "scale(0.95)";
setTimeout(() => {
element.style.transform = "";
}, 150);
});
});
}
}
// Initialize when DOM is loaded
document.addEventListener("DOMContentLoaded", () => {
new GlassSidebar();
new SidebarEnhancements();
});
// Add glass effect based on scroll
window.addEventListener("scroll", () => {
const sidebar = document.getElementById("glass-sidebar");
const scrollY = window.scrollY;
if (sidebar) {
const blurAmount = Math.min(20 + scrollY / 50, 30);
sidebar.style.backdropFilter = `blur(${blurAmount}px)`;
sidebar.style.webkitBackdropFilter = `blur(${blurAmount}px)`;
}
});

443
js/tech-stack.js Normal file
View File

@@ -0,0 +1,443 @@
// Completely Reformed Experimentation Toolkit
const techStackData = {
languages: {
name: "Programming Languages",
color: "#ff6b6b",
icon: "icon-code",
techs: [
{
name: "Python",
proficiency: "Hobby Projects",
experience: "Building small scripts and automation tools",
level: 3,
},
{
name: "JavaScript",
proficiency: "Learning",
experience: "Frontend experiments and this portfolio",
level: 2,
},
{
name: "Bash",
proficiency: "Daily Use",
experience: "System automation and server management",
level: 4,
},
{
name: "HTML/CSS",
proficiency: "Experimenting",
experience: "Web design and portfolio building",
level: 3,
},
],
},
systems: {
name: "Operating Systems & Tools",
color: "#4ecdc4",
icon: "icon-system",
techs: [
{
name: "Linux",
proficiency: "Home Lab",
experience: "Ubuntu/Debian servers, command line daily",
level: 4,
},
{
name: "Windows",
proficiency: "Daily Driver",
experience: "Main desktop OS, PowerShell scripting",
level: 4,
},
{
name: "Android",
proficiency: "ROM Tinkering",
experience: "Custom ROMs, kernel modifications",
level: 3,
},
{
name: "Docker",
proficiency: "Container Fun",
experience: "Self-hosted services containerization",
level: 2,
},
],
},
webtech: {
name: "Web Technologies",
color: "#45b7d1",
icon: "icon-web",
techs: [
{
name: "Apache",
proficiency: "Home Server",
experience: "Local web server for projects",
level: 3,
},
{
name: "Nginx",
proficiency: "Reverse Proxy",
experience: "Load balancing for self-hosted apps",
level: 2,
},
{
name: "Git",
proficiency: "Version Control",
experience: "GitHub for project management",
level: 3,
},
{
name: "Vercel",
proficiency: "Deployment",
experience: "Static site hosting experiments",
level: 2,
},
],
},
databases: {
name: "Data & Databases",
color: "#96ceb4",
icon: "icon-database",
techs: [
{
name: "SQLite",
proficiency: "Simple Projects",
experience: "Lightweight apps and data storage",
level: 3,
},
{
name: "MariaDB",
proficiency: "Home Lab",
experience: "Database for self-hosted applications",
level: 2,
},
{
name: "JSON",
proficiency: "Data Format",
experience: "API work and configuration files",
level: 4,
},
{
name: "CSV",
proficiency: "Data Analysis",
experience: "Spreadsheet automation and imports",
level: 3,
},
],
},
cloud: {
name: "Cloud & Hosting",
color: "#feca57",
icon: "icon-cloud",
techs: [
{
name: "AWS EC2",
proficiency: "Experimenting",
experience: "Free tier server experiments",
level: 1,
},
{
name: "OVH VPS",
proficiency: "Hosting",
experience: "Affordable cloud server testing",
level: 2,
},
{
name: "GitHub Pages",
proficiency: "Static Hosting",
experience: "Portfolio and project demos",
level: 3,
},
{
name: "Cloudflare",
proficiency: "DNS/CDN",
experience: "Domain management and caching",
level: 2,
},
],
},
selfhosted: {
name: "Self-Hosted Services",
color: "#ff9ff3",
icon: "icon-server",
techs: [
{
name: "Plex",
proficiency: "Media Server",
experience: "Personal streaming setup at home",
level: 4,
},
{
name: "Jellyfin",
proficiency: "Open Source",
experience: "Alternative media server testing",
level: 3,
},
{
name: "Pi-hole",
proficiency: "Network Filter",
experience: "Network-wide ad blocking",
level: 3,
},
{
name: "Nextcloud",
proficiency: "File Sync",
experience: "Personal cloud storage solution",
level: 2,
},
],
},
hardware: {
name: "Hardware & Electronics",
color: "#54a0ff",
icon: "icon-hardware",
techs: [
{
name: "Arduino",
proficiency: "Weekend Projects",
experience: "Basic sensors and LED experiments",
level: 2,
},
{
name: "Raspberry Pi",
proficiency: "Home Automation",
experience: "Self-hosting and IoT projects",
level: 3,
},
{
name: "PC Building",
proficiency: "Enthusiast",
experience: "Custom builds for gaming and servers",
level: 4,
},
{
name: "Network Setup",
proficiency: "Home Lab",
experience: "Router configuration and VLANs",
level: 3,
},
],
},
tools: {
name: "Development Tools",
color: "#5f27cd",
icon: "icon-devtools",
techs: [
{
name: "VS Code",
proficiency: "Daily Editor",
experience: "Primary code editor with extensions",
level: 4,
},
{
name: "Terminal",
proficiency: "Command Line",
experience: "Daily workflow, multiple shell environments",
level: 4,
},
{
name: "GitHub",
proficiency: "Version Control",
experience: "Repository management and collaboration",
level: 3,
},
{
name: "Postman",
proficiency: "API Testing",
experience: "REST API development and testing",
level: 2,
},
],
},
};
class ExperimentationToolkit {
constructor() {
this.currentFilter = "all";
this.searchTerm = "";
this.selectedTech = null;
this.init();
}
init() {
this.renderTechGrid();
this.setupEventListeners();
this.createTechModal();
}
renderTechGrid() {
const grid = document.getElementById("tech-grid");
if (!grid) return;
grid.innerHTML = "";
Object.entries(techStackData).forEach(([category, data]) => {
if (this.currentFilter !== "all" && this.currentFilter !== category)
return;
data.techs.forEach((tech) => {
if (
this.searchTerm &&
!tech.name.toLowerCase().includes(this.searchTerm.toLowerCase())
)
return;
const techCard = this.createEnhancedTechCard(tech, category, data);
grid.appendChild(techCard);
});
});
}
createEnhancedTechCard(tech, category, categoryData) {
const card = document.createElement("div");
card.className = "tech-card enhanced";
card.setAttribute("data-category", category);
card.setAttribute("data-tech", tech.name);
// Proficiency level bars
const proficiencyBars = this.createProficiencyBars(tech.level);
card.innerHTML = `
<div class="tech-card-inner">
<div class="tech-icon ${categoryData.icon}"></div>
<div class="tech-name">${tech.name}</div>
<div class="tech-proficiency">${tech.proficiency}</div>
<div class="proficiency-bars">${proficiencyBars}</div>
<div class="tech-experience">${tech.experience}</div>
<div class="learn-more">Click to learn more</div>
</div>
`;
// Enhanced hover effects
card.addEventListener("mouseenter", () => {
card.style.borderColor = categoryData.color;
card.style.boxShadow = `0 15px 35px ${categoryData.color}40`;
card.style.transform = "translateY(-8px) scale(1.02)";
});
card.addEventListener("mouseleave", () => {
card.style.borderColor = "rgba(255, 255, 255, 0.1)";
card.style.boxShadow = "0 8px 32px rgba(0, 0, 0, 0.3)";
card.style.transform = "translateY(0) scale(1)";
});
// Click to show detailed modal
card.addEventListener("click", () => {
this.showTechModal(tech, categoryData);
});
return card;
}
createProficiencyBars(level) {
let bars = "";
for (let i = 1; i <= 5; i++) {
const isActive = i <= level;
bars += `<div class="prof-bar ${isActive ? "active" : ""}"></div>`;
}
return bars;
}
createTechModal() {
const modal = document.createElement("div");
modal.id = "tech-modal";
modal.className = "tech-modal";
modal.innerHTML = `
<div class="modal-overlay"></div>
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title"></h3>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<div class="modal-icon"></div>
<div class="modal-proficiency"></div>
<div class="modal-experience"></div>
<div class="modal-level"></div>
</div>
</div>
`;
document.body.appendChild(modal);
// Close modal events
modal
.querySelector(".modal-close")
.addEventListener("click", () => this.closeTechModal());
modal
.querySelector(".modal-overlay")
.addEventListener("click", () => this.closeTechModal());
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") this.closeTechModal();
});
}
showTechModal(tech, categoryData) {
const modal = document.getElementById("tech-modal");
modal.querySelector(".modal-title").textContent = tech.name;
modal.querySelector(".modal-icon").className =
`modal-icon ${categoryData.icon}`;
modal.querySelector(".modal-proficiency").innerHTML = `
<strong>Current Level:</strong> ${tech.proficiency}
`;
modal.querySelector(".modal-experience").innerHTML = `
<strong>My Experience:</strong><br>${tech.experience}
`;
modal.querySelector(".modal-level").innerHTML = `
<strong>Proficiency:</strong><br>
<div class="modal-prof-bars">${this.createProficiencyBars(tech.level)}</div>
<span class="prof-text">${this.getProficiencyText(tech.level)}</span>
`;
modal.style.display = "flex";
setTimeout(() => modal.classList.add("active"), 10);
document.body.style.overflow = "hidden";
}
closeTechModal() {
const modal = document.getElementById("tech-modal");
modal.classList.remove("active");
setTimeout(() => {
modal.style.display = "none";
document.body.style.overflow = "";
}, 300);
}
getProficiencyText(level) {
const levels = {
1: "Just started experimenting",
2: "Getting familiar with basics",
3: "Comfortable with common tasks",
4: "Use regularly and confidently",
5: "Deep knowledge and daily use",
};
return levels[level] || "Learning in progress";
}
setupEventListeners() {
// Filter buttons
const filterButtons = document.querySelectorAll(".filter-btn");
filterButtons.forEach((btn) => {
btn.addEventListener("click", () => {
filterButtons.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
this.currentFilter = btn.getAttribute("data-category");
this.renderTechGrid();
});
});
// Search input
const searchInput = document.getElementById("tech-search");
if (searchInput) {
searchInput.addEventListener("input", (e) => {
this.searchTerm = e.target.value;
this.renderTechGrid();
});
}
}
}
// Initialize experimentation toolkit
document.addEventListener("DOMContentLoaded", function () {
new ExperimentationToolkit();
});

67
js/typewriter.js Normal file
View File

@@ -0,0 +1,67 @@
// Enhanced Typewriter Effect with Authentic Hobbyist Sequence
class TypewriterEffect {
constructor(element, texts, options = {}) {
this.element = element;
this.texts = texts;
this.speed = options.speed || 100;
this.deleteSpeed = options.deleteSpeed || 50;
this.pauseDuration = options.pauseDuration || 1000;
this.currentIndex = 0;
this.currentText = '';
this.isDeleting = false;
this.start();
}
start() {
this.type();
}
type() {
const fullText = this.texts[this.currentIndex];
if (this.isDeleting) {
this.currentText = fullText.substring(0, this.currentText.length - 1);
} else {
this.currentText = fullText.substring(0, this.currentText.length + 1);
}
this.element.textContent = this.currentText;
let typeSpeed = this.speed;
if (this.isDeleting) {
typeSpeed = this.deleteSpeed;
}
if (!this.isDeleting && this.currentText === fullText) {
typeSpeed = this.pauseDuration;
this.isDeleting = true;
} else if (this.isDeleting && this.currentText === '') {
this.isDeleting = false;
this.currentIndex = (this.currentIndex + 1) % this.texts.length;
typeSpeed = 200;
}
setTimeout(() => this.type(), typeSpeed);
}
}
// Initialize typewriter on page load
document.addEventListener('DOMContentLoaded', function() {
const typewriterElement = document.getElementById('typewriter');
if (typewriterElement) {
const texts = [
'Hobby Programmer',
'Home Lab Enthusiast',
'Weekend Tinkerer',
'Tech Explorer',
'ROM Experimenter'
];
new TypewriterEffect(typewriterElement, texts, {
speed: 100,
deleteSpeed: 50,
pauseDuration: 2000
});
}
});