UNPKG

@anik8mishra/portfolio

Version:

Aniket's interactive CLI portfolio with optimized starry space animation, skills, and minigames

515 lines (490 loc) 17.1 kB
#!/usr/bin/env node import { readFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import chalk from 'chalk'; import boxen from 'boxen'; import inquirer from 'inquirer'; import open from 'open'; import figlet from 'figlet'; import cliSpinners from 'cli-spinners'; import { initializeApp } from "firebase/app"; import { getDatabase, ref, push } from "firebase/database"; // Get the directory name const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Load portfolio data const dataPath = join(__dirname, 'data.json'); const data = JSON.parse(readFileSync(dataPath, 'utf8')); // Firebase configuration const firebaseConfig = { apiKey: "AIzaSyCO99cjDn3I9QueNQH_THuxf2SsdWd9mTE", authDomain: "anik8mishraportfolio.firebaseapp.com", databaseURL: "https://anik8mishraportfolio-default-rtdb.firebaseio.com", projectId: "anik8mishraportfolio", storageBucket: "anik8mishraportfolio.firebasestorage.app", messagingSenderId: "973751308764", appId: "1:973751308764:web:5221e2fed9aff6e452ff19", measurementId: "G-8FZ343M8MS" }; const app = initializeApp(firebaseConfig); const database = getDatabase(app); // Optimized Starry Space Effect function createStarrySpace(duration = 3000) { return new Promise((resolve) => { const width = Math.min(process.stdout.columns || 80, 80); const height = Math.min(process.stdout.rows || 24, 20); const starCount = 30; let stars = Array.from({ length: starCount }, () => ({ x: Math.floor(Math.random() * width), y: Math.floor(Math.random() * height), char: Math.random() > 0.7 ? '✦' : Math.random() > 0.4 ? '·' : '*', brightness: Math.random() })); const startTime = Date.now(); const animate = () => { if (Date.now() - startTime > duration) { console.clear(); resolve(); return; } console.clear(); let screen = Array.from({ length: height }, () => Array(width).fill(' ')); stars.forEach(star => { if (star.x >= 0 && star.x < width && star.y >= 0 && star.y < height) { let starChar; if (star.brightness > 0.8) { starChar = chalk.white.bold(star.char); } else if (star.brightness > 0.6) { starChar = chalk.white(star.char); } else if (star.brightness > 0.3) { starChar = chalk.gray(star.char); } else { starChar = chalk.gray.dim(star.char); } screen[star.y][star.x] = starChar; } }); screen.forEach(row => console.log(row.join(''))); stars = stars.map(star => ({ ...star, y: (star.y + (Math.random() > 0.85 ? 1 : 0)) % height, brightness: Math.max(0.1, star.brightness + (Math.random() - 0.5) * 0.08) })); setTimeout(animate, 200); }; animate(); }); } // Simple Loading Animation class LoadingAnimation { constructor(text = 'Loading') { this.text = text; this.spinner = cliSpinners.dots; this.isSpinning = false; this.frameIndex = 0; this.interval = null; } start() { this.isSpinning = true; this.animate(); } stop() { this.isSpinning = false; if (this.interval) clearTimeout(this.interval); process.stdout.write('\r\x1b[K'); } animate() { if (!this.isSpinning) return; const frame = this.spinner.frames[this.frameIndex]; process.stdout.write(`\r${chalk.cyan(frame)} ${this.text}...`); this.frameIndex = (this.frameIndex + 1) % this.spinner.frames.length; this.interval = setTimeout(() => this.animate(), this.spinner.interval); } } // Clean welcome header function displayWelcomeHeader() { console.clear(); try { const asciiArt = figlet.textSync('ANIKET', { font: 'Standard', horizontalLayout: 'default' }); console.log(chalk.cyan(asciiArt)); } catch (error) { console.log(chalk.cyan.bold('\n ANIKET MISHRA\n')); } console.log(boxen( chalk.white('Welcome to my interactive portfolio!\nLet\'s explore my work together.'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'cyan', textAlignment: 'center' } )); } // Typewriter effect async function typeWriter(text, speed = 40) { for (let i = 0; i < text.length; i++) { process.stdout.write(text[i]); await new Promise(resolve => setTimeout(resolve, speed)); } console.log(); } // Animated progress bar (dynamic, color-coded) async function animateProgressBar(skillName, level, category) { const barLength = 30; const steps = 25; let barColor; switch (category) { case 'frontend': barColor = 'cyan'; break; case 'backend': barColor = 'green'; break; case 'database': barColor = 'yellow'; break; case 'tools': barColor = 'magenta'; break; default: barColor = 'white'; } for (let i = 0; i <= steps; i++) { const currentLevel = (level * i) / steps; const filledLength = Math.floor((currentLevel / 100) * barLength); const emptyLength = barLength - filledLength; const filled = '█'.repeat(filledLength); const empty = '░'.repeat(emptyLength); const bar = chalk[barColor](filled) + chalk.gray(empty); const percentage = Math.floor(currentLevel); process.stdout.write( `\r${chalk.white(skillName.padEnd(18))} [${bar}] ${chalk[barColor](percentage + '%')}` ); await new Promise(resolve => setTimeout(resolve, 30)); } console.log(); } // Dynamic, animated, color-coded skills display async function displaySkillsAnimated() { console.clear(); console.log(chalk.bold.cyan('\n🛠️ Technical Skills\n')); const skillCategories = [ { name: 'Frontend Development', category: 'frontend', skills: [ { name: 'React', level: 90 }, { name: 'JavaScript', level: 95 }, { name: 'TypeScript', level: 85 }, { name: 'HTML/CSS', level: 90 } ] }, { name: 'Backend Development', category: 'backend', skills: [ { name: 'Node.js', level: 85 }, { name: 'Python', level: 80 }, { name: 'Express.js', level: 85 } ] }, { name: 'Database & Cloud', category: 'database', skills: [ { name: 'MongoDB', level: 80 }, { name: 'Firebase', level: 85 }, { name: 'AWS', level: 70 } ] }, { name: 'Tools & DevOps', category: 'tools', skills: [ { name: 'Git', level: 90 }, { name: 'Docker', level: 75 }, { name: 'VS Code', level: 95 } ] } ]; for (const categoryData of skillCategories) { console.log(chalk.bold.white(`${categoryData.name}:`)); for (const skill of categoryData.skills) { await animateProgressBar(skill.name, skill.level, categoryData.category); await new Promise(resolve => setTimeout(resolve, 150)); } console.log(); await new Promise(resolve => setTimeout(resolve, 200)); } console.log(chalk.green('✨ Skills showcase complete!\n')); } // Firebase helpers async function recordVisitor(userName) { try { const visitorsRef = ref(database, 'visitors'); await push(visitorsRef, { name: userName, timestamp: new Date().toISOString() }); } catch (error) { console.error(chalk.red('Error recording visitor. Please check your connection.')); } } async function saveMessage(userName, subject, message) { try { const messagesRef = ref(database, 'messages'); await push(messagesRef, { name: userName, subject: subject || '', message, timestamp: new Date().toISOString() }); } catch (error) { console.error(chalk.red('Error saving message. Please check your connection.')); } } // Startup async function startup() { await createStarrySpace(3000); displayWelcomeHeader(); await typeWriter(chalk.cyan('Initializing portfolio interface...'), 30); await new Promise(resolve => setTimeout(resolve, 600)); startConversation(); } // Start conversation async function startConversation() { const nameResponse = await inquirer.prompt([ { type: 'input', name: 'userName', message: 'Hi there! What\'s your name?', default: 'friend' } ]); const loader = new LoadingAnimation('Setting up your session'); loader.start(); await recordVisitor(nameResponse.userName); await new Promise(resolve => setTimeout(resolve, 700)); loader.stop(); console.log(chalk.cyan(`\nNice to meet you, ${chalk.bold(nameResponse.userName)}! 👋`)); await typeWriter(chalk.white(`I'm Aniket, thanks for checking out my portfolio.`), 35); await new Promise(resolve => setTimeout(resolve, 400)); showMainMenu(nameResponse.userName); } // Main menu async function showMainMenu(userName) { const menuResponse = await inquirer.prompt([ { type: 'list', name: 'action', message: `${userName}, what would you like to explore?`, choices: [ { name: '👤 About Me', value: 'about' }, { name: '🛠️ My Skills', value: 'skills' }, { name: '🌐 Portfolio Website', value: 'website' }, { name: '📄 Download Resume', value: 'resume' }, { name: '✉️ Send Message', value: 'message' }, { name: '🔗 Connect', value: 'connect' }, { name: '🎮 Play Rock, Paper, Scissors', value: 'rps' }, { name: '👋 Exit', value: 'exit' } ] } ]); await handleMenuSelection(menuResponse.action, userName); } // Rock, Paper, Scissors Minigame async function playRPSGame(userName) { console.clear(); console.log(chalk.magenta.bold('\nRock, Paper, Scissors!')); await typeWriter('Best of 3 rounds. Can you beat the computer?\n', 30); const options = ['Rock 🪨', 'Paper 📄', 'Scissors ✂️']; let userScore = 0, compScore = 0; for (let round = 1; round <= 3; round++) { console.log(chalk.cyan(`\nRound ${round}:`)); const user = await inquirer.prompt([{ type: 'list', name: 'choice', message: 'Choose your move:', choices: options }]); const userChoice = options.indexOf(user.choice); const compChoice = Math.floor(Math.random() * 3); process.stdout.write(chalk.gray('Computer is choosing')); for (let i = 0; i < 3; i++) { process.stdout.write('.'); await new Promise(res => setTimeout(res, 300)); } console.log(); const resultMsg = `You: ${options[userChoice]} | Computer: ${options[compChoice]}`; if (userChoice === compChoice) { console.log(chalk.yellow(`${resultMsg} → Draw!`)); } else if ( (userChoice === 0 && compChoice === 2) || (userChoice === 1 && compChoice === 0) || (userChoice === 2 && compChoice === 1) ) { userScore++; console.log(chalk.green(`${resultMsg} → You win this round!`)); } else { compScore++; console.log(chalk.red(`${resultMsg} → Computer wins this round!`)); } await new Promise(res => setTimeout(res, 700)); } console.log(); if (userScore > compScore) { console.log(chalk.green.bold(`🎉 You win! Final Score: ${userScore} - ${compScore}\n`)); } else if (userScore < compScore) { console.log(chalk.red.bold(`💻 Computer wins! Final Score: ${userScore} - ${compScore}\n`)); } else { console.log(chalk.yellow.bold(`🤝 It's a draw! Final Score: ${userScore} - ${compScore}\n`)); } await continuePrompt(userName); } // Handle menu selection async function handleMenuSelection(action, userName) { switch(action) { case 'about': console.log(boxen( chalk.white(`Hi! I'm Aniket, a passionate full-stack developer with a love for creating innovative solutions. I specialize in modern web technologies and enjoy building applications that make a real difference. When I'm not coding, you'll find me exploring new technologies, contributing to open-source projects, or planning my next adventure. I believe in writing clean, maintainable code and creating exceptional user experiences. Currently focused on: React, Node.js, and cloud technologies.`), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'blue', title: '📝 About Me', titleAlignment: 'center' } )); await continuePrompt(userName); break; case 'skills': await displaySkillsAnimated(); await continuePrompt(userName); break; case 'website': console.log(chalk.cyan('\n🌐 Opening portfolio website...\n')); await open('https://an8ketmishra.netlify.app/'); console.log(chalk.green('✅ Website opened in your browser!')); await continuePrompt(userName); break; case 'resume': console.log(chalk.cyan('\n📄 Opening resume...\n')); await open('https://drive.google.com/file/d/1WG3GX3nA-KTk7U2LI6FWzCeZX9v2t1iK/view?usp=sharing'); console.log(chalk.green('✅ Resume opened in your browser!')); await continuePrompt(userName); break; case 'message': console.log(chalk.bold.magenta('\n✉️ Leave a Message\n')); await typeWriter(chalk.white('I\'d love to hear from you! Your message will be saved securely.'), 30); console.log(); const messageResponse = await inquirer.prompt([ { type: 'input', name: 'subject', message: 'Subject (optional):', validate: input => input.length <= 100 || 'Subject must be 100 characters or less' }, { type: 'editor', name: 'message', message: 'Your message:', validate: input => input.trim().length > 0 || 'Message cannot be empty' } ]); const saveLoader = new LoadingAnimation('Saving your message'); saveLoader.start(); await saveMessage(userName, messageResponse.subject, messageResponse.message); await new Promise(resolve => setTimeout(resolve, 1000)); saveLoader.stop(); console.log(chalk.green('\n✅ Message saved! Thank you for reaching out.\n')); await continuePrompt(userName); break; case 'connect': const connectResponse = await inquirer.prompt([ { type: 'list', name: 'platform', message: 'Choose a platform:', choices: [ { name: '🐙 GitHub', value: 'github' }, { name: '💼 LinkedIn', value: 'linkedin' }, { name: '📧 Email', value: 'email' }, { name: '⬅️ Back', value: 'back' } ] } ]); switch(connectResponse.platform) { case 'github': console.log(chalk.cyan('\n🐙 Opening GitHub...')); await open(data.github); console.log(chalk.green('✅ GitHub opened!')); break; case 'linkedin': console.log(chalk.blue('\n💼 Opening LinkedIn...')); await open(data.linkedin); console.log(chalk.green('✅ LinkedIn opened!')); break; case 'email': console.log(chalk.red('\n📧 Opening email...')); await open(`mailto:${data.email}`); console.log(chalk.green('✅ Email client opened!')); break; case 'back': await showMainMenu(userName); return; } if (connectResponse.platform !== 'back') { await new Promise(resolve => setTimeout(resolve, 1000)); await showMainMenu(userName); } return; case 'rps': await playRPSGame(userName); break; case 'exit': console.log(boxen( chalk.cyan(`Thanks for visiting, ${userName}!\nHave a wonderful day! ✨`), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'cyan', textAlignment: 'center' } )); process.exit(0); } } async function continuePrompt(userName) { const continueResponse = await inquirer.prompt([ { type: 'list', name: 'continue', message: 'What next?', choices: [ { name: '⬅️ Main Menu', value: 'menu' }, { name: '👋 Exit', value: 'exit' } ] } ]); if (continueResponse.continue === 'menu') { await showMainMenu(userName); } else { console.log(boxen( chalk.cyan(`Thanks for visiting, ${userName}!\nHave a wonderful day! ✨`), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'cyan', textAlignment: 'center' } )); process.exit(0); } } // Start the application startup();