feat: comprehensive print optimization for professional PDF output

Print CSS overhaul:
- Fixed photo aspect ratio (60x80, 3:4 portrait) with contain fit
- Unified font sizes across all sections (10pt titles, 9pt content, 8pt metadata)
- Removed excessive spacing (reduced by 50-70%)
- Applied clean theme automatically (no sidebars, icons, logos, badges)
- Force short version for concise 5-page output
- Natural page breaks (removed forced breaks causing blank spaces)
- Consistent section title spacing with proper breathing room
- Match Training/Skills spacing pattern across all sections
- Fixed Languages and References spacing
- Equalized Experience, Courses, Projects, and Awards formatting
- Single separator for "See all projects" link

UI improvements:
- Enhanced icon toggle visibility with better contrast
- Reorganized navigation menu structure
This commit is contained in:
juanatsap
2025-11-10 14:00:32 +00:00
parent eda746407e
commit 18db4011f8
3 changed files with 852 additions and 190 deletions
+174 -32
View File
@@ -194,19 +194,12 @@
<!-- Navigation Menu (Hidden by default) -->
<nav id="navigation-menu" class="navigation-menu no-print" role="navigation" aria-label="CV sections">
<div class="menu-content">
<a href="#" class="menu-item menu-item-action" onclick="expandAllSections(event)">
<iconify-icon icon="mdi:arrow-expand-all" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Expandir Todo{{else}}Expand All{{end}}</span>
</a>
<a href="#" class="menu-item menu-item-action" onclick="collapseAllSections(event)">
<iconify-icon icon="mdi:arrow-collapse-all" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Colapsar Todo{{else}}Collapse All{{end}}</span>
</a>
<!-- CV Sections - Quick Navigation -->
<div class="menu-item-submenu">
<a href="#" class="menu-item has-submenu">
<iconify-icon icon="mdi:menu" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Secciones CV{{else}}CV Sections{{end}}</span>
<iconify-icon icon="mdi:chevron-down" width="16" height="16" class="submenu-arrow"></iconify-icon>
<iconify-icon icon="mdi:chevron-right" width="16" height="16" class="submenu-arrow"></iconify-icon>
</a>
<div class="submenu-content">
<a href="#education" class="menu-item" onclick="scrollToSection('education')">
@@ -247,6 +240,94 @@
</a>
</div>
</div>
<!-- Quick Actions Section -->
<div class="menu-section-wrapper">
<div class="menu-item menu-item-header">
<iconify-icon icon="mdi:cog-outline" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Acciones Rápidas{{else}}Quick Actions{{end}}</span>
</div>
<a href="#" class="menu-item menu-item-action" onclick="expandAllSections(event)">
<iconify-icon icon="mdi:arrow-expand-all" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Expandir Todo{{else}}Expand All{{end}}</span>
</a>
<a href="#" class="menu-item menu-item-action" onclick="collapseAllSections(event)">
<iconify-icon icon="mdi:arrow-collapse-all" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Colapsar Todo{{else}}Collapse All{{end}}</span>
</a>
</div>
<!-- View Controls in menu (visible only on mobile < 900px) -->
<div class="menu-controls-section">
<div class="menu-item menu-item-header">
<iconify-icon icon="mdi:tune-variant" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Controles de Vista{{else}}View Controls{{end}}</span>
</div>
<!-- CV Length toggle -->
<div class="menu-control-item">
<label class="menu-control-label">
<iconify-icon icon="mdi:file-document-outline" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Longitud{{else}}Length{{end}}</span>
</label>
<label class="icon-toggle">
<input type="checkbox" id="lengthToggleMenu" onchange="toggleCVLength()">
<span class="icon-toggle-slider">
<iconify-icon icon="mdi:file-document-outline" width="16" height="16" class="icon-left"></iconify-icon>
<iconify-icon icon="mdi:file-document-multiple-outline" width="16" height="16" class="icon-right"></iconify-icon>
</span>
</label>
</div>
<!-- Logo toggle -->
<div class="menu-control-item">
<label class="menu-control-label">
<iconify-icon icon="mdi:image-multiple-outline" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Logos{{else}}Logos{{end}}</span>
</label>
<label class="icon-toggle">
<input type="checkbox" id="logoToggleMenu" checked onchange="toggleLogos()">
<span class="icon-toggle-slider">
<iconify-icon icon="mdi:image-off-outline" width="16" height="16" class="icon-left"></iconify-icon>
<iconify-icon icon="mdi:image-multiple-outline" width="16" height="16" class="icon-right"></iconify-icon>
</span>
</label>
</div>
<!-- Theme toggle -->
<div class="menu-control-item">
<label class="menu-control-label">
<iconify-icon icon="mdi:page-layout-sidebar-left" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Vista{{else}}View{{end}}</span>
</label>
<label class="icon-toggle">
<input type="checkbox" id="themeToggleMenu" onchange="toggleTheme()">
<span class="icon-toggle-slider">
<iconify-icon icon="mdi:page-layout-sidebar-left" width="16" height="16" class="icon-left"></iconify-icon>
<iconify-icon icon="mdi:page-layout-body" width="16" height="16" class="icon-right"></iconify-icon>
</span>
</label>
</div>
</div>
<!-- Action Buttons in menu (visible only on mobile < 900px) -->
<div class="menu-actions-section">
<div class="menu-item menu-item-header">
<iconify-icon icon="mdi:lightning-bolt" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Acciones{{else}}Actions{{end}}</span>
</div>
<a class="menu-action-btn" href="/export/pdf?lang={{.Lang}}" download>
<iconify-icon icon="mdi:download" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Descargar como PDF{{else}}Download as PDF{{end}}</span>
</a>
<button class="menu-action-btn" onclick="printFriendly()">
<iconify-icon icon="mdi:leaf" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Imprimir amigable{{else}}Print Friendly{{end}}</span>
</button>
</div>
</div>
</nav>
@@ -361,6 +442,17 @@
menu.classList.remove('menu-hover');
hamburgerBtn.setAttribute('aria-expanded', 'false');
});
// Position submenu dynamically
const submenuTrigger = document.querySelector('.menu-item-submenu');
const submenuContent = document.querySelector('.submenu-content');
if (submenuTrigger && submenuContent) {
submenuTrigger.addEventListener('mouseenter', function() {
const triggerRect = submenuTrigger.getBoundingClientRect();
submenuContent.style.top = `${triggerRect.top}px`;
});
}
});
// Legacy toggle function - kept for compatibility
@@ -486,13 +578,21 @@
}
function toggleCVLength() {
const toggle = document.getElementById('lengthToggle');
const headerToggle = document.getElementById('lengthToggle');
const menuToggle = document.getElementById('lengthToggleMenu');
const paper = document.querySelector('.cv-paper');
// Get the state from whichever toggle was clicked
const isChecked = event?.target?.id === 'lengthToggleMenu' ? menuToggle?.checked : headerToggle?.checked;
// Sync both toggles
if (headerToggle) headerToggle.checked = isChecked;
if (menuToggle) menuToggle.checked = isChecked;
// Save current scroll position
const currentScrollY = window.scrollY || window.pageYOffset;
if (toggle.checked) {
if (isChecked) {
paper.classList.add('cv-long');
paper.classList.remove('cv-short');
localStorage.setItem('cv-length', 'long');
@@ -509,13 +609,21 @@
}
function toggleLogos() {
const toggle = document.getElementById('logoToggle');
const headerToggle = document.getElementById('logoToggle');
const menuToggle = document.getElementById('logoToggleMenu');
const paper = document.querySelector('.cv-paper');
// Get the state from whichever toggle was clicked
const isChecked = event?.target?.id === 'logoToggleMenu' ? menuToggle?.checked : headerToggle?.checked;
// Sync both toggles
if (headerToggle) headerToggle.checked = isChecked;
if (menuToggle) menuToggle.checked = isChecked;
// Save current scroll position
const currentScrollY = window.scrollY || window.pageYOffset;
if (toggle.checked) {
if (isChecked) {
paper.classList.add('show-logos');
localStorage.setItem('cv-logos', 'show');
} else {
@@ -530,10 +638,18 @@
}
function toggleTheme() {
const toggle = document.getElementById('themeToggle');
const headerToggle = document.getElementById('themeToggle');
const menuToggle = document.getElementById('themeToggleMenu');
const container = document.querySelector('.cv-container');
if (toggle.checked) {
// Get the state from whichever toggle was clicked
const isChecked = event?.target?.id === 'themeToggleMenu' ? menuToggle?.checked : headerToggle?.checked;
// Sync both toggles
if (headerToggle) headerToggle.checked = isChecked;
if (menuToggle) menuToggle.checked = isChecked;
if (isChecked) {
container.classList.add('theme-clean');
localStorage.setItem('cv-theme', 'clean');
} else {
@@ -542,25 +658,38 @@
}
}
// Print Friendly - applies Clean theme before printing
// Print Friendly - Apply Clean Theme + Short Version for minimal printing
function printFriendly() {
const container = document.querySelector('.cv-container');
const paper = document.querySelector('.cv-paper');
const wasClean = container.classList.contains('theme-clean');
const wasLong = paper.classList.contains('cv-long');
// Apply clean theme for printing
// Apply clean theme for minimal print (no sidebars, no header, no icons)
if (!wasClean) {
container.classList.add('theme-clean');
}
// Print
window.print();
// Force SHORT version for print (hide detailed content)
paper.classList.remove('cv-long');
paper.classList.add('cv-short');
// Restore original theme after print dialog
// Small delay to let CSS apply
setTimeout(() => {
if (!wasClean) {
container.classList.remove('theme-clean');
}
}, 100);
window.print();
// Restore original theme and length after print dialog closes
setTimeout(() => {
if (!wasClean) {
container.classList.remove('theme-clean');
}
// Restore original length
if (wasLong) {
paper.classList.remove('cv-short');
paper.classList.add('cv-long');
}
}, 100);
}, 50);
}
// Initialize with saved preferences or defaults
@@ -595,30 +724,43 @@
// Restore CV length preference
const savedLength = localStorage.getItem('cv-length') || 'short';
if (savedLength === 'long') {
const lengthChecked = savedLength === 'long';
if (lengthChecked) {
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;
}
// Sync both header and menu toggles
const headerLengthToggle = document.getElementById('lengthToggle');
const menuLengthToggle = document.getElementById('lengthToggleMenu');
if (headerLengthToggle) headerLengthToggle.checked = lengthChecked;
if (menuLengthToggle) menuLengthToggle.checked = lengthChecked;
// Restore logos preference
const savedLogos = localStorage.getItem('cv-logos') || 'show';
if (savedLogos === 'show') {
const logosChecked = savedLogos === 'show';
if (logosChecked) {
paper.classList.add('show-logos');
document.getElementById('logoToggle').checked = true;
} else {
paper.classList.remove('show-logos');
document.getElementById('logoToggle').checked = false;
}
// Sync both header and menu toggles
const headerLogoToggle = document.getElementById('logoToggle');
const menuLogoToggle = document.getElementById('logoToggleMenu');
if (headerLogoToggle) headerLogoToggle.checked = logosChecked;
if (menuLogoToggle) menuLogoToggle.checked = logosChecked;
// Restore theme preference
const savedTheme = localStorage.getItem('cv-theme') || 'default';
if (savedTheme === 'clean') {
document.getElementById('themeToggle').checked = true;
const themeChecked = savedTheme === 'clean';
// Sync both header and menu toggles
const headerThemeToggle = document.getElementById('themeToggle');
const menuThemeToggle = document.getElementById('themeToggleMenu');
if (headerThemeToggle) headerThemeToggle.checked = themeChecked;
if (menuThemeToggle) menuThemeToggle.checked = themeChecked;
if (themeChecked) {
toggleTheme();
}
});