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
|
# Copy this file to .env and customize as needed
|
||||||
|
|
||||||
# Server Configuration
|
# Server Configuration
|
||||||
PORT=8080
|
PORT=1999
|
||||||
HOST=localhost
|
HOST=localhost
|
||||||
GO_ENV=development
|
GO_ENV=development
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ curl -o photo.jpg "URL_DE_TU_FOTO"
|
|||||||
## ✅ Verificar
|
## ✅ Verificar
|
||||||
|
|
||||||
1. Reinicia el servidor: `./cv-server`
|
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
|
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".
|
Si no funciona, verás un placeholder gris con el texto "Add your photo".
|
||||||
|
|||||||
+7
-7
@@ -114,7 +114,7 @@ handler := middleware.Recovery(
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
cfg := config.Load() // Reads from env vars
|
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
|
cfg.Template.HotReload // Auto-detects development mode
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -278,19 +278,19 @@ if r.Header.Get("HX-Request") != "" {
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Health check
|
# 1. Health check
|
||||||
curl http://localhost:8080/health
|
curl http://localhost:1999/health
|
||||||
|
|
||||||
# 2. Happy path
|
# 2. Happy path
|
||||||
curl "http://localhost:8080/?lang=en"
|
curl "http://localhost:1999/?lang=en"
|
||||||
|
|
||||||
# 3. Error cases
|
# 3. Error cases
|
||||||
curl "http://localhost:8080/?lang=invalid" # 400 Bad Request
|
curl "http://localhost:1999/?lang=invalid" # 400 Bad Request
|
||||||
|
|
||||||
# 4. HTMX requests
|
# 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
|
# 5. Security headers
|
||||||
curl -I http://localhost:8080/
|
curl -I http://localhost:1999/
|
||||||
```
|
```
|
||||||
|
|
||||||
### Future: Automated Tests
|
### Future: Automated Tests
|
||||||
@@ -325,7 +325,7 @@ go build -o cv-server -ldflags="-s -w" .
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t cv-server .
|
docker build -t cv-server .
|
||||||
docker run -p 8080:8080 cv-server
|
docker run -p 1999:1999 cv-server
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Cloud Platforms
|
### 3. Cloud Platforms
|
||||||
|
|||||||
+3
-3
@@ -32,15 +32,15 @@ COPY data data/
|
|||||||
COPY static static/
|
COPY static static/
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 8080
|
EXPOSE 1999
|
||||||
|
|
||||||
# Set production environment
|
# Set production environment
|
||||||
ENV GO_ENV=production
|
ENV GO_ENV=production
|
||||||
ENV PORT=8080
|
ENV PORT=1999
|
||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
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
|
# Run the binary
|
||||||
CMD ["./cv-server"]
|
CMD ["./cv-server"]
|
||||||
|
|||||||
@@ -631,7 +631,7 @@ terser static/js/main.js -o static/js/main.min.js -c -m
|
|||||||
**Create `.env.production`:**
|
**Create `.env.production`:**
|
||||||
```env
|
```env
|
||||||
GO_ENV=production
|
GO_ENV=production
|
||||||
PORT=8080
|
PORT=1999
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
ALLOWED_ORIGINS=https://yoursite.com
|
ALLOWED_ORIGINS=https://yoursite.com
|
||||||
CACHE_CONTROL_MAX_AGE=86400
|
CACHE_CONTROL_MAX_AGE=86400
|
||||||
@@ -802,13 +802,13 @@ go run main.go
|
|||||||
### Test HTMX endpoints
|
### Test HTMX endpoints
|
||||||
```bash
|
```bash
|
||||||
# Test initial load
|
# Test initial load
|
||||||
curl -s 'http://localhost:8080/?lang=en' | head -50
|
curl -s 'http://localhost:1999/?lang=en' | head -50
|
||||||
|
|
||||||
# Test HTMX partial
|
# 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
|
# 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
|
### 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
|
npm install -g lighthouse
|
||||||
|
|
||||||
# Run audit
|
# Run audit
|
||||||
lighthouse http://localhost:8080/?lang=en --view
|
lighthouse http://localhost:1999/?lang=en --view
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test accessibility
|
### Test accessibility
|
||||||
@@ -826,7 +826,7 @@ lighthouse http://localhost:8080/?lang=en --view
|
|||||||
npm install -g @axe-core/cli
|
npm install -g @axe-core/cli
|
||||||
|
|
||||||
# Run audit
|
# Run audit
|
||||||
axe http://localhost:8080/?lang=en
|
axe http://localhost:1999/?lang=en
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -24,20 +24,20 @@ run: build
|
|||||||
test:
|
test:
|
||||||
@echo "🧪 Testing endpoints..."
|
@echo "🧪 Testing endpoints..."
|
||||||
@echo "\n1. Health check:"
|
@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):"
|
@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):"
|
@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:"
|
@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"
|
@echo "\n✓ All tests complete"
|
||||||
|
|
||||||
# Test error handling
|
# Test error handling
|
||||||
test-errors:
|
test-errors:
|
||||||
@echo "🧪 Testing error handling..."
|
@echo "🧪 Testing error handling..."
|
||||||
@echo "\n1. Invalid language:"
|
@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 "\n2. Error logging check"
|
||||||
@echo "✓ Error tests complete"
|
@echo "✓ Error tests complete"
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ docker-build:
|
|||||||
|
|
||||||
docker-run:
|
docker-run:
|
||||||
@echo "🐳 Running Docker container..."
|
@echo "🐳 Running Docker container..."
|
||||||
docker run -p 8080:8080 cv-server:latest
|
docker run -p 1999:1999 cv-server:latest
|
||||||
|
|
||||||
# Help
|
# Help
|
||||||
help:
|
help:
|
||||||
|
|||||||
@@ -401,7 +401,7 @@ func main() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ... start server with handler
|
// ... 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
|
### 4. Test Security Headers
|
||||||
```bash
|
```bash
|
||||||
curl -I http://localhost:8080/
|
curl -I http://localhost:1999/
|
||||||
# Should see security headers in response
|
# Should see security headers in response
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -503,7 +503,7 @@ curl -I http://localhost:8080/
|
|||||||
```
|
```
|
||||||
|
|
||||||
4. **Validate with tools:**
|
4. **Validate with tools:**
|
||||||
- Lighthouse: `lighthouse http://localhost:8080`
|
- Lighthouse: `lighthouse http://localhost:1999`
|
||||||
- WAVE: Install browser extension
|
- WAVE: Install browser extension
|
||||||
- axe DevTools: Install browser extension
|
- axe DevTools: Install browser extension
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,10 @@
|
|||||||
go build -o cv-server && ./cv-server
|
go build -o cv-server && ./cv-server
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
Open **http://localhost:8080**
|
Open **http://localhost:1999**
|
||||||
|
|
||||||
- 🇬🇧 English: http://localhost:8080/?lang=en
|
- 🇬🇧 English: http://localhost:1999/?lang=en
|
||||||
- 🇪🇸 Spanish: http://localhost:8080/?lang=es
|
- 🇪🇸 Spanish: http://localhost:1999/?lang=es
|
||||||
|
|
||||||
## 📄 Updating Your CV
|
## 📄 Updating Your CV
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ type ServerConfig struct {
|
|||||||
|
|
||||||
// TemplateConfig contains template-specific settings
|
// TemplateConfig contains template-specific settings
|
||||||
type TemplateConfig struct {
|
type TemplateConfig struct {
|
||||||
Dir string
|
Dir string
|
||||||
PartialsDir string
|
PartialsDir string
|
||||||
HotReload bool
|
HotReload bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DataConfig contains data directory settings
|
// DataConfig contains data directory settings
|
||||||
@@ -37,15 +37,15 @@ type DataConfig struct {
|
|||||||
func Load() *Config {
|
func Load() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Server: ServerConfig{
|
Server: ServerConfig{
|
||||||
Port: getEnv("PORT", "8080"),
|
Port: getEnv("PORT", "1999"),
|
||||||
Host: getEnv("HOST", "localhost"),
|
Host: getEnv("HOST", "localhost"),
|
||||||
ReadTimeout: getEnvAsInt("READ_TIMEOUT", 15),
|
ReadTimeout: getEnvAsInt("READ_TIMEOUT", 15),
|
||||||
WriteTimeout: getEnvAsInt("WRITE_TIMEOUT", 15),
|
WriteTimeout: getEnvAsInt("WRITE_TIMEOUT", 15),
|
||||||
},
|
},
|
||||||
Template: TemplateConfig{
|
Template: TemplateConfig{
|
||||||
Dir: getEnv("TEMPLATE_DIR", "templates"),
|
Dir: getEnv("TEMPLATE_DIR", "templates"),
|
||||||
PartialsDir: getEnv("PARTIALS_DIR", "templates/partials"),
|
PartialsDir: getEnv("PARTIALS_DIR", "templates/partials"),
|
||||||
HotReload: getEnvAsBool("TEMPLATE_HOT_RELOAD", isDevelopment()),
|
HotReload: getEnvAsBool("TEMPLATE_HOT_RELOAD", isDevelopment()),
|
||||||
},
|
},
|
||||||
Data: DataConfig{
|
Data: DataConfig{
|
||||||
Dir: getEnv("DATA_DIR", "data"),
|
Dir: getEnv("DATA_DIR", "data"),
|
||||||
|
|||||||
+167
-263
@@ -1,17 +1,14 @@
|
|||||||
/* Minimal CV Design - A4 Page Simulation */
|
/* CV Design - Original Style Recreation */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--bg-gray: #525659;
|
--bg-gray: #525659;
|
||||||
|
--sidebar-gray: #d9d9d9;
|
||||||
|
--black-bar: #2b2b2b;
|
||||||
--paper-white: #ffffff;
|
--paper-white: #ffffff;
|
||||||
--text-dark: #2d2d2d;
|
--text-dark: #2d2d2d;
|
||||||
--text-gray: #555555;
|
--text-gray: #555555;
|
||||||
--accent-blue: #0066cc;
|
--accent-blue: #0066cc;
|
||||||
--border-gray: #dddddd;
|
--border-gray: #dddddd;
|
||||||
|
|
||||||
/* A4 dimensions */
|
|
||||||
--a4-width: 210mm;
|
|
||||||
--a4-height: 297mm;
|
|
||||||
--page-padding: 20mm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -21,7 +18,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Inter', Arial, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
background-color: var(--bg-gray);
|
background-color: var(--bg-gray);
|
||||||
color: var(--text-dark);
|
color: var(--text-dark);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@@ -36,21 +33,23 @@ a:hover {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Action Bar */
|
/* Single Black Top Bar */
|
||||||
.action-bar {
|
.action-bar {
|
||||||
background: white;
|
background: var(--black-bar);
|
||||||
border-bottom: 1px solid var(--border-gray);
|
color: white;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-bar-content {
|
.action-bar-content {
|
||||||
max-width: var(--a4-width);
|
max-width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0;
|
||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem;
|
||||||
display: flex;
|
display: grid;
|
||||||
justify-content: space-between;
|
grid-template-columns: auto 1fr auto;
|
||||||
|
gap: 2rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,35 +60,64 @@ a:hover {
|
|||||||
|
|
||||||
.lang-btn {
|
.lang-btn {
|
||||||
padding: 0.4rem 1rem;
|
padding: 0.4rem 1rem;
|
||||||
border: 1px solid var(--border-gray);
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
background: white;
|
background: transparent;
|
||||||
|
color: white;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-btn:hover {
|
.lang-btn:hover {
|
||||||
background: #f5f5f5;
|
background: rgba(255,255,255,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-btn.active {
|
.lang-btn.active {
|
||||||
background: var(--accent-blue);
|
background: rgba(255,255,255,0.2);
|
||||||
color: white;
|
border-color: white;
|
||||||
border-color: var(--accent-blue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.export-btn {
|
.export-btn {
|
||||||
padding: 0.4rem 1.2rem;
|
padding: 0.4rem 1.2rem;
|
||||||
background: var(--accent-blue);
|
background: transparent;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.export-btn:hover {
|
.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 */
|
/* Loading Indicator */
|
||||||
@@ -103,7 +131,7 @@ a:hover {
|
|||||||
|
|
||||||
.loader {
|
.loader {
|
||||||
border: 2px solid #f3f3f3;
|
border: 2px solid #f3f3f3;
|
||||||
border-top: 2px solid var(--accent-blue);
|
border-top: 2px solid white;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@@ -115,27 +143,26 @@ a:hover {
|
|||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A4 Page Container */
|
/* Main CV Container */
|
||||||
.cv-container {
|
.cv-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 1200px;
|
||||||
margin: 0;
|
margin: 0 auto;
|
||||||
padding: 2rem 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A4 Paper - Exact dimensions */
|
/* CV Paper - Two-column layout with shadow */
|
||||||
.cv-paper {
|
.cv-paper {
|
||||||
width: var(--a4-width);
|
width: 100%;
|
||||||
min-height: var(--a4-height);
|
|
||||||
background: var(--paper-white);
|
background: var(--paper-white);
|
||||||
padding: var(--page-padding);
|
box-shadow: 0 0 30px rgba(0,0,0,0.4);
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
margin: 0;
|
||||||
margin: 0 auto;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 300px 1fr;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Page break helpers */
|
/* Page break helpers */
|
||||||
@@ -149,31 +176,55 @@ a:hover {
|
|||||||
break-inside: avoid;
|
break-inside: avoid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header - Photo on right, inline with text */
|
/* Sidebar - Left column */
|
||||||
.cv-header {
|
.cv-sidebar {
|
||||||
display: flex;
|
background: var(--sidebar-gray);
|
||||||
justify-content: space-between;
|
padding: 2rem 1.5rem;
|
||||||
align-items: flex-start;
|
font-size: 0.9rem;
|
||||||
gap: 2rem;
|
}
|
||||||
border-bottom: 2px solid var(--text-dark);
|
|
||||||
padding-bottom: 1.5rem;
|
.sidebar-section {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cv-header-left {
|
.sidebar-title {
|
||||||
flex: 1;
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
color: var(--text-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cv-header-right {
|
.sidebar-content {
|
||||||
flex-shrink: 0;
|
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 {
|
.cv-photo {
|
||||||
width: 100px;
|
width: 150px;
|
||||||
height: 100px;
|
height: 200px;
|
||||||
border-radius: 50%;
|
flex-shrink: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 3px solid var(--border-gray);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cv-photo img {
|
.cv-photo img {
|
||||||
@@ -183,230 +234,117 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cv-name {
|
.cv-name {
|
||||||
font-size: 2rem;
|
font-size: 2.5rem;
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cv-title {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--text-gray);
|
margin-bottom: 0.5rem;
|
||||||
margin-bottom: 1rem;
|
color: var(--text-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cv-contact {
|
.cv-experience-years {
|
||||||
display: grid;
|
font-size: 1rem;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 0.4rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--text-gray);
|
color: var(--text-gray);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sections */
|
/* Sections */
|
||||||
.cv-section {
|
.cv-section {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 2rem;
|
||||||
page-break-inside: avoid;
|
page-break-inside: avoid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 1.2rem;
|
font-size: 1.1rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
padding-bottom: 0.3rem;
|
color: var(--text-dark);
|
||||||
border-bottom: 1px solid var(--border-gray);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-text {
|
.summary-text {
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
font-size: 0.9rem;
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Experience - with separators */
|
/* Experience */
|
||||||
.experience-item {
|
.experience-item {
|
||||||
margin-bottom: 1.2rem;
|
margin-bottom: 1.5rem;
|
||||||
padding-bottom: 1.2rem;
|
|
||||||
border-bottom: 1px solid var(--border-gray);
|
|
||||||
page-break-inside: avoid;
|
page-break-inside: avoid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.experience-item:last-child {
|
.experience-header {
|
||||||
border-bottom: none;
|
margin-bottom: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.experience-header {
|
.experience-title-line {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 0.6rem;
|
align-items: baseline;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
align-items: center;
|
flex-wrap: wrap;
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.position {
|
.position {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 0.2rem;
|
margin: 0;
|
||||||
}
|
color: var(--text-dark);
|
||||||
|
|
||||||
.company {
|
|
||||||
color: var(--text-gray);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.experience-period {
|
.experience-period {
|
||||||
color: var(--text-gray);
|
color: var(--text-gray);
|
||||||
font-size: 0.8rem;
|
font-size: 0.85rem;
|
||||||
white-space: nowrap;
|
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.short-desc {
|
.short-desc {
|
||||||
color: var(--text-gray);
|
color: var(--text-dark);
|
||||||
font-size: 0.85rem;
|
font-size: 0.9rem;
|
||||||
line-height: 1.5;
|
line-height: 1.6;
|
||||||
margin-bottom: 0.6rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsibilities {
|
.responsibilities {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin-bottom: 0.6rem;
|
margin-top: 0.5rem;
|
||||||
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsibilities li {
|
.responsibilities li {
|
||||||
padding-left: 1rem;
|
padding-left: 1.2rem;
|
||||||
margin-bottom: 0.3rem;
|
margin-bottom: 0.4rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 0.85rem;
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-dark);
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsibilities li:before {
|
.responsibilities li:before {
|
||||||
content: "•";
|
content: "•";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.technologies {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: var(--text-gray);
|
color: var(--text-gray);
|
||||||
font-style: italic;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Education */
|
/* Education */
|
||||||
.education-item {
|
.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;
|
margin-bottom: 1rem;
|
||||||
font-size: 0.9rem;
|
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 */
|
||||||
.languages-list {
|
.languages-list {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
flex-wrap: wrap;
|
||||||
gap: 0.5rem;
|
gap: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-item {
|
.language-item {
|
||||||
font-size: 0.85rem;
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
@@ -417,30 +355,6 @@ footer {
|
|||||||
font-size: 0.85rem;
|
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 */
|
/* Short CV - Hide detailed content */
|
||||||
.cv-short .long-only {
|
.cv-short .long-only {
|
||||||
@@ -468,57 +382,47 @@ footer {
|
|||||||
/* Responsive - tablet/mobile */
|
/* Responsive - tablet/mobile */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.cv-paper {
|
.cv-paper {
|
||||||
width: 100%;
|
grid-template-columns: 1fr;
|
||||||
min-height: auto;
|
|
||||||
padding: 15mm;
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cv-container {
|
.cv-sidebar {
|
||||||
padding: 1rem;
|
padding: 1.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cv-main {
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cv-name {
|
.cv-name {
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cv-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cv-photo {
|
.cv-photo {
|
||||||
order: -1;
|
width: 120px;
|
||||||
margin-bottom: 1rem;
|
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 {
|
.action-bar-content {
|
||||||
flex-wrap: wrap;
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-toggle,
|
||||||
|
.title-badges,
|
||||||
|
.action-buttons {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cv-length-toggle {
|
.title-badges {
|
||||||
order: 1;
|
order: -1;
|
||||||
width: 100%;
|
}
|
||||||
justify-content: center;
|
|
||||||
margin-top: 0.5rem;
|
.experience-title-line {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+106
-139
@@ -1,153 +1,120 @@
|
|||||||
<!-- CV Content Template - Minimal Design -->
|
<!-- Left Sidebar - Skills -->
|
||||||
<div class="cv-header">
|
<aside class="cv-sidebar">
|
||||||
<div class="cv-header-left">
|
<!-- Skills Section -->
|
||||||
<div class="cv-header-main">
|
<section class="sidebar-section">
|
||||||
<h1 class="cv-name">{{.CV.Personal.Name}}</h1>
|
<h3 class="sidebar-title">{{if eq .Lang "es"}}Lenguajes de Programación{{else}}Programming Languages{{end}}</h3>
|
||||||
<h2 class="cv-title">{{.CV.Personal.Title}}</h2>
|
{{range .CV.Skills.Technical}}
|
||||||
</div>
|
{{if eq .Category "Programming Languages"}}
|
||||||
<div class="cv-contact">
|
<div class="sidebar-content">
|
||||||
<div class="contact-item">{{.CV.Personal.Location}}</div>
|
{{range .Items}}<div class="skill-item">{{.}}</div>{{end}}
|
||||||
<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}}
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
{{end}}
|
||||||
{{end}}
|
</section>
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Education -->
|
<section class="sidebar-section">
|
||||||
<section class="cv-section">
|
<h3 class="sidebar-title">{{if eq .Lang "es"}}Desarrollo Web{{else}}Web Development{{end}}</h3>
|
||||||
<h3 class="section-title">{{if eq .Lang "es"}}Formación{{else}}Education{{end}}</h3>
|
{{range .CV.Skills.Technical}}
|
||||||
|
{{if eq .Category "Web Development"}}
|
||||||
{{range .CV.Education}}
|
<div class="sidebar-content">
|
||||||
<div class="education-item">
|
{{range .Items}}<div class="skill-item">{{.}}</div>{{end}}
|
||||||
<div class="education-header">
|
</div>
|
||||||
<h4 class="degree">{{.Degree}}</h4>
|
{{end}}
|
||||||
<div class="education-period">{{.StartDate}} - {{.EndDate}}</div>
|
{{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>
|
||||||
<div class="institution">{{.Institution}}, {{.Location}}</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Skills -->
|
<!-- Summary -->
|
||||||
<section class="cv-section">
|
<section class="cv-section">
|
||||||
<h3 class="section-title">{{if eq .Lang "es"}}Competencias{{else}}Skills{{end}}</h3>
|
<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}}
|
<!-- Education -->
|
||||||
<div class="skill-block">
|
<section class="cv-section">
|
||||||
<h4 class="skill-title">{{.Category}}</h4>
|
<h3 class="section-title">{{if eq .Lang "es"}}Formación{{else}}Training{{end}}</h3>
|
||||||
<p class="skill-list">
|
{{range .CV.Education}}
|
||||||
{{range $index, $item := .Items}}{{if $index}}, {{end}}{{$item}}{{end}}
|
<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>
|
</p>
|
||||||
</div>
|
</section>
|
||||||
{{end}}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Projects -->
|
<!-- Experience -->
|
||||||
{{if .CV.Projects}}
|
<section class="cv-section">
|
||||||
<section class="cv-section">
|
<h3 class="section-title">{{if eq .Lang "es"}}Experiencia{{else}}Experience{{end}}</h3>
|
||||||
<h3 class="section-title">{{if eq .Lang "es"}}Proyectos{{else}}Projects{{end}}</h3>
|
|
||||||
|
|
||||||
{{range .CV.Projects}}
|
{{range .CV.Experience}}
|
||||||
<div class="project-item">
|
<div class="experience-item">
|
||||||
<div class="project-header">
|
<div class="experience-header">
|
||||||
<h4 class="project-name">{{.Name}}</h4>
|
<div class="experience-title-line">
|
||||||
<div class="project-period">{{.Period}}</div>
|
<h4 class="position">{{.Position}} / {{if eq $.Lang "es"}}Analista Programador{{else}}Analyst Programmer{{end}}</h4>
|
||||||
</div>
|
<span class="experience-period">{{.StartDate}} / {{if .Current}}{{if eq $.Lang "es"}}presente{{else}}now{{end}}{{else}}{{.EndDate}}{{end}} - ({{.Location}})</span>
|
||||||
<div class="project-role">{{.Role}}</div>
|
</div>
|
||||||
<p class="project-description">{{.Description}}</p>
|
</div>
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</section>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<!-- Certifications -->
|
{{if .ShortDescription}}
|
||||||
{{if .CV.Certifications}}
|
<p class="short-desc">{{.ShortDescription}}</p>
|
||||||
<section class="cv-section">
|
{{end}}
|
||||||
<h3 class="section-title">{{if eq .Lang "es"}}Certificaciones{{else}}Certifications{{end}}</h3>
|
|
||||||
|
|
||||||
{{range .CV.Certifications}}
|
<div class="long-only">
|
||||||
<div class="cert-item">
|
<ul class="responsibilities">
|
||||||
<strong>{{.Name}}</strong> - {{.Issuer}} ({{.Date}})
|
{{range .Responsibilities}}
|
||||||
</div>
|
<li>{{.}}</li>
|
||||||
{{end}}
|
{{end}}
|
||||||
</section>
|
</ul>
|
||||||
{{end}}
|
</div>
|
||||||
|
|
||||||
<!-- 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>
|
</div>
|
||||||
{{end}}
|
{{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">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Language & Export Bar (hidden in print) -->
|
<!-- Single Black Bar with Everything -->
|
||||||
<div class="action-bar no-print">
|
<div class="action-bar no-print">
|
||||||
<div class="action-bar-content">
|
<div class="action-bar-content">
|
||||||
|
<!-- Left: Language buttons -->
|
||||||
<div class="language-toggle">
|
<div class="language-toggle">
|
||||||
<button
|
<button
|
||||||
class="lang-btn {{if eq .Lang "en"}}active{{end}}"
|
class="lang-btn {{if eq .Lang "en"}}active{{end}}"
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
hx-target="#cv-content"
|
hx-target="#cv-content"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
hx-indicator="#loading">
|
hx-indicator="#loading">
|
||||||
🇬🇧 English
|
English
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="lang-btn {{if eq .Lang "es"}}active{{end}}"
|
class="lang-btn {{if eq .Lang "es"}}active{{end}}"
|
||||||
@@ -38,28 +39,34 @@
|
|||||||
hx-target="#cv-content"
|
hx-target="#cv-content"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
hx-indicator="#loading">
|
hx-indicator="#loading">
|
||||||
🇪🇸 Español
|
Español
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cv-length-toggle">
|
<!-- Center: Title badges -->
|
||||||
<button
|
<div class="title-badges">
|
||||||
class="length-btn active"
|
<span class="title-badge">ANALYST PROGRAMMER</span>
|
||||||
onclick="toggleCVLength('short')">
|
<span class="title-separator">|</span>
|
||||||
{{if eq .Lang "es"}}Corto{{else}}Short{{end}}
|
<span class="title-badge">NODEJS + REACTJS DEVELOPER</span>
|
||||||
</button>
|
<span class="title-separator">|</span>
|
||||||
<button
|
<span class="title-badge">WEB DEVELOPER</span>
|
||||||
class="length-btn"
|
<span class="title-separator">|</span>
|
||||||
onclick="toggleCVLength('long')">
|
<span class="title-badge">JAVA DEVELOPER</span>
|
||||||
{{if eq .Lang "es"}}Largo{{else}}Long{{end}}
|
<span class="title-separator">|</span>
|
||||||
</button>
|
<span class="title-badge">PHP DEVELOPER</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="export-actions">
|
<!-- Right: Action buttons -->
|
||||||
|
<div class="action-buttons">
|
||||||
<button
|
<button
|
||||||
class="export-btn"
|
class="export-btn"
|
||||||
onclick="window.print()">
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user