diff --git a/Projects/Job Application Tracker Pro/README.md b/Projects/Job Application Tracker Pro/README.md new file mode 100644 index 00000000..bfd7e840 --- /dev/null +++ b/Projects/Job Application Tracker Pro/README.md @@ -0,0 +1,29 @@ +# Job Application Tracker Pro + +A premium, interactive developer career search center featuring horizontal Kanban pipeline lanes, salary projections, sub-task milestone checklists, and interview notes logs. + +## Core Features + +- **Kanban Board Pipeline Lanes**: Organize job applications across 5 columns: + * Wishlist -> Applied -> Interviewing -> Offer -> Rejected. + * Shift card status columns interactively to recalculate recruitment analytics. +- **Career Search Analytics Pro**: Custom dashboards summarizing metrics: + * Conversion rates (Applied to Interviewing, and Offered rates). + * Salary distributions (median, average, and max expected base salary figures). + * Offers Value: Total value of active offers. +- **Relational Inspector Panel**: Click a card to expand the inspector drawer to: + * Edit Company, Job Title, Salary, applied date, location status (Remote/Hybrid/Onsite), and Listing URL. + * Add custom sub-task checklists (e.g. Practicing DSA, System Design prep, Send follow-up). + * Write interviewer names, notes, and logs. +- **Filtering & Searches**: Fast text query searches indexing companies and roles. +- **Local Storage State Persistence**: Keeps job profiles and checklists synced locally. + +## Startup instructions + +Open `index.html` in any web browser. + +## Tech stack + +- HTML5 +- CSS3 (Vanilla variable parameters, flexbox columns, grid alignments, transition frames) +- Vanilla JavaScript (Kanban pipeline shifting controllers, statistics calculators, local storage sync logs) diff --git a/Projects/Job Application Tracker Pro/index.html b/Projects/Job Application Tracker Pro/index.html new file mode 100644 index 00000000..0158906d --- /dev/null +++ b/Projects/Job Application Tracker Pro/index.html @@ -0,0 +1,273 @@ + + + + + + Job Application Tracker Pro + + + + + + + + + +
+ + +
+
+ + +
+ Applied: 0 + Interviews: 0 + Offers: 0 +
+
+
+ + +
+ + + + + +
+
+

Recruitment Board

+ +
+ + +
+ + +
+
+ Wishlist + 0 +
+
+ +
+
+ + +
+
+ Applied + 0 +
+
+ +
+
+ + +
+
+ Interviewing + 0 +
+
+ +
+
+ + +
+
+ Offer + 0 +
+
+ +
+
+ + +
+
+ Rejected + 0 +
+
+ +
+
+ +
+
+ + +
+ + +
+
+ +
+

Application Inspector

+

Select any job card from the Kanban columns to view task checklists, modify salaries, or log interview questions.

