# Customization Guide **Note**: This is my personal CV website. While the code is open-source (MIT license), this guide documents my own customizations. The site is subject to modifications without notice, and I don't intend for others to use this as a template - it's publicly available code, but it's designed for my personal use. ## Table of Contents - [Introduction](#introduction) - [Prerequisites](#prerequisites) - [Quick Customization](#quick-customization) - [Content Customization](#content-customization) - [Personal Information](#personal-information) - [JSON Schema Explained](#json-schema-explained) - [Adding/Removing Sections](#addingremoving-sections) - [Visual Customization](#visual-customization) - [Colors & Fonts](#colors--fonts) - [Layout Changes](#layout-changes) - [Branding](#branding) - [Template Customization](#template-customization) - [Analytics Configuration](#analytics-configuration) - [Option 1: Configure Your Own Analytics](#option-1-configure-your-own-analytics) - [Option 2: Remove Analytics Entirely](#option-2-remove-analytics-entirely) - [Option 3: Use Alternative Analytics Service](#option-3-use-alternative-analytics-service) - [Option 3: Use Alternative Analytics](#option-3-use-google-analytics-or-other-service) - [Advanced Customization](#advanced-customization) - [Testing Your Changes](#testing-your-changes) - [Examples](#examples) --- ## Introduction This CV/Resume application is designed to be easily customizable. You can adapt it for your own CV by modifying JSON files, templates, and styles without deep Go programming knowledge. **Architecture Overview**: - **Data**: JSON files (`data/cv-en.json`, `data/cv-es.json`) - **Models**: Go structs (`internal/models/cv.go`) define data structure - **Templates**: Go HTML templates (`templates/*.html`) render the CV - **Styles**: CSS (`static/css/main.css`) controls appearance - **Assets**: Images, fonts (`static/` directory) **Customization Levels**: 1. **Basic**: Edit JSON files only (name, experience, skills) 2. **Intermediate**: Modify CSS styles and add images 3. **Advanced**: Change templates and Go models --- ## Prerequisites ### Knowledge Requirements - **Basic**: JSON syntax, file editing - **Intermediate**: HTML/CSS, command-line basics - **Advanced**: Go templates, basic Go programming ### Tools Needed - **Text editor**: VS Code, Sublime Text, or any editor - **Go 1.25.1+**: For building and testing (see [DEPLOYMENT.md](DEPLOYMENT.md)) - **Git**: For version control (optional but recommended) - **Browser**: For testing (Chrome/Firefox recommended) ### Optional Tools - **JSON validator**: [JSONLint](https://jsonlint.com/) - **Image editor**: For logo/photo preparation - **Make**: For using Makefile commands --- ## Quick Customization **Get started in 5 minutes**: ```bash # 1. Clone or download the project git clone https://github.com/juanatsap/cv-site.git my-cv cd my-cv # 2. Edit your information nano data/cv-en.json # or use your favorite editor # 3. Replace your photo cp ~/my-photo.jpg static/images/profile/dni.jpeg # 4. Test locally make dev # 5. Open browser open http://localhost:1999 ``` **Minimal changes to make it yours**: 1. Replace `personal` section in `data/cv-en.json` 2. Replace `summary` section 3. Replace `experience` section with your jobs 4. Replace `education` section 5. Update `skills` section 6. Replace profile photo 7. **Configure or remove analytics** (see [Analytics Configuration](#analytics-configuration) below) --- ## Content Customization ### Personal Information Edit `data/cv-en.json` (and `data/cv-es.json` for Spanish): **Location**: Top of JSON file, `personal` object ```json { "personal": { "name": "Your Full Name", "title": "Your Professional Title", "location": "Your City, Country", "email": "your.email@example.com", "phone": "+1 234 567 8900", "dateOfBirth": "1990-01-01", "placeOfBirth": "Your Birthplace", "citizenship": "Your Nationality", "linkedin": "https://www.linkedin.com/in/your-profile", "github": "https://github.com/yourusername", "domestika": "https://www.domestika.org/en/yourusername", "website": "https://yourwebsite.com", "photo": "/static/images/profile.jpg" } } ``` **Field Descriptions**: - `name`: Full name (displayed prominently) - `title`: Job title or professional tagline - `location`: Current location - `email`: Contact email (clickable in CV) - `phone`: Phone number with country code - `dateOfBirth`: Birth date (YYYY-MM-DD format) - `placeOfBirth`: Birthplace - `citizenship`: Nationality/citizenship - `linkedin`, `github`, `domestika`, `website`: Social/professional links - `photo`: Path to profile photo (relative to project root) **Tips**: - Use **consistent formatting** across English and Spanish versions - Keep URLs **absolute** (include `https://`) - Use **international phone format** (+XX XXX XXX XXXX) - Photo should be **square** (400x400px minimum) for best results --- ### JSON Schema Explained The CV data follows a structured schema. Each section has specific fields. #### Summary Section ```json { "summary": "Your professional summary. 2-3 sentences highlighting your expertise, experience, and career goals. This appears prominently at the top of your CV." } ``` **Tips**: - Keep it **concise** (100-150 words) - Highlight **key achievements** and expertise - Tailor to your **target audience** --- #### Experience Section **Structure**: ```json { "experience": [ { "position": "Job Title", "company": "Company Name", "companyURL": "https://company.com", // Optional "companyLogo": "company-logo.png", // Optional, in static/images/companies/ "location": "City, Country", "startDate": "2020-01", // YYYY-MM format "endDate": "2023-06", // Or "present" "current": false, // true if still working here "expired": false, // Optional, true if company closed "shortDescription": "Brief one-line summary for compact view", "responsibilities": [ "Responsibility or achievement 1", "Responsibility or achievement 2", "Use bullet points for clarity" ], "technologies": [ "Technology 1", "Technology 2", "List relevant tech stack" ], "highlights": [ // Optional "Major achievement 1", "Major achievement 2" ] } ] } ``` **Field Details**: - `position`: Job title - `company`: Company name - `companyURL`: Company website (optional, makes company name clickable) - `companyLogo`: Logo filename (place in `static/images/companies/`) - `location`: Office location - `startDate`: Start date (YYYY-MM format) - `endDate`: End date or `"present"` for current job - `current`: Boolean, `true` if still employed - `expired`: Boolean, `true` if company no longer exists (grays out logo) - `shortDescription`: One-liner for short CV version (HTML allowed) - `responsibilities`: Array of bullet points (HTML allowed) - `technologies`: Array of technologies used - `highlights`: Optional array of major achievements **HTML in Descriptions**: You can use HTML tags: ```json "shortDescription": "Led development of major platform serving 1M+ users." ``` **Adding Company Logos**: 1. Place logo in `static/images/companies/` 2. Reference filename in `companyLogo` field 3. Recommended size: 100x100px, PNG with transparency 4. Fallback icon appears if logo missing --- #### Education Section ```json { "education": [ { "degree": "Bachelor's Degree in Computer Science", "institution": "University Name", "location": "City, Country", "startDate": "2015-09", "endDate": "2019-06", "field": "Computer Science and Engineering" } ] } ``` **Tips**: - List **highest degree first** - Include **relevant coursework** in degree name if needed - Use `field` for specialization --- #### Skills Section **Two types**: Technical skills (with sidebar placement) and soft skills ```json { "skills": { "technical": [ { "category": "Programming Languages", "proficiency": 5, // 1-5 scale (not displayed, for internal use) "sidebar": "left", // "left" or "right" or omit for main content "items": [ "JavaScript (ES6+)", "Python", "Go" ] }, { "category": "Frontend Technologies", "proficiency": 5, "sidebar": "left", "items": [ "React", "HTMX", "CSS3" ] } ], "soft_skills": [ "Leadership & Team Management", "Problem-Solving", "Communication" ] } } ``` **Sidebar Layout**: - **Left sidebar**: Skills displayed on page 1 left side - **Right sidebar**: Skills displayed on page 2 right side - **No sidebar**: Skills displayed in main content area **Organizing Skills**: 1. Group by **category** (e.g., "Programming Languages", "Databases") 2. Order by **importance** (most important categories first) 3. Use **specific names** (e.g., "PostgreSQL" not just "SQL") 4. Balance **left/right sidebars** for visual symmetry --- #### Projects Section ```json { "projects": [ { "title": "Full Project Title", // Used if no projectName "projectName": "Project Name", // Optional: linkable part "projectDesc": "Project Description", // Optional: non-linkable part "url": "https://project.com", // Optional "projectLogo": "project-logo.png", // Optional, in static/images/projects/ "gitRepoUrl": "/path/to/local/repo", // Optional: for dynamic dates "location": "City or 'Online'", "startDate": "2023", // Optional: YYYY or YYYY-MM "current": true, // true if ongoing "maintainedBy": "Company Name", // Optional: if transferred "technologies": [ "Tech 1", "Tech 2" ], "shortDescription": "One-line project summary", "responsibilities": [ "What you built or contributed", "Use bullet points" ] } ] } ``` **Dynamic Dates** (Advanced): - Use `gitRepoUrl` to point to a local git repository - Application will extract first commit date as start date - Useful for open-source projects where you want automatic dating **Project Logos**: - Place in `static/images/projects/` - Recommended: 80x80px, PNG with transparency --- #### Languages Section ```json { "languages": [ { "language": "English", "proficiency": "Native", "level": 5, // 1-5 scale (not displayed) "detail": "Oral (Advanced) Written (Advanced)" // Optional }, { "language": "Spanish", "proficiency": "Professional Working Proficiency", "level": 4 } ] } ``` **Proficiency Levels** (suggested): - Native - Bilingual/Fluent - Professional Working Proficiency - Limited Working Proficiency - Elementary/Comprehension --- #### Courses/Certifications Sections **Courses**: ```json { "courses": [ { "title": "Course Title", "institution": "Platform or Institution", "courseLogo": "platform-logo.png", // Optional, in static/images/courses/ "location": "Online or City", "date": "2024-03", // YYYY-MM or range "duration": "40 hours", "shortDescription": "Brief course overview", "responsibilities": [ // Optional: detailed course content "Topic 1 covered", "Topic 2 covered" ] } ] } ``` **Certifications**: ```json { "certifications": [ { "name": "Certification Name", "issuer": "Issuing Organization", "date": "2024-01", "description": "What this certification covers" } ] } ``` **Course Logos**: - Place in `static/images/courses/` - Examples: Codecademy, LinkedIn Learning, Coursera logos --- #### Awards Section ```json { "awards": [ { "title": "Award Title", "issuer": "Issuing Organization", "date": "09 2023", // MM YYYY format "shortDescription": "Brief description of award", "responsibilities": [ // Optional: what you did to earn it "Achievement 1", "Achievement 2" ], "awardLogo": "award-logo.png" // Optional, in static/images/companies/ } ] } ``` --- #### References Section ```json { "references": [ { "title": "Full reference text", "url": "https://example.com", "type": "recommendation", // recommendation, portfolio, profile, cv, presentation "textBefore": "Text before link", // Optional "linkText": "Clickable text", // Optional: bold linked text "textAfter": "text after link" // Optional } ] } ``` **Types**: - `recommendation`: Reference letters - `portfolio`: Online portfolio - `profile`: LinkedIn, GitHub, etc. - `cv`: Other CV versions - `presentation`: Presentation letters **Example rendering**: ``` Text before link Clickable text text after link ``` --- #### Other Section ```json { "other": { "driverLicense": "Type B" // HTML allowed } } ``` Add any miscellaneous information here. --- #### Meta Section ```json { "meta": { "version": "2025-11-09", // Your CV version "lastUpdated": "2025-11-08", // Last update date "format": "JSON Resume Extended", "language": "en" // "en" or "es" } } ``` Update `lastUpdated` when you make changes. --- ### Adding/Removing Sections #### Removing Sections **Option 1**: Empty the array/object ```json { "awards": [], "courses": [] } ``` Templates automatically hide empty sections. **Option 2**: Remove from template Edit `templates/cv-content.html` and delete the section: ```html {{if .CV.Awards}}
...
{{end}} ``` #### Adding New Sections **Step 1**: Add to JSON ```json { "volunteering": [ { "organization": "Organization Name", "role": "Volunteer Role", "startDate": "2020-01", "endDate": "present", "description": "What you did" } ] } ``` **Step 2**: Add to Go model (`internal/models/cv.go`) ```go type CV struct { // ... existing fields ... Volunteering []Volunteering `json:"volunteering"` } type Volunteering struct { Organization string `json:"organization"` Role string `json:"role"` StartDate string `json:"startDate"` EndDate string `json:"endDate"` Description string `json:"description"` } ``` **Step 3**: Add to template (`templates/cv-content.html`) ```html {{if .CV.Volunteering}}

{{if eq .Lang "es"}}Voluntariado{{else}}Volunteering{{end}}

{{range .CV.Volunteering}}
{{.Role}} - {{.Organization}}
{{.StartDate}} / {{.EndDate}}

{{.Description}}

{{end}}
{{end}} ``` **Step 4**: Rebuild and test ```bash make build make dev ``` --- ## Visual Customization ### Colors & Fonts **Location**: `static/css/main.css` #### Color Scheme **CSS Variables** (lines 6-15): ```css :root { --bg-gray: rgb(82, 86, 89); /* Page background */ --sidebar-gray: #d1d4d2; /* Sidebar background */ --black-bar: #2b2b2b; /* Top action bar */ --paper-white: #ffffff; /* CV paper background */ --text-dark: rgb(0, 0, 0); /* Main text */ --text-gray: rgb(51, 51, 51); /* Secondary text */ --accent-blue: #0066cc; /* Links and accents */ --border-gray: #dddddd; /* Borders */ } ``` **Changing Colors**: ```css /* Example: Blue theme */ :root { --bg-gray: #1a365d; /* Dark blue background */ --sidebar-gray: #e3f2fd; /* Light blue sidebar */ --accent-blue: #2196f3; /* Bright blue links */ --black-bar: #0d47a1; /* Deep blue bar */ } /* Example: Dark theme */ :root { --bg-gray: #1e1e1e; /* Dark background */ --sidebar-gray: #2d2d2d; /* Dark sidebar */ --paper-white: #252526; /* Dark paper */ --text-dark: #d4d4d4; /* Light text */ --text-gray: #9d9d9d; /* Gray text */ --accent-blue: #569cd6; /* Blue accent */ } ``` #### Fonts **Current fonts** (line 4): ```css @import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&family=Source+Sans+Pro:wght@400;600&family=Inter:wght@400;500;600;700&display=swap'); ``` **Changing fonts**: 1. **Choose fonts** from [Google Fonts](https://fonts.google.com/) 2. **Update import**: ```css @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Open+Sans:wght@400;600&display=swap'); ``` 3. **Update font family** (line 24): ```css body { font-family: 'Roboto', 'Open Sans', -apple-system, system-ui, sans-serif; } ``` 4. **Customize headings** (add after body): ```css h1, h2, h3 { font-family: 'Playfair Display', serif; } ``` **Font sizes**: ```css /* Increase base font size */ body { font-size: 18px; /* Default: 16px */ } /* Adjust specific elements */ .cv-name { font-size: 2.5rem; /* Larger name */ } .section-title { font-size: 1.3rem; /* Larger section titles */ } ``` --- ### Layout Changes #### Page Width **Current**: A4 paper size (210mm x 297mm) **Wider layout**: ```css .cv-page { max-width: 250mm; /* Wider (default: fit to A4) */ min-height: 330mm; /* Taller */ } ``` **US Letter**: ```css .cv-page { max-width: 8.5in; /* Letter width */ min-height: 11in; /* Letter height */ } ``` #### Grid Adjustments **Sidebar widths** (find `.page-content` section): ```css .page-content { display: grid; grid-template-columns: 200px 1fr; /* Left sidebar | Main */ } /* Page 2: Main | Right sidebar */ .page-2 .page-content { grid-template-columns: 1fr 200px; /* Make sidebar wider */ } ``` **Adjust gap**: ```css .page-content { gap: 25px; /* Space between sidebar and main (default: 20px) */ } ``` #### Responsive Breakpoints **Mobile view** (add to end of CSS): ```css @media screen and (max-width: 768px) { .page-content { grid-template-columns: 1fr; /* Stack vertically */ } .cv-sidebar { order: 2; /* Sidebars after main content */ } .cv-main { order: 1; /* Main content first */ } } ``` #### Print Styles **Current print styles** handle page breaks. Customize: ```css @media print { body { background: white; /* No background texture when printing */ } .cv-page { box-shadow: none; /* Remove shadow for printing */ margin: 0; page-break-after: always; } /* Hide elements when printing */ .action-bar { display: none; } /* Prevent page breaks inside elements */ .experience-item, .project-item { page-break-inside: avoid; } } ``` --- ### Branding #### Adding Your Logo **Step 1**: Prepare logo - Format: PNG with transparency preferred - Size: 200x80px (width x height) - Location: `static/images/logo.png` **Step 2**: Add to template (`templates/cv-content.html`) Add after header: ```html
``` **Step 3**: Style logo (`static/css/main.css`): ```css .cv-logo { text-align: center; margin-bottom: 20px; } .cv-logo img { max-width: 200px; height: auto; } ``` #### Favicon Replacement **Current favicon**: Browser tab icon **Replace**: 1. Create favicon (16x16, 32x32, 48x48 px) 2. Use [Favicon Generator](https://realfavicongenerator.net/) 3. Place in `static/images/favicon/` 4. Update in `templates/index.html`: ```html ``` #### Custom Icons **Current**: Using [Iconify](https://iconify.design/) **Change icons** in templates: ```html ``` **Use custom icons**: Replace with `` tags: ```html Icon ``` --- ## Template Customization Templates use **Go template syntax**. Basic knowledge helps but isn't required for simple changes. ### Go Template Syntax Basics **Variables**: ```html {{.CV.Personal.Name}} {{.CV.Personal.Email}} ``` **Conditionals**: ```html {{if .CV.Awards}}
Awards here
{{end}} {{if eq .Lang "es"}} Español {{else}} English {{end}} ``` **Loops**: ```html {{range .CV.Experience}}
{{.Position}} at {{.Company}}
{{end}} ``` **Safe HTML** (allow HTML in content): ```html {{.Description | safeHTML}} ``` ### Modifying cv-content.html **Location**: `templates/cv-content.html` #### Example: Change Section Order Move "Education" before "Experience": 1. Find Education section (around line 50) 2. Cut the entire `
...
` block 3. Paste it before the Experience section (line 86) #### Example: Change Header Layout **Current** (lines 35-46): ```html

Moreno Rubio, Juan Andrés

{{.YearsOfExperience}} years

{{.CV.Summary}}
{{.CV.Personal.Name}}
``` **Customize**: Change name format ```html

{{.CV.Personal.Name}}

``` **Add contact info to header**: ```html

{{.CV.Personal.Name}}

{{.CV.Personal.Email}} | {{.CV.Personal.Phone}}

{{.CV.Summary}}
``` #### Example: Custom Section Add "Hobbies" section: ```html

{{if eq .Lang "es"}}Aficiones{{else}}Hobbies{{end}}

``` Don't forget to add to JSON and Go model! ### Adding Custom Template Functions **Location**: `main.go` (where templates are loaded) **Example**: Add function to format dates ```go // In main.go, find template loading section and add: funcMap := template.FuncMap{ "safeHTML": func(s string) template.HTML { return template.HTML(s) }, "formatDate": func(dateStr string) string { // Parse YYYY-MM format and return "Month Year" parts := strings.Split(dateStr, "-") if len(parts) != 2 { return dateStr } months := []string{"", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} monthNum, _ := strconv.Atoi(parts[1]) return months[monthNum] + " " + parts[0] }, } tmpl := template.New("").Funcs(funcMap) ``` **Use in template**: ```html {{formatDate .StartDate}} / {{formatDate .EndDate}} ``` ### Conditional Rendering **Show section only in long version**: ```html
{{range .Responsibilities}}
  • {{. | safeHTML}}
  • {{end}}
    ``` **Show section only in short version**: ```html
    {{.ShortDescription | safeHTML}}
    ``` **Language-specific content**: ```html {{if eq .Lang "es"}}

    Contenido en español

    {{else}}

    Content in English

    {{end}} ``` --- ## Analytics Configuration This template includes a self-hosted analytics implementation as a learning example. You have three options: ### Option 1: Configure Your Own Analytics If you want to use self-hosted analytics: 1. **Set up your analytics server** (Matomo, Plausible, or similar) 2. **Update tracking code** in `templates/index.html`: ```javascript // Find this section near the end of the file var _paq = window._paq = window._paq || []; _paq.push(['trackPageView']); _paq.push(['enableLinkTracking']); var u="https://YOUR-ANALYTICS-SERVER.COM/"; // Replace this _paq.push(['setTrackerUrl', u+'matomo.php']); _paq.push(['setSiteId', 'YOUR-SITE-ID']); // Replace this ``` 3. **Update CSP headers** in `internal/middleware/security.go`: ```go // Find the CSP policy and update these directives: "script-src 'self' 'unsafe-inline' https://YOUR-ANALYTICS-SERVER.COM; " + "connect-src 'self' https://YOUR-ANALYTICS-SERVER.COM; " ``` 4. **Update PRIVACY.md** with your own privacy policy details ### Option 2: Remove Analytics Entirely If you don't want analytics: 1. **Remove tracking code** from `templates/index.html`: - Delete the entire `` section (usually at the end of ``) 2. **Simplify CSP headers** in `internal/middleware/security.go`: ```go // Remove analytics domains from: "script-src 'self' 'unsafe-inline'; " + // Remove analytics domain "connect-src 'self'; " // Remove analytics domain ``` 3. **Update PRIVACY.md**: - Simplify to state no tracking is used - Or remove the file if not needed ### Option 3: Use Alternative Analytics Service If you prefer Google Analytics, Plausible Cloud, or another service: 1. **Replace tracking code** in `templates/index.html` with your provider's script 2. **Update CSP headers** with your provider's domains 3. **Update PRIVACY.md** according to your provider's data handling 4. **Note**: External services may require additional privacy disclosures (GDPR, CCPA) ### Testing Analytics After configuration: ```bash # Start development server make dev # Visit site in browser open http://localhost:1999 # Check browser console for errors # Verify analytics requests in Network tab ``` **Security Note:** Always use HTTPS in production to protect analytics data in transit. ### Privacy Compliance **Important legal considerations:** - ✅ Add cookie consent banner if required in your jurisdiction - ✅ Create privacy policy explaining data collection - ✅ Provide opt-out mechanism - ✅ Comply with GDPR, CCPA, or local privacy laws - ✅ Update privacy policy when changing analytics providers **See [PRIVACY.md](PRIVACY.md) for privacy policy template.** --- ## Advanced Customization ### Adding New Languages (Beyond en/es) **Step 1**: Create JSON file ```bash cp data/cv-en.json data/cv-fr.json # Edit cv-fr.json with French content ``` **Step 2**: Update Go model validation (`internal/models/cv.go`) ```go func LoadCV(lang string) (*CV, error) { // Validate language if lang != "en" && lang != "es" && lang != "fr" { return nil, fmt.Errorf("unsupported language: %s", lang) } // ... rest of function } ``` **Step 3**: Add language selector to template ```html
    English | Español | Français
    ``` **Step 4**: Update all `{{if eq .Lang "es"}}` conditions: ```html {{if eq .Lang "es"}} Español {{else if eq .Lang "fr"}} Français {{else}} English {{end}} ``` ### Custom PDF Styles PDF generation uses same templates but renders with Chromium. Customize print styles: **Add to `static/css/main.css`**: ```css @media print { /* PDF-specific styles */ body { background: white; } .cv-page { box-shadow: none; margin: 0; } /* Custom PDF header/footer */ @page { margin: 20mm; @top-center { content: "Your Name - CV"; } @bottom-right { content: "Page " counter(page) " of " counter(pages); } } /* Prevent orphans/widows */ p, li { orphans: 3; widows: 3; } } ``` ### Additional Export Formats #### Adding Word Export **Step 1**: Install library ```bash go get github.com/nguyenthenguyen/docx ``` **Step 2**: Create export handler (new file `internal/export/docx.go`) ```go package export import ( "github.com/nguyenthenguyen/docx" "your-cv/internal/models" ) func GenerateDOCX(cv *models.CV, filename string) error { doc := docx.NewFile() // Add content doc.AddHeading(cv.Personal.Name, 1) doc.AddParagraph(cv.Summary) // Add experience for _, exp := range cv.Experience { doc.AddHeading(exp.Position + " - " + exp.Company, 2) for _, resp := range exp.Responsibilities { doc.AddListItem(resp) } } return doc.Save(filename) } ``` **Step 3**: Add route in `main.go` ```go http.HandleFunc("/download/docx", func(w http.ResponseWriter, r *http.Request) { lang := r.URL.Query().Get("lang") cv, _ := models.LoadCV(lang) filename := "/tmp/cv.docx" export.GenerateDOCX(cv, filename) w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.document") w.Header().Set("Content-Disposition", "attachment; filename=cv.docx") http.ServeFile(w, r, filename) }) ``` ### API Endpoints **Add JSON API** for CV data: **In `main.go`**: ```go // JSON API endpoint http.HandleFunc("/api/cv", func(w http.ResponseWriter, r *http.Request) { lang := r.URL.Query().Get("lang") if lang == "" { lang = "en" } cv, err := models.LoadCV(lang) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") // CORS json.NewEncoder(w).Encode(cv) }) ``` **Usage**: ```bash curl http://localhost:1999/api/cv?lang=en | jq . ``` ### Database Integration (Replacing JSON) **For dynamic CVs** that update frequently: **Step 1**: Choose database (PostgreSQL example) ```bash go get github.com/lib/pq ``` **Step 2**: Create database schema ```sql CREATE TABLE cv_data ( id SERIAL PRIMARY KEY, language VARCHAR(2) NOT NULL, content JSONB NOT NULL, updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_language ON cv_data(language); ``` **Step 3**: Update LoadCV function ```go func LoadCVFromDB(lang string, db *sql.DB) (*CV, error) { var content []byte err := db.QueryRow( "SELECT content FROM cv_data WHERE language = $1 ORDER BY updated_at DESC LIMIT 1", lang, ).Scan(&content) if err != nil { return nil, err } var cv CV if err := json.Unmarshal(content, &cv); err != nil { return nil, err } return &cv, nil } ``` **Step 4**: Add admin panel to update CV through web interface --- ## Testing Your Changes ### Local Testing Workflow **1. Make changes** ```bash # Edit JSON nano data/cv-en.json # Edit CSS nano static/css/main.css # Edit templates nano templates/cv-content.html ``` **2. Test immediately** (with hot-reload): ```bash # Start development server make dev # Or manually GO_ENV=development TEMPLATE_HOT_RELOAD=true go run main.go ``` **3. Open browser** ``` http://localhost:1999 http://localhost:1999?lang=es ``` **4. Check for errors** ```bash # Watch terminal for errors # Check browser console (F12) ``` ### Validating JSON **Online validators**: - [JSONLint](https://jsonlint.com/) - [JSON Formatter](https://jsonformatter.org/) **Command-line validation**: ```bash # Using Python python3 -m json.tool data/cv-en.json # Using jq jq . data/cv-en.json # Should output formatted JSON without errors ``` **Common JSON errors**: - Missing comma: `"name": "John" "title": "Dev"` ← Need comma - Trailing comma: `["item1", "item2",]` ← Remove last comma - Unescaped quotes: `"He said "hello""` ← Use `"He said \"hello\""` - Wrong brackets: `{...]` ← Mismatched brackets ### Browser Testing Checklist **Visual checks**: - [ ] Profile photo displays correctly - [ ] Company logos appear (or fallback icons) - [ ] All sections render - [ ] No overlapping text - [ ] Colors look correct - [ ] Links are clickable and work - [ ] Icons display (Iconify loaded) **Functionality checks**: - [ ] Language toggle works (`?lang=en` vs `?lang=es`) - [ ] Print view looks good (Cmd/Ctrl+P) - [ ] PDF download works - [ ] Long/short CV toggle works (if implemented) - [ ] Responsive on mobile (if implemented) **Testing commands**: ```bash # Test health endpoint curl http://localhost:1999/health # Test English version curl http://localhost:1999/?lang=en | head -100 # Test Spanish version curl http://localhost:1999/?lang=es | head -100 # Test PDF generation curl http://localhost:1999/download/pdf?lang=en --output test.pdf open test.pdf ``` ### PDF Export Verification **Check PDF quality**: ```bash # Generate PDF curl http://localhost:1999/download/pdf?lang=en --output cv.pdf # Open and verify open cv.pdf # macOS xdg-open cv.pdf # Linux start cv.pdf # Windows # Check file size ls -lh cv.pdf # Should be ~500KB - 2MB depending on images ``` **PDF checklist**: - [ ] All content visible (not cut off) - [ ] Page breaks in correct places - [ ] Images/logos render correctly - [ ] Links are clickable (if viewing digitally) - [ ] Text is selectable (not rasterized) - [ ] Colors accurate - [ ] No weird formatting issues **Debug PDF issues**: ```bash # If PDF generation fails, check: # 1. Chromium installed chromium-browser --version # 2. Chromium path which chromium-browser # 3. Set environment variable if needed export CHROME_BIN=/usr/bin/chromium-browser ``` --- ## Examples ### Example 1: Complete Personal Rebrand **Goal**: Replace all content with your own **Steps**: ```bash # 1. Backup original cp data/cv-en.json data/cv-en.json.backup # 2. Edit personal info nano data/cv-en.json # Update: personal, summary, experience, education, skills, projects # 3. Replace photo cp ~/my-headshot.jpg static/images/profile/dni.jpeg # 4. Update colors to match your brand nano static/css/main.css # Change: --accent-blue to your brand color # 5. Test make dev open http://localhost:1999 # 6. Generate PDF curl http://localhost:1999/download/pdf?lang=en --output my-cv.pdf ``` ### Example 2: Academic CV Style **Goal**: Convert to academic CV format **Changes needed**: 1. **Reorder sections**: Education first, then Publications, then Experience 2. **Add Publications section** (follow pattern from "Adding New Sections") 3. **Remove** "Projects" and "Awards" sections 4. **Change styling** to more conservative colors **CSS changes**: ```css :root { --accent-blue: #2c3e50; /* Conservative dark blue */ --bg-gray: #f4f4f4; /* Light background */ } body { font-family: 'Times New Roman', serif; /* Traditional font */ } ``` ### Example 3: Portfolio Website Integration **Goal**: Use CV data to populate portfolio website **Create new template** `templates/portfolio.html`: ```html {{.CV.Personal.Name}} - Portfolio

    {{.CV.Personal.Name}}

    {{.CV.Personal.Title}}

    {{range .CV.Projects}}

    {{.Title}}

    {{.ShortDescription}}

    View Project
    {{end}}
    ``` **Add route in `main.go`**: ```go http.HandleFunc("/portfolio", func(w http.ResponseWriter, r *http.Request) { // Render portfolio template using same CV data }) ``` ### Example 4: Multi-Language Support (Adding French) **Complete implementation**: **1. Create French JSON**: ```bash cp data/cv-en.json data/cv-fr.json # Translate all content to French ``` **2. Update model validation** (`internal/models/cv.go`): ```go func LoadCV(lang string) (*CV, error) { if lang != "en" && lang != "es" && lang != "fr" { return nil, fmt.Errorf("unsupported language: %s", lang) } // ... } ``` **3. Update template conditionals** (all instances): ```html {{if eq .Lang "fr"}} Français text {{else if eq .Lang "es"}} Texto en español {{else}} English text {{end}} ``` **4. Add language selector**: ```html
    EN ES FR
    ``` --- ## Common Customization Patterns ### Pattern 1: Responsive Sidebar Make sidebars collapse on mobile: ```css @media screen and (max-width: 768px) { .page-content { grid-template-columns: 1fr; } .cv-sidebar { display: none; /* Hide sidebars on mobile */ } /* Or show as accordion */ .cv-sidebar details { margin-bottom: 15px; } } ``` ### Pattern 2: Dark Mode Toggle Add dark mode switch: **CSS**: ```css /* Dark mode variables */ [data-theme="dark"] { --bg-gray: #1e1e1e; --sidebar-gray: #2d2d2d; --paper-white: #252526; --text-dark: #d4d4d4; --text-gray: #9d9d9d; } ``` **JavaScript** (add to template): ```html ``` ### Pattern 3: Skills Progress Bars Add visual skill levels: **Template** (in skills section): ```html {{range $category.Items}}
    {{.}}
    {{end}} ``` **CSS**: ```css .skill-bar { background: #e0e0e0; height: 8px; border-radius: 4px; overflow: hidden; margin-top: 4px; } .skill-level { background: var(--accent-blue); height: 100%; transition: width 0.3s ease; } ``` --- ## Troubleshooting Customization ### Issue: Changes Not Appearing **Solutions**: ```bash # 1. Hard refresh browser # Press Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux) # 2. Clear browser cache # Or use private/incognito window # 3. Restart server if Go code changed pkill cv-server make dev # 4. Check for errors # Look in terminal and browser console (F12) ``` ### Issue: JSON Parse Error **Solutions**: ```bash # Validate JSON syntax python3 -m json.tool data/cv-en.json # Common fixes: # - Add missing commas between items # - Remove trailing commas in arrays/objects # - Escape quotes in strings: \" instead of " # - Check matching brackets: { } [ ] ``` ### Issue: Template Rendering Error **Solutions**: ```bash # Check error message in terminal # Common issues: # - Undefined variable: Check spelling, case-sensitivity # - Wrong field name: Verify against models/cv.go # - Missing | safeHTML for HTML content ``` ### Issue: Styling Not Applied **Solutions**: ```bash # 1. Verify CSS file loaded # Check browser Network tab (F12) for main.css # 2. Check CSS syntax # Use browser DevTools to inspect elements # 3. Check specificity # Use !important to test: color: red !important; # 4. Verify class names match # Template: class="cv-name" # CSS: .cv-name { ... } ``` --- ## Next Steps After customization: 1. **Test thoroughly** with checklist above 2. **Generate PDF** and verify quality 3. **Deploy** using [DEPLOYMENT.md](DEPLOYMENT.md) guide 4. **Set up CI/CD** for automatic deployments 5. **Share** your customized CV! **Further Resources**: - [Go Templates Documentation](https://pkg.go.dev/text/template) - [CSS Grid Guide](https://css-tricks.com/snippets/css/complete-guide-grid/) - [Iconify Icon Sets](https://icon-sets.iconify.design/) - [Google Fonts](https://fonts.google.com/) **Need Help?** - Check existing issues on GitHub - Open new issue with details - Include error messages and screenshots Happy customizing! 🎨