@anik8mishra/portfolio
Version:
Aniket's interactive CLI portfolio with optimized starry space animation, skills, and minigames
515 lines (490 loc) • 17.1 kB
JavaScript
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();