diff --git a/.env.example b/.env.example
index 7359138..8caece3 100644
--- a/.env.example
+++ b/.env.example
@@ -2,7 +2,7 @@
# Copy this file to .env and customize as needed
# Server Configuration
-PORT=8080
+PORT=1999
HOST=localhost
GO_ENV=development
diff --git a/ADDING-YOUR-PHOTO.md b/ADDING-YOUR-PHOTO.md
index 0552177..589c56a 100644
--- a/ADDING-YOUR-PHOTO.md
+++ b/ADDING-YOUR-PHOTO.md
@@ -50,7 +50,7 @@ curl -o photo.jpg "URL_DE_TU_FOTO"
## ✅ Verificar
1. Reinicia el servidor: `./cv-server`
-2. Abre http://localhost:8080
+2. Abre http://localhost:1999
3. Deberías ver tu foto en la esquina superior izquierda
Si no funciona, verás un placeholder gris con el texto "Add your photo".
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index df90a8b..9526f0f 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -114,7 +114,7 @@ handler := middleware.Recovery(
```go
cfg := config.Load() // Reads from env vars
-cfg.Server.Port // Defaults to "8080"
+cfg.Server.Port // Defaults to "1999"
cfg.Template.HotReload // Auto-detects development mode
```
@@ -278,19 +278,19 @@ if r.Header.Get("HX-Request") != "" {
```bash
# 1. Health check
-curl http://localhost:8080/health
+curl http://localhost:1999/health
# 2. Happy path
-curl "http://localhost:8080/?lang=en"
+curl "http://localhost:1999/?lang=en"
# 3. Error cases
-curl "http://localhost:8080/?lang=invalid" # 400 Bad Request
+curl "http://localhost:1999/?lang=invalid" # 400 Bad Request
# 4. HTMX requests
-curl -H "HX-Request: true" "http://localhost:8080/cv?lang=es"
+curl -H "HX-Request: true" "http://localhost:1999/cv?lang=es"
# 5. Security headers
-curl -I http://localhost:8080/
+curl -I http://localhost:1999/
```
### Future: Automated Tests
@@ -325,7 +325,7 @@ go build -o cv-server -ldflags="-s -w" .
```bash
docker build -t cv-server .
-docker run -p 8080:8080 cv-server
+docker run -p 1999:1999 cv-server
```
### 3. Cloud Platforms
diff --git a/Dockerfile b/Dockerfile
index 9b94982..4fdd05b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -32,15 +32,15 @@ COPY data data/
COPY static static/
# Expose port
-EXPOSE 8080
+EXPOSE 1999
# Set production environment
ENV GO_ENV=production
-ENV PORT=8080
+ENV PORT=1999
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
- CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
+ CMD wget --no-verbose --tries=1 --spider http://localhost:1999/health || exit 1
# Run the binary
CMD ["./cv-server"]
diff --git a/HTMX-PRODUCTION-RECOMMENDATIONS.md b/HTMX-PRODUCTION-RECOMMENDATIONS.md
index b743d43..e1e0c8e 100644
--- a/HTMX-PRODUCTION-RECOMMENDATIONS.md
+++ b/HTMX-PRODUCTION-RECOMMENDATIONS.md
@@ -631,7 +631,7 @@ terser static/js/main.js -o static/js/main.min.js -c -m
**Create `.env.production`:**
```env
GO_ENV=production
-PORT=8080
+PORT=1999
HOST=0.0.0.0
ALLOWED_ORIGINS=https://yoursite.com
CACHE_CONTROL_MAX_AGE=86400
@@ -802,13 +802,13 @@ go run main.go
### Test HTMX endpoints
```bash
# Test initial load
-curl -s 'http://localhost:8080/?lang=en' | head -50
+curl -s 'http://localhost:1999/?lang=en' | head -50
# Test HTMX partial
-curl -s 'http://localhost:8080/cv?lang=es' | head -50
+curl -s 'http://localhost:1999/cv?lang=es' | head -50
# Test performance
-curl -o /dev/null -s -w "Time: %{time_total}s\n" 'http://localhost:8080/cv?lang=en'
+curl -o /dev/null -s -w "Time: %{time_total}s\n" 'http://localhost:1999/cv?lang=en'
```
### Run Lighthouse audit
@@ -817,7 +817,7 @@ curl -o /dev/null -s -w "Time: %{time_total}s\n" 'http://localhost:8080/cv?lang=
npm install -g lighthouse
# Run audit
-lighthouse http://localhost:8080/?lang=en --view
+lighthouse http://localhost:1999/?lang=en --view
```
### Test accessibility
@@ -826,7 +826,7 @@ lighthouse http://localhost:8080/?lang=en --view
npm install -g @axe-core/cli
# Run audit
-axe http://localhost:8080/?lang=en
+axe http://localhost:1999/?lang=en
```
---
diff --git a/Makefile b/Makefile
index a37953c..80c20a4 100644
--- a/Makefile
+++ b/Makefile
@@ -24,20 +24,20 @@ run: build
test:
@echo "🧪 Testing endpoints..."
@echo "\n1. Health check:"
- @curl -s http://localhost:8080/health | jq .
+ @curl -s http://localhost:1999/health | jq .
@echo "\n2. English CV (first 50 chars):"
- @curl -s "http://localhost:8080/?lang=en" | head -c 50
+ @curl -s "http://localhost:1999/?lang=en" | head -c 50
@echo "\n\n3. Spanish CV content (first 50 chars):"
- @curl -s "http://localhost:8080/cv?lang=es" | head -c 50
+ @curl -s "http://localhost:1999/cv?lang=es" | head -c 50
@echo "\n\n4. Security headers:"
- @curl -I http://localhost:8080/ 2>&1 | grep -E "^(X-|Content-Security)"
+ @curl -I http://localhost:1999/ 2>&1 | grep -E "^(X-|Content-Security)"
@echo "\n✓ All tests complete"
# Test error handling
test-errors:
@echo "🧪 Testing error handling..."
@echo "\n1. Invalid language:"
- @curl -i "http://localhost:8080/?lang=invalid" 2>&1 | head -15
+ @curl -i "http://localhost:1999/?lang=invalid" 2>&1 | head -15
@echo "\n2. Error logging check"
@echo "✓ Error tests complete"
@@ -55,7 +55,7 @@ docker-build:
docker-run:
@echo "🐳 Running Docker container..."
- docker run -p 8080:8080 cv-server:latest
+ docker run -p 1999:1999 cv-server:latest
# Help
help:
diff --git a/QUICK-START-IMPROVEMENTS.md b/QUICK-START-IMPROVEMENTS.md
index 964bf26..c825d46 100644
--- a/QUICK-START-IMPROVEMENTS.md
+++ b/QUICK-START-IMPROVEMENTS.md
@@ -401,7 +401,7 @@ func main() {
)
// ... start server with handler
- log.Fatal(http.ListenAndServe(":8080", handler))
+ log.Fatal(http.ListenAndServe(":1999", handler))
}
```
@@ -436,7 +436,7 @@ go run main.go
### 4. Test Security Headers
```bash
-curl -I http://localhost:8080/
+curl -I http://localhost:1999/
# Should see security headers in response
```
@@ -503,7 +503,7 @@ curl -I http://localhost:8080/
```
4. **Validate with tools:**
- - Lighthouse: `lighthouse http://localhost:8080`
+ - Lighthouse: `lighthouse http://localhost:1999`
- WAVE: Install browser extension
- axe DevTools: Install browser extension
diff --git a/README.md b/README.md
index 02622d1..b4d41b6 100644
--- a/README.md
+++ b/README.md
@@ -26,10 +26,10 @@
go build -o cv-server && ./cv-server
\`\`\`
-Open **http://localhost:8080**
+Open **http://localhost:1999**
-- 🇬🇧 English: http://localhost:8080/?lang=en
-- 🇪🇸 Spanish: http://localhost:8080/?lang=es
+- 🇬🇧 English: http://localhost:1999/?lang=en
+- 🇪🇸 Spanish: http://localhost:1999/?lang=es
## 📄 Updating Your CV
diff --git a/internal/config/config.go b/internal/config/config.go
index e16ebb6..37a7683 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -23,9 +23,9 @@ type ServerConfig struct {
// TemplateConfig contains template-specific settings
type TemplateConfig struct {
- Dir string
+ Dir string
PartialsDir string
- HotReload bool
+ HotReload bool
}
// DataConfig contains data directory settings
@@ -37,15 +37,15 @@ type DataConfig struct {
func Load() *Config {
return &Config{
Server: ServerConfig{
- Port: getEnv("PORT", "8080"),
+ Port: getEnv("PORT", "1999"),
Host: getEnv("HOST", "localhost"),
ReadTimeout: getEnvAsInt("READ_TIMEOUT", 15),
WriteTimeout: getEnvAsInt("WRITE_TIMEOUT", 15),
},
Template: TemplateConfig{
- Dir: getEnv("TEMPLATE_DIR", "templates"),
+ Dir: getEnv("TEMPLATE_DIR", "templates"),
PartialsDir: getEnv("PARTIALS_DIR", "templates/partials"),
- HotReload: getEnvAsBool("TEMPLATE_HOT_RELOAD", isDevelopment()),
+ HotReload: getEnvAsBool("TEMPLATE_HOT_RELOAD", isDevelopment()),
},
Data: DataConfig{
Dir: getEnv("DATA_DIR", "data"),
diff --git a/static/css/main.css b/static/css/main.css
index 330ec84..e200d66 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -1,17 +1,14 @@
-/* Minimal CV Design - A4 Page Simulation */
+/* CV Design - Original Style Recreation */
:root {
--bg-gray: #525659;
+ --sidebar-gray: #d9d9d9;
+ --black-bar: #2b2b2b;
--paper-white: #ffffff;
--text-dark: #2d2d2d;
--text-gray: #555555;
--accent-blue: #0066cc;
--border-gray: #dddddd;
-
- /* A4 dimensions */
- --a4-width: 210mm;
- --a4-height: 297mm;
- --page-padding: 20mm;
}
* {
@@ -21,7 +18,7 @@
}
body {
- font-family: 'Inter', Arial, sans-serif;
+ font-family: Arial, Helvetica, sans-serif;
background-color: var(--bg-gray);
color: var(--text-dark);
line-height: 1.6;
@@ -36,21 +33,23 @@ a:hover {
text-decoration: underline;
}
-/* Action Bar */
+/* Single Black Top Bar */
.action-bar {
- background: white;
- border-bottom: 1px solid var(--border-gray);
+ background: var(--black-bar);
+ color: white;
position: sticky;
top: 0;
z-index: 100;
+ box-shadow: 0 2px 5px rgba(0,0,0,0.3);
}
.action-bar-content {
- max-width: var(--a4-width);
- margin: 0 auto;
+ max-width: 100%;
+ margin: 0;
padding: 1rem 2rem;
- display: flex;
- justify-content: space-between;
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ gap: 2rem;
align-items: center;
}
@@ -61,35 +60,64 @@ a:hover {
.lang-btn {
padding: 0.4rem 1rem;
- border: 1px solid var(--border-gray);
- background: white;
+ border: 1px solid rgba(255,255,255,0.3);
+ background: transparent;
+ color: white;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
}
.lang-btn:hover {
- background: #f5f5f5;
+ background: rgba(255,255,255,0.1);
}
.lang-btn.active {
- background: var(--accent-blue);
- color: white;
- border-color: var(--accent-blue);
+ background: rgba(255,255,255,0.2);
+ border-color: white;
}
.export-btn {
padding: 0.4rem 1.2rem;
- background: var(--accent-blue);
+ background: transparent;
color: white;
- border: none;
+ border: 1px solid rgba(255,255,255,0.3);
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
}
.export-btn:hover {
- background: #0052a3;
+ background: rgba(255,255,255,0.1);
+}
+
+/* Title badges in center of bar */
+.title-badges {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 0.8rem;
+}
+
+.title-badge {
+ font-size: 0.75rem;
+ letter-spacing: 1px;
+ font-weight: 400;
+ color: white;
+ white-space: nowrap;
+}
+
+.title-separator {
+ color: rgba(255,255,255,0.4);
+ font-size: 0.75rem;
+}
+
+/* Action buttons on right */
+.action-buttons {
+ display: flex;
+ gap: 0.5rem;
+ align-items: center;
}
/* Loading Indicator */
@@ -103,7 +131,7 @@ a:hover {
.loader {
border: 2px solid #f3f3f3;
- border-top: 2px solid var(--accent-blue);
+ border-top: 2px solid white;
border-radius: 50%;
width: 20px;
height: 20px;
@@ -115,27 +143,26 @@ a:hover {
100% { transform: rotate(360deg); }
}
-/* A4 Page Container */
+/* Main CV Container */
.cv-container {
width: 100%;
- max-width: 100%;
- margin: 0;
- padding: 2rem 0;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0;
display: flex;
flex-direction: column;
- align-items: center;
- gap: 2rem;
}
-/* A4 Paper - Exact dimensions */
+/* CV Paper - Two-column layout with shadow */
.cv-paper {
- width: var(--a4-width);
- min-height: var(--a4-height);
+ width: 100%;
background: var(--paper-white);
- padding: var(--page-padding);
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
- margin: 0 auto;
+ box-shadow: 0 0 30px rgba(0,0,0,0.4);
+ margin: 0;
position: relative;
+ display: grid;
+ grid-template-columns: 300px 1fr;
+ min-height: 100vh;
}
/* Page break helpers */
@@ -149,31 +176,55 @@ a:hover {
break-inside: avoid;
}
-/* Header - Photo on right, inline with text */
-.cv-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- gap: 2rem;
- border-bottom: 2px solid var(--text-dark);
- padding-bottom: 1.5rem;
+/* Sidebar - Left column */
+.cv-sidebar {
+ background: var(--sidebar-gray);
+ padding: 2rem 1.5rem;
+ font-size: 0.9rem;
+}
+
+.sidebar-section {
margin-bottom: 2rem;
}
-.cv-header-left {
- flex: 1;
+.sidebar-title {
+ font-size: 1rem;
+ font-weight: 700;
+ margin-bottom: 0.8rem;
+ color: var(--text-dark);
}
-.cv-header-right {
- flex-shrink: 0;
+.sidebar-content {
+ line-height: 1.8;
+}
+
+.skill-item {
+ margin-bottom: 0.3rem;
+ color: var(--text-dark);
+}
+
+/* Main Content - Right column */
+.cv-main {
+ background: var(--paper-white);
+ padding: 2rem 2.5rem;
+}
+
+/* Header with photo and name */
+.cv-header {
+ margin-bottom: 2rem;
+}
+
+.cv-header-content {
+ display: flex;
+ align-items: flex-start;
+ gap: 1.5rem;
}
.cv-photo {
- width: 100px;
- height: 100px;
- border-radius: 50%;
+ width: 150px;
+ height: 200px;
+ flex-shrink: 0;
overflow: hidden;
- border: 3px solid var(--border-gray);
}
.cv-photo img {
@@ -183,230 +234,117 @@ a:hover {
}
.cv-name {
- font-size: 2rem;
- font-weight: 700;
- margin-bottom: 0.5rem;
-}
-
-.cv-title {
- font-size: 1.1rem;
+ font-size: 2.5rem;
font-weight: 400;
- color: var(--text-gray);
- margin-bottom: 1rem;
+ margin-bottom: 0.5rem;
+ color: var(--text-dark);
}
-.cv-contact {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 0.4rem;
- font-size: 0.85rem;
+.cv-experience-years {
+ font-size: 1rem;
color: var(--text-gray);
+ margin: 0;
}
/* Sections */
.cv-section {
- margin-bottom: 1.5rem;
+ margin-bottom: 2rem;
page-break-inside: avoid;
}
.section-title {
- font-size: 1.2rem;
+ font-size: 1.1rem;
font-weight: 700;
margin-bottom: 1rem;
- padding-bottom: 0.3rem;
- border-bottom: 1px solid var(--border-gray);
+ color: var(--text-dark);
}
.summary-text {
line-height: 1.6;
text-align: justify;
- font-size: 0.9rem;
+ font-size: 0.95rem;
+ color: var(--text-dark);
}
-/* Experience - with separators */
+/* Experience */
.experience-item {
- margin-bottom: 1.2rem;
- padding-bottom: 1.2rem;
- border-bottom: 1px solid var(--border-gray);
+ margin-bottom: 1.5rem;
page-break-inside: avoid;
}
-.experience-item:last-child {
- border-bottom: none;
+.experience-header {
+ margin-bottom: 0.6rem;
}
-.experience-header {
+.experience-title-line {
display: flex;
justify-content: space-between;
- margin-bottom: 0.6rem;
+ align-items: baseline;
gap: 1rem;
- align-items: center;
-}
-
-.company-logo {
- width: 35px;
- height: 35px;
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 0.75rem;
-}
-
-.company-logo img {
- max-width: 100%;
- max-height: 100%;
- object-fit: contain;
-}
-
-.experience-title {
- flex: 1;
+ flex-wrap: wrap;
}
.position {
font-size: 1rem;
font-weight: 600;
- margin-bottom: 0.2rem;
-}
-
-.company {
- color: var(--text-gray);
- font-size: 0.85rem;
+ margin: 0;
+ color: var(--text-dark);
}
.experience-period {
color: var(--text-gray);
- font-size: 0.8rem;
- white-space: nowrap;
+ font-size: 0.85rem;
font-style: italic;
}
.short-desc {
- color: var(--text-gray);
- font-size: 0.85rem;
- line-height: 1.5;
- margin-bottom: 0.6rem;
+ color: var(--text-dark);
+ font-size: 0.9rem;
+ line-height: 1.6;
+ margin-top: 0.5rem;
}
.responsibilities {
list-style: none;
- margin-bottom: 0.6rem;
+ margin-top: 0.5rem;
+ padding-left: 0;
}
.responsibilities li {
- padding-left: 1rem;
- margin-bottom: 0.3rem;
+ padding-left: 1.2rem;
+ margin-bottom: 0.4rem;
position: relative;
- font-size: 0.85rem;
+ font-size: 0.9rem;
+ color: var(--text-dark);
+ line-height: 1.5;
}
.responsibilities li:before {
content: "•";
position: absolute;
left: 0;
- font-weight: bold;
-}
-
-.technologies {
- font-size: 0.8rem;
color: var(--text-gray);
- font-style: italic;
}
/* Education */
.education-item {
- margin-bottom: 0.8rem;
- font-size: 0.9rem;
-}
-
-.education-header {
- display: flex;
- justify-content: space-between;
- margin-bottom: 0.3rem;
-}
-
-.degree {
- font-size: 0.95rem;
- font-weight: 600;
-}
-
-.education-period {
- color: var(--text-gray);
- font-size: 0.8rem;
- font-style: italic;
-}
-
-.institution {
- color: var(--text-gray);
- font-size: 0.85rem;
-}
-
-/* Skills */
-.skill-block {
- margin-bottom: 0.8rem;
-}
-
-.skill-title {
- font-size: 0.95rem;
- font-weight: 600;
- margin-bottom: 0.3rem;
-}
-
-.skill-list {
- color: var(--text-gray);
- font-size: 0.85rem;
-}
-
-/* Projects */
-.project-item {
margin-bottom: 1rem;
font-size: 0.9rem;
+ line-height: 1.6;
+ color: var(--text-dark);
}
-.project-header {
- display: flex;
- justify-content: space-between;
- margin-bottom: 0.3rem;
-}
-
-.project-name {
- font-size: 1rem;
- font-weight: 600;
-}
-
-.project-period {
- color: var(--text-gray);
- font-size: 0.8rem;
- font-style: italic;
-}
-
-.project-role {
- color: var(--text-gray);
- font-size: 0.85rem;
- margin-bottom: 0.3rem;
-}
-
-.project-description {
- margin-bottom: 0.3rem;
- font-size: 0.85rem;
-}
-
-/* Certifications & Awards */
-.cert-item,
-.award-item {
- margin-bottom: 0.4rem;
- font-size: 0.85rem;
-}
/* Languages */
.languages-list {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 0.5rem;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1.5rem;
}
.language-item {
- font-size: 0.85rem;
+ font-size: 0.9rem;
+ color: var(--text-dark);
}
/* Footer */
@@ -417,30 +355,6 @@ footer {
font-size: 0.85rem;
}
-/* CV Length Toggle */
-.cv-length-toggle {
- display: flex;
- gap: 0.5rem;
-}
-
-.length-btn {
- padding: 0.4rem 1rem;
- border: 1px solid var(--border-gray);
- background: white;
- border-radius: 4px;
- cursor: pointer;
- font-size: 0.9rem;
-}
-
-.length-btn:hover {
- background: #f5f5f5;
-}
-
-.length-btn.active {
- background: var(--accent-blue);
- color: white;
- border-color: var(--accent-blue);
-}
/* Short CV - Hide detailed content */
.cv-short .long-only {
@@ -468,57 +382,47 @@ footer {
/* Responsive - tablet/mobile */
@media (max-width: 900px) {
.cv-paper {
- width: 100%;
- min-height: auto;
- padding: 15mm;
+ grid-template-columns: 1fr;
box-shadow: none;
}
-
- .cv-container {
- padding: 1rem;
+
+ .cv-sidebar {
+ padding: 1.5rem 1rem;
}
-
+
+ .cv-main {
+ padding: 1.5rem 1rem;
+ }
+
.cv-name {
font-size: 1.8rem;
}
-
- .cv-header {
- flex-direction: column;
- align-items: center;
- text-align: center;
- }
-
+
.cv-photo {
- order: -1;
- margin-bottom: 1rem;
+ width: 120px;
+ height: 150px;
}
-
- .cv-contact {
- grid-template-columns: 1fr;
- text-align: center;
- }
-
- .experience-header,
- .project-header,
- .education-header {
- flex-direction: column;
- gap: 0.25rem;
- }
-
- .company-logo {
- display: none;
- }
-
+
.action-bar-content {
- flex-wrap: wrap;
+ grid-template-columns: 1fr;
+ gap: 1rem;
+ padding: 1rem;
+ }
+
+ .language-toggle,
+ .title-badges,
+ .action-buttons {
justify-content: center;
}
-
- .cv-length-toggle {
- order: 1;
- width: 100%;
- justify-content: center;
- margin-top: 0.5rem;
+
+ .title-badges {
+ order: -1;
+ }
+
+ .experience-title-line {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.25rem;
}
}
diff --git a/templates/cv-content.html b/templates/cv-content.html
index 7c7d98c..b8919e4 100644
--- a/templates/cv-content.html
+++ b/templates/cv-content.html
@@ -1,153 +1,120 @@
-
-
-
-
-
- {{if eq .Lang "es"}}Resumen{{else}}Summary{{end}}
- {{.CV.Summary}}
-
-
-
-
- {{if eq .Lang "es"}}Experiencia Laboral{{else}}Work History{{end}}
-
- {{range .CV.Experience}}
-
-
-
- {{if .ShortDescription}}
-
{{.ShortDescription}}
- {{end}}
-
-
- {{range .Responsibilities}}
- - {{.}}
- {{end}}
-
-
- {{if .Technologies}}
-
- {{range $index, $tech := .Technologies}}{{if $index}}, {{end}}{{$tech}}{{end}}
+
+
- {{end}}
-
+ {{end}}
+
-
-
- {{if eq .Lang "es"}}Formación{{else}}Education{{end}}
-
- {{range .CV.Education}}
-
-