2025-11-19 16:30:18 +00:00
# PDF Export Feature Documentation
## Overview
The CV application provides a comprehensive PDF export system with three predefined options and dynamic filename generation. Users can download their CV in different formats through an interactive modal interface with an intuitive, clear naming convention.
2025-11-20 11:21:43 +00:00
## 🎯 Architecture: Dual Rendering Modes
**Critical Design Decision ** : The PDF generator uses **different media emulation ** depending on the version:
### Clean Version (Short CV) - Print Mode
- **Uses**: `@media print` CSS rules (default browser print behavior)
- **Purpose**: Print-friendly, minimal layout
- **Characteristics**:
- No skills sidebars (hidden by print.css)
- No UI elements (navigation, buttons, footer hidden by print.css)
- Print-optimized typography, margins, and page breaks
- ~4 pages, ~2.2 MB
- Clean, professional print appearance
### Extended Version (Long CV) - Screen Mode + UI Hiding
- **Uses**: `@media screen` CSS (emulated via CDP) + injected CSS to hide UI elements
- **Purpose**: Pixel-perfect screen capture with content sidebars, minus UI chrome
- **Characteristics**:
- Shows skills sidebars (screen layout preserved)
- No UI elements (hidden via injected CSS: `.action-bar` , `.navigation-menu` , `footer` , etc.)
- Uses natural screen layout (pixel-perfect rendering)
- ~16 pages, ~3.6 MB
- Full digital experience without UI chrome
**Why This Approach? **
1. **Clean version ** needs print-optimized layout (compact, professional)
2. **Extended version ** needs pixel-perfect screen layout with sidebars visible
3. CDP's `emulation.SetEmulatedMedia()` allows switching between `@media print` and `@media screen`
4. Injected CSS surgically hides only UI elements without affecting content layout
**Implementation Details ** :
- `RenderModePrint` : Uses default `@media print` (hides sidebars + UI via print.css)
- `RenderModeScreen` :
1. Emulates `@media screen` to preserve natural layout
2. Injects CSS to hide UI: `.no-print, .action-bar, .navigation-menu, .hamburger-btn, footer, .back-to-top, .info-button, .info-modal, .error-toast, .cv-title-badges-header, .cv-footer`
3. Keeps all content and sidebars with their natural screen styling
**See ** : `internal/pdf/generator.go` lines 146-165 for implementation
2025-11-19 16:30:18 +00:00
## Feature Specifications
### Export Options
2025-11-19 17:40:06 +00:00
#### 1. Short CV (Clean Version - **Short**)
- **Length**: `short` (essential information only)
2025-11-19 16:30:18 +00:00
- **Version**: `clean` (no skills sidebar)
- **Page Count**: 4 pages
- **Use Case**: Job applications requiring concise CVs
2025-11-19 17:40:06 +00:00
- **Parameters**: `?lang={lang}&length=short&icons=show&version=clean`
- **Filename**: `cv-short-jamr-{year}-{lang}.pdf` * (version omitted for clean) *
2025-11-19 16:30:18 +00:00
#### 2. Long CV (Extended Version - **With Skills**)
- **Length**: `extended` (comprehensive information)
- **Version**: `with_skills` (includes skills sidebar)
2025-11-20 11:21:43 +00:00
- **Page Count**: ~16 pages (natural screen layout with sidebars preserved)
2025-11-19 16:30:18 +00:00
- **Use Case**: Detailed applications requiring full work history with skills showcase
2025-11-20 11:21:43 +00:00
- **Parameters**: `?lang={lang}&length=long&icons=show&version=with_skills`
2025-11-20 11:48:34 +00:00
- **Filename**: `cv-long-with-skills-jamr-{year}-{lang}.pdf`
2025-11-19 16:30:18 +00:00
#### 3. Current View
- **Length**: From localStorage (`cv-length` ) - mapped to new naming
- **Version**: From localStorage (`cv-theme` ) - mapped to new naming
- **Icons**: From localStorage (`cv-icons` )
- **Page Count**: Variable based on settings
- **Use Case**: Export exactly what's displayed on screen
- **Parameters**: Dynamic based on localStorage with automatic mapping
### Naming Convention - Clear and Descriptive
All exported PDFs follow a consistent, intuitive naming convention:
```
cv-{length}[-{version}]-{initials}-{year}-{lang}.pdf
WHERE:
2025-11-20 11:21:43 +00:00
{length} = short | long
2025-11-19 16:30:18 +00:00
{version} = OMITTED for clean | with_skills for extended
{initials} = User initials (e.g., "jamr")
{year} = Current year (2025)
{lang} = es | en
```
**Key Design Decision ** : Version is **OMITTED ** when it's "clean" to keep filenames concise and clear.
### Filename Examples
| Modal Option | Settings | Generated Filename |
|-------------|----------|-------------------|
2025-11-19 17:40:06 +00:00
| **Short CV ** | short + clean | `cv-short-jamr-2025-es.pdf` |
2025-11-20 11:48:34 +00:00
| **Long CV ** | long + with_skills | `cv-long-with-skills-jamr-2025-en.pdf` |
| **Current View ** | short + with_skills | `cv-short-with-skills-jamr-2025-es.pdf` |
2025-11-20 11:21:43 +00:00
| **Current View ** | long + clean | `cv-long-jamr-2025-en.pdf` |
2025-11-19 16:30:18 +00:00
### Comprehensive Combinations Matrix
| Length | Version | Filename Pattern |
|--------|---------|------------------|
2025-11-19 17:40:06 +00:00
| **short ** | clean | `cv-short-jamr-{year}-{lang}.pdf` |
2025-11-20 11:48:34 +00:00
| **short ** | with_skills | `cv-short-with-skills-jamr-{year}-{lang}.pdf` |
2025-11-20 11:21:43 +00:00
| **long ** | clean | `cv-long-jamr-{year}-{lang}.pdf` |
2025-11-20 11:48:34 +00:00
| **long ** | with_skills | `cv-long-with-skills-jamr-{year}-{lang}.pdf` |
2025-11-19 16:30:18 +00:00
### Dynamic Features
#### 1. Year Placeholder System
Static PDF URLs in JSON data files use a `{{YEAR}}` placeholder that's automatically replaced with the current year when the application loads.
**JSON Configuration: **
``` json
{
2025-11-19 17:40:06 +00:00
"url" : "https://juan.andres.morenorub.io/static/pdf/cv-short-jamr-{{YEAR}}-es.pdf"
2025-11-19 16:30:18 +00:00
}
```
**Runtime Replacement: **
- Handled by `LoadCV()` function in `internal/models/cv.go`
- Replaces `{{YEAR}}` with `time.Now().Year()`
- Ensures URLs always reference current year's PDFs
#### 2. Initials Extraction
Initials are dynamically generated from the user's full name:
``` go
nameParts := strings . Fields ( cv . Personal . Name )
initials := ""
for _ , part := range nameParts {
if len ( part ) > 0 {
initials += string ( [ ] rune ( part ) [ 0 ] )
}
}
initials = strings . ToLower ( initials )
```
**Example: ** "Juan Andrés Moreno Rubio" → "jamr"
#### 3. Legacy LocalStorage Mapping
For backwards compatibility, the system automatically maps old localStorage values to the new naming convention:
``` javascript
2025-11-19 17:40:06 +00:00
// Old → New mapping (for backwards compatibility)
2025-11-20 11:21:43 +00:00
'long' → 'long'
'long' ( theme ) → 'with_skills'
2025-11-19 17:40:06 +00:00
// Note: 'short' now stays as 'short' (no longer maps to 'detailed')
2025-11-19 16:30:18 +00:00
'clean' → 'clean' ( unchanged )
```
This ensures existing users' preferences continue to work seamlessly.
## Print-Friendly Design: Light Mode Only
### Critical Policy
**PDFs are ALWAYS generated in light mode, regardless of the user's color theme preference. **
This is a fundamental design decision for print-friendliness and readability:
- ✅ Light backgrounds with dark text for optimal printing
- ✅ Professional appearance in any context
- ✅ Reduced printer ink consumption
- ✅ Consistent output across all use cases
- ❌ Dark mode is NEVER used for PDF generation
### Multi-Layer Enforcement
The system enforces light mode at THREE levels to guarantee print-friendly PDFs:
#### 1. Backend Cookie Enforcement
**File:** `internal/handlers/cv.go`
The PDF export handler ALWAYS sets the `color-theme` cookie to "light":
``` go
// CRITICAL: ALWAYS force light mode for PDF generation (print-friendly)
// This ensures PDFs are NEVER generated in dark mode, regardless of user's preference
cookies [ "color-theme" ] = "light"
```
This ensures the browser context used for PDF generation starts with light mode enabled.
#### 2. CSS Print Media Query Override
**File:** `static/css/08-contexts/_print.css`
The print stylesheet forcibly overrides ALL color theme CSS variables:
``` css
@ media print {
/* CRITICAL: FORCE LIGHT MODE FOR ALL PDFs */
/* PDF generation MUST ALWAYS use light mode colors */
/* This overrides ANY color theme (dark/auto/light) */
* ,
: root ,
[ data-color-theme = "dark" ] ,
[ data-color-theme = "auto" ] ,
[ data-color-theme = "light" ] ,
html ,
body {
--page-bg : #b8bbbe !important ;
--paper-bg : #ffffff !important ;
--text-primary : #1a1a1a !important ;
--text-secondary : #333333 !important ;
/* ... all light mode variables ... */
}
}
```
This ensures that even if JavaScript fails or cookies don't propagate, the CSS will force light mode colors during print/PDF generation.
#### 3. Print Color Accuracy
The print stylesheet also includes critical browser directives:
``` css
@ media print {
* {
-webkit- print-color-adjust : exact !important ;
print-color-adjust : exact !important ;
color-adjust : exact !important ;
}
}
```
This ensures browsers render colors exactly as specified, preventing automatic adjustments that could affect print quality.
### Why Three Layers?
This defense-in-depth approach guarantees light mode PDFs even if:
- Cookies fail to set or propagate
- JavaScript doesn't execute properly
- Browser preferences override theme settings
- CSS cascade issues occur
**Result: ** Bulletproof light mode enforcement for all PDF exports.
## Technical Implementation
### File Structure
```
/Users/txeo/Git/yo/cv/
├── internal/
│ ├── handlers/
│ │ └── cv.go # PDF export handler with filename generation
│ └── models/
│ └── cv.go # CV data loading with year placeholder replacement
├── templates/
│ └── partials/
│ └── modals/
│ └── pdf-modal.html # Interactive PDF download modal
├── static/
│ ├── css/
│ │ └── 04-interactive/
│ │ └── _remaining.css # Modal styling (red theme for PDF)
│ └── pdf/
2025-11-19 17:40:06 +00:00
│ ├── cv-short-jamr-2025-es.pdf
│ ├── cv-short-jamr-2025-en.pdf
2025-11-20 11:48:34 +00:00
│ ├── cv-long-with-skills-jamr-2025-es.pdf
│ └── cv-long-with-skills-jamr-2025-en.pdf
2025-11-19 16:30:18 +00:00
├── data/
│ ├── cv-es.json # Spanish CV data with {{YEAR}} placeholders
│ └── cv-en.json # English CV data with {{YEAR}} placeholders
└── tests/
└── mjs/
├── 14-pdf-modal.test.mjs # Modal UI and interaction tests
└── 24-pdf-download-params.test.mjs # Parameter validation tests
```
### Backend: Parameter Validation and Filename Generation
**File: ** `internal/handlers/cv.go`
**Parameter Validation: **
``` go
2025-11-20 11:21:43 +00:00
// Length parameter: "short" or "long"
2025-11-19 16:30:18 +00:00
length := r . URL . Query ( ) . Get ( "length" )
if length == "" {
2025-11-19 17:40:06 +00:00
length = "short"
2025-11-19 16:30:18 +00:00
}
2025-11-20 11:21:43 +00:00
if length != "short" && length != "long" {
HandleError ( w , r , BadRequestError ( "Unsupported length. Use 'short' or 'long'" ) )
2025-11-19 16:30:18 +00:00
return
}
// Version parameter: "clean" or "with_skills"
version := r . URL . Query ( ) . Get ( "version" )
if version == "" {
version = "with_skills"
}
if version != "with_skills" && version != "clean" {
HandleError ( w , r , BadRequestError ( "Unsupported version. Use 'with_skills' or 'clean'" ) )
return
}
```
**Filename Generation: **
``` go
// Generate initials from name
nameParts := strings . Fields ( cv . Personal . Name )
initials := ""
for _ , part := range nameParts {
if len ( part ) > 0 {
initials += string ( [ ] rune ( part ) [ 0 ] )
}
}
initials = strings . ToLower ( initials )
// Get current year
currentYear := time . Now ( ) . Year ( )
// Build filename: cv-{length}[-{version}]-{initials}-{year}-{lang}.pdf
// Omit version if it's "clean"
var filename string
if version == "clean" {
filename = fmt . Sprintf ( "cv-%s-%s-%d-%s.pdf" , length , initials , currentYear , lang )
} else {
filename = fmt . Sprintf ( "cv-%s-%s-%s-%d-%s.pdf" , length , version , initials , currentYear , lang )
}
```
**Examples: **
2025-11-19 17:40:06 +00:00
- short + clean → `cv-short-jamr-2025-es.pdf`
2025-11-20 11:48:34 +00:00
- long + with_skills → `cv-long-with-skills-jamr-2025-en.pdf`
2025-11-19 16:30:18 +00:00
### Frontend: Modal Interaction with Legacy Mapping
**File: ** `templates/partials/modals/pdf-modal.html`
``` javascript
function downloadPDF ( ) {
const selectedCard = document . querySelector ( '#pdf-modal .pdf-option-card.selected' ) ;
const selectedFormat = selectedCard . getAttribute ( 'data-cv-format' ) ;
const lang = '{{.Lang}}' ;
let url ;
if ( selectedFormat === 'short' ) {
2025-11-19 17:40:06 +00:00
// Short CV: clean version (no skills), short length
url = ` /export/pdf?lang= ${ lang } &length=short&icons=show&version=clean ` ;
2025-11-19 16:30:18 +00:00
} else if ( selectedFormat === 'long' ) {
// Long CV: with skills sidebar, extended length
2025-11-20 11:21:43 +00:00
url = ` /export/pdf?lang= ${ lang } &length=long&icons=show&version=with_skills ` ;
2025-11-19 16:30:18 +00:00
} else if ( selectedFormat === 'current' ) {
// Current view: use localStorage settings with mapping
let currentLength = localStorage . getItem ( 'cv-length' ) || 'short' ;
// Map old values to new naming convention
2025-11-20 11:21:43 +00:00
if ( currentLength === 'long' ) currentLength = 'long' ;
2025-11-19 17:40:06 +00:00
// 'short' stays as 'short' - no mapping needed
2025-11-19 16:30:18 +00:00
const currentIcons = localStorage . getItem ( 'cv-icons' ) || 'show' ;
const currentTheme = localStorage . getItem ( 'cv-theme' ) || 'default' ;
const version = currentTheme === 'clean' ? 'clean' : 'with_skills' ;
url = ` /export/pdf?lang= ${ lang } &length= ${ currentLength } &icons= ${ currentIcons } &version= ${ version } ` ;
}
window . location . href = url ;
}
```
## Testing
### Test Suite
#### 1. Modal UI Test: `14-pdf-modal.test.mjs`
Tests the modal interface and user interactions:
- ✅ Modal structure (3 thumbnail cards, download button)
- ✅ Card selection behavior (radio button pattern)
- ✅ Download button enable/disable logic
- ✅ Keyboard navigation (Tab, Enter, Space, ESC)
- ✅ Accessibility (ARIA attributes, screen reader support)
- ✅ Responsive layout (mobile/tablet/desktop)
- ✅ Multilingual support (EN/ES)
#### 2. Parameter Validation Test: `24-pdf-download-params.test.mjs`
Tests PDF export parameters and filename generation:
2025-11-19 17:40:06 +00:00
- ✅ Short CV parameters: `length=short&version=clean`
2025-11-20 11:21:43 +00:00
- ✅ Long CV parameters: `length=long&version=with_skills`
2025-11-19 16:30:18 +00:00
- ✅ Current View parameters: reads from localStorage with mapping
- ✅ Filename format: `cv-{length}[-{version}]-{initials}-{year}-{lang}.pdf`
- ✅ Version omitted for clean
- ✅ Dynamic year generation
2025-11-19 17:40:06 +00:00
- ✅ Legacy value mapping (long→extended)
2025-11-19 16:30:18 +00:00
**Run Tests: **
``` bash
# Run modal UI tests
bun tests/mjs/14-pdf-modal.test.mjs
# Run parameter validation tests
bun tests/mjs/24-pdf-download-params.test.mjs
# Run all tests
bun tests/run-all.mjs
```
### Manual Testing Checklist
- [ ] Open PDF modal from download button
- [ ] Select each of the three options
- [ ] Verify download button enables after selection
- [ ] Click download button for each option
- [ ] Verify correct filename generated (check version omission for clean)
- [ ] Check PDF opens correctly
- [ ] Test in Spanish and English
- [ ] Test on mobile/tablet/desktop
- [ ] Verify keyboard navigation (Tab, Enter, ESC)
- [ ] Test with different localStorage settings for Current View
- [ ] Verify legacy localStorage values map correctly
## API Endpoint
### `/export/pdf`
**Method: ** GET
**Parameters: **
- `lang` (optional, default: "en"): `es` or `en`
2025-11-19 17:40:06 +00:00
- `length` (optional, default: "short"): `short` or `extended`
2025-11-19 16:30:18 +00:00
- `icons` (optional, default: "show"): `show` or `hide`
- `version` (optional, default: "with_skills"): `clean` or `with_skills`
**Response: **
- **Success**: PDF file with appropriate filename and `Content-Disposition` header
- **Rate Limited**: `429 Too Many Requests` with "Rate limit exceeded" message
- **Error**: `400 Bad Request` for invalid parameters, `500 Internal Server Error` for generation failures
**Example Requests: **
``` bash
2025-11-19 17:40:06 +00:00
# Short clean CV in Spanish
curl -O http://localhost:1999/export/pdf?lang= es& length = short& icons = show& version = clean
# Filename: cv-short-jamr-2025-es.pdf
2025-11-19 16:30:18 +00:00
# Extended with skills CV in English
2025-11-20 11:21:43 +00:00
curl -O http://localhost:1999/export/pdf?lang= en& length = long& icons = show& version = with_skills
2025-11-20 11:48:34 +00:00
# Filename: cv-long-with-skills-jamr-2025-en.pdf
2025-11-19 16:30:18 +00:00
```
## Design Philosophy
### Why This Naming Convention?
2025-11-20 11:21:43 +00:00
1. **Clarity ** : "short" and "long" clearly communicate content depth
2025-11-19 16:30:18 +00:00
2. **Simplicity ** : Version omitted for clean keeps filenames concise
3. **Consistency ** : All components follow the same pattern
4. **Intuitive ** : Non-technical users can understand what each filename means
5. **Professional ** : Matches industry standards for document naming
### Old vs New Comparison
| Old Naming | New Naming | Improvement |
|-----------|------------|-------------|
2025-11-19 17:40:06 +00:00
| `cv-detailed-jamr-2025-es.pdf` (v1) | `cv-short-jamr-2025-es.pdf` | Simpler, more intuitive |
2025-11-20 11:48:34 +00:00
| `cv-long-extended-en-jamr-2025.pdf` | `cv-long-with-skills-jamr-2025-en.pdf` | More descriptive, better clarity |
2025-11-19 16:30:18 +00:00
| Language before year | Language after year | Better organization |
## Maintenance
### Updating Static PDFs
To regenerate static PDFs referenced in JSON files:
``` bash
2025-11-19 17:40:06 +00:00
# Short + clean (version omitted)
curl -o static/pdf/cv-short-jamr-2025-es.pdf \
"http://localhost:1999/export/pdf?lang=es&length=short&icons=show&version=clean"
2025-11-19 16:30:18 +00:00
2025-11-19 17:40:06 +00:00
curl -o static/pdf/cv-short-jamr-2025-en.pdf \
"http://localhost:1999/export/pdf?lang=en&length=short&icons=show&version=clean"
2025-11-19 16:30:18 +00:00
# Extended + with_skills
2025-11-20 11:48:34 +00:00
curl -o static/pdf/cv-long-with-skills-jamr-2025-es.pdf \
2025-11-20 11:21:43 +00:00
"http://localhost:1999/export/pdf?lang=es&length=long&icons=show&version=with_skills"
2025-11-19 16:30:18 +00:00
2025-11-20 11:48:34 +00:00
curl -o static/pdf/cv-long-with-skills-jamr-2025-en.pdf \
2025-11-20 11:21:43 +00:00
"http://localhost:1999/export/pdf?lang=en&length=long&icons=show&version=with_skills"
2025-11-19 16:30:18 +00:00
```
### Year Rollover
The system automatically handles year rollovers:
- **Filename Generation**: Uses `time.Now().Year()` at runtime
- **URL Placeholders**: `{{YEAR}}` replaced during data load
- **No Manual Updates Required**: All year references are dynamic
## Troubleshooting
### Wrong Filename Format
**Symptom ** : Filename doesn't match `cv-{length}[-{version}]-{initials}-{year}-{lang}.pdf`
**Solutions: **
1. Verify backend logic in `internal/handlers/cv.go` lines 306-313
2. Check version is correctly omitted for clean
3. Ensure language appears at the end
4. Verify year extraction logic
### Legacy localStorage Not Mapping
**Symptom ** : Old localStorage values (`short` , `long` ) not working in Current View
**Solutions: **
1. Check JavaScript mapping logic in `pdf-modal.html` lines 257-260
2. Verify mapping: `short` → `detailed` , `long` → `extended`
3. Test with console.log to see actual values
4. Clear localStorage and test with fresh values
### Parameter Validation Errors
**Symptom ** : 400 Bad Request when downloading PDF
**Solutions: **
2025-11-19 17:40:06 +00:00
1. Check allowed values: `length` ∈ {short, extended}, `version` ∈ {clean, with_skills}
2025-11-19 16:30:18 +00:00
2. Verify frontend sends correct parameters
3. Check browser network tab for actual request
4. Run parameter validation test: `bun tests/mjs/24-pdf-download-params.test.mjs`
### PDF Generated in Dark Mode
**Symptom ** : Downloaded PDF has dark background and light text
**Root Cause ** : Light mode enforcement not working at one or more layers
**Solutions: **
1. **Verify Backend Cookie Setting ** (`internal/handlers/cv.go:260-270` ):
```go
// Ensure this line exists:
cookies["color-theme"] = "light"
` ``
- Check server logs for cookie setting
- Restart server if code was recently updated
2. **Verify CSS Print Overrides** (` static/css/08-contexts/_print.css:4-60`):
` ``css
@media print {
/* Verify this section exists at the top */
*,
:root,
[data-color-theme="dark"],
[data-color-theme="auto"] {
--paper-bg: #ffffff !important;
--text-primary: #1a1a1a !important;
/* ... all light mode variables ... */
}
}
` ``
- Clear browser cache
- Check CSS file is being served correctly
3. **Test with Browser Dev Tools**:
- Open page in browser
- Enable dark mode
- Open Print Preview (Cmd/Ctrl+P)
- Verify preview shows light background
- If preview is dark, CSS overrides aren't working
4. **Manual Testing**:
` ``bash
# Set user to dark mode
# Then generate PDF
2025-11-19 17:40:06 +00:00
curl -O "http://localhost:1999/export/pdf?lang=es&length=short&version=clean"
2025-11-19 16:30:18 +00:00
# Open PDF and verify it has white background
` ``
**Expected Result**: PDF ALWAYS has white background (` #ffffff `) and dark text (` #1a1a1a `), regardless of user's color theme preference.
---
## Changelog
### Version 2.0.0 (2025-11-19) - New Naming Convention & Light Mode Enforcement
**🎯 Major Changes:**
#### 1. Complete Naming Convention Overhaul
2025-11-19 17:40:06 +00:00
- **BREAKING**: Changed from ` short/long` to ` short/extended` for better clarity
2025-11-19 16:30:18 +00:00
- **BREAKING**: Changed from ` extended` (theme) to ` with_skills` for better clarity
- **NEW**: Version omitted from filename when ` clean` (no skills sidebar)
- **NEW**: Language moved to end: ` cv-{length}[-{version}]-{initials}-{year}-{lang}.pdf`
2025-11-19 17:40:06 +00:00
- **UPDATE**: Originally used ` detailed/extended`, changed to ` short/extended` for better contrast
2025-11-19 16:30:18 +00:00
**Old Naming Examples:**
2025-11-19 17:40:06 +00:00
- ❌ ` cv-short-clean-es-jamr-2025.pdf` (v0)
- ❌ ` cv-long-extended-en-jamr-2025.pdf` (v0)
2025-11-19 16:30:18 +00:00
**New Naming Examples:**
2025-11-19 17:40:06 +00:00
- ✅ ` cv-short-jamr-2025-es.pdf` (version omitted, final naming)
2025-11-20 11:48:34 +00:00
- ✅ ` cv-long-with-skills-jamr-2025-en.pdf` (version included)
2025-11-19 16:30:18 +00:00
#### 2. Light Mode Enforcement - Defense in Depth
**CRITICAL**: PDFs are now GUARANTEED to always use light mode, regardless of user's color theme preference.
**Three-Layer Protection:**
1. **Backend Cookie Enforcement** (` internal/handlers/cv.go:262-264`)
- Forces ` color-theme=light` cookie for all PDF generation requests
- Ensures browser context starts in light mode
2. **CSS Print Media Query Override** (` static/css/08-contexts/_print.css:4-60`)
- Forcibly overrides ALL CSS variables to light mode values
- Uses ` !important` to override any theme settings
- Applies to ` :root`, ` [data-color-theme="dark"]`, ` [data-color-theme="auto"]`
3. **Browser Print Directives** (` static/css/08-contexts/_print.css:65-69`)
- ` print-color-adjust: exact !important`
- Ensures browsers render colors exactly as specified
**Why Three Layers?**
- Guarantees light mode even if cookies fail, JavaScript doesn't execute, or browser settings override
- Provides optimal print quality and reduced ink consumption
- Professional appearance in all contexts
#### 3. Backend Improvements
- **Added**: ` time` package import to ` internal/models/cv.go` for year placeholder system
- **Updated**: Parameter validation for new naming (` detailed`/` extended`, ` clean`/` with_skills`)
- **Updated**: Filename generation logic with conditional version inclusion
#### 4. Frontend Updates
- **Updated**: PDF modal JavaScript to use new parameters
- **Added**: Legacy localStorage mapping for backwards compatibility
- ` long` → ` extended`
- ` extended` (theme) → ` with_skills`
2025-11-19 17:40:06 +00:00
- Note: ` short` now stays as ` short` (no longer maps to ` detailed`)
2025-11-19 16:30:18 +00:00
#### 5. Static Assets
- **Generated**: 4 new PDFs with correct naming convention (2.2 MB each)
2025-11-19 17:40:06 +00:00
- ` cv-short-jamr-2025-es.pdf` (updated from ` cv-detailed`)
- ` cv-short-jamr-2025-en.pdf` (updated from ` cv-detailed`)
2025-11-20 11:48:34 +00:00
- ` cv-long-with-skills-jamr-2025-es.pdf`
- ` cv-long-with-skills-jamr-2025-en.pdf`
2025-11-19 17:40:06 +00:00
- **Removed**: Old PDFs with deprecated naming (` cv-detailed-*`)
2025-11-19 16:30:18 +00:00
#### 6. Documentation
- **NEW**: "Print-Friendly Design: Light Mode Only" section
- **NEW**: Comprehensive multi-layer enforcement explanation
- **NEW**: Troubleshooting section for dark mode PDF issues
- **Updated**: All examples to use new naming convention
- **Updated**: Filename combinations matrix
- **Updated**: Design philosophy section
#### 7. Tests
- **Updated**: ` 24-pdf-download-params.test.mjs` with new parameter expectations
- **Note**: Test needs adjustments to handle PDF downloads (doesn't navigate page)
**Migration Path for Existing Users: **
- Old localStorage values automatically map to new naming
- No user action required - seamless transition
- Old PDFs can be regenerated with new naming using provided curl commands
**Benefits: **
- ✅ Clearer, more intuitive naming convention
- ✅ More professional filename format
- ✅ Bulletproof light mode enforcement
- ✅ Better print quality and ink efficiency
- ✅ Backwards compatible with existing user preferences
- ✅ Comprehensive documentation and troubleshooting
---
**Last Updated ** : 2025-11-19
**Version ** : 2.0.0 (New Naming Convention + Light Mode Enforcement)
**Status ** : Production ✅
**Maintainer ** : Development Team