From 6b85f2dd15f950fe2432fa29531d5f99db270d34 Mon Sep 17 00:00:00 2001 From: Ashutosh Singh Date: Sat, 23 Aug 2025 12:54:33 +0530 Subject: [PATCH] feat: Overhaul UI and add page preview features --- index.html | 291 +++++++++---------- script.js | 826 +++++++++++++++++++++++++---------------------------- style.css | 796 +++++++++++++++++++++++++-------------------------- 3 files changed, 914 insertions(+), 999 deletions(-) diff --git a/index.html b/index.html index 8bf9cf2..e247621 100644 --- a/index.html +++ b/index.html @@ -9,13 +9,7 @@ - - - @@ -24,11 +18,29 @@
-

Resume Details

- +
+ + +
+ +
+ + +
+ +
+ + +
@@ -56,24 +68,20 @@

Resume Details

- - - - - - - - - -
-
+ + + + + + + +
+
-
- + +
@@ -86,36 +94,21 @@

Resume Details

-
- + +
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
+
+
+
+
+
-
- + +
@@ -128,9 +121,9 @@

Resume Details

-
- + +
@@ -142,9 +135,9 @@

Resume Details

-
- + +
@@ -154,123 +147,117 @@

Resume Details

- + +
+
+ + +
+ +
+
- +
+ +
+ + +
- - -
-
-

John Doe

-
- john@example.com - +91-9876543210 - LinkedIn - GitHub - +
+
+
+ +

John Doe

+
+ john@example.com + +91-9876543210 + LinkedIn + GitHub +
-
- -
-

Professional Summary

-

Passionate developer with 3 years of experience...

-
-
-

Education

-
    -
  • IIT Bombay, B.Tech in Computer Science
    GPA: 9.2 | Mumbai, India | 2018-2022
  • -
-
+
+

Professional Summary

+

Passionate developer with 3 years of experience...

+
-
-

Skills Summary

-
-
Languages: JavaScript, Python
-
Frameworks: React, Django
-
Tools: Git, VS Code
-
Platforms: AWS, Firebase
-
Soft Skills: Leadership, Communication
+
+

Education

+
    +
  • IIT Bombay, B.Tech in Computer Science
    GPA: 9.2 | Mumbai, India | 2018-2022
  • +
-
-
-

Work Experience

-
    -
  • - Software Engineer, Google | 2022-Present -
      -
    • Built: Scalable backend services using Node.js
    • -
    • Optimized: API response times by 40%
    • -
    -
  • -
-
+
+

Skills Summary

+
+
Languages: JavaScript, Python
+
Frameworks: React, Django
+
Tools: Git, VS Code
+
Platforms: AWS, Firebase
+
Soft Skills: Leadership, Communication
+
+
-
-

Certificates

-
    -
  • - AWS Certified Developer (AWS) | 2023 -
      -
    • Demonstrated proficiency in cloud architecture
    • -
    -
  • -
-
+
+

Work Experience

+
    +
  • + Software Engineer, Google | 2022-Present +
      +
    • Built: Scalable backend services using Node.js
    • +
    • Optimized: API response times by 40%
    • +
    +
  • +
+
-
-

Projects

-
    -
  • - Resume Generator - -
      -
    • Built: Full-stack resume builder using React
    • -
    • Integrated: PDF export feature
    • -
    -
  • -
-
- +
+

Certificates

+
    +
  • + AWS Certified Developer (AWS) | 2023 +
      +
    • Demonstrated proficiency in cloud architecture
    • +
    +
  • +
+
- -
- -
-
- - +
+

Projects

+
    +
  • + Resume Generator + +
      +
    • Built: Full-stack resume builder using React
    • +
    • Integrated: PDF export feature
    • +
    +
  • +
+
+
+
+
+
+ +
-
- - -
-
- - - +

© 2025. All rights reserved. Built by Rajdeep Paul

