refactor: standardize port to 1999 across all files
- Updated default port from 8080 to 1999 in config, Docker, and documentation files - Modified example URLs and test commands to use new port - Ensured consistent port references in environment configs and deployment examples - Updated health check endpoints in Docker and testing scripts The port change aligns with LIV Golf port allocation standards for staging environments (5000-9999 range).
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
# Copy this file to .env and customize as needed
|
||||
|
||||
# Server Configuration
|
||||
PORT=8080
|
||||
PORT=1999
|
||||
HOST=localhost
|
||||
GO_ENV=development
|
||||
|
||||
|
||||
@@ -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".
|
||||
|
||||
+7
-7
@@ -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
|
||||
|
||||
+3
-3
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
+167
-263
@@ -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;
|
||||
}
|
||||
|
||||
.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;
|
||||
width: 120px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+106
-139
@@ -1,153 +1,120 @@
|
||||
<!-- CV Content Template - Minimal Design -->
|
||||
<div class="cv-header">
|
||||
<div class="cv-header-left">
|
||||
<div class="cv-header-main">
|
||||
<h1 class="cv-name">{{.CV.Personal.Name}}</h1>
|
||||
<h2 class="cv-title">{{.CV.Personal.Title}}</h2>
|
||||
</div>
|
||||
<div class="cv-contact">
|
||||
<div class="contact-item">{{.CV.Personal.Location}}</div>
|
||||
<div class="contact-item"><a href="mailto:{{.CV.Personal.Email}}">{{.CV.Personal.Email}}</a></div>
|
||||
<div class="contact-item">{{.CV.Personal.Phone}}</div>
|
||||
<div class="contact-item"><a href="{{.CV.Personal.LinkedIn}}" target="_blank">LinkedIn</a></div>
|
||||
<div class="contact-item"><a href="{{.CV.Personal.GitHub}}" target="_blank">GitHub</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cv-header-right">
|
||||
<div class="cv-photo">
|
||||
<img src="/static/images/profile/photo.jpg" alt="{{.CV.Personal.Name}}" onerror="this.src='/static/images/profile/placeholder.svg'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary -->
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Resumen{{else}}Summary{{end}}</h3>
|
||||
<p class="summary-text">{{.CV.Summary}}</p>
|
||||
</section>
|
||||
|
||||
<!-- Experience -->
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Experiencia Laboral{{else}}Work History{{end}}</h3>
|
||||
|
||||
{{range .CV.Experience}}
|
||||
<div class="experience-item">
|
||||
<div class="experience-header">
|
||||
{{if .CompanyLogo}}
|
||||
<div class="company-logo">
|
||||
<img src="/static/images/companies/{{.CompanyLogo}}" alt="{{.Company}}" onerror="this.style.display='none'">
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="experience-title">
|
||||
<h4 class="position">{{.Position}}</h4>
|
||||
<div class="company">{{.Company}}, {{.Location}}</div>
|
||||
</div>
|
||||
<div class="experience-period">
|
||||
{{.StartDate}} - {{if .Current}}{{if eq $.Lang "es"}}Presente{{else}}Present{{end}}{{else}}{{.EndDate}}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .ShortDescription}}
|
||||
<p class="short-desc">{{.ShortDescription}}</p>
|
||||
{{end}}
|
||||
|
||||
<ul class="responsibilities long-only">
|
||||
{{range .Responsibilities}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
{{if .Technologies}}
|
||||
<div class="technologies long-only">
|
||||
{{range $index, $tech := .Technologies}}{{if $index}}, {{end}}{{$tech}}{{end}}
|
||||
<!-- Left Sidebar - Skills -->
|
||||
<aside class="cv-sidebar">
|
||||
<!-- Skills Section -->
|
||||
<section class="sidebar-section">
|
||||
<h3 class="sidebar-title">{{if eq .Lang "es"}}Lenguajes de Programación{{else}}Programming Languages{{end}}</h3>
|
||||
{{range .CV.Skills.Technical}}
|
||||
{{if eq .Category "Programming Languages"}}
|
||||
<div class="sidebar-content">
|
||||
{{range .Items}}<div class="skill-item">{{.}}</div>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
<!-- Education -->
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Formación{{else}}Education{{end}}</h3>
|
||||
|
||||
{{range .CV.Education}}
|
||||
<div class="education-item">
|
||||
<div class="education-header">
|
||||
<h4 class="degree">{{.Degree}}</h4>
|
||||
<div class="education-period">{{.StartDate}} - {{.EndDate}}</div>
|
||||
<section class="sidebar-section">
|
||||
<h3 class="sidebar-title">{{if eq .Lang "es"}}Desarrollo Web{{else}}Web Development{{end}}</h3>
|
||||
{{range .CV.Skills.Technical}}
|
||||
{{if eq .Category "Web Development"}}
|
||||
<div class="sidebar-content">
|
||||
{{range .Items}}<div class="skill-item">{{.}}</div>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
<section class="sidebar-section">
|
||||
<h3 class="sidebar-title">{{if eq .Lang "es"}}Frameworks JavaScript{{else}}Javascript Frameworks{{end}}</h3>
|
||||
{{range .CV.Skills.Technical}}
|
||||
{{if eq .Category "JavaScript Frameworks"}}
|
||||
<div class="sidebar-content">
|
||||
{{range .Items}}<div class="skill-item">{{.}}</div>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="cv-main">
|
||||
<!-- Header with Name and Photo -->
|
||||
<div class="cv-header">
|
||||
<div class="cv-header-content">
|
||||
<div class="cv-photo">
|
||||
<img src="/static/images/profile/photo.jpg" alt="{{.CV.Personal.Name}}" onerror="this.src='/static/images/profile/placeholder.svg'">
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="cv-name">{{.CV.Personal.Name}}</h1>
|
||||
<p class="cv-experience-years">{{if eq .Lang "es"}}20 años de experiencia{{else}}20 years of experience{{end}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="institution">{{.Institution}}, {{.Location}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
<!-- Skills -->
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Competencias{{else}}Skills{{end}}</h3>
|
||||
<!-- Summary -->
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Resumen{{else}}Training{{end}}</h3>
|
||||
<p class="summary-text">{{.CV.Summary}}</p>
|
||||
</section>
|
||||
|
||||
{{range .CV.Skills.Technical}}
|
||||
<div class="skill-block">
|
||||
<h4 class="skill-title">{{.Category}}</h4>
|
||||
<p class="skill-list">
|
||||
{{range $index, $item := .Items}}{{if $index}}, {{end}}{{$item}}{{end}}
|
||||
<!-- Education -->
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Formación{{else}}Training{{end}}</h3>
|
||||
{{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}})
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
<!-- Skills Summary -->
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Competencias{{else}}Skills{{end}}</h3>
|
||||
<p class="summary-text">
|
||||
{{if eq .Lang "es"}}
|
||||
Amplio conocimiento en entornos web, tanto J2EE como PHP. Experto en tecnologías front-end, aunque con considerable experiencia en sistemas back-end. Receptivo al aprendizaje de nuevas tecnologías, y con una gran dosis de creatividad. Capacidad de analizar problemas y aportar soluciones específicas adaptadas a cada tipo de cliente. Me gusta trabajar tanto solo como en grupos.
|
||||
{{else}}
|
||||
Extensive knowledge in web environments, both J2EE and PHP. Expert in front-end technologies, although with considerable experience in back-end systems. Receptive to learning new technologies, and with a large dose of creativity. Ability to analyze problems and provide specific solutions tailored to each client type. I like to work both alone and in groups.
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- Projects -->
|
||||
{{if .CV.Projects}}
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Proyectos{{else}}Projects{{end}}</h3>
|
||||
<!-- Experience -->
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Experiencia{{else}}Experience{{end}}</h3>
|
||||
|
||||
{{range .CV.Projects}}
|
||||
<div class="project-item">
|
||||
<div class="project-header">
|
||||
<h4 class="project-name">{{.Name}}</h4>
|
||||
<div class="project-period">{{.Period}}</div>
|
||||
</div>
|
||||
<div class="project-role">{{.Role}}</div>
|
||||
<p class="project-description">{{.Description}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
{{range .CV.Experience}}
|
||||
<div class="experience-item">
|
||||
<div class="experience-header">
|
||||
<div class="experience-title-line">
|
||||
<h4 class="position">{{.Position}} / {{if eq $.Lang "es"}}Analista Programador{{else}}Analyst Programmer{{end}}</h4>
|
||||
<span class="experience-period">{{.StartDate}} / {{if .Current}}{{if eq $.Lang "es"}}presente{{else}}now{{end}}{{else}}{{.EndDate}}{{end}} - ({{.Location}})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Certifications -->
|
||||
{{if .CV.Certifications}}
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Certificaciones{{else}}Certifications{{end}}</h3>
|
||||
{{if .ShortDescription}}
|
||||
<p class="short-desc">{{.ShortDescription}}</p>
|
||||
{{end}}
|
||||
|
||||
{{range .CV.Certifications}}
|
||||
<div class="cert-item">
|
||||
<strong>{{.Name}}</strong> - {{.Issuer}} ({{.Date}})
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- Awards -->
|
||||
{{if .CV.Awards}}
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Premios{{else}}Awards{{end}}</h3>
|
||||
|
||||
{{range .CV.Awards}}
|
||||
<div class="award-item">
|
||||
<strong>{{.Title}}</strong> - {{.Issuer}} ({{.Date}})
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- Languages -->
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Idiomas{{else}}Languages{{end}}</h3>
|
||||
|
||||
<div class="languages-list">
|
||||
{{range .CV.Languages}}
|
||||
<div class="language-item">
|
||||
<strong>{{.Language}}</strong>: {{.Proficiency}}
|
||||
<div class="long-only">
|
||||
<ul class="responsibilities">
|
||||
{{range .Responsibilities}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- Languages -->
|
||||
<section class="cv-section">
|
||||
<h3 class="section-title">{{if eq .Lang "es"}}Idiomas{{else}}Languages{{end}}</h3>
|
||||
<div class="languages-list">
|
||||
{{range .CV.Languages}}
|
||||
<div class="language-item">
|
||||
<strong>{{.Language}}</strong>: {{.Proficiency}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
+23
-16
@@ -20,9 +20,10 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Language & Export Bar (hidden in print) -->
|
||||
<!-- Single Black Bar with Everything -->
|
||||
<div class="action-bar no-print">
|
||||
<div class="action-bar-content">
|
||||
<!-- Left: Language buttons -->
|
||||
<div class="language-toggle">
|
||||
<button
|
||||
class="lang-btn {{if eq .Lang "en"}}active{{end}}"
|
||||
@@ -30,7 +31,7 @@
|
||||
hx-target="#cv-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-indicator="#loading">
|
||||
🇬🇧 English
|
||||
English
|
||||
</button>
|
||||
<button
|
||||
class="lang-btn {{if eq .Lang "es"}}active{{end}}"
|
||||
@@ -38,28 +39,34 @@
|
||||
hx-target="#cv-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-indicator="#loading">
|
||||
🇪🇸 Español
|
||||
Español
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="cv-length-toggle">
|
||||
<button
|
||||
class="length-btn active"
|
||||
onclick="toggleCVLength('short')">
|
||||
{{if eq .Lang "es"}}Corto{{else}}Short{{end}}
|
||||
</button>
|
||||
<button
|
||||
class="length-btn"
|
||||
onclick="toggleCVLength('long')">
|
||||
{{if eq .Lang "es"}}Largo{{else}}Long{{end}}
|
||||
</button>
|
||||
<!-- Center: Title badges -->
|
||||
<div class="title-badges">
|
||||
<span class="title-badge">ANALYST PROGRAMMER</span>
|
||||
<span class="title-separator">|</span>
|
||||
<span class="title-badge">NODEJS + REACTJS DEVELOPER</span>
|
||||
<span class="title-separator">|</span>
|
||||
<span class="title-badge">WEB DEVELOPER</span>
|
||||
<span class="title-separator">|</span>
|
||||
<span class="title-badge">JAVA DEVELOPER</span>
|
||||
<span class="title-separator">|</span>
|
||||
<span class="title-badge">PHP DEVELOPER</span>
|
||||
</div>
|
||||
|
||||
<div class="export-actions">
|
||||
<!-- Right: Action buttons -->
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
class="export-btn"
|
||||
onclick="window.print()">
|
||||
📄 {{if eq .Lang "es"}}Descargar PDF{{else}}Download PDF{{end}}
|
||||
📥 {{if eq .Lang "es"}}Descargar PDF{{else}}Download as PDF{{end}}
|
||||
</button>
|
||||
<button
|
||||
class="export-btn"
|
||||
onclick="window.print()">
|
||||
🖨️ {{if eq .Lang "es"}}Imprimir{{else}}Print Friendly{{end}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user