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:
juanatsap
2025-11-09 11:42:52 +00:00
parent e64e63de98
commit 2ce13481d0
13 changed files with 975 additions and 33 deletions
+60 -17
View File
@@ -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) {