feat: enhance CV with project title links, experience durations, certifications, and improved navigation
## Project Title Links - Add projectName and projectDesc fields to Project struct - Split project titles to make only project name clickable - Update template logic for conditional title rendering - Apply changes to both English and Spanish versions ## Experience Duration Display - Restore duration calculation display in experience section - Move duration from date line to after company name - Style duration in light gray (#999) for subtle appearance - Calculate durations dynamically (e.g., "4 years 10 months") ## Certifications Section Enhancement - Add Codecademy Certifications (2022-2024) with AI Transformers and React courses - Add LinkedIn Learning Certifications (2019-2020) with 5 professional courses - Implement colored icon system with brand colors (purple, cyan, green, etc.) - Use responsibilities format matching Third Party Contributions layout - Reorder courses chronologically (most recent first) ## localStorage Improvements - Save CV length preference (short/long) - Save logos visibility preference (show/hide) - Save theme preference (default/clean) - Restore all preferences on page load ## Navigation UX Enhancements - Fix scroll positioning to show sections below action bar - Add keepHeaderVisible flag to maintain header visibility after navigation - Ensure smooth scrolling with proper offset calculations - Reset flag on scroll up to restore normal hide/show behavior ## Files Modified - internal/models/cv.go: Add ProjectName, ProjectDesc fields - templates/cv-content.html: Update project and experience rendering - static/css/main.css: Add duration-text styling - templates/index.html: Enhance scroll behavior and localStorage - data/cv-*.json: Add certifications, split project titles, reorder courses - static/images/courses/: Add codecademy.png, linkedin.png
This commit is contained in:
+60
-17
@@ -277,28 +277,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Flag to keep header visible after navigation
|
||||
let keepHeaderVisible = false;
|
||||
|
||||
// Scroll to section smoothly
|
||||
function scrollToSection(sectionId) {
|
||||
event.preventDefault(); // Prevent default anchor behavior
|
||||
|
||||
const section = document.getElementById(sectionId);
|
||||
if (section) {
|
||||
const actionBarHeight = document.querySelector('.action-bar').offsetHeight;
|
||||
const menuHeight = document.querySelector('.navigation-menu').offsetHeight;
|
||||
const offset = actionBarHeight + (menuHeight || 0) + 20; // Add 20px padding
|
||||
// Ensure header is visible before scrolling
|
||||
const actionBar = document.querySelector('.action-bar');
|
||||
const navMenu = document.querySelector('.navigation-menu');
|
||||
actionBar.classList.remove('header-hidden');
|
||||
navMenu.classList.remove('header-hidden');
|
||||
|
||||
const elementPosition = section.getBoundingClientRect().top;
|
||||
const offsetPosition = elementPosition + window.pageYOffset - offset;
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
// Set flag to keep header visible
|
||||
keepHeaderVisible = true;
|
||||
|
||||
// Close menu after clicking
|
||||
const menu = document.getElementById('navigation-menu');
|
||||
menu.classList.remove('menu-open');
|
||||
navMenu.classList.remove('menu-open');
|
||||
document.querySelector('.hamburger-btn').setAttribute('aria-expanded', 'false');
|
||||
|
||||
// Wait a bit for header to be visible, then calculate offset
|
||||
setTimeout(() => {
|
||||
const actionBarHeight = actionBar.offsetHeight;
|
||||
const offset = actionBarHeight + 20; // Add 20px padding
|
||||
|
||||
const elementPosition = section.getBoundingClientRect().top;
|
||||
const offsetPosition = elementPosition + window.pageYOffset - offset;
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,9 +361,11 @@
|
||||
if (toggle.checked) {
|
||||
paper.classList.add('cv-long');
|
||||
paper.classList.remove('cv-short');
|
||||
localStorage.setItem('cv-length', 'long');
|
||||
} else {
|
||||
paper.classList.add('cv-short');
|
||||
paper.classList.remove('cv-long');
|
||||
localStorage.setItem('cv-length', 'short');
|
||||
}
|
||||
|
||||
// Restore scroll position after DOM updates
|
||||
@@ -368,8 +383,10 @@
|
||||
|
||||
if (toggle.checked) {
|
||||
paper.classList.add('show-logos');
|
||||
localStorage.setItem('cv-logos', 'show');
|
||||
} else {
|
||||
paper.classList.remove('show-logos');
|
||||
localStorage.setItem('cv-logos', 'hide');
|
||||
}
|
||||
|
||||
// Restore scroll position after DOM updates
|
||||
@@ -412,10 +429,31 @@
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Initialize with short version, logos enabled, and saved theme
|
||||
// Initialize with saved preferences or defaults
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelector('.cv-paper').classList.add('cv-short');
|
||||
document.querySelector('.cv-paper').classList.add('show-logos');
|
||||
const paper = document.querySelector('.cv-paper');
|
||||
|
||||
// Restore CV length preference
|
||||
const savedLength = localStorage.getItem('cv-length') || 'short';
|
||||
if (savedLength === 'long') {
|
||||
paper.classList.add('cv-long');
|
||||
paper.classList.remove('cv-short');
|
||||
document.getElementById('lengthToggle').checked = true;
|
||||
} else {
|
||||
paper.classList.add('cv-short');
|
||||
paper.classList.remove('cv-long');
|
||||
document.getElementById('lengthToggle').checked = false;
|
||||
}
|
||||
|
||||
// Restore logos preference
|
||||
const savedLogos = localStorage.getItem('cv-logos') || 'show';
|
||||
if (savedLogos === 'show') {
|
||||
paper.classList.add('show-logos');
|
||||
document.getElementById('logoToggle').checked = true;
|
||||
} else {
|
||||
paper.classList.remove('show-logos');
|
||||
document.getElementById('logoToggle').checked = false;
|
||||
}
|
||||
|
||||
// Restore theme preference
|
||||
const savedTheme = localStorage.getItem('cv-theme') || 'default';
|
||||
@@ -436,10 +474,15 @@
|
||||
const currentScroll = window.pageYOffset || document.documentElement.scrollTop;
|
||||
const isMenuOpen = navMenu.classList.contains('menu-open');
|
||||
|
||||
// If scrolling up, reset the keepHeaderVisible flag
|
||||
if (currentScroll < lastScrollTop) {
|
||||
keepHeaderVisible = false;
|
||||
}
|
||||
|
||||
// Hide/show header based on scroll direction
|
||||
if (currentScroll > scrollThreshold) {
|
||||
if (currentScroll > lastScrollTop) {
|
||||
// Scrolling down - hide header
|
||||
if (currentScroll > lastScrollTop && !keepHeaderVisible) {
|
||||
// Scrolling down - hide header (only if keepHeaderVisible is false)
|
||||
actionBar.classList.add('header-hidden');
|
||||
// Only hide menu if it's open
|
||||
if (isMenuOpen) {
|
||||
|
||||
Reference in New Issue
Block a user