refactor: Extract all hardcoded content to JSON files
- Move all bilingual text from templates to UI JSON (labels, buttons, modals) - Move skills summary paragraph to CV JSON with HTML support - Add new UI sections: navigation, viewControls, sections, footer, portfolio, pdfModal, shortcutsModal, infoModal, widgets - Update Go structs to match expanded JSON structure - Add template.HTML type for CV.SkillsSummary field - Add JSON content validation test (70-json-content-validation.test.mjs) Templates now contain only structural logic (CSS classes, HTML attributes) while all user-visible text loads from JSON files for proper i18n support.
This commit is contained in:
@@ -6,10 +6,10 @@
|
||||
<p style="text-align: center; margin-bottom: 0.5rem;">
|
||||
<a href="https://github.com/juanatsap/cv-site" target="_blank" rel="noopener noreferrer" class="github-repo-link" style="text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem;">
|
||||
<iconify-icon icon="mdi:github" width="20" height="20"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Ver este proyecto en GitHub{{else}}View this project on GitHub{{end}}
|
||||
{{.UI.Footer.ViewOnGithub}}
|
||||
</a>
|
||||
</p>
|
||||
<p>© {{.CV.Meta.LastUpdated}} {{.CV.Personal.Name}} |
|
||||
{{if eq .Lang "es"}}Última actualización{{else}}Last updated{{end}}: {{.CV.Meta.LastUpdated}}</p>
|
||||
{{.UI.Footer.LastUpdated}}: {{.CV.Meta.LastUpdated}}</p>
|
||||
</footer>
|
||||
{{end}}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
{{define "title-badges"}}
|
||||
<!-- Professional Title Badges - Full Width Top Bar -->
|
||||
<div class="cv-title-badges-header">
|
||||
<span class="title-badge">{{if eq .Lang "es"}}CONSULTOR TÉCNICO{{else}}TECHNICAL CONSULTANT{{end}}</span>
|
||||
<span class="badge-separator">|</span>
|
||||
<span class="title-badge">{{if eq .Lang "es"}}INGENIERO FULL-STACK{{else}}FULL-STACK ENGINEER{{end}}</span>
|
||||
<span class="badge-separator">|</span>
|
||||
<span class="title-badge">{{if eq .Lang "es"}}ESPECIALISTA EN AUTENTICACIÓN{{else}}AUTHENTICATION SPECIALIST{{end}}</span>
|
||||
<span class="badge-separator">|</span>
|
||||
<span class="title-badge">{{if eq .Lang "es"}}ARQUITECTO DE SOLUCIONES{{else}}SOLUTION ARCHITECT{{end}}</span>
|
||||
{{range $i, $badge := .CV.Personal.TitleBadges}}{{if $i}}<span class="badge-separator">|</span>{{end}}<span class="title-badge">{{$badge}}</span>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<dialog id="info-modal" class="info-modal no-print"
|
||||
_="on click call closeOnBackdrop(me, event)">
|
||||
<div class="info-modal-content">
|
||||
<button class="info-modal-close" onclick="document.getElementById('info-modal').close()" aria-label="Close">
|
||||
<button class="info-modal-close" onclick="document.getElementById('info-modal').close()" aria-label="{{.UI.PdfModal.Close}}">
|
||||
<iconify-icon icon="mdi:close" width="24" height="24"></iconify-icon>
|
||||
</button>
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<div class="pdf-loading-content">
|
||||
<div class="pdf-loading-spinner"></div>
|
||||
<h3 class="pdf-loading-title" id="pdf-loading-title">
|
||||
{{if eq .Lang "es"}}Preparando PDF...{{else}}Preparing PDF...{{end}}
|
||||
{{.UI.PdfModal.PreparingPdf}}
|
||||
</h3>
|
||||
<p class="pdf-loading-message" id="pdf-loading-message">
|
||||
{{if eq .Lang "es"}}Por favor espera mientras generamos tu CV{{else}}Please wait while we generate your CV{{end}}
|
||||
{{.UI.PdfModal.PleaseWait}}
|
||||
</p>
|
||||
<p class="pdf-loading-estimate" id="pdf-loading-estimate"></p>
|
||||
</div>
|
||||
@@ -20,16 +20,16 @@
|
||||
<!-- Close Button -->
|
||||
<button class="info-modal-close"
|
||||
onclick="document.getElementById('pdf-modal').close()"
|
||||
aria-label="{{if eq .Lang "es"}}Cerrar{{else}}Close{{end}}">
|
||||
aria-label="{{.UI.PdfModal.Close}}">
|
||||
<iconify-icon icon="mdi:close" width="24" height="24"></iconify-icon>
|
||||
</button>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="info-modal-header">
|
||||
<iconify-icon icon="catppuccin:pdf" width="40" height="40" style="margin-bottom: 0.5rem;"></iconify-icon>
|
||||
<h2>{{if eq .Lang "es"}}Descargar PDF{{else}}Download PDF{{end}}</h2>
|
||||
<h2>{{.UI.PdfModal.Title}}</h2>
|
||||
<p class="pdf-modal-subtitle">
|
||||
{{if eq .Lang "es"}}Elige tu formato preferido{{else}}Choose your preferred format{{end}}
|
||||
{{.UI.PdfModal.Subtitle}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
data-cv-format="short"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="{{if eq .Lang "es"}}CV Corto - 4 páginas, información esencial{{else}}Short CV - 4 pages, essential information{{end}}"
|
||||
aria-label="{{.UI.PdfModal.ShortCv.AriaLabel}}"
|
||||
tabindex="0"
|
||||
_="on click call selectPdfCard(me)
|
||||
on keydown call handlePdfCardKey(me, event)">
|
||||
@@ -57,13 +57,13 @@
|
||||
|
||||
<!-- Page count badge -->
|
||||
<div class="thumbnail-badge">
|
||||
{{if eq .Lang "es"}}4 Páginas{{else}}4 Pages{{end}}
|
||||
{{.UI.PdfModal.ShortCv.Pages}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pdf-option-info">
|
||||
<h3>{{if eq .Lang "es"}}CV Corto (4 páginas){{else}}Short CV (4 pages){{end}}</h3>
|
||||
<p>{{if eq .Lang "es"}}Información esencial{{else}}Essential info{{end}}</p>
|
||||
<h3>{{.UI.PdfModal.ShortCv.Title}}</h3>
|
||||
<p>{{.UI.PdfModal.ShortCv.Description}}</p>
|
||||
</div>
|
||||
|
||||
<div class="pdf-option-badge">
|
||||
@@ -76,7 +76,7 @@
|
||||
data-cv-format="default"
|
||||
role="radio"
|
||||
aria-checked="true"
|
||||
aria-label="{{if eq .Lang "es"}}CV Por Defecto - 5 páginas con habilidades (Recomendado){{else}}Default CV - 5 pages with skills (Recommended){{end}}"
|
||||
aria-label="{{.UI.PdfModal.DefaultCv.AriaLabel}}"
|
||||
tabindex="0"
|
||||
_="on click call selectPdfCard(me)
|
||||
on keydown call handlePdfCardKey(me, event)">
|
||||
@@ -97,16 +97,16 @@
|
||||
|
||||
<!-- Page count badge with star -->
|
||||
<div class="thumbnail-badge" style="font-weight: 600;">
|
||||
⭐ {{if eq .Lang "es"}}5 Páginas{{else}}5 Pages{{end}}
|
||||
⭐ {{.UI.PdfModal.DefaultCv.Pages}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pdf-option-info">
|
||||
<h3>
|
||||
{{if eq .Lang "es"}}CV Por Defecto (5 páginas){{else}}Default CV (5 pages){{end}}
|
||||
{{.UI.PdfModal.DefaultCv.Title}}
|
||||
<span style="color: #667eea; font-size: 0.9em;">⭐</span>
|
||||
</h3>
|
||||
<p style="font-weight: 500;">{{if eq .Lang "es"}}Corto con habilidades - Recomendado{{else}}Short with skills - Recommended{{end}}</p>
|
||||
<p style="font-weight: 500;">{{.UI.PdfModal.DefaultCv.Description}}</p>
|
||||
</div>
|
||||
|
||||
<div class="pdf-option-badge">
|
||||
@@ -119,7 +119,7 @@
|
||||
data-cv-format="long"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="{{if eq .Lang "es"}}CV Extendido - 9 páginas, versión completa{{else}}Extended CV - 9 pages, full version{{end}}"
|
||||
aria-label="{{.UI.PdfModal.ExtendedCv.AriaLabel}}"
|
||||
tabindex="0"
|
||||
_="on click call selectPdfCard(me)
|
||||
on keydown call handlePdfCardKey(me, event)">
|
||||
@@ -137,13 +137,13 @@
|
||||
|
||||
<!-- Page count badge -->
|
||||
<div class="thumbnail-badge">
|
||||
{{if eq .Lang "es"}}9 Páginas{{else}}9 Pages{{end}}
|
||||
{{.UI.PdfModal.ExtendedCv.Pages}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pdf-option-info">
|
||||
<h3>{{if eq .Lang "es"}}CV Extendido (9 páginas){{else}}Extended CV (9 pages){{end}}</h3>
|
||||
<p>{{if eq .Lang "es"}}Todos los detalles{{else}}All details{{end}}</p>
|
||||
<h3>{{.UI.PdfModal.ExtendedCv.Title}}</h3>
|
||||
<p>{{.UI.PdfModal.ExtendedCv.Description}}</p>
|
||||
</div>
|
||||
|
||||
<div class="pdf-option-badge">
|
||||
@@ -158,7 +158,7 @@
|
||||
id="pdf-download-btn"
|
||||
onclick="downloadPDF()">
|
||||
<iconify-icon icon="mdi:download" width="20" height="20"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Descargar PDF{{else}}Download PDF{{end}}
|
||||
{{.UI.PdfModal.DownloadButton}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<dialog id="shortcuts-modal" class="info-modal no-print"
|
||||
_="on click call closeOnBackdrop(me, event)">
|
||||
<div class="info-modal-content">
|
||||
<button class="info-modal-close" onclick="document.getElementById('shortcuts-modal').close()" aria-label="{{if eq .Lang "es"}}Cerrar{{else}}Close{{end}}">
|
||||
<button class="info-modal-close" onclick="document.getElementById('shortcuts-modal').close()" aria-label="{{.UI.ShortcutsModal.Close}}">
|
||||
<iconify-icon icon="mdi:close" width="24" height="24"></iconify-icon>
|
||||
</button>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<span class="keyboard-icon-wrapper">
|
||||
<iconify-icon icon="mdi:keyboard-outline" width="32" height="32"></iconify-icon>
|
||||
</span>
|
||||
{{if eq .Lang "es"}}Aprende los Atajos{{else}}Learn the Shortcuts{{end}}
|
||||
{{.UI.ShortcutsModal.Subtitle}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,22 +5,22 @@
|
||||
id="action-bar-pdf-btn"
|
||||
class="action-btn pdf-btn has-tooltip"
|
||||
onclick="document.getElementById('pdf-modal').showModal()"
|
||||
aria-label="{{if eq .Lang "es"}}Descargar como PDF{{else}}Download as PDF{{end}}"
|
||||
data-tooltip="{{if eq .Lang "es"}}Descargar como PDF{{else}}Download as PDF{{end}}"
|
||||
aria-label="{{.UI.Widgets.ActionButtons.DownloadPdf}}"
|
||||
data-tooltip="{{.UI.Widgets.ActionButtons.DownloadPdf}}"
|
||||
_="on mouseenter call syncPdfHover(true)
|
||||
on mouseleave call syncPdfHover(false)">
|
||||
<iconify-icon icon="catppuccin:pdf" width="24" height="24"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Descargar como PDF{{else}}Download as PDF{{end}}
|
||||
{{.UI.Widgets.ActionButtons.DownloadPdf}}
|
||||
</button>
|
||||
<button
|
||||
class="action-btn print-btn action-bar-print-btn has-tooltip"
|
||||
aria-label="{{if eq .Lang "es"}}Imprimir amigable{{else}}Print Friendly{{end}}"
|
||||
data-tooltip="{{if eq .Lang "es"}}Imprimir amigable{{else}}Print Friendly{{end}}"
|
||||
aria-label="{{.UI.Widgets.ActionButtons.PrintFriendly}}"
|
||||
data-tooltip="{{.UI.Widgets.ActionButtons.PrintFriendly}}"
|
||||
_="on click call printFriendly()
|
||||
on mouseenter call syncPrintHover(true)
|
||||
on mouseleave call syncPrintHover(false)">
|
||||
<iconify-icon icon="mdi:leaf" width="24" height="24"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Imprimir amigable{{else}}Print Friendly{{end}}
|
||||
{{.UI.Widgets.ActionButtons.PrintFriendly}}
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -6,54 +6,54 @@
|
||||
<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>
|
||||
<span>{{.UI.Navigation.CvSections}}</span>
|
||||
<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"
|
||||
_="on click call scrollToSection(event, 'education')">
|
||||
<iconify-icon icon="mdi:school" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Formación{{else}}Training{{end}}</span>
|
||||
<span>{{.UI.Navigation.Training}}</span>
|
||||
</a>
|
||||
<a href="#skills" class="menu-item"
|
||||
_="on click call scrollToSection(event, 'skills')">
|
||||
<iconify-icon icon="mdi:brain" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Competencias{{else}}Skills{{end}}</span>
|
||||
<span>{{.UI.Navigation.Skills}}</span>
|
||||
</a>
|
||||
<a href="#experience" class="menu-item"
|
||||
_="on click call scrollToSection(event, 'experience')">
|
||||
<iconify-icon icon="mdi:office-building" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Experiencia{{else}}Experience{{end}}</span>
|
||||
<span>{{.UI.Navigation.Experience}}</span>
|
||||
</a>
|
||||
<a href="#awards" class="menu-item"
|
||||
_="on click call scrollToSection(event, 'awards')">
|
||||
<iconify-icon icon="mdi:trophy" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Premios y Reconocimientos{{else}}Awards{{end}}</span>
|
||||
<span>{{.UI.Navigation.Awards}}</span>
|
||||
</a>
|
||||
<a href="#projects" class="menu-item"
|
||||
_="on click call scrollToSection(event, 'projects')">
|
||||
<iconify-icon icon="mdi:web" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Proyectos Personales / Freelance{{else}}Personal / Freelance Projects{{end}}</span>
|
||||
<span>{{.UI.Navigation.Projects}}</span>
|
||||
</a>
|
||||
<a href="#courses" class="menu-item"
|
||||
_="on click call scrollToSection(event, 'courses')">
|
||||
<iconify-icon icon="mdi:school" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Cursos Realizados{{else}}Courses{{end}}</span>
|
||||
<span>{{.UI.Navigation.Courses}}</span>
|
||||
</a>
|
||||
<a href="#languages" class="menu-item"
|
||||
_="on click call scrollToSection(event, 'languages')">
|
||||
<iconify-icon icon="mdi:translate" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Idiomas{{else}}Languages{{end}}</span>
|
||||
<span>{{.UI.Navigation.Languages}}</span>
|
||||
</a>
|
||||
<a href="#references" class="menu-item"
|
||||
_="on click call scrollToSection(event, 'references')">
|
||||
<iconify-icon icon="mdi:link-variant" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Referencias{{else}}References{{end}}</span>
|
||||
<span>{{.UI.Navigation.References}}</span>
|
||||
</a>
|
||||
<a href="#other" class="menu-item"
|
||||
_="on click call scrollToSection(event, 'other')">
|
||||
<iconify-icon icon="mdi:information" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Otros{{else}}Other{{end}}</span>
|
||||
<span>{{.UI.Navigation.Other}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,21 +62,21 @@
|
||||
<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>
|
||||
<span>{{.UI.Navigation.QuickActions}}</span>
|
||||
</div>
|
||||
|
||||
<a href="#" class="menu-item menu-item-action" _="on click call 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>
|
||||
<span>{{.UI.Navigation.CollapseAll}}</span>
|
||||
</a>
|
||||
<a href="#" class="menu-item menu-item-action" _="on click call 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>
|
||||
<span>{{.UI.Navigation.ExpandAll}}</span>
|
||||
</a>
|
||||
<a href="#" id="show-zoom-menu-btn" class="menu-item menu-item-action zoom-hidden"
|
||||
_="on click call showZoomControl()">
|
||||
<iconify-icon icon="mdi:magnify" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Zoom{{else}}Zoom{{end}}</span>
|
||||
<span>{{.UI.Navigation.Zoom}}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -84,14 +84,14 @@
|
||||
<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>
|
||||
<span>{{.UI.Navigation.ViewControls}}</span>
|
||||
</div>
|
||||
|
||||
<!-- CV Length toggle -->
|
||||
<div class="menu-control-item" id="mobile-length-toggle">
|
||||
<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>
|
||||
<span>{{.UI.ViewControls.Length}}</span>
|
||||
</label>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<label class="icon-toggle">
|
||||
@@ -118,7 +118,7 @@
|
||||
<div class="menu-control-item" id="mobile-icon-toggle">
|
||||
<label class="menu-control-label">
|
||||
<iconify-icon icon="mdi:image-multiple-outline" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Iconos{{else}}Icons{{end}}</span>
|
||||
<span>{{.UI.ViewControls.Icons}}</span>
|
||||
</label>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<label class="icon-toggle">
|
||||
@@ -145,7 +145,7 @@
|
||||
<div class="menu-control-item" id="mobile-theme-toggle">
|
||||
<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>
|
||||
<span>{{.UI.ViewControls.View}}</span>
|
||||
</label>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<label class="icon-toggle">
|
||||
@@ -173,7 +173,7 @@
|
||||
<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>
|
||||
<span>{{.UI.Navigation.Actions}}</span>
|
||||
</div>
|
||||
|
||||
<button class="menu-action-btn menu-pdf-btn"
|
||||
@@ -181,7 +181,7 @@
|
||||
_="on mouseenter call syncPdfHover(true)
|
||||
on mouseleave call syncPdfHover(false)">
|
||||
<iconify-icon icon="catppuccin:pdf" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Descargar como PDF{{else}}Download as PDF{{end}}</span>
|
||||
<span>{{.UI.Widgets.ActionButtons.DownloadPdf}}</span>
|
||||
</button>
|
||||
|
||||
<button class="menu-action-btn menu-print-btn"
|
||||
@@ -189,7 +189,7 @@
|
||||
on mouseenter call syncPrintHover(true)
|
||||
on mouseleave call syncPrintHover(false)">
|
||||
<iconify-icon icon="mdi:leaf" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Imprimir amigable{{else}}Print Friendly{{end}}</span>
|
||||
<span>{{.UI.Widgets.ActionButtons.PrintFriendly}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="view-controls-center">
|
||||
<!-- CV Length toggle -->
|
||||
<div class="selector-group" id="desktop-length-toggle">
|
||||
<label class="selector-label">{{if eq .Lang "es"}}Longitud{{else}}Length{{end}}:</label>
|
||||
<label class="selector-label">{{.UI.ViewControls.Length}}:</label>
|
||||
<label class="icon-toggle">
|
||||
<input type="checkbox"
|
||||
id="lengthToggle"
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<!-- Icon toggle -->
|
||||
<div class="selector-group" id="desktop-icon-toggle">
|
||||
<label class="selector-label">{{if eq .Lang "es"}}Iconos{{else}}Icons{{end}}:</label>
|
||||
<label class="selector-label">{{.UI.ViewControls.Icons}}:</label>
|
||||
<label class="icon-toggle">
|
||||
<input type="checkbox"
|
||||
id="iconToggle"
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
<!-- Theme toggle -->
|
||||
<div class="selector-group" id="desktop-theme-toggle">
|
||||
<label class="selector-label">{{if eq .Lang "es"}}Vista{{else}}View{{end}}:</label>
|
||||
<label class="selector-label">{{.UI.ViewControls.View}}:</label>
|
||||
<label class="icon-toggle">
|
||||
<input type="checkbox"
|
||||
id="themeToggle"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<summary>
|
||||
<h3 class="section-title">
|
||||
<iconify-icon icon="mdi:trophy" width="24" height="24" class="section-icon"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Premios y Reconocimientos{{else}}Awards{{end}}
|
||||
{{.UI.Navigation.Awards}}
|
||||
</h3>
|
||||
</summary>
|
||||
{{range .CV.Awards}}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<summary>
|
||||
<h3 class="section-title">
|
||||
<iconify-icon icon="mdi:school" width="24" height="24" class="section-icon"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Cursos Realizados{{else}}Courses{{end}}
|
||||
{{.UI.Navigation.Courses}}
|
||||
</h3>
|
||||
</summary>
|
||||
{{range .CV.Courses}}
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
<summary>
|
||||
<h3 class="section-title">
|
||||
<iconify-icon icon="mdi:school" width="24" height="24" class="section-icon"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Formación{{else}}Training{{end}}
|
||||
{{.UI.Navigation.Training}}
|
||||
</h3>
|
||||
</summary>
|
||||
{{range .CV.Education}}
|
||||
<div class="education-item">
|
||||
<strong>{{.Degree}}</strong> ({{.StartDate}}-{{.EndDate}}) {{if eq $.Lang "es"}}obtenido de{{else}}obtained from the{{end}} <strong>{{.Institution}}</strong> ({{.Location}})
|
||||
<strong>{{.Degree}}</strong> ({{.StartDate}}-{{.EndDate}}) {{$.UI.Sections.ObtainedFrom}} <strong>{{.Institution}}</strong> ({{.Location}})
|
||||
</div>
|
||||
{{end}}
|
||||
</details>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<summary>
|
||||
<h3 class="section-title">
|
||||
<iconify-icon icon="mdi:office-building" width="24" height="24" class="section-icon"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Experiencia{{else}}Experience{{end}}
|
||||
{{.UI.Navigation.Experience}}
|
||||
</h3>
|
||||
</summary>
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
</div>
|
||||
<div class="experience-content">
|
||||
<strong>{{.Position}}{{if .Company}} - {{if .CompanyURL}}<a href="{{.CompanyURL}}" target="_blank" rel="noopener noreferrer">{{.Company}}</a>{{else}}{{.Company}}{{end}}{{if .Duration}} - <span class="duration-text">{{.Duration}}</span>{{end}}{{end}}</strong>
|
||||
{{if .Current}}<span class="current-badge">{{if eq $.Lang "es"}}ACTUAL{{else}}CURRENT{{end}}</span>{{end}}
|
||||
{{if .Expired}}<span class="expired-badge">{{if eq $.Lang "es"}}EXPIRADO{{else}}EXPIRED{{end}}</span>{{end}}
|
||||
{{if .Current}}<span class="current-badge">{{$.UI.Sections.CurrentBadge}}</span>{{end}}
|
||||
{{if .Expired}}<span class="expired-badge">{{$.UI.Sections.ExpiredBadge}}</span>{{end}}
|
||||
<br>
|
||||
<small>{{.StartDate}} / {{if .Current}}{{if eq $.Lang "es"}}presente{{else}}now{{end}}{{else}}{{.EndDate}}{{end}} - ({{.Location}})</small>
|
||||
<small>{{.StartDate}} / {{if .Current}}{{$.UI.Sections.Present}}{{else}}{{.EndDate}}{{end}} - ({{.Location}})</small>
|
||||
|
||||
{{if .ShortDescription}}
|
||||
<p class="experience-desc short-desc">{{.ShortDescription | safeHTML}}</p>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="cv-header-content">
|
||||
<div class="cv-header-left">
|
||||
<h1 class="cv-name">Moreno Rubio, Juan Andrés</h1>
|
||||
<p class="years-experience">{{.YearsOfExperience}} {{if eq .Lang "es"}}años de experiencia{{else}}years of experience{{end}}</p>
|
||||
<p class="years-experience">{{.YearsOfExperience}} {{.UI.Sections.YearsOfExperience}}</p>
|
||||
|
||||
<!-- Photo positioned for mobile (centered between name and intro) -->
|
||||
<div class="cv-photo">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<summary>
|
||||
<h3 class="section-title">
|
||||
<iconify-icon icon="mdi:translate" width="24" height="24" class="section-icon"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Idiomas{{else}}Languages{{end}}
|
||||
{{.UI.Navigation.Languages}}
|
||||
</h3>
|
||||
</summary>
|
||||
{{range .CV.Languages}}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
<summary>
|
||||
<h3 class="section-title">
|
||||
<iconify-icon icon="mdi:information" width="24" height="24" class="section-icon"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Otros{{else}}Other{{end}}
|
||||
{{.UI.Navigation.Other}}
|
||||
</h3>
|
||||
</summary>
|
||||
<div class="other-content">
|
||||
{{if eq .Lang "es"}}Carnet de conducir tipo <strong>{{.CV.Other.DriverLicense}}</strong>{{else}}Driving License type <strong>{{.CV.Other.DriverLicense}}</strong>{{end}}
|
||||
{{.UI.Sections.DrivingLicense}} <strong>{{.CV.Other.DriverLicense}}</strong>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<summary>
|
||||
<h3 class="section-title">
|
||||
<iconify-icon icon="mdi:web" width="24" height="24" class="section-icon"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Proyectos Personales / Freelance{{else}}Personal / Freelance Projects{{end}}
|
||||
{{.UI.Navigation.Projects}}
|
||||
</h3>
|
||||
</summary>
|
||||
{{range .CV.Projects}}
|
||||
@@ -31,9 +31,9 @@
|
||||
{{end}}
|
||||
</strong>
|
||||
{{if .Current}}<span class="live-badge"><iconify-icon icon="mdi:wifi" width="14" height="14"></iconify-icon>LIVE</span>{{end}}
|
||||
{{if .MaintainedBy}}<span class="maintained-badge">{{if eq $.Lang "es"}}MANTENIDO POR{{else}}MAINTAINED BY{{end}} {{.MaintainedBy}}</span>{{end}}
|
||||
{{if .MaintainedBy}}<span class="maintained-badge">{{$.UI.Sections.MaintainedBy}} {{.MaintainedBy}}</span>{{end}}
|
||||
<br>
|
||||
<small>{{if .StartDate}}{{.StartDate}}{{if .Current}}{{if .DynamicDate}} / {{.DynamicDate}}{{else}} / {{if eq $.Lang "es"}}presente{{else}}ahora{{end}}{{end}}{{end}}{{end}} - ({{.Location}})</small>
|
||||
<small>{{if .StartDate}}{{.StartDate}}{{if .Current}}{{if .DynamicDate}} / {{.DynamicDate}}{{else}} / {{$.UI.Sections.Present}}{{end}}{{end}}{{end}} - ({{.Location}})</small>
|
||||
|
||||
{{if .ShortDescription}}
|
||||
<p class="project-desc short-desc">{{.ShortDescription | safeHTML}}</p>
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
{{if .Technologies}}
|
||||
<div class="project-technologies long-only">
|
||||
<strong>{{if eq $.Lang "es"}}Tecnologías:{{else}}Technologies:{{end}}</strong>
|
||||
<strong>{{$.UI.Sections.Technologies}}</strong>
|
||||
{{range $index, $tech := .Technologies}}{{if $index}}, {{end}}{{$tech}}{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -59,8 +59,8 @@
|
||||
|
||||
<!-- Link to full portfolio -->
|
||||
<div class="projects-footer">
|
||||
<p>{{if eq .Lang "es"}}Ver todos los proyectos en mi{{else}}See all projects on my{{end}}
|
||||
<a href="{{.CV.Personal.Domestika}}" target="_blank" rel="noopener noreferrer"><strong>{{if eq .Lang "es"}}portfolio de Domestika{{else}}Domestika portfolio{{end}}</strong></a></p>
|
||||
<p>{{.UI.Portfolio.SeeAllProjects}}
|
||||
<a href="{{.CV.Personal.Domestika}}" target="_blank" rel="noopener noreferrer"><strong>{{.UI.Portfolio.DomestikaPortfolio}}</strong></a></p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<summary>
|
||||
<h3 class="section-title">
|
||||
<iconify-icon icon="mdi:link-variant" width="24" height="24" class="section-icon"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Referencias{{else}}References{{end}}
|
||||
{{.UI.Navigation.References}}
|
||||
</h3>
|
||||
</summary>
|
||||
{{range .CV.References}}
|
||||
|
||||
@@ -7,16 +7,10 @@
|
||||
<summary>
|
||||
<h3 class="section-title">
|
||||
<iconify-icon icon="mdi:brain" width="24" height="24" class="section-icon"></iconify-icon>
|
||||
{{if eq .Lang "es"}}Competencias{{else}}Skills{{end}}
|
||||
{{.UI.Navigation.Skills}}
|
||||
</h3>
|
||||
</summary>
|
||||
<p class="summary-text">
|
||||
{{if eq .Lang "es"}}
|
||||
Desarrollador <strong>full-stack</strong> con experiencia en <strong>Go</strong>, <strong>Node.js</strong>, <strong>React</strong> y <strong>HTMX</strong> para <strong>aplicaciones modernas</strong>, además de conocimientos en Java y PHP para proyectos legacy. He trabajado en <strong>unos 20 sitios web</strong> y realizado <strong>consultoría para 35-40 clientes internacionales</strong>, desde e-commerce y plataformas empresariales hasta <strong>sistemas de autenticación</strong> que gestionan <strong>millones de usuarios</strong>. Familiarizado con flujos de trabajo asistidos por <strong>IA</strong> y gestión de infraestructura (<strong>Linux</strong>, <strong>Docker</strong>, <strong>CI/CD</strong>). Me adapto bien tanto al trabajo independiente como colaborativo en equipos internacionales.
|
||||
{{else}}
|
||||
<strong>Full-stack</strong> developer with experience in <strong>Go</strong>, <strong>Node.js</strong>, <strong>React</strong>, and <strong>HTMX</strong> for <strong>modern applications</strong>, plus Java and PHP knowledge for legacy projects. I've worked on <strong>around 20 websites</strong> and provided <strong>consulting for 35-40 international clients</strong>, from e-commerce and enterprise platforms to <strong>authentication systems</strong> managing <strong>millions of users</strong>. Familiar with <strong>AI-assisted development</strong> workflows and infrastructure management (<strong>Linux</strong>, <strong>Docker</strong>, <strong>CI/CD</strong>). I adapt well to both independent work and collaborative teams across different countries.
|
||||
{{end}}
|
||||
</p>
|
||||
<p class="summary-text">{{.CV.SkillsSummary}}</p>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<!-- Back to Top Link - Hyperscript smooth scroll without URL pollution -->
|
||||
<button id="back-to-top"
|
||||
class="back-to-top no-print has-tooltip tooltip-left"
|
||||
aria-label="{{if eq .Lang "es"}}Volver arriba{{else}}Back to top{{end}}"
|
||||
data-tooltip="{{if eq .Lang "es"}}Volver arriba{{else}}Back to top{{end}}"
|
||||
aria-label="{{.UI.Widgets.BackToTop.AriaLabel}}"
|
||||
data-tooltip="{{.UI.Widgets.BackToTop.Tooltip}}"
|
||||
style="display: none;"
|
||||
_="on click call scrollToTop(event)">
|
||||
<iconify-icon icon="mdi:arrow-up" width="24" height="24"></iconify-icon>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<button
|
||||
id="download-button"
|
||||
class="fixed-btn download-btn no-print has-tooltip"
|
||||
aria-label="{{if eq .Lang "es"}}Descargar PDF{{else}}Download as PDF{{end}}"
|
||||
data-tooltip="{{if eq .Lang "es"}}Descargar PDF{{else}}Download as PDF{{end}}"
|
||||
aria-label="{{.UI.Widgets.Download.AriaLabel}}"
|
||||
data-tooltip="{{.UI.Widgets.Download.Tooltip}}"
|
||||
onclick="openPdfModal()"
|
||||
_="on mouseenter call syncPdfHover(true)
|
||||
on mouseleave call syncPdfHover(false)">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{{define "info-button"}}
|
||||
<!-- Info Button (Bottom Left) -->
|
||||
<button id="info-button" class="info-button no-print has-tooltip"
|
||||
aria-label="{{if eq .Lang "es"}}Información{{else}}Information{{end}}"
|
||||
data-tooltip="{{if eq .Lang "es"}}Información{{else}}Information{{end}}"
|
||||
aria-label="{{.UI.Widgets.Info.AriaLabel}}"
|
||||
data-tooltip="{{.UI.Widgets.Info.Tooltip}}"
|
||||
onclick="document.getElementById('info-modal').showModal()">
|
||||
<iconify-icon icon="mdi:information-outline" width="24" height="24"></iconify-icon>
|
||||
</button>
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<div id="pdf-toast" class="success-toast no-print" role="status" aria-live="polite" aria-atomic="true">
|
||||
<span class="toast-icon" id="pdf-toast-icon">📥</span>
|
||||
<div class="toast-content">
|
||||
<p class="toast-title" id="pdf-toast-title">{{if eq .Lang "es"}}Preparando PDF{{else}}Preparing PDF{{end}}</p>
|
||||
<p class="toast-title" id="pdf-toast-title">{{.UI.Widgets.PdfToast.Title}}</p>
|
||||
<p class="toast-message" id="pdf-toast-message"></p>
|
||||
</div>
|
||||
<button aria-label="{{if eq .Lang "es"}}Cerrar notificación{{else}}Close notification{{end}}" class="toast-close" onclick="document.getElementById('pdf-toast').classList.remove('show')">×</button>
|
||||
<button aria-label="{{.UI.Widgets.PdfToast.CloseLabel}}" class="toast-close" onclick="document.getElementById('pdf-toast').classList.remove('show')">×</button>
|
||||
<div class="toast-progress">
|
||||
<div class="toast-progress-bar" id="pdf-toast-progress"></div>
|
||||
</div>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<button
|
||||
id="print-friendly-button"
|
||||
class="fixed-btn print-friendly-btn no-print has-tooltip"
|
||||
aria-label="{{if eq .Lang "es"}}Imprimir CV{{else}}Print Friendly{{end}}"
|
||||
data-tooltip="{{if eq .Lang "es"}}Imprimir CV{{else}}Print Friendly{{end}}"
|
||||
aria-label="{{.UI.Widgets.Print.AriaLabel}}"
|
||||
data-tooltip="{{.UI.Widgets.Print.Tooltip}}"
|
||||
onclick="window.print()"
|
||||
_="on mouseenter call syncPrintHover(true)
|
||||
on mouseleave call syncPrintHover(false)">
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
id="shortcuts-button"
|
||||
class="fixed-btn shortcuts-btn no-print has-tooltip"
|
||||
onclick="document.getElementById('shortcuts-modal').showModal()"
|
||||
aria-label="{{if eq .Lang "es"}}Atajos de teclado{{else}}Keyboard shortcuts{{end}}"
|
||||
data-tooltip="{{if eq .Lang "es"}}Atajos de teclado (?){{else}}Keyboard shortcuts (?){{end}}">
|
||||
aria-label="{{.UI.Widgets.Shortcuts.AriaLabel}}"
|
||||
data-tooltip="{{.UI.Widgets.Shortcuts.Tooltip}}">
|
||||
<iconify-icon icon="mdi:keyboard-outline" width="28" height="28"></iconify-icon>
|
||||
</button>
|
||||
{{end}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{{define "zoom-control"}}
|
||||
<!-- Zoom Control (Fixed Bottom Center, Draggable) - Hyperscript Enhanced -->
|
||||
<div id="zoom-control" class="zoom-control no-print zoom-hidden" role="group" aria-label="{{if eq .Lang "es"}}Control de zoom{{else}}Zoom control{{end}}"
|
||||
<div id="zoom-control" class="zoom-control no-print zoom-hidden" role="group" aria-label="{{.UI.Widgets.ZoomControl.GroupLabel}}"
|
||||
_="on load call initZoomControl(me)
|
||||
on mousedown(clientX, clientY) if isZoomDragTarget(event.target) call startZoomDrag(me, clientX, clientY) then halt the event end
|
||||
on mousemove(clientX, clientY) from document if moveZoomDrag(me, clientX, clientY) halt the event end
|
||||
@@ -9,8 +9,8 @@
|
||||
<button
|
||||
id="zoom-close"
|
||||
class="zoom-close-btn"
|
||||
aria-label="{{if eq .Lang "es"}}Cerrar control de zoom{{else}}Close zoom control{{end}}"
|
||||
title="{{if eq .Lang "es"}}Cerrar{{else}}Close{{end}}"
|
||||
aria-label="{{.UI.Widgets.ZoomControl.CloseLabel}}"
|
||||
title="{{.UI.Widgets.ZoomControl.CloseTitle}}"
|
||||
_="on click call hideZoomControl()">
|
||||
<iconify-icon icon="mdi:close" width="16" height="16" style="pointer-events: none;"></iconify-icon>
|
||||
</button>
|
||||
@@ -25,7 +25,7 @@
|
||||
max="300"
|
||||
step="1"
|
||||
value="100"
|
||||
aria-label="{{if eq .Lang "es"}}Ajustar nivel de zoom del CV{{else}}Adjust CV zoom level{{end}}"
|
||||
aria-label="{{.UI.Widgets.ZoomControl.SliderLabel}}"
|
||||
aria-valuemin="25"
|
||||
aria-valuemax="300"
|
||||
aria-valuenow="100"
|
||||
@@ -37,8 +37,8 @@
|
||||
<button
|
||||
id="zoom-reset"
|
||||
class="zoom-reset-btn"
|
||||
aria-label="{{if eq .Lang "es"}}Restablecer zoom al 100%{{else}}Reset zoom to 100%{{end}}"
|
||||
title="{{if eq .Lang "es"}}Restablecer{{else}}Reset{{end}}"
|
||||
aria-label="{{.UI.Widgets.ZoomControl.ResetLabel}}"
|
||||
title="{{.UI.Widgets.ZoomControl.ResetTitle}}"
|
||||
aria-live="polite"
|
||||
_="on click call handleZoomReset()">
|
||||
<span id="zoom-value-current">100</span>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<button
|
||||
id="zoom-toggle-button"
|
||||
class="fixed-btn zoom-toggle-btn no-print has-tooltip"
|
||||
aria-label="{{if eq .Lang "es"}}Alternar control de zoom{{else}}Toggle zoom control{{end}}"
|
||||
data-tooltip="{{if eq .Lang "es"}}Control de zoom{{else}}Zoom control{{end}}"
|
||||
aria-label="{{.UI.Widgets.ZoomToggle.AriaLabel}}"
|
||||
data-tooltip="{{.UI.Widgets.ZoomToggle.Tooltip}}"
|
||||
_="on click call toggleZoomControl()
|
||||
on mouseenter call highlightZoomControl(true)
|
||||
on mouseleave call highlightZoomControl(false)">
|
||||
|
||||
Reference in New Issue
Block a user