+
+ + + + +
+ +
+ + + + + + + diff --git a/Projects/Job Application Tracker Pro/project.json b/Projects/Job Application Tracker Pro/project.json new file mode 100644 index 00000000..2518dc17 --- /dev/null +++ b/Projects/Job Application Tracker Pro/project.json @@ -0,0 +1,17 @@ +{ + "title": "Job Application Tracker Pro", + "description": "A premium client-side career search dashboard featuring horizontal Kanban pipeline lanes, salary projections, and sub-task milestone checklists.", + "author": { + "name": "Sujal", + "github": "Sujal" + }, + "tags": [ + "productivity", + "recruitment", + "kanban", + "vanilla-js", + "localstorage" + ], + "entry": "index.html", + "thumbnail": "thumbnail.svg" +} diff --git a/Projects/Job Application Tracker Pro/script.js b/Projects/Job Application Tracker Pro/script.js new file mode 100644 index 00000000..2e60c361 --- /dev/null +++ b/Projects/Job Application Tracker Pro/script.js @@ -0,0 +1,603 @@ +// Job Application Tracker Pro - Interaction Logic + +// Default seed job profiles to start the user with a beautiful, populated dashboard +const SEED_JOBS = [ + { + id: "seed-google", + company: "Google", + role: "Frontend Engineer - Cloud UX", + status: "Offer", + dateApplied: "2026-05-15", + salary: 145000, + location: "Hybrid", + url: "https://careers.google.com", + notes: "Loved the team! Met with Sarah (EM) and Dave (Tech Lead). Base salary offered is $145,000/yr + 15% bonus. Negotiated sign-on bonus.", + tasks: [ + { id: "g-task-1", text: "Phone recruiter screening", completed: true }, + { id: "g-task-2", text: "Technical coding interview (Algorithms & DOM)", completed: true }, + { id: "g-task-3", text: "Onsite panel review & System design", completed: true }, + { id: "g-task-4", text: "Review written compensation package offer", completed: true } + ] + }, + { + id: "seed-stripe", + company: "Stripe", + role: "Senior JavaScript Architect", + status: "Interviewing", + dateApplied: "2026-06-01", + salary: 185000, + location: "Remote", + url: "https://stripe.com/jobs", + notes: "Recruiter screen went great. Next step is system design session scheduled for June 18th. Focus on idempotency, rate limiting, and API design patterns.", + tasks: [ + { id: "s-task-1", text: "Submit resume & application", completed: true }, + { id: "s-task-2", text: "Portfolio design review", completed: true }, + { id: "s-task-3", text: "Prepare system design architecture session", completed: false }, + { id: "s-task-4", text: "Manager alignment interview", completed: false } + ] + }, + { + id: "seed-netflix", + company: "Netflix", + role: "UI Engineer - Core Experience", + status: "Applied", + dateApplied: "2026-06-10", + salary: 190000, + location: "Onsite", + url: "https://jobs.netflix.com", + notes: "Applied on the careers portal. Sent follow-up connection message to hiring manager on LinkedIn. Let's see if they respond within a week.", + tasks: [ + { id: "n-task-1", text: "Submit application form", completed: true }, + { id: "n-task-2", text: "Follow up via LinkedIn recruiter", completed: false } + ] + }, + { + id: "seed-vercel", + company: "Vercel", + role: "Developer Relations Engineer", + status: "Wishlist", + dateApplied: "2026-06-12", + salary: 130000, + location: "Remote", + url: "https://vercel.com/careers", + notes: "Wishlist job. Highly interested in their DevRel open headcount. Will request Jake for a warm referral once my new portfolio is clean.", + tasks: [ + { id: "v-task-1", text: "Polish portfolio site & blog posts", completed: false }, + { id: "v-task-2", text: "Get referral from Jake", completed: false } + ] + } +]; + +// Kanban lane pipeline sequence +const LANES = ["Wishlist", "Applied", "Interviewing", "Offer", "Rejected"]; + +// App State +let jobs = []; +let activeJobId = null; + +// DOM Elements references +const txtSearch = document.getElementById("txt-search"); +const selLocationFilter = document.getElementById("sel-location-filter"); + +const lblHeaderApplied = document.getElementById("lbl-header-applied"); +const lblHeaderInterviews = document.getElementById("lbl-header-interviews"); +const lblHeaderOffers = document.getElementById("lbl-header-offers"); + +const lblOffersValue = document.getElementById("lbl-offers-value"); +const lblAvgSalary = document.getElementById("lbl-avg-salary"); +const lblInterviewRate = document.getElementById("lbl-interview-rate"); + +const btnResetWorkspace = document.getElementById("btn-reset-workspace"); +const btnAddJob = document.getElementById("btn-add-job"); + +const colWishlist = document.getElementById("col-wishlist"); +const colApplied = document.getElementById("col-applied"); +const colInterviewing = document.getElementById("col-interviewing"); +const colOffer = document.getElementById("col-offer"); +const colRejected = document.getElementById("col-rejected"); + +const inspectorEmpty = document.getElementById("inspector-empty"); +const inspectorActive = document.getElementById("inspector-active"); + +// Form controls references +const jobCompany = document.getElementById("job-company"); +const jobRole = document.getElementById("job-role"); +const jobDate = document.getElementById("job-date"); +const selStatus = document.getElementById("sel-status"); +const jobSalary = document.getElementById("job-salary"); +const selLocation = document.getElementById("sel-location"); +const jobUrl = document.getElementById("job-url"); +const jobNotes = document.getElementById("job-notes"); + +const tasksChecklist = document.getElementById("tasks-checklist"); +const txtNewTask = document.getElementById("txt-new-task"); +const btnAddTask = document.getElementById("btn-add-task"); + +const btnSaveJob = document.getElementById("btn-save-job"); +const btnDeleteJob = document.getElementById("btn-delete-job"); + +// INITIALIZATION +window.addEventListener("DOMContentLoaded", () => { + loadData(); + setupEventListeners(); + calculateAnalytics(); + renderKanban(); +}); + +// Load from localStorage or seeds +function loadData() { + const storedJobs = localStorage.getItem("jobtrack_pro_jobs"); + if (storedJobs) { + try { + jobs = JSON.parse(storedJobs); + } catch (e) { + console.error("Error parsing jobs from local storage, resetting to defaults", e); + jobs = [...SEED_JOBS]; + } + } else { + jobs = [...SEED_JOBS]; + saveToStorage(false); // don't trigger immediate render during init + } +} + +// Save back to storage and recompute +function saveToStorage(shouldRender = true) { + localStorage.setItem("jobtrack_pro_jobs", JSON.stringify(jobs)); + if (shouldRender) { + calculateAnalytics(); + renderKanban(); + if (activeJobId) { + // Keep inspector updated + const activeJob = jobs.find(j => j.id === activeJobId); + if (activeJob) { + renderTasksChecklist(activeJob); + } + } + } +} + +// Global Event Listeners +function setupEventListeners() { + // Search & Filter + txtSearch.addEventListener("input", renderKanban); + selLocationFilter.addEventListener("change", renderKanban); + + // Buttons + btnAddJob.addEventListener("click", addNewJobSpec); + btnResetWorkspace.addEventListener("click", resetWorkspace); + + // Inspector Actions + btnSaveJob.addEventListener("click", saveActiveJobChanges); + btnDeleteJob.addEventListener("click", deleteActiveJob); + btnAddTask.addEventListener("click", handleAddTaskSubmit); + txtNewTask.addEventListener("keypress", (e) => { + if (e.key === "Enter") { + handleAddTaskSubmit(); + } + }); + + // Track status changing from the dropdown to automatically update visual lanes in real time + selStatus.addEventListener("change", () => { + if (activeJobId) { + const job = jobs.find(j => j.id === activeJobId); + if (job) { + job.status = selStatus.value; + saveToStorage(); + } + } + }); +} + +// CALCULATE ANALYTICS +function calculateAnalytics() { + // Header Counter Indicators + const appliedCount = jobs.filter(j => j.status === "Applied").length; + const interviewingCount = jobs.filter(j => j.status === "Interviewing").length; + const offerCount = jobs.filter(j => j.status === "Offer").length; + + lblHeaderApplied.textContent = appliedCount; + lblHeaderInterviews.textContent = interviewingCount; + lblHeaderOffers.textContent = offerCount; + + // Active Offers Value Sum + const offerVal = jobs + .filter(j => j.status === "Offer") + .reduce((sum, j) => sum + (Number(j.salary) || 0), 0); + lblOffersValue.textContent = formatSalary(offerVal); + + // Average Salary of all jobs that have a salary > 0 + const jobsWithSalary = jobs.filter(j => Number(j.salary) > 0); + const avgSalary = jobsWithSalary.length > 0 + ? Math.round(jobsWithSalary.reduce((sum, j) => sum + Number(j.salary), 0) / jobsWithSalary.length) + : 0; + lblAvgSalary.textContent = formatSalary(avgSalary); + + // Interview Conversion Rate + // Rate = (Interviewing + Offer) / (All applied jobs i.e. not in Wishlist) * 100 + const totalAppliedFunnel = jobs.filter(j => j.status !== "Wishlist").length; + const reachedInterview = jobs.filter(j => j.status === "Interviewing" || j.status === "Offer").length; + const conversionRate = totalAppliedFunnel > 0 + ? Math.round((reachedInterview / totalAppliedFunnel) * 100) + : 0; + lblInterviewRate.textContent = `${conversionRate}%`; + + // Update counts in lane headers + document.getElementById("count-wishlist").textContent = jobs.filter(j => j.status === "Wishlist").length; + document.getElementById("count-applied").textContent = appliedCount; + document.getElementById("count-interviewing").textContent = interviewingCount; + document.getElementById("count-offer").textContent = offerCount; + document.getElementById("count-rejected").textContent = jobs.filter(j => j.status === "Rejected").length; +} + +// Format number to local currency format e.g. $145,000/yr +function formatSalary(amount) { + if (!amount || amount <= 0) return "$0/yr"; + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + maximumFractionDigits: 0 + }).format(amount) + "/yr"; +} + +// RENDER KANBAN PIPELINE BOARD +function renderKanban() { + const searchQuery = txtSearch.value.trim().toLowerCase(); + const locationFilter = selLocationFilter.value; + + // Clear lane wrapper structures + colWishlist.innerHTML = ""; + colApplied.innerHTML = ""; + colInterviewing.innerHTML = ""; + colOffer.innerHTML = ""; + colRejected.innerHTML = ""; + + const filteredJobs = jobs.filter(job => { + // 1. Search Query Match + const matchesSearch = + job.company.toLowerCase().includes(searchQuery) || + job.role.toLowerCase().includes(searchQuery) || + job.notes.toLowerCase().includes(searchQuery); + + // 2. Location Filter Match + const matchesLocation = locationFilter === "All" || job.location === locationFilter; + + return matchesSearch && matchesLocation; + }); + + filteredJobs.forEach(job => { + const card = createJobCard(job); + + // Dispatch to matching lane wrapper + switch (job.status) { + case "Wishlist": + colWishlist.appendChild(card); + break; + case "Applied": + colApplied.appendChild(card); + break; + case "Interviewing": + colInterviewing.appendChild(card); + break; + case "Offer": + colOffer.appendChild(card); + break; + case "Rejected": + colRejected.appendChild(card); + break; + } + }); + + // Display helpful message if a lane is empty and no jobs exist + const wrappers = [ + { col: colWishlist, text: "No wishlist specs" }, + { col: colApplied, text: "No applications submitted" }, + { col: colInterviewing, text: "No active interviews" }, + { col: colOffer, text: "No offers received yet" }, + { col: colRejected, text: "No profiles logged here" } + ]; + + wrappers.forEach(w => { + if (w.col.children.length === 0) { + const emptyDiv = document.createElement("div"); + emptyDiv.className = "empty-lane-placeholder"; + emptyDiv.style.textAlign = "center"; + emptyDiv.style.padding = "1.5rem 0.5rem"; + emptyDiv.style.color = "var(--text-muted)"; + emptyDiv.style.fontSize = "0.75rem"; + emptyDiv.style.border = "1px dashed rgba(255, 255, 255, 0.02)"; + emptyDiv.style.borderRadius = "var(--border-radius-sm)"; + emptyDiv.textContent = w.text; + w.col.appendChild(emptyDiv); + } + }); +} + +// CREATE CARD COMPONENT +function createJobCard(job) { + const card = document.createElement("div"); + card.className = "job-card"; + if (job.id === activeJobId) { + card.classList.add("active-card"); + } + + // Calculate Subtask Checklist percentage + const totalTasks = job.tasks ? job.tasks.length : 0; + const completedTasks = job.tasks ? job.tasks.filter(t => t.completed).length : 0; + const pct = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0; + + card.innerHTML = ` +
${escapeHtml(job.company)}
+
${escapeHtml(job.role)}
+
+ ${job.salary ? formatSalaryShort(job.salary) : "$--"} + ${job.location} +
+ +
+
+ Milestones + ${completedTasks}/${totalTasks} (${pct}%) +
+
+
+
+
+ +
+ + Move + +
+ `; + + // Handle Card Click to inspect details + card.addEventListener("click", () => { + selectJob(job.id); + }); + + // Shift logic left/right + const btnLeft = card.querySelector(".btn-shift-left"); + const btnRight = card.querySelector(".btn-shift-right"); + + const currentIndex = LANES.indexOf(job.status); + + if (currentIndex === 0) { + btnLeft.classList.add("disabled"); + } + if (currentIndex === LANES.length - 1) { + btnRight.classList.add("disabled"); + } + + btnLeft.addEventListener("click", (e) => { + e.stopPropagation(); // Prevent inspecting card on shift click + shiftJobLane(job.id, -1); + }); + + btnRight.addEventListener("click", (e) => { + e.stopPropagation(); // Prevent inspecting card on shift click + shiftJobLane(job.id, 1); + }); + + return card; +} + +// Format salary to simple K notation e.g. $145K +function formatSalaryShort(salary) { + const num = Number(salary); + if (!num || num <= 0) return "$0"; + return num >= 1000 ? `$${Math.round(num / 1000)}k` : `$${num}`; +} + +// SHIFT CARD STATUS LANE +function shiftJobLane(jobId, direction) { + const job = jobs.find(j => j.id === jobId); + if (!job) return; + + const currentIndex = LANES.indexOf(job.status); + const newIndex = currentIndex + direction; + + if (newIndex >= 0 && newIndex < LANES.length) { + job.status = LANES[newIndex]; + saveToStorage(); + + // Update active inspector dropdown status sync if inspecting this job + if (activeJobId === jobId) { + selStatus.value = job.status; + } + } +} + +// SELECT A JOB TO SHOW IN THE INSPECTOR +function selectJob(jobId) { + activeJobId = jobId; + const job = jobs.find(j => j.id === jobId); + if (!job) { + deselectJob(); + return; + } + + // Visual classes highlight updates + document.querySelectorAll(".job-card").forEach(c => c.classList.remove("active-card")); + + // Fill form controls with details + jobCompany.value = job.company; + jobRole.value = job.role; + jobDate.value = job.dateApplied || ""; + selStatus.value = job.status; + jobSalary.value = job.salary || ""; + selLocation.value = job.location || "Remote"; + jobUrl.value = job.url || ""; + jobNotes.value = job.notes || ""; + + // Render subtasks checklist + renderTasksChecklist(job); + + // Toggle visible containers + inspectorEmpty.classList.add("hidden"); + inspectorActive.classList.remove("hidden"); + + // Rerender cards to update active styling outline + renderKanban(); +} + +// DESELECT INSPECTED CARD +function deselectJob() { + activeJobId = null; + inspectorActive.classList.add("hidden"); + inspectorEmpty.classList.remove("hidden"); + renderKanban(); +} + +// SAVE CHANGES IN ACTIVE INSPECTOR PANEL +function saveActiveJobChanges() { + if (!activeJobId) return; + + const job = jobs.find(j => j.id === activeJobId); + if (!job) return; + + // Validate basic inputs + if (!jobCompany.value.trim() || !jobRole.value.trim()) { + alert("Please fill in both the Company and Role Title fields."); + return; + } + + job.company = jobCompany.value.trim(); + job.role = jobRole.value.trim(); + job.dateApplied = jobDate.value; + job.status = selStatus.value; + job.salary = jobSalary.value ? Number(jobSalary.value) : null; + job.location = selLocation.value; + job.url = jobUrl.value.trim(); + job.notes = jobNotes.value.trim(); + + saveToStorage(); +} + +// DELETE ACTIVE JOB PROFILE +function deleteActiveJob() { + if (!activeJobId) return; + + const job = jobs.find(j => j.id === activeJobId); + if (!job) return; + + if (confirm(`Are you sure you want to delete the job profile for ${job.company} - ${job.role}?`)) { + jobs = jobs.filter(j => j.id !== activeJobId); + saveToStorage(); + deselectJob(); + } +} + +// ADD NEW BLANK JOB PROFILE +function addNewJobSpec() { + const newJob = { + id: "job-" + Date.now().toString(), + company: "New Company Spec", + role: "Software Engineer", + status: "Wishlist", + dateApplied: new Date().toISOString().split("T")[0], + salary: 100000, + location: "Remote", + url: "", + notes: "", + tasks: [ + { id: "task-" + Date.now() + "-1", text: "Polish portfolio and profile spec", completed: false }, + { id: "task-" + Date.now() + "-2", text: "Customize resume for role description", completed: false }, + { id: "task-" + Date.now() + "-3", text: "Locate referral opportunities", completed: false } + ] + }; + + jobs.unshift(newJob); + saveToStorage(); + selectJob(newJob.id); +} + +// RENDER CHECKLIST TASKS +function renderTasksChecklist(job) { + tasksChecklist.innerHTML = ""; + + if (!job.tasks || job.tasks.length === 0) { + tasksChecklist.innerHTML = `
  • No milestones created yet.
  • `; + return; + } + + job.tasks.forEach(task => { + const li = document.createElement("li"); + li.className = `task-item ${task.completed ? "completed" : ""}`; + + li.innerHTML = ` +
    + + ${escapeHtml(task.text)} +
    + + `; + + // Toggle complete + li.querySelector("input").addEventListener("change", () => { + task.completed = !task.completed; + saveToStorage(); + }); + + li.querySelector(".task-left").addEventListener("click", (e) => { + // Toggle completed when clicking details span + if (e.target.tagName !== "INPUT") { + task.completed = !task.completed; + saveToStorage(); + } + }); + + // Delete task milestone + li.querySelector(".btn-delete-task").addEventListener("click", (e) => { + e.stopPropagation(); + job.tasks = job.tasks.filter(t => t.id !== task.id); + saveToStorage(); + }); + + tasksChecklist.appendChild(li); + }); +} + +// ADD NEW CUSTOM TASK +function handleAddTaskSubmit() { + if (!activeJobId) return; + + const job = jobs.find(j => j.id === activeJobId); + if (!job) return; + + const taskText = txtNewTask.value.trim(); + if (!taskText) return; + + if (!job.tasks) { + job.tasks = []; + } + + const newTask = { + id: "task-" + Date.now() + "-" + Math.random().toString(36).substr(2, 4), + text: taskText, + completed: false + }; + + job.tasks.push(newTask); + txtNewTask.value = ""; + saveToStorage(); +} + +// RESET WORKSPACE MEMORY +function resetWorkspace() { + if (confirm("Are you absolutely sure you want to reset all jobs data? This will restore the default sandbox specs.")) { + jobs = [...SEED_JOBS]; + saveToStorage(); + deselectJob(); + } +} + +// ESCAPE HTML TO PREVENT XSS IN USER GENERATED STRINGS +function escapeHtml(str) { + if (!str) return ""; + return str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} diff --git a/Projects/Job Application Tracker Pro/style.css b/Projects/Job Application Tracker Pro/style.css new file mode 100644 index 00000000..161a69c6 --- /dev/null +++ b/Projects/Job Application Tracker Pro/style.css @@ -0,0 +1,892 @@ +/* Job Application Tracker Pro - Styling */ + +:root { + --bg-main: #090d16; + --bg-card: #111827; + --bg-card-hover: #1f2937; + --border-color: #374151; + --text-main: #f3f4f6; + --text-muted: #9ca3af; + --text-highlight: #ffffff; + + /* Status Color Palette */ + --color-wishlist: #6366f1; + --color-wishlist-glow: rgba(99, 102, 241, 0.15); + + --color-applied: #0ea5e9; + --color-applied-glow: rgba(14, 165, 233, 0.15); + + --color-interviewing: #f59e0b; + --color-interviewing-glow: rgba(245, 158, 11, 0.15); + + --color-offer: #10b981; + --color-offer-glow: rgba(16, 185, 129, 0.15); + + --color-rejected: #ef4444; + --color-rejected-glow: rgba(239, 68, 68, 0.15); + + --font-headers: 'Outfit', sans-serif; + --font-body: 'Inter', sans-serif; + + --transition-speed: 0.25s; + --border-radius-sm: 8px; + --border-radius-md: 12px; + --border-radius-lg: 16px; + --glow-shadow: 0 0 20px rgba(14, 165, 233, 0.2); +} + +/* GENERAL STYLES */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background-color: var(--bg-main); + color: var(--text-main); + font-family: var(--font-body); + min-height: 100vh; + display: flex; + flex-direction: column; + position: relative; + overflow-x: hidden; + line-height: 1.5; +} + +/* GLOW BACKGROUND */ +.glow-bg { + position: absolute; + top: -150px; + left: 50%; + transform: translateX(-50%); + width: 800px; + height: 400px; + background: radial-gradient(circle, rgba(99, 102, 241, 0.12) 0%, rgba(14, 165, 233, 0.05) 50%, rgba(9, 13, 22, 0) 100%); + filter: blur(80px); + z-index: -1; + pointer-events: none; +} + +/* SCROLLBARS */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.1); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-muted); +} + +/* HEADER styling */ +.app-header { + background: rgba(17, 24, 39, 0.7); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--border-color); + padding: 1rem 2rem; + position: sticky; + top: 0; + z-index: 100; +} + +.header-container { + max-width: 1600px; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; +} + +.logo { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.logo h1 { + font-family: var(--font-headers); + font-size: 1.75rem; + font-weight: 800; + letter-spacing: -0.5px; + background: linear-gradient(135deg, #ffffff 30%, var(--color-applied) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.logo h1 span { + font-weight: 400; + color: var(--text-muted); + -webkit-text-fill-color: var(--text-muted); +} + +.glow-icon { + font-size: 1.5rem; + color: var(--color-applied); + text-shadow: 0 0 10px rgba(14, 165, 233, 0.5); + animation: pulse 3s infinite alternate; +} + +.logo .badge { + font-size: 0.75rem; + font-weight: 600; + padding: 0.25rem 0.6rem; + background: rgba(31, 41, 55, 0.6); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-sm); + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1px; +} + +.header-stats { + display: flex; + gap: 1rem; +} + +.stat-badge { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.4rem 0.8rem; + background: rgba(17, 24, 39, 0.9); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-sm); + font-size: 0.85rem; + color: var(--text-muted); + font-weight: 500; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + transition: border-color var(--transition-speed); +} + +.stat-badge:hover { + border-color: rgba(255, 255, 255, 0.15); +} + +.stat-badge strong { + color: var(--text-highlight); + font-size: 1rem; +} + +.text-azure { color: var(--color-applied); } +.text-amber { color: var(--color-interviewing); } +.text-emerald { color: var(--color-offer); } + +/* MAIN CONTAINER LAYOUT */ +.main-container { + display: grid; + grid-template-columns: 280px 1fr 340px; + gap: 1.5rem; + max-width: 1600px; + width: 100%; + margin: 0 auto; + padding: 1.5rem 2rem; + flex: 1; +} + +/* SECTION CARDS STYLING */ +.section-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-md); + padding: 1.25rem; + margin-bottom: 1.25rem; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + transition: transform var(--transition-speed), border-color var(--transition-speed); +} + +.section-card:hover { + border-color: rgba(255, 255, 255, 0.08); +} + +.card-header-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + padding-bottom: 0.5rem; +} + +.card-header-row h3 { + font-family: var(--font-headers); + font-size: 1.1rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-highlight); +} + +/* FORM CONTROLS */ +.form-group { + margin-bottom: 1rem; +} + +.form-group label { + display: block; + font-size: 0.8rem; + font-weight: 500; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 0.4rem; + letter-spacing: 0.5px; +} + +input[type="text"], +input[type="number"], +input[type="date"], +select, +textarea { + width: 100%; + padding: 0.65rem 0.9rem; + background-color: rgba(9, 13, 22, 0.6); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-sm); + color: var(--text-main); + font-family: var(--font-body); + font-size: 0.9rem; + transition: border-color var(--transition-speed), box-shadow var(--transition-speed); +} + +input[type="text"]:focus, +input[type="number"]:focus, +input[type="date"]:focus, +select:focus, +textarea:focus { + outline: none; + border-color: var(--color-applied); + box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.2); +} + +textarea { + min-height: 100px; + resize: vertical; +} + +.form-row { + display: flex; + gap: 1rem; +} + +.flex-1 { + flex: 1; +} + +/* BUTTONS */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.65rem 1.25rem; + border-radius: var(--border-radius-sm); + font-family: var(--font-body); + font-weight: 600; + font-size: 0.9rem; + cursor: pointer; + transition: all var(--transition-speed); + border: 1px solid transparent; +} + +.btn-sm { + padding: 0.45rem 0.9rem; + font-size: 0.8rem; +} + +.btn-primary { + background: linear-gradient(135deg, var(--color-applied) 0%, #0284c7 100%); + color: white; + box-shadow: var(--glow-shadow); +} + +.btn-primary:hover { + transform: translateY(-1px); + filter: brightness(1.1); + box-shadow: 0 0 25px rgba(14, 165, 233, 0.35); +} + +.btn-secondary { + background: rgba(31, 41, 55, 0.8); + border-color: var(--border-color); + color: var(--text-main); +} + +.btn-secondary:hover { + background: rgba(55, 65, 81, 0.8); + border-color: var(--text-muted); +} + +.btn-danger { + background: linear-gradient(135deg, var(--color-rejected) 0%, #dc2626 100%); + color: white; +} + +.btn-danger:hover { + transform: translateY(-1px); + filter: brightness(1.1); + box-shadow: 0 0 15px rgba(239, 68, 68, 0.3); +} + +/* LEFT SIDEBAR (SEARCH, FILTERS, ANALYTICS) */ +.sidebar-column { + display: flex; + flex-direction: column; +} + +.search-input-wrapper { + position: relative; +} + +.search-input-wrapper i { + position: absolute; + left: 0.9rem; + top: 50%; + transform: translateY(-50%); + color: var(--text-muted); +} + +.search-input-wrapper input { + padding-left: 2.2rem; +} + +.highlights-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.75rem; +} + +.highlight-item { + background: rgba(9, 13, 22, 0.5); + border: 1px solid rgba(255, 255, 255, 0.03); + padding: 0.75rem; + border-radius: var(--border-radius-sm); + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.col-span-2 { + grid-column: span 2; +} + +.h-lbl { + font-size: 0.75rem; + color: var(--text-muted); + font-weight: 500; + text-transform: uppercase; +} + +.h-val { + font-family: var(--font-headers); + font-size: 1.25rem; + font-weight: 700; +} + +.text-cyan { + color: #22d3ee; +} + +.footer-btn { + background: transparent; + border: 1px dashed var(--border-color); + color: var(--text-muted); + padding: 0.75rem; + border-radius: var(--border-radius-sm); + cursor: pointer; + transition: all var(--transition-speed); + font-family: var(--font-body); + font-size: 0.85rem; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + width: 100%; +} + +.footer-btn:hover { + border-color: var(--color-rejected); + color: var(--color-rejected); + background: rgba(239, 68, 68, 0.05); +} + +/* CENTER KANBAN COLUMN */ +.kanban-column { + display: flex; + flex-direction: column; +} + +.kanban-actions-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.kanban-actions-row h2 { + font-family: var(--font-headers); + font-size: 1.35rem; + font-weight: 700; + color: var(--text-highlight); +} + +.kanban-board-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 0.75rem; + flex: 1; + min-height: calc(100vh - 220px); + align-items: stretch; +} + +.kanban-lane-col { + background: rgba(17, 24, 39, 0.4); + border: 1px solid rgba(255, 255, 255, 0.03); + border-radius: var(--border-radius-md); + display: flex; + flex-direction: column; + padding: 0.5rem; + transition: border-color var(--transition-speed), background var(--transition-speed); +} + +.kanban-lane-col:hover { + border-color: rgba(255, 255, 255, 0.05); + background: rgba(17, 24, 39, 0.5); +} + +.lane-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.6rem 0.75rem; + border-radius: var(--border-radius-sm); + margin-bottom: 0.75rem; + font-family: var(--font-headers); + font-weight: 700; + font-size: 0.9rem; +} + +.lane-count { + font-size: 0.75rem; + font-weight: 600; + background: rgba(0, 0, 0, 0.3); + padding: 0.1rem 0.5rem; + border-radius: 20px; + color: var(--text-highlight); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.wishlist-header { + background: var(--color-wishlist-glow); + color: #a5b4fc; + border-left: 4px solid var(--color-wishlist); +} +.applied-header { + background: var(--color-applied-glow); + color: #7dd3fc; + border-left: 4px solid var(--color-applied); +} +.interviewing-header { + background: var(--color-interviewing-glow); + color: #fde047; + border-left: 4px solid var(--color-interviewing); +} +.offer-header { + background: var(--color-offer-glow); + color: #6ee7b7; + border-left: 4px solid var(--color-offer); +} +.rejected-header { + background: var(--color-rejected-glow); + color: #fca5a5; + border-left: 4px solid var(--color-rejected); +} + +.lane-cards-wrapper { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.6rem; + min-height: 200px; + overflow-y: auto; + padding-bottom: 1rem; +} + +/* JOB CARDS IN KANBAN */ +.job-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-sm); + padding: 0.75rem; + cursor: pointer; + transition: all var(--transition-speed); + position: relative; + display: flex; + flex-direction: column; + gap: 0.4rem; + user-select: none; +} + +.job-card:hover { + transform: translateY(-2px); + border-color: var(--text-muted); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.job-card.active-card { + border-color: var(--color-applied); + background: rgba(31, 41, 55, 0.5); + box-shadow: 0 0 10px rgba(14, 165, 233, 0.15); +} + +.job-card .card-company { + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; + color: var(--text-muted); + letter-spacing: 0.5px; +} + +.job-card .card-role { + font-family: var(--font-headers); + font-size: 0.9rem; + font-weight: 600; + color: var(--text-highlight); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.job-card .card-meta { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.75rem; + margin-top: 0.25rem; +} + +.job-card .card-salary { + font-weight: 600; + color: #e5e7eb; +} + +.job-card .card-loc-badge { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.08); + padding: 0.1rem 0.4rem; + border-radius: 4px; + font-size: 0.7rem; + color: var(--text-muted); +} + +/* Card Progress bar */ +.card-progress-wrapper { + margin-top: 0.35rem; +} +.card-progress-text { + font-size: 0.7rem; + color: var(--text-muted); + display: flex; + justify-content: space-between; + margin-bottom: 0.15rem; +} +.card-progress-bar { + height: 3px; + background: rgba(255, 255, 255, 0.08); + border-radius: 2px; + overflow: hidden; +} +.card-progress-fill { + height: 100%; + width: 0%; + background: var(--color-applied); + border-radius: 2px; + transition: width var(--transition-speed); +} + +/* Card Quick Movement Controls */ +.card-quick-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 0.5rem; + padding-top: 0.5rem; + border-top: 1px solid rgba(255, 255, 255, 0.03); +} + +.card-quick-btn { + background: transparent; + border: none; + color: var(--text-muted); + cursor: pointer; + font-size: 0.8rem; + padding: 0.2rem 0.4rem; + border-radius: 4px; + transition: all var(--transition-speed); +} + +.card-quick-btn:hover { + color: var(--text-highlight); + background: rgba(255, 255, 255, 0.08); +} + +.card-quick-btn.disabled { + opacity: 0.2; + cursor: not-allowed; + pointer-events: none; +} + +/* RIGHT INSPECTOR COLUMN */ +.inspector-column { + display: flex; + flex-direction: column; +} + +.inspector-welcome-card { + background: var(--bg-card); + border: 1px dashed var(--border-color); + border-radius: var(--border-radius-md); + padding: 2.5rem 1.5rem; + text-align: center; + color: var(--text-muted); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; + min-height: 400px; +} + +.pulse-icon { + font-size: 2.5rem; + color: rgba(255, 255, 255, 0.15); + animation: float 4s infinite ease-in-out; +} + +.inspector-welcome-card h4 { + font-family: var(--font-headers); + color: var(--text-highlight); + font-size: 1.15rem; + font-weight: 600; +} + +.inspector-welcome-card p { + font-size: 0.85rem; + line-height: 1.4; +} + +.inspector-workspace { + animation: fadeIn var(--transition-speed) ease; +} + +.hidden { + display: none !important; +} + +/* Tasks Checklist inside Inspector */ +.tasks-checklist-section { + margin: 1.25rem 0; + padding: 1rem; + background: rgba(9, 13, 22, 0.4); + border: 1px solid rgba(255, 255, 255, 0.03); + border-radius: var(--border-radius-sm); +} + +.tasks-checklist-section h5 { + font-family: var(--font-headers); + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 0.75rem; + letter-spacing: 0.5px; +} + +.tasks-ul { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-bottom: 0.75rem; + max-height: 160px; + overflow-y: auto; +} + +.task-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + padding: 0.4rem 0.5rem; + border-radius: 4px; + background: rgba(17, 24, 39, 0.6); + border: 1px solid rgba(255, 255, 255, 0.02); + transition: all var(--transition-speed); +} + +.task-item:hover { + background: rgba(17, 24, 39, 0.9); +} + +.task-left { + display: flex; + align-items: center; + gap: 0.5rem; + flex: 1; + cursor: pointer; +} + +.task-left input[type="checkbox"] { + accent-color: var(--color-applied); + cursor: pointer; + width: 15px; + height: 15px; +} + +.task-text { + font-size: 0.85rem; + transition: color var(--transition-speed); + word-break: break-word; +} + +.task-item.completed .task-text { + text-decoration: line-through; + color: var(--text-muted); +} + +.btn-delete-task { + background: transparent; + border: none; + color: var(--text-muted); + cursor: pointer; + font-size: 0.8rem; + padding: 0.2rem; + transition: color var(--transition-speed); +} + +.btn-delete-task:hover { + color: var(--color-rejected); +} + +.add-task-row { + display: flex; + gap: 0.5rem; +} + +.add-task-row input { + padding: 0.45rem 0.75rem; + font-size: 0.8rem; +} + +.add-task-row button { + padding: 0.45rem; + width: 35px; + height: 35px; + display: flex; + align-items: center; + justify-content: center; +} + +/* Inspector Action buttons wrapper */ +.inspector-actions { + display: flex; + justify-content: space-between; + gap: 1rem; + margin-top: 1.5rem; + padding-top: 1rem; + border-top: 1px solid rgba(255, 255, 255, 0.05); +} + +.inspector-actions .btn { + flex: 1; +} + +/* FOOTER */ +.app-footer { + border-top: 1px solid var(--border-color); + padding: 1.5rem 2rem; + text-align: center; + background-color: rgba(9, 13, 22, 0.8); + font-size: 0.85rem; + color: var(--text-muted); + margin-top: auto; +} + +.footer-container a { + color: var(--color-applied); + text-decoration: none; + transition: color var(--transition-speed); +} + +.footer-container a:hover { + color: #38bdf8; + text-decoration: underline; +} + +/* ANIMATIONS */ +@keyframes pulse { + 0% { text-shadow: 0 0 8px rgba(14, 165, 233, 0.3); } + 100% { text-shadow: 0 0 16px rgba(14, 165, 233, 0.7); } +} + +@keyframes float { + 0% { transform: translateY(0px); } + 50% { transform: translateY(-8px); } + 100% { transform: translateY(0px); } +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(5px); } + to { opacity: 1; transform: translateY(0); } +} + +/* RESPONSIVE LAYOUT BREAKPOINTS */ +@media (max-width: 1200px) { + .main-container { + grid-template-columns: 260px 1fr; + } + .inspector-column { + grid-column: span 2; + } + .inspector-welcome-card { + min-height: 200px; + padding: 1.5rem; + } +} + +@media (max-width: 900px) { + .main-container { + grid-template-columns: 1fr; + padding: 1rem; + } + .sidebar-column, .kanban-column, .inspector-column { + grid-column: span 1; + } + .kanban-board-grid { + grid-template-columns: 1fr; + gap: 1rem; + min-height: auto; + } + .kanban-lane-col { + min-height: 180px; + } + .header-container { + flex-direction: column; + align-items: flex-start; + } + .header-stats { + width: 100%; + justify-content: space-between; + } +} diff --git a/Projects/Job Application Tracker Pro/thumbnail.svg b/Projects/Job Application Tracker Pro/thumbnail.svg new file mode 100644 index 00000000..48d88abb --- /dev/null +++ b/Projects/Job Application Tracker Pro/thumbnail.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + JobTrackPro + + + KANBAN + + + + + + + + Offers: 1 + + + + + Interviews: 1 + + + + + + + + + + Wishlist + + 1 + + + + + VERCEL + DevRel Eng + + $130k + + + + + + + + + + + + + Applied + + 1 + + + + + NETFLIX + UI Eng + + $190k + + + + + + + + + + + + + Interview + + 1 + + + + + STRIPE + JS Architect + + $185k + + + + + + + + + + + + + Offer + + 1 + + + + + GOOGLE + Frontend Eng + + $145k + + + + + + + + + + + + + Rejected + + 0 + + + Empty + +