+ - + \ No newline at end of file diff --git a/script.js b/script.js index e560e4a..1818a75 100644 --- a/script.js +++ b/script.js @@ -1,4 +1,26 @@ -// Live Preview Bindings +// --- STATE MANAGEMENT (for Undo/Redo) --- +let undoStack = []; +let redoStack = []; + +document.addEventListener("DOMContentLoaded", () => { + // Bindings for all form fields + bindInputToPreview("input-name", "preview-name"); + bindInputToPreview("input-email", "preview-email"); + + + setupCharacterCounter("input-about"); + initializeDynamicSections(); + setupValidationListeners(); + initializeFeatures(); + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) entry.target.classList.add("show"); + }); + }, { threshold: 0.1 }); + document.querySelectorAll(".fade-in").forEach(el => observer.observe(el)); +}); +// --- LIVE PREVIEW BINDINGS & HELPERS --- function bindInputToPreview(inputId, previewId, isLink = false) { const input = document.getElementById(inputId); const preview = document.getElementById(previewId); @@ -12,19 +34,14 @@ function bindInputToPreview(inputId, previewId, isLink = false) { }); } -bindInputToPreview("input-name", "preview-name"); -bindInputToPreview("input-email", "preview-email"); -bindInputToPreview("input-phone", "preview-phone"); -bindInputToPreview("input-linkedin", "preview-linkedin", true); -bindInputToPreview("input-github", "preview-github", true); -bindInputToPreview("input-about", "preview-about"); -bindInputToPreview("input-languages", "preview-languages"); -bindInputToPreview("input-frameworks", "preview-frameworks"); -bindInputToPreview("input-tools", "preview-tools"); -bindInputToPreview("input-platforms", "preview-platforms"); -bindInputToPreview("input-soft-skills", "preview-soft-skills"); - -// handles live character counting +function bindTitle(inputId, previewId) { + const input = document.getElementById(inputId); + const preview = document.getElementById(previewId); + input.addEventListener('input', () => { + preview.textContent = input.value; + }); +} + function setupCharacterCounter(inputId) { const inputElement = document.getElementById(inputId); const charCountElement = document.getElementById(`charCount-${inputId}`); @@ -43,9 +60,51 @@ function setupCharacterCounter(inputId) { inputElement.addEventListener('input', updateCount); } } -setupCharacterCounter("input-about"); -// Dynamic Section Helpers +// --- INITIALIZE BINDINGS ON LOAD --- +document.addEventListener("DOMContentLoaded", () => { + // Basic Info + bindInputToPreview("input-name", "preview-name"); + bindInputToPreview("input-email", "preview-email"); + bindInputToPreview("input-phone", "preview-phone"); + bindInputToPreview("input-linkedin", "preview-linkedin", true); + bindInputToPreview("input-github", "preview-github", true); + bindInputToPreview("input-about", "preview-about"); + // Skills + bindInputToPreview("input-languages", "preview-languages"); + bindInputToPreview("input-frameworks", "preview-frameworks"); + bindInputToPreview("input-tools", "preview-tools"); + bindInputToPreview("input-platforms", "preview-platforms"); + bindInputToPreview("input-soft-skills", "preview-soft-skills"); + // Custom Section Titles + bindTitle("title-about", "preview-title-about"); + bindTitle("title-education", "preview-title-education"); + bindTitle("title-skills", "preview-title-skills"); + bindTitle("title-experience", "preview-title-experience"); + bindTitle("title-certificates", "preview-title-certificates"); + bindTitle("title-projects", "preview-title-projects"); + + setupCharacterCounter("input-about"); + initializeDynamicSections(); + setupValidationListeners(); + initializeFeatures(); + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add("show"); + } + }); + }, { + threshold: 0.1 + }); + + document.querySelectorAll(".fade-in").forEach(el => { + observer.observe(el); + }); +}); + +// --- DYNAMIC SECTION MANAGEMENT --- function createRemoveBtn() { const btn = document.createElement("button"); btn.className = "remove-btn"; @@ -62,8 +121,8 @@ function handleAddSection(buttonId, containerId, inputClass, previewContainerId, const original = container.querySelector(`.${inputClass}`); const clone = original.cloneNode(true); clone.querySelectorAll("input, textarea").forEach(input => input.value = ""); + const removeBtn = createRemoveBtn(); - removeBtn.addEventListener("click", () => { clone.remove(); updatePreviewList(container, previewContainer, inputClass, generateHTML); @@ -88,10 +147,9 @@ function updatePreviewList(container, previewContainer, inputClass, generateHTML }); } -// Education Section -handleAddSection( - "add-education", "education-inputs", "education-inputs", "preview-education", - (entry) => { +function initializeDynamicSections() { + // Education Section + handleAddSection("add-education", "education-inputs", "education-inputs", "preview-education", (entry) => { const inst = entry.querySelector(".education-institution").value; const deg = entry.querySelector(".education-degree").value; const gpa = entry.querySelector(".education-gpa").value; @@ -101,503 +159,399 @@ handleAddSection( return `
  • ${inst}, ${deg}
    GPA: ${gpa} | ${loc} | ${dates}
  • `; } return ""; - } -); + }); -// Experience Section -handleAddSection( - "add-experience", "experience-inputs", "experience-inputs", "preview-experience", - (entry) => { + // Experience Section + handleAddSection("add-experience", "experience-inputs", "experience-inputs", "preview-experience", (entry) => { const role = entry.querySelector(".experience-role").value; const comp = entry.querySelector(".experience-company").value; const link = entry.querySelector(".experience-link").value; const dates = entry.querySelector(".experience-dates").value; const desc = entry.querySelector(".experience-desc").value.trim().split("\n").filter(l => l).map(l => `
  • ${l}
  • `).join(""); - if (role || comp || dates || desc) { - return ` -
  • - ${role}, ${comp} | ${dates} -
      ${desc}
    -
  • - `; + return `
  • ${role}, ${comp} | ${dates}
      ${desc}
  • `; } return ""; - } -); + }); -// Certificates Section -handleAddSection( - "add-certificate", "certifications-inputs", "certifications-inputs", "preview-cert", - (entry) => { + // Certificates Section + handleAddSection("add-certificate", "certifications-inputs", "certifications-inputs", "preview-cert", (entry) => { const title = entry.querySelector(".certificate-title").value; const issuer = entry.querySelector(".certificate-issuer").value; const date = entry.querySelector(".certificate-date").value; const desc = entry.querySelector(".certificate-desc").value.trim().split("\n").filter(l => l).map(l => `
  • ${l}
  • `).join(""); - if (title || issuer || date || desc) { - return ` -
  • - ${title} (${issuer}) | ${date} -
      ${desc}
    -
  • - `; + return `
  • ${title} (${issuer}) | ${date}
      ${desc}
  • `; } return ""; - } -); + }); -// Projects Section -handleAddSection( - "add-project", "projects-inputs", "project-input", "preview-projects", - (entry) => { + // Projects Section + handleAddSection("add-project", "projects-inputs", "project-input", "preview-projects", (entry) => { const title = entry.querySelector(".project-title").value; const link = entry.querySelector(".project-link").value; const desc = entry.querySelector(".project-desc").value.trim().split("\n").filter(l => l).map(l => `
  • ${l}
  • `).join(""); - if (title || link || desc) { - return ` -
  • - ${title} - -
      ${desc}
    -
  • - `; + return `
  • ${title}
      ${desc}
  • `; } return ""; - } -); - - -// --- GLOBAL FUNCTIONS FOR VALIDATION --- -function clearErrors() { - document.querySelectorAll('.error-message').forEach(span => { - span.textContent = ''; }); } + +// --- FORM VALIDATION --- function clearErrors() { - const errorFields = document.querySelectorAll(".error-message"); - errorFields.forEach(field => field.textContent = ""); + document.querySelectorAll('.error-message').forEach(span => span.textContent = ''); +} + +function validateField(input, errorEl, validationFn, message) { + if (!validationFn(input.value.trim())) { + errorEl.textContent = message; + return false; + } + errorEl.textContent = ''; + return true; } function validateForm() { clearErrors(); - let isValid = true; - let incompleteFields = []; - - const nameInput = document.getElementById('input-name'); - if (nameInput.value.trim().length < 3) { - document.getElementById('error-name').textContent = 'Full Name must be at least 3 characters.'; - isValid = false; - incompleteFields.push("Full Name"); - } - - const emailInput = document.getElementById('input-email'); - const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailPattern.test(emailInput.value.trim())) { - document.getElementById('error-email').textContent = 'Please enter a valid email address.'; - isValid = false; - incompleteFields.push("Email"); + + isValid &= validateField(document.getElementById('input-name'), document.getElementById('error-name'), val => val.length >= 3, 'Full Name must be at least 3 characters.'); + isValid &= validateField(document.getElementById('input-email'), document.getElementById('error-email'), val => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val), 'Please enter a valid email address.'); + isValid &= validateField(document.getElementById('input-phone'), document.getElementById('error-phone'), val => /^\+?[0-9\s-]{10,}$/.test(val), 'Please enter a valid phone number.'); + isValid &= validateField(document.getElementById('input-linkedin'), document.getElementById('error-linkedin'), val => !val || /^(https?:\/\/(www\.)?linkedin\.com\/in\/[a-zA-Z0-9_-]+\/?)$/i.test(val), 'Please enter a valid LinkedIn URL.'); + isValid &= validateField(document.getElementById('input-github'), document.getElementById('error-github'), val => !val || /^(https?:\/\/(www\.)?github\.com\/[a-zA-Z0-9_-]+\/?)$/i.test(val), 'Please enter a valid GitHub URL.'); + isValid &= validateField(document.getElementById('input-about'), document.getElementById('error-about'), val => val.length >= 50, 'Professional Summary should be at least 50 characters.'); + + if (!isValid) { + alert("Please correct the highlighted errors in the form before downloading your resume."); } - - const phoneInput = document.getElementById('input-phone'); - const phonePattern = /^\+?[0-9\s-]{10,}$/; - if (!phonePattern.test(phoneInput.value.trim())) { - document.getElementById('error-phone').textContent = 'Please enter a valid phone number.'; - isValid = false; - incompleteFields.push("Phone"); - } - - const linkedinInput = document.getElementById('input-linkedin'); - const linkedinPattern = /^(https?:\/\/(www\.)?linkedin\.com\/in\/[a-zA-Z0-9_-]+\/?)$/i; - if (linkedinInput.value.trim() && !linkedinPattern.test(linkedinInput.value.trim())) { - document.getElementById('error-linkedin').textContent = 'Please enter a valid LinkedIn URL.'; - isValid = false; - incompleteFields.push("LinkedIn"); - } - - const githubInput = document.getElementById('input-github'); - const githubPattern = /^(https?:\/\/(www\.)?github\.com\/[a-zA-Z0-9_-]+\/?)$/i; - if (githubInput.value.trim() && !githubPattern.test(githubInput.value.trim())) { - document.getElementById('error-github').textContent = 'Please enter a valid GitHub URL.'; - isValid = false; - incompleteFields.push("GitHub"); - } - - const aboutInput = document.getElementById('input-about'); - if (aboutInput.value.trim().length < 50) { - document.getElementById('error-about').textContent = 'Professional Summary should be at least 50 characters.'; - isValid = false; - incompleteFields.push("Professional Summary"); - } - - // If all good, return true - if (isValid) return true; - - // Else ask user if they want to proceed anyway - return confirm("Please correct the highlighted errors in the form before downloading your resume."); + return !!isValid; } +function setupValidationListeners() { + document.getElementById('input-name').addEventListener('blur', (e) => validateField(e.target, document.getElementById('error-name'), val => val.length >= 3, 'Full Name must be at least 3 characters.')); + document.getElementById('input-email').addEventListener('blur', (e) => validateField(e.target, document.getElementById('error-email'), val => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val), 'Please enter a valid email address.')); + document.getElementById('input-phone').addEventListener('blur', (e) => validateField(e.target, document.getElementById('error-phone'), val => /^\+?[0-9\s-]{10,}$/.test(val), 'Please enter a valid phone number.')); + document.getElementById('input-linkedin').addEventListener('blur', (e) => validateField(e.target, document.getElementById('error-linkedin'), val => !val || /^(https?:\/\/(www\.)?linkedin\.com\/in\/[a-zA-Z0-9_-]+\/?)$/i.test(val), 'Please enter a valid LinkedIn URL.')); + document.getElementById('input-github').addEventListener('blur', (e) => validateField(e.target, document.getElementById('error-github'), val => !val || /^(https?:\/\/(www\.)?github\.com\/[a-zA-Z0-9_-]+\/?)$/i.test(val), 'Please enter a valid GitHub URL.')); + document.getElementById('input-about').addEventListener('blur', (e) => validateField(e.target, document.getElementById('error-about'), val => val.length >= 50, 'Professional Summary should be at least 50 characters.')); +} +// --- FEATURE INITIALIZATION --- +function initializeFeatures() { + // PDF Download + document.getElementById("downloadBtn").addEventListener("click", () => { + if (!validateForm()) return; + + // Hide non-printable elements before generating PDF + const nonPrintable = document.querySelectorAll('.no-print'); + nonPrintable.forEach(el => el.style.display = 'none'); + + const content = document.querySelector('#resumeContent'); + const options = { + margin: 0.5, + filename: 'resume.pdf', + image: { type: 'jpeg', quality: 0.98 }, + html2canvas: { scale: 2 }, + jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }, + pagebreak: { mode: ['css', 'legacy'] } // Better page break handling + }; -document.getElementById("downloadBtn").addEventListener("click", () => { - if (!validateForm()) return; - -// --- PDF Download Button --- - - const content = document.querySelector('#resume-sections'); - const options = { - margin: 0.5, - filename: 'resume.pdf', - image: { type: 'jpeg', quality: 0.98 }, - html2canvas: { scale: 2 }, - jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' } - }; - html2pdf().from(content).set(options).save(); - -}) - -// --- DOCX Download Button --- -document.getElementById("downloadDocxBtn").addEventListener("click", () => { - if (!validateForm()) return; - - const resumeContent = document.querySelector('#resume-sections').cloneNode(true); - const preHtml = ` - - - `; - const postHtml = ""; - const html = preHtml + resumeContent.innerHTML + postHtml; - - const blob = new Blob(['\ufeff', html], { - type: 'application/msword' - }); - - const url = 'data:application/vnd.ms-word;charset=utf-8,' + encodeURIComponent(html); - const link = document.createElement("a"); - link.href = url; - link.download = 'resume.doc'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); -}); - - -// --- DOMContentLoaded Listener for fade-in animations --- -document.addEventListener("DOMContentLoaded", () => { - const observer = new IntersectionObserver((entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - entry.target.classList.add("show"); - observer.unobserve(entry.target); // Animate only once - } + html2pdf().from(content).set(options).save().then(() => { + // Restore non-printable elements after PDF is saved + nonPrintable.forEach(el => el.style.display = ''); }); - }, { - threshold: 0.1 }); - document.querySelectorAll(".fade-in").forEach(el => { - observer.observe(el); + // DOCX Download + document.getElementById("downloadDocxBtn").addEventListener("click", () => { + if (!validateForm()) return; + // Clone the content to avoid modifying the live preview + const resumeContent = document.querySelector('#resume-sections').cloneNode(true); + // Remove non-printable elements from the clone + resumeContent.querySelectorAll('.no-print').forEach(el => el.remove()); + + const content = resumeContent.innerHTML; + const html = `${content}`; + const blob = new Blob(['\ufeff', html], { type: 'application/msword' }); + saveAs(blob, 'resume.docx'); }); -}); -window.history.scrollRestoration = "manual"; -window.scrollTo(0, 0); -if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) { - // Run observer code if motion is not reduced -} - -// --- Theme Toggle functionality --- -const toggle = document.getElementById('themeToggle'); -const icon = toggle.querySelector('i'); - -// Check localStorage on load -window.addEventListener('DOMContentLoaded', () => { - const savedTheme = localStorage.getItem('theme'); - - if (savedTheme === 'dark') { + + // Theme Toggle + const themeToggle = document.getElementById('themeToggle'); + const icon = themeToggle.querySelector('i'); + if (localStorage.getItem('theme') === 'dark') { document.body.classList.add('dark'); - icon.classList.remove('fa-sun'); // Ensure sun is not there - icon.classList.add('fa-moon'); // Add moon for dark mode - icon.style.color='white'; - } else { - document.body.classList.remove('dark'); // Ensure light mode - icon.classList.remove('fa-moon'); // Ensure moon is not there - icon.classList.add('fa-sun'); // Add sun for light mode - icon.style.color='#FFB300'; - } -}); - -toggle.addEventListener('click', () => { - const isDark = document.body.classList.toggle('dark'); - - if (isDark) { - icon.classList.remove('fa-sun'); // Remove sun - icon.classList.add('fa-moon'); // Add moon for dark mode - icon.style.color = 'white'; - localStorage.setItem('theme', 'dark'); - } else { - icon.classList.remove('fa-moon'); // Remove moon - icon.classList.add('fa-sun'); // Add sun for light mode - icon.style.color = '#FFB300'; - localStorage.setItem('theme', 'light'); + icon.classList.replace('fa-sun', 'fa-moon'); } -}); -// back to top section -const backToTopButton = document.getElementById('backToTop'); - - // Show/hide button based on scroll position - window.addEventListener('scroll', function() { - if (window.pageYOffset > 300) { - backToTopButton.classList.add('show'); - } else { - backToTopButton.classList.remove('show'); - } - }); + themeToggle.addEventListener('click', () => { + const isDark = document.body.classList.toggle('dark'); + icon.classList.toggle('fa-sun', !isDark); + icon.classList.toggle('fa-moon', isDark); + localStorage.setItem('theme', isDark ? 'dark' : 'light'); + }); - // Smooth scroll to top when button is clicked - backToTopButton.addEventListener('click', function() { - window.scrollTo({ - top: 0, - behavior: 'smooth' - }); - }); + // Back to Top Button + const backToTopButton = document.getElementById('backToTop'); + window.addEventListener('scroll', () => { + backToTopButton.classList.toggle('show', window.pageYOffset > 300); + }); + backToTopButton.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' })); - // Optional: Add keyboard support (Enter or Space key) - backToTopButton.addEventListener('keydown', function(e) { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - window.scrollTo({ - top: 0, - behavior: 'smooth' - }); - } - }); -// --- Draggable Resume Section Reordering --- -document.addEventListener("DOMContentLoaded", () => { + // Draggable Sections & Undo/Redo const container = document.getElementById("resume-sections"); let draggedItem = null; - - container.addEventListener("dragstart", function (e) { + container.addEventListener("dragstart", e => { if (e.target.classList.contains("section")) { draggedItem = e.target; - e.target.classList.add("dragging"); + setTimeout(() => e.target.classList.add("dragging"), 0); } }); - - container.addEventListener("dragend", function (e) { - if (draggedItem) { + container.addEventListener("dragend", e => { + if(draggedItem) { draggedItem.classList.remove("dragging"); - saveCurrentOrder(); // Save new order + saveOrder(); draggedItem = null; } }); - - container.addEventListener("dragover", function (e) { + container.addEventListener("dragover", e => { e.preventDefault(); const afterElement = getDragAfterElement(container, e.clientY); - if (draggedItem && afterElement == null) { - container.appendChild(draggedItem); - } else if (draggedItem && afterElement) { - container.insertBefore(draggedItem, afterElement); + if (draggedItem) { + if (afterElement == null) container.appendChild(draggedItem); + else container.insertBefore(draggedItem, afterElement); } }); - - function getDragAfterElement(container, y) { - const draggableElements = [...container.querySelectorAll(".section:not(.dragging)")]; - return draggableElements.reduce((closest, child) => { - const box = child.getBoundingClientRect(); - const offset = y - box.top - box.height / 2; - if (offset < 0 && offset > closest.offset) { - return { offset: offset, element: child }; - } else { - return closest; - } - }, { offset: Number.NEGATIVE_INFINITY }).element; + const pageLimitSelector = document.getElementById('page-limit-selector'); + const resumePage = document.getElementById('resume-page'); + + function checkPageOverflow() { + if (pageLimitSelector.value === 'one-page') { + const isOverflowing = resumePage.scrollHeight > resumePage.clientHeight; + resumePage.classList.toggle('overflowing', isOverflowing); + } else { + resumePage.classList.remove('overflowing'); + } } -}); - + + pageLimitSelector.addEventListener('change', () => { + resumePage.classList.toggle('one-page-limit', pageLimitSelector.value === 'one-page'); + checkPageOverflow(); + }); -function bindInputToPreview(inputId, previewId, isLink = false) { - const input = document.getElementById(inputId); - const preview = document.getElementById(previewId); + // Re-check overflow whenever the content might change + const formSection = document.querySelector('.form-section'); + formSection.addEventListener('input', checkPageOverflow); + formSection.addEventListener('click', (e) => { + if (e.target.classList.contains('add-btn') || e.target.classList.contains('remove-btn')) { + // Use a small timeout to allow the DOM to update before checking height + setTimeout(checkPageOverflow, 100); + } + }); + // Photo Uploader + document.getElementById('input-photo').addEventListener('change', function(event) { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = function(e) { + const photoPreview = document.getElementById('preview-photo'); + photoPreview.src = e.target.result; + photoPreview.classList.remove('hidden'); + } + reader.readAsDataURL(file); + } + }); - input.addEventListener("input", () => { - const value = input.value.trim(); - - if (previewId === 'preview-email') { - preview.textContent = value ? `Email: ${value}` : ''; - } else if (previewId === 'preview-phone') { - preview.textContent = value ? `Phone: ${value}` : ''; - } else if (isLink && value) { - let label = 'Link'; - if (previewId === 'preview-linkedin') label = 'LinkedIn'; - else if (previewId === 'preview-github') label = 'GitHub'; - - preview.innerHTML = `${label}`; + // Template Selector + document.getElementById('template-selector').addEventListener('change', function(event) { + const previewSection = document.getElementById('resumeContent'); + previewSection.classList.remove('template-classic', 'template-modern'); + if (event.target.value === 'modern') { + previewSection.classList.add('template-modern'); } else { - preview.textContent = value; + previewSection.classList.add('template-classic'); } }); -} -const scrollUpBtn = document.querySelector(".scroll-up-btn"); + // Save/Load Data + document.getElementById('saveDataBtn').addEventListener('click', saveData); + document.getElementById('loadDataInput').addEventListener('change', loadData); +} -scrollUpBtn.addEventListener("click", () => { - window.scrollTo({ - top: 0, - behavior: "smooth" - }); -}); +function getDragAfterElement(container, y) { + const draggableElements = [...container.querySelectorAll(".section:not(.dragging)")]; + return draggableElements.reduce((closest, child) => { + const box = child.getBoundingClientRect(); + const offset = y - box.top - box.height / 2; + if (offset < 0 && offset > closest.offset) { + return { offset: offset, element: child }; + } else { + return closest; + } + }, { offset: Number.NEGATIVE_INFINITY }).element; +} -/* // Undo Button - document.getElementById("undoBtn").addEventListener("click", () => { +// --- UNDO/REDO LOGIC --- +function saveOrder() { + const currentOrder = [...document.querySelectorAll('#resume-sections .section')].map(el => el.dataset.id); + undoStack.push(currentOrder); + redoStack = []; // Clear redo stack on new action +} +document.getElementById('undoBtn').addEventListener('click', () => { if (undoStack.length > 1) { - const current = undoStack.pop(); - redoStack.push(current); - restoreOrder(undoStack[undoStack.length - 1]); + redoStack.push(undoStack.pop()); + const lastOrder = undoStack[undoStack.length - 1]; + restoreOrder(lastOrder); } - }); - - // Redo Button - document.getElementById("redoBtn").addEventListener("click", () => { +}); +document.getElementById('redoBtn').addEventListener('click', () => { if (redoStack.length > 0) { - const next = redoStack.pop(); - undoStack.push(next); - restoreOrder(next); - } - }); */ - -// Storing Inputs to SessionStorage -function saveToSessionStorage(id) { - const input = document.getElementById(id) - if (input) { - input.addEventListener("input", () => { - sessionStorage.setItem(id, input.value) - }) + const nextOrder = redoStack.pop(); + undoStack.push(nextOrder); + restoreOrder(nextOrder); } +}); +function restoreOrder(order) { + const container = document.getElementById('resume-sections'); + const sections = new Map([...container.querySelectorAll('.section')].map(el => [el.dataset.id, el])); + order.forEach(id => container.appendChild(sections.get(id))); } -function loadFromSessionStorage(id) { - const input = document.getElementById(id) - const storedInput = sessionStorage.getItem(id) - - if (input && (storedInput != null)) { - input.value = storedInput - input.dispatchEvent(new Event("input")) +// --- SAVE/LOAD DATA --- +function saveData() { + const data = { + inputs: {}, + textareas: {}, + dynamic: {} + }; + // Save simple inputs and textareas + document.querySelectorAll('.form-section input[type="text"], .form-section input[type="email"], .form-section input[type="tel"], .form-section input[type="url"], .form-section select').forEach(el => data.inputs[el.id] = el.value); + document.querySelectorAll('.form-section textarea').forEach(el => data.textareas[el.id] = el.value); + + // Save dynamic sections + const dynamicSections = { + 'education-inputs': ['institution', 'degree', 'gpa', 'location', 'dates'], + 'experience-inputs': ['role', 'company', 'link', 'dates', 'desc'], + 'certifications-inputs': ['title', 'issuer', 'date', 'desc'], + 'project-input': ['title', 'link', 'desc'] + }; + for (const className in dynamicSections) { + data.dynamic[className] = []; + document.querySelectorAll(`#${className.replace('-input', 's')} > .${className}`).forEach(section => { + const entry = {}; + dynamicSections[className].forEach(field => { + entry[field] = section.querySelector(`.${className.split('-')[0]}-${field}`).value; + }); + data.dynamic[className].push(entry); + }); } -} -document.addEventListener("DOMContentLoaded", () => { - const inputIds = [ - "input-name", - "input-email", - "input-phone", - "input-linkedin", - "input-github", - "input-about", - "input-languages", - "input-frameworks", - "input-tools", - "input-platforms", - "input-soft-skills", - "education-institution", - "education-degree", - "education-gpa", - "education-location", - "education-dates", - "experience-role", - "experience-company", - "experience-link", - "experience-dates", - "certificate-title", - "certificate-issuer", - "certificate-date", - "project-title", - "project-link", - "experience-desc", - "certificate-desc", - "project-desc", - ]; - - const textareaClass = [ - "", - ] - - inputIds.forEach(id => { - saveToSessionStorage(id); - loadFromSessionStorage(id); - }); -}); - -function clearAndReload() { - sessionStorage.clear(); - window.location.reload(); + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); + saveAs(blob, 'resume-data.json'); } -async function improveText(textareaId) { - const textarea = document.getElementById(textareaId); - const suggestionBox = document.getElementById(`suggestion-${textareaId}`); - let text = textarea.value; - - // Show loading state - suggestionBox.classList.add("loading"); - suggestionBox.style.display = "block"; - suggestionBox.innerText = "Checking for improvements..."; - - try { - let improved = text; - let hasMoreErrors = true; - let safetyCounter = 0; // Avoid infinite loop - - while (hasMoreErrors && safetyCounter < 5) { - const response = await fetch("https://api.languagetool.org/v2/check", { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded" - }, - body: new URLSearchParams({ - text: improved, - language: "en-US" - }) - }); - - const data = await response.json(); - const matches = data.matches; - - if (matches.length === 0) { - hasMoreErrors = false; - break; - } - - // Fix matches from last to first - matches - .sort((a, b) => b.offset - a.offset) - .forEach(match => { - if (match.replacements.length > 0) { - const replacement = match.replacements[0].value; - improved = - improved.slice(0, match.offset) + - replacement + - improved.slice(match.offset + match.length); - } - }); +function loadData(event) { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = function(e) { + const data = JSON.parse(e.target.result); + // Load simple inputs and textareas + Object.keys(data.inputs).forEach(id => { + const el = document.getElementById(id); + if (el) el.value = data.inputs[id]; + }); + Object.keys(data.textareas).forEach(id => { + const el = document.getElementById(id); + if (el) el.value = data.textareas[id]; + }); - safetyCounter++; + // Trigger input event for all loaded fields to update preview + document.querySelectorAll('.form-section input, .form-section textarea, .form-section select').forEach(el => el.dispatchEvent(new Event('input'))); + }; + reader.readAsText(file); } +} - suggestionBox.classList.remove("loading"); +// --- UTILITY FUNCTIONS --- +function clearAndReload() { + sessionStorage.clear(); + window.location.reload(); +} - if (improved !== text) { - suggestionBox.innerHTML = `✨ Suggested: "${improved}"`; - } else { - suggestionBox.innerText = "✅ Your text looks good!"; +function autofillResume() { + const dummyData = { + 'input-name': 'Amelia Chen', + 'input-email': 'amelia.chen@email.com', + 'input-phone': '+1-555-010-2345', + 'input-linkedin': 'https://linkedin.com/in/ameliachen', + 'input-github': 'https://github.com/ameliachen', + 'input-about': 'Highly motivated and detail-oriented software engineer with 5 years of experience in developing scalable web applications. Proficient in full-stack development with a strong focus on user-centric design and performance optimization. Eager to contribute to a dynamic team and tackle challenging projects.', + 'education-institution': 'University of Technology', + 'education-degree': 'B.S. in Computer Science', + 'education-gpa': '3.9', + 'education-location': 'Metropolis, USA', + 'education-dates': '2015 - 2019', + 'input-languages': 'JavaScript (ES6+), Python, HTML5, CSS3', + 'input-frameworks': 'React, Node.js, Express, Django', + 'input-tools': 'Git, Docker, Webpack, Jenkins', + 'input-platforms': 'AWS, Vercel, Firebase', + 'input-soft-skills': 'Agile Methodologies, Team Collaboration, Problem Solving, Communication', + 'experience-role': 'Senior Software Engineer', + 'experience-company': 'Innovatech Solutions Inc.', + 'experience-link': 'https://innovatech.com', + 'experience-dates': '2021 - Present', + 'experience-desc': 'Led the development of a new client-facing analytics dashboard, improving data visualization and user engagement by 30%.\nMentored junior developers, providing code reviews and guidance on best practices.', + 'certificate-title': 'AWS Certified Solutions Architect', + 'certificate-issuer': 'Amazon Web Services', + 'certificate-date': '2022', + 'certificate-desc': 'Validated technical expertise in designing and deploying scalable, highly available, and fault-tolerant systems on AWS.', + 'project-title': 'Real-Time Collaborative Code Editor', + 'project-link': 'https://github.com/ameliachen/code-editor', + 'project-desc': 'Developed a web-based code editor using React and WebSockets, allowing multiple users to edit code simultaneously.\nImplemented syntax highlighting for multiple languages.' + }; + for (const id in dummyData) { + const element = document.getElementById(id); + if (element) { + element.value = dummyData[id]; + element.dispatchEvent(new Event('input')); + } } - - } catch (error) { - suggestionBox.classList.remove("loading"); - suggestionBox.innerText = "❌ Error checking grammar."; - console.error(error); - } } +async function improveText(textareaId) { + const textarea = document.getElementById(textareaId); + const suggestionBox = document.getElementById(`suggestion-${textareaId}`); + let text = textarea.value; + + suggestionBox.classList.add("loading"); + suggestionBox.style.display = "block"; + suggestionBox.innerText = "Checking for improvements..."; + + try { + const response = await fetch("https://api.languagetool.org/v2/check", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ text: text, language: "en-US" }) + }); + const data = await response.json(); + let improved = text; + data.matches.sort((a, b) => b.offset - a.offset).forEach(match => { + if (match.replacements.length > 0) { + improved = improved.slice(0, match.offset) + match.replacements[0].value + improved.slice(match.offset + match.length); + } + }); + + suggestionBox.classList.remove("loading"); + if (improved !== text) { + suggestionBox.innerHTML = `✨ Suggested: "${improved}"`; + } else { + suggestionBox.innerText = "✅ Your text looks good!"; + } + } catch (error) { + suggestionBox.classList.remove("loading"); + suggestionBox.innerText = "❌ Error checking grammar."; + console.error(error); + } +} \ No newline at end of file diff --git a/style.css b/style.css index 4091a5f..9338430 100644 --- a/style.css +++ b/style.css @@ -9,12 +9,7 @@ --accent: #667eea; } -/* Make layout math predictable everywhere */ -*, -*::before, -*::after { - box-sizing: border-box; -} +*, *::before, *::after { box-sizing: border-box; } body.dark { --primary: #667eea; @@ -28,8 +23,7 @@ body.dark { } body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, - Ubuntu, Cantarell, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: var(--text); background: linear-gradient(135deg, #1a202c 0%, #2d3748 50%, #4a5568 100%); @@ -39,7 +33,6 @@ body { min-height: 100vh; } -/* Main layout */ .main-container { display: flex; flex-wrap: wrap; @@ -49,45 +42,32 @@ body { background: transparent; } -/* Cards */ -.form-section, -.preview-section { +.form-section, .preview-section { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); border-radius: 16px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); padding: 2rem; - /* allow these cards to shrink when screen is narrow */ min-width: 0; border: 1px solid rgba(255, 255, 255, 0.1); } -/* dark-mode tint */ -body.dark .form-section, -body.dark .preview-section { +body.dark .form-section, body.dark .preview-section { background: rgba(45, 55, 72, 0.95); color: var(--text); border: 1px solid rgba(102, 126, 234, 0.2); } -.form-section { - flex: 1 1 400px; - max-width: 500px; -} +.form-section { flex: 1 1 400px; max-width: 500px; } -/* FIX: ensure readable text on white background */ .preview-section { flex: 2 1 600px; font-size: 14px; background: white; - color: #1f2937; /* was white; causes white-on-white */ + color: #1f2937; } -/* keep preview readable in dark mode too */ -body.dark .preview-section { - background: white; - color: #1f2937; /* keep text dark on white sheet */ -} +body.dark .preview-section { background: white; color: #1f2937; } .form-section h2 { color: var(--accent); @@ -97,9 +77,7 @@ body.dark .preview-section { text-align: center; } -.form-group { - margin-bottom: 1.5rem; -} +.form-group { margin-bottom: 1.5rem; } label { display: block; @@ -109,67 +87,34 @@ label { color: var(--text); } -/* ======= FIXED INPUTS & TEXTAREAS (no overflow) ======= - - Use width:100% and box-sizing to avoid overflow. - - Remove centering margin that caused calc + padding conflicts. - - Keep padding, border, radius and other visuals intact. -*/ -input, -textarea { +input, textarea, select { width: 100%; max-width: 100%; padding: 12px 16px; border: 2px solid var(--border); border-radius: 12px; font-size: 14px; - margin: 0 0 0.5rem 0; /* normal stacked spacing (no auto centering) */ + margin: 0 0 0.5rem 0; background-color: var(--input-bg); color: var(--text); transition: all 0.3s ease; font-family: inherit; display: block; box-sizing: border-box; - min-width: 0; /* allow flex children to shrink */ -} - -/* keep specific grouped inputs still full width (explicitly) */ -.education-inputs input, -.experience-inputs input, -.experience-inputs textarea, -.certifications-inputs input, -.certifications-inputs textarea, -.project-input input, -.project-input textarea, -.skills-grid input, -.skills-grid textarea { - width: 100%; - max-width: 100%; - margin: 0 0 0.5rem 0; - box-sizing: border-box; + min-width: 0; } -/* focus state preserved */ -input:focus, -textarea:focus { +input:focus, textarea:focus, select:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.12); transform: translateY(-1px); } -textarea { - min-height: 100px; - resize: vertical; -} +textarea { min-height: 100px; resize: vertical; } -/* Buttons */ -.add-btn, -.download-btn { - background: linear-gradient( - 135deg, - var(--accent) 0%, - var(--primary-hover) 100% - ); +.add-btn, .download-btn { + background: linear-gradient(135deg, var(--accent) 0%, var(--primary-hover) 100%); color: white; border: none; padding: 12px 24px; @@ -182,14 +127,11 @@ textarea { transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); } - -.add-btn:hover, -.download-btn:hover { +.add-btn:hover, .download-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); } -/* Remove button inserted by JS */ .remove-btn { display: inline-block; margin-top: 0.5rem; @@ -208,11 +150,15 @@ textarea { transform: translateY(-1px); box-shadow: 0 6px 18px rgba(249, 115, 22, 0.35); } -.remove-btn:active { - transform: translateY(0); +.remove-btn:active { transform: translateY(0); } + +.education-inputs:first-child > .remove-btn, +.experience-inputs:first-child > .remove-btn, +.certifications-inputs:first-child > .remove-btn, +.project-input:first-child > .remove-btn { + display: none; } -/* Grammar improve / suggestion UI (JS referenced) */ .improve-btn { display: inline-block; margin: 0.25rem 0 0.75rem 0; @@ -257,7 +203,6 @@ body.dark .suggestion-box { opacity: 0.9; } -/* Cards / grouped inputs */ .education-inputs, .experience-inputs, .certifications-inputs, @@ -268,7 +213,7 @@ body.dark .suggestion-box { padding: 1.5rem; background: rgba(102, 126, 234, 0.05); transition: all 0.3s ease; - min-width: 0; /* allow inner content to shrink on small screens */ + min-width: 0; } .education-inputs:hover, @@ -286,22 +231,11 @@ body.dark .suggestion-box { margin-bottom: 1rem; } -/* make grid children able to shrink */ -.skills-grid > * { - min-width: 0; -} - -.skills-grid > div { - margin-bottom: 0.5rem; -} +.skills-grid > * { min-width: 0; } +.skills-grid > div { margin-bottom: 0.5rem; } +.skills-grid label { font-size: 0.9rem; margin-bottom: 0.5rem; font-weight: 600; } -.skills-grid label { - font-size: 0.9rem; - margin-bottom: 0.5rem; - font-weight: 600; -} - -/* Preview Section Styles */ +/* --- Preview Section Styles --- */ .header { margin-bottom: 2rem; padding-bottom: 1.5rem; @@ -315,7 +249,6 @@ body.dark .suggestion-box { font-weight: 700; } -/* contact info */ .contact-info { display: flex; flex-wrap: wrap; @@ -324,33 +257,19 @@ body.dark .suggestion-box { font-size: 14px; margin-bottom: 0; } - -/* allow contact items to wrap and shrink */ -.contact-info span { - min-width: 0; - display: flex; - align-items: center; - gap: 0.3rem; -} - -.preview-section .contact-info a { - color: #667eea; - text-decoration: none; -} - -.preview-section .contact-info a:hover { - text-decoration: underline; -} +.contact-info span { min-width: 0; display: flex; align-items: center; gap: 0.3rem; } +.preview-section .contact-info a { color: #667eea; text-decoration: none; } +.preview-section .contact-info a:hover { text-decoration: underline; } .section { margin-bottom: 1.5rem; transition: transform 0.2s ease, box-shadow 0.2s ease; - cursor: grab; /* visual hint that sections are draggable */ -} - -.section:active { - cursor: grabbing; + cursor: grab; + /* FIX: Prevents sections from splitting across pages */ + break-inside: avoid; + page-break-inside: avoid; } +.section:active { cursor: grabbing; } .preview-section .section h2 { font-size: 1.25rem; @@ -360,115 +279,27 @@ body.dark .suggestion-box { margin-bottom: 1rem; font-weight: 700; } +.preview-section .section p { color: #4a5568; line-height: 1.7; margin-bottom: 1rem; } -.preview-section .section p { - color: #4a5568; - line-height: 1.7; - margin-bottom: 1rem; -} - -ul { - padding-left: 1.5rem; - margin: 0.8rem 0; - list-style: none; -} - -.preview-section ul li { - margin-bottom: 1rem; - position: relative; - color: #2d3748; - line-height: 1.6; -} - -.preview-section ul li::before { - content: "â€ĸ"; - color: #667eea; - font-weight: bold; - position: absolute; - left: -1.5rem; - top: 0; -} - -.preview-section ul li strong { - color: #2d3748; - font-weight: 600; -} +ul { padding-left: 1.5rem; margin: 0.8rem 0; list-style: none; } +.preview-section ul li { margin-bottom: 1rem; position: relative; color: #2d3748; line-height: 1.6; } +.preview-section ul li::before { content: "â€ĸ"; color: #667eea; font-weight: bold; position: absolute; left: -1.5rem; top: 0; } +.preview-section ul li strong { color: #2d3748; font-weight: 600; } +ul li ul { margin-top: 0.5rem; padding-left: 1.2rem; } +.preview-section ul li ul li { margin-bottom: 0.3rem; font-size: 0.95rem; color: #4a5568; } +.preview-section ul li ul li::before { content: "â—Ļ"; color: #667eea; left: -1.2rem; } -ul li ul { - margin-top: 0.5rem; - padding-left: 1.2rem; -} +.preview-section .skills-grid strong { color: #2d3748; font-weight: 600; } +.preview-section .skills-grid span { color: #4a5568; } -.preview-section ul li ul li { - margin-bottom: 0.3rem; - font-size: 0.95rem; - color: #4a5568; -} +.project-links { margin: 0.5rem 0; } +.preview-section .project-links a { color: #667eea; text-decoration: none; font-size: 13px; font-weight: 500; margin-right: 1rem; display: inline-flex; align-items: center; gap: 0.3rem; } +.preview-section .project-links a::before { content: "🔗"; font-size: 12px; } +.preview-section .project-links a:hover { text-decoration: underline; } -.preview-section ul li ul li::before { - content: "â—Ļ"; - color: #667eea; - left: -1.2rem; -} +.fade-in { opacity: 0; transform: translateY(20px); transition: opacity 0.6s ease-out, transform 0.6s ease-out; will-change: opacity, transform; } +.fade-in.show { opacity: 1; transform: translateY(0); } -.skills-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 1rem; - margin-top: 0.8rem; -} - -.skills-grid > div { - margin-bottom: 0.8rem; -} - -.preview-section .skills-grid strong { - color: #2d3748; - font-weight: 600; -} - -.preview-section .skills-grid span { - color: #4a5568; -} - -.project-links { - margin: 0.5rem 0; -} - -.preview-section .project-links a { - color: #667eea; - text-decoration: none; - font-size: 13px; - font-weight: 500; - margin-right: 1rem; - display: inline-flex; - align-items: center; - gap: 0.3rem; -} - -.preview-section .project-links a::before { - content: "🔗"; - font-size: 12px; -} - -.preview-section .project-links a:hover { - text-decoration: underline; -} - -/* Fade in animations */ -.fade-in { - opacity: 0; - transform: translateY(20px); - transition: opacity 0.6s ease-out, transform 0.6s ease-out; - will-change: opacity, transform; -} - -.fade-in.show { - opacity: 1; - transform: translateY(0); -} - -/* Theme toggle button */ #themeToggle { position: fixed; top: 20px; @@ -485,25 +316,10 @@ ul li ul { transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); } +#themeToggle:hover { transform: scale(1.1); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); } -#themeToggle:hover { - transform: scale(1.1); - box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); -} - -/* Footer actions */ -.footer-actions { - display: flex; - justify-content: center; - gap: 1rem; - padding: 1.5rem; - border-top: 1px solid #e2e8f0; - background: transparent; - margin-top: 2rem; -} - -.btn-refresh, -.btn-autofill { +.footer-actions { display: flex; justify-content: center; gap: 1rem; padding: 1.5rem; border-top: 1px solid #e2e8f0; background: transparent; margin-top: 2rem; } +.btn-refresh, .btn-autofill { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; color: white; @@ -516,111 +332,93 @@ ul li ul { transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); } +.btn-refresh:hover, .btn-autofill:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); } -.btn-refresh:hover, -.btn-autofill:hover { - transform: translateY(-2px); - box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); -} +.footer { background: rgba(26, 32, 44, 0.95); color: #f5f5f5; margin-top: 3rem; padding: 2rem 0; text-align: center; border-top: 1px solid rgba(102, 126, 234, 0.3); } +.footer-link { color: #667eea; text-decoration: none; } +.footer-link:hover { text-decoration: underline; } -.footer { - background: rgba(26, 32, 44, 0.95); - color: #f5f5f5; - margin-top: 3rem; - padding: 2rem 0; - text-align: center; - border-top: 1px solid rgba(102, 126, 234, 0.3); -} +.char-count { font-size: 0.8rem; color: var(--muted); display: block; margin-top: -0.3rem; text-align: right; padding-right: 0.7rem; } +.char-count.exceeded { color: #ef4444; font-weight: 600; } +.error-message { color: #f56565; font-size: 0.85rem; margin-top: -0.3rem; margin-bottom: 0.5rem; display: block; font-weight: 500; } -.footer-link { - color: #667eea; - text-decoration: none; +.section.dragging { opacity: 0.6; background-color: rgba(102, 126, 234, 0.1); border: 2px dashed #667eea; cursor: grabbing; transform: scale(1.02); transition: all 0.2s ease-in-out; } + +/* --- Photo, Undo/Redo, and Template Styles --- */ +#preview-photo { + width: 100px; + height: 100px; + border-radius: 50%; + object-fit: cover; + border: 3px solid var(--accent); + margin-bottom: 1rem; } +#preview-photo.hidden { display: none; } -.footer-link:hover { - text-decoration: underline; +#controls { + padding-bottom: 1rem; + border-bottom: 1px dashed #ccc; + margin-bottom: 1rem; } +#controls button { margin-right: 0.5rem; padding: 5px 10px; } -/* Character count */ -.char-count { - font-size: 0.8rem; - color: var(--muted); - display: block; - margin-top: -0.3rem; - text-align: right; - padding-right: 0.7rem; +/* ======== CORRECTED Two-Column Template Styles v2 ======== */ +.preview-section.template-modern #resume-sections { + display: grid; + grid-template-columns: 1fr 2fr; + gap: 2rem; + align-items: start; } -/* exceed color referenced in JS */ -.char-count.exceeded { - color: #ef4444; - font-weight: 600; +.preview-section.template-modern .header { + grid-column: 1 / -1; + grid-row: 1; /* Header is in the first row */ } -.error-message { - color: #f56565; - font-size: 0.85rem; - margin-top: -0.3rem; - margin-bottom: 0.5rem; - display: block; - font-weight: 500; +/* --- Left Column Content --- */ +.preview-section.template-modern .section[data-id="Education"] { + grid-column: 1; + grid-row: 2; /* Starts on row 2 */ +} +.preview-section.template-modern .section[data-id="skills-summary"] { + grid-column: 1; + grid-row: 3; /* Starts on row 3 */ +} +.preview-section.template-modern .section[data-id="Certificates"] { + grid-column: 1; + grid-row: 4; /* Starts on row 4 */ } -/* Drag and Drop Styles */ -.section.dragging { - opacity: 0.6; - background-color: rgba(102, 126, 234, 0.1); - border: 2px dashed #667eea; - cursor: grabbing; - transform: scale(1.02); - transition: all 0.2s ease-in-out; +/* --- Right Column Content --- */ +.preview-section.template-modern .section[data-id="summary"] { + grid-column: 2; + grid-row: 2; /* Starts on row 2 */ } +.preview-section.template-modern .section[data-id="experience"] { + grid-column: 2; + grid-row: 3; /* Starts on row 3 */ +} +.preview-section.template-modern .section[data-id="Projects"] { + grid-column: 2; + grid-row: 4; /* Starts on row 4 */ +} +/* ======================================================= */ -/* Responsive Design */ @media (max-width: 900px) { - .main-container { - flex-direction: column; - gap: 1.5rem; - padding: 0 0.5rem; - } - - .form-section, - .preview-section { - max-width: 100%; - min-width: 0; - padding: 1.5rem; - } - - .skills-grid { - grid-template-columns: 1fr; - } - - .contact-info { - flex-direction: column; - gap: 0.8rem; - } - - .preview-section .header h1 { - font-size: 2rem; - } + .main-container { flex-direction: column; gap: 1.5rem; padding: 0 0.5rem; } + .form-section, .preview-section { max-width: 100%; min-width: 0; padding: 1.5rem; } + .skills-grid { grid-template-columns: 1fr; } + .contact-info { flex-direction: column; gap: 0.8rem; } + .preview-section .header h1 { font-size: 2rem; } + /* Disable two-column layout on smaller screens */ + .preview-section.template-modern #resume-sections { display: block; } } @media (max-width: 768px) { - .footer { - flex-direction: column; - text-align: center; - align-items: center; - padding: 1.5rem; - gap: 1rem; - } - - #themeToggle { - font-size: 20px; - padding: 10px; - } + .footer { flex-direction: column; text-align: center; align-items: center; padding: 1.5rem; gap: 1rem; } + #themeToggle { font-size: 20px; padding: 10px; } } -/* back-to-top button section */ .back-to-top { position: fixed; bottom: 30px; @@ -642,113 +440,289 @@ ul li ul { align-items: center; justify-content: center; } +.back-to-top.show { opacity: 1; visibility: visible; transform: translateY(0); } +.back-to-top:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); background: linear-gradient(135deg, #1481ca, #2563eb); } +.back-to-top:active { transform: translateY(-1px); } +.back-to-top::before { content: "↑"; font-weight: bold; } -.back-to-top.show { - opacity: 1; - visibility: visible; - transform: translateY(0); +@media (max-width: 768px) { + .container { margin: 10px; border-radius: 0; } + .header, .section { padding: 20px; } + .back-to-top { bottom: 20px; right: 20px; width: 50px; height: 50px; font-size: 20px; } } -.back-to-top:hover { - transform: translateY(-3px); - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); - background: linear-gradient(135deg, #1481ca, #2563eb); +@media print { + .no-print { display: none; } + .preview-section { box-shadow: none; border: none; background: white; color: #111827; } + body { background: white !important; } } -.back-to-top:active { - transform: translateY(-1px); +a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 3px solid rgba(102, 126, 234, 0.4); outline-offset: 2px; border-radius: 12px; } + +.education-inputs:hover, .experience-inputs:hover, .certifications-inputs:hover, .project-input:hover { box-shadow: 0 6px 18px rgba(102, 126, 234, 0.18); } + +.preview-section a { color: #4f46e5; } +.preview-section a:hover { text-decoration: underline; } + +.footer-actions.no-print { border-top: 1px solid #e5e7eb; } + +.preview-section .contact-info span, +.preview-section .project-links a { word-break: break-word; overflow-wrap: anywhere; } + +.form-section, .preview-section, input, textarea, .add-btn, .download-btn { + transition: background-color 0.25s ease, color 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease, transform 0.2s ease; +} + + + +:root { + --primary: #3b82f6; + --primary-hover: #2563eb; + --text: #1a202c; + --muted: #64748b; + --border: #94a3b8; + --bg: #f8fafc; + --input-bg: #fff; + --accent: #667eea; } -/* Arrow icon */ -.back-to-top::before { - content: "↑"; - font-weight: bold; +*, *::before, *::after { box-sizing: border-box; } + +body.dark { + --primary: #667eea; + --primary-hover: #5a67d8; + --text: #f5f5f5; + --muted: #cbd5e1; + --border: #4a5568; + --bg: #1a202c; + --input-bg: #2d3748; + --accent: #667eea; } -/* Responsive tweaks for back-to-top container mention retained */ -@media (max-width: 768px) { - .container { - margin: 10px; - border-radius: 0; - } - - .header, - .section { - padding: 20px; - } - - .back-to-top { - bottom: 20px; - right: 20px; - width: 50px; - height: 50px; - font-size: 20px; - } -} - -/* Print layout */ -@media print { - .no-print { - display: none; - } - - .preview-section { - box-shadow: none; - border: none; - background: white; - color: #111827; /* ensure readable when printing */ - } - - body { - background: white !important; - } -} - -/* Accessibility improvements (no HTML/JS changes needed) */ -a:focus-visible, -button:focus-visible, -input:focus-visible, -textarea:focus-visible { - outline: 3px solid rgba(102, 126, 234, 0.4); - outline-offset: 2px; - border-radius: 12px; +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + line-height: 1.6; + color: var(--text); + background: linear-gradient(135deg, #1a202c 0%, #2d3748 50%, #4a5568 100%); + margin: 0; + padding: 1rem; + transition: background-color 0.3s ease, color 0.3s ease; + min-height: 100vh; } -/* Optional subtle hover for cards */ -.education-inputs:hover, -.experience-inputs:hover, -.certifications-inputs:hover, -.project-input:hover { - box-shadow: 0 6px 18px rgba(102, 126, 234, 0.18); +.main-container { + display: flex; + flex-wrap: wrap; + gap: 2rem; + max-width: 1400px; + margin: 0 auto; + align-items: flex-start; } -/* Keep links inside preview readable and consistent */ -.preview-section a { - color: #4f46e5; +.form-section, .preview-section { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); } -.preview-section a:hover { - text-decoration: underline; + +body.dark .form-section { + background: rgba(45, 55, 72, 0.95); + color: var(--text); + border: 1px solid rgba(102, 126, 234, 0.2); } -/* Subtle divider for footer actions when printing disabled */ -.footer-actions.no-print { - border-top: 1px solid #e5e7eb; +.form-section { + flex: 1 1 400px; + max-width: 500px; + padding: 2rem; } -/* Ensure long URLs wrap in preview (avoids overflow) */ -.preview-section .contact-info span, -.preview-section .project-links a { - word-break: break-word; - overflow-wrap: anywhere; +/* --- Realistic Page Preview Styles --- */ +.preview-section { + flex: 2 1 600px; + padding: 2rem; + background: transparent; + border: none; +} + +#resume-page { + background: white; + color: #1f2937; + width: 8.5in; /* Standard Letter width */ + min-height: 11in; /* Standard Letter height */ + padding: 0.5in; + margin: 0 auto; + box-shadow: 0 0 15px rgba(0,0,0,0.15); + overflow: hidden; /* Hide content that overflows in paged mode */ +} + +#resume-page.one-page-limit { + height: 11in; /* Fixed height for one-page mode */ +} + +#resume-page.overflowing { + border: 2px solid #ef4444; /* Red border warning */ + box-shadow: 0 0 15px rgba(239, 68, 68, 0.5); } -/* Smooth transitions for theme toggling without layout shifts */ -.form-section, -.preview-section, -input, -textarea, -.add-btn, +body.dark .preview-section { background: transparent; } +body.dark #resume-page { color: #1f2937; } + +.form-section h2 { + color: var(--accent); + font-size: 1.8rem; + font-weight: 700; + margin-bottom: 2rem; + text-align: center; +} + +.form-group { margin-bottom: 1.5rem; } + +label { + display: block; + margin-bottom: 0.5rem; + font-weight: 600; + font-size: 0.95rem; + color: var(--text); +} + +input, textarea, select { + width: 100%; + max-width: 100%; + padding: 12px 16px; + border: 2px solid var(--border); + border-radius: 12px; + font-size: 14px; + margin: 0 0 0.5rem 0; + background-color: var(--input-bg); + color: var(--text); + transition: all 0.3s ease; + font-family: inherit; + display: block; + box-sizing: border-box; + min-width: 0; +} + +input:focus, textarea:focus, select:focus { + border-color: var(--accent); + outline: none; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.12); + transform: translateY(-1px); +} + +textarea { min-height: 100px; resize: vertical; } + .download-btn { - transition: background-color 0.25s ease, color 0.25s ease, - border-color 0.25s ease, box-shadow 0.25s ease, transform 0.2s ease; + background: linear-gradient(135deg, var(--accent) 0%, var(--primary-hover) 100%); + color: white; + border: none; + padding: 12px 24px; + border-radius: 12px; + font-size: 14px; + font-weight: 600; + margin-top: 0.5rem; + margin-right: 0.5rem; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); +} +.download-btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); +} + +/* --- Moved Action Button Styles --- */ +.action-buttons { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + background: rgba(102, 126, 234, 0.05); + padding: 1rem; + border-radius: 12px; +} +.tool-btn { + background-color: rgba(255,255,255,0.1); + border: 1px solid var(--border); + color: var(--text); + padding: 8px 12px; + border-radius: 8px; + cursor: pointer; + transition: background-color 0.2s ease; +} +.tool-btn:hover { + background-color: rgba(102, 126, 234, 0.1); +} + +.header { margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 2px solid #667eea; } +.preview-section .header h1 { font-size: 2.5rem; color: #667eea; margin-bottom: 0.5rem; font-weight: 700; } +.contact-info { display: flex; flex-wrap: wrap; gap: 1.5rem; color: #4a5568; font-size: 14px; margin-bottom: 0; } +.contact-info span { min-width: 0; display: flex; align-items: center; gap: 0.3rem; } + +.section { + margin-bottom: 1.5rem; + cursor: grab; + break-inside: avoid; + page-break-inside: avoid; + position: relative; + padding-left: 1.5rem; /* Space for drag handle */ +} +.section:active { cursor: grabbing; } + +.preview-section .section h2 { + font-size: 1.25rem; + color: #667eea; + border-bottom: 1px solid #e2e8f0; + padding-bottom: 0.5rem; + margin-bottom: 1rem; + font-weight: 700; +} + +/* Drag Handle Icon */ +.section h2::before { + content: 'â ŋ'; + position: absolute; + left: 0; + top: 6px; + font-size: 1.2rem; + color: #cbd5e1; + cursor: grab; +} + +ul { padding-left: 1.5rem; margin: 0.8rem 0; list-style: none; } +.preview-section ul li { margin-bottom: 1rem; position: relative; color: #2d3748; line-height: 1.6; } +.preview-section ul li::before { content: "â€ĸ"; color: #667eea; font-weight: bold; position: absolute; left: -1.5rem; top: 0; } + + +.template-modern #resume-sections { + display: grid; + grid-template-columns: 1fr 2fr; + gap: 2rem; + align-items: start; } +.template-modern .header { + grid-column: 1 / -1; + grid-row: 1; +} +.template-modern .section[data-id="Education"], +.template-modern .section[data-id="skills-summary"], +.template-modern .section[data-id="Certificates"] { + grid-column: 1; +} +.template-modern .section[data-id="summary"], +.template-modern .section[data-id="experience"], +.template-modern .section[data-id="Projects"] { + grid-column: 2; +} + +@media (max-width: 1200px) { + .main-container { flex-direction: column; } + .form-section, .preview-section { max-width: 100%; } + #resume-page { width: 100%; min-height: 0; height: auto; } +} + +@media (max-width: 900px) { + .template-modern #resume-sections { display: block; } +} +