feat: enhance CV with project title links, experience durations, certifications, and improved navigation
## Project Title Links - Add projectName and projectDesc fields to Project struct - Split project titles to make only project name clickable - Update template logic for conditional title rendering - Apply changes to both English and Spanish versions ## Experience Duration Display - Restore duration calculation display in experience section - Move duration from date line to after company name - Style duration in light gray (#999) for subtle appearance - Calculate durations dynamically (e.g., "4 years 10 months") ## Certifications Section Enhancement - Add Codecademy Certifications (2022-2024) with AI Transformers and React courses - Add LinkedIn Learning Certifications (2019-2020) with 5 professional courses - Implement colored icon system with brand colors (purple, cyan, green, etc.) - Use responsibilities format matching Third Party Contributions layout - Reorder courses chronologically (most recent first) ## localStorage Improvements - Save CV length preference (short/long) - Save logos visibility preference (show/hide) - Save theme preference (default/clean) - Restore all preferences on page load ## Navigation UX Enhancements - Fix scroll positioning to show sections below action bar - Add keepHeaderVisible flag to maintain header visibility after navigation - Ensure smooth scrolling with proper offset calculations - Reset flag on scroll up to restore normal hide/show behavior ## Files Modified - internal/models/cv.go: Add ProjectName, ProjectDesc fields - templates/cv-content.html: Update project and experience rendering - static/css/main.css: Add duration-text styling - templates/index.html: Enhance scroll behavior and localStorage - data/cv-*.json: Add certifications, split project titles, reorder courses - static/images/courses/: Add codecademy.png, linkedin.png
This commit is contained in:
@@ -0,0 +1,133 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
|
identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
|
community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
|
any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email address,
|
||||||
|
without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official email address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement via GitHub issues
|
||||||
|
or by contacting the project maintainer directly.
|
||||||
|
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series of
|
||||||
|
actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or permanent
|
||||||
|
ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||||
|
community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.1, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||||
|
[https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
+296
@@ -0,0 +1,296 @@
|
|||||||
|
# Contributing to CV Site
|
||||||
|
|
||||||
|
First off, thank you for considering contributing to this project! This CV site is a personal project, but contributions are welcome to improve the template, fix bugs, or add features that others might find useful.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Code of Conduct](#code-of-conduct)
|
||||||
|
- [How Can I Contribute?](#how-can-i-contribute)
|
||||||
|
- [Reporting Bugs](#reporting-bugs)
|
||||||
|
- [Suggesting Enhancements](#suggesting-enhancements)
|
||||||
|
- [Pull Requests](#pull-requests)
|
||||||
|
- [Development Setup](#development-setup)
|
||||||
|
- [Style Guidelines](#style-guidelines)
|
||||||
|
- [Go Code Style](#go-code-style)
|
||||||
|
- [HTMX Patterns](#htmx-patterns)
|
||||||
|
- [CSS Style](#css-style)
|
||||||
|
- [Testing](#testing)
|
||||||
|
- [Commit Messages](#commit-messages)
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
This project and everyone participating in it is governed by our [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to the project maintainer.
|
||||||
|
|
||||||
|
## How Can I Contribute?
|
||||||
|
|
||||||
|
### Reporting Bugs
|
||||||
|
|
||||||
|
Before creating bug reports, please check existing issues to avoid duplicates. When creating a bug report, include as many details as possible:
|
||||||
|
|
||||||
|
**Bug Report Template:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '...'
|
||||||
|
3. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
What you expected to happen.
|
||||||
|
|
||||||
|
**Environment:**
|
||||||
|
- Go version: [e.g., 1.21.5]
|
||||||
|
- OS: [e.g., macOS 14.0, Ubuntu 22.04]
|
||||||
|
- Browser (if applicable): [e.g., Chrome 120, Firefox 121]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suggesting Enhancements
|
||||||
|
|
||||||
|
Enhancement suggestions are welcome! Please create an issue with:
|
||||||
|
|
||||||
|
- **Clear title** describing the enhancement
|
||||||
|
- **Detailed description** of the proposed functionality
|
||||||
|
- **Use case** explaining why this would be useful
|
||||||
|
- **Implementation ideas** (optional) if you have thoughts on how to implement it
|
||||||
|
|
||||||
|
### Pull Requests
|
||||||
|
|
||||||
|
1. **Fork the repository** and create your branch from `main`
|
||||||
|
2. **Branch naming convention:**
|
||||||
|
- `feature/description` - New features (e.g., `feature/add-dark-mode`)
|
||||||
|
- `fix/description` - Bug fixes (e.g., `fix/pdf-export-fonts`)
|
||||||
|
- `docs/description` - Documentation updates (e.g., `docs/update-readme`)
|
||||||
|
- `refactor/description` - Code refactoring (e.g., `refactor/simplify-handlers`)
|
||||||
|
|
||||||
|
3. **Make your changes:**
|
||||||
|
- Follow the [style guidelines](#style-guidelines)
|
||||||
|
- Add tests if adding new functionality (see [Testing](#testing))
|
||||||
|
- Update documentation as needed
|
||||||
|
|
||||||
|
4. **Test your changes:**
|
||||||
|
- Run `make dev` to test locally
|
||||||
|
- Test PDF export functionality
|
||||||
|
- Test both English and Spanish versions
|
||||||
|
- Test responsive design on different screen sizes
|
||||||
|
|
||||||
|
5. **Commit your changes** with clear commit messages (see [Commit Messages](#commit-messages))
|
||||||
|
|
||||||
|
6. **Push to your fork** and submit a pull request to the `main` branch
|
||||||
|
|
||||||
|
7. **Pull Request Template:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**Description**
|
||||||
|
Brief description of what this PR does.
|
||||||
|
|
||||||
|
**Type of change**
|
||||||
|
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||||
|
- [ ] New feature (non-breaking change which adds functionality)
|
||||||
|
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||||
|
- [ ] Documentation update
|
||||||
|
|
||||||
|
**How Has This Been Tested?**
|
||||||
|
Describe the tests you ran and how to reproduce them.
|
||||||
|
|
||||||
|
**Checklist:**
|
||||||
|
- [ ] My code follows the style guidelines of this project
|
||||||
|
- [ ] I have performed a self-review of my own code
|
||||||
|
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||||
|
- [ ] I have made corresponding changes to the documentation
|
||||||
|
- [ ] My changes generate no new warnings
|
||||||
|
- [ ] I have tested the changes locally
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- **Go 1.21+** installed
|
||||||
|
- **Git** for version control
|
||||||
|
- **Make** (optional, but recommended)
|
||||||
|
- **Chrome/Chromium** for PDF generation testing
|
||||||
|
|
||||||
|
### Setup Steps
|
||||||
|
|
||||||
|
1. **Clone your fork:**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/YOUR-USERNAME/cv.git
|
||||||
|
cd cv
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install dependencies:**
|
||||||
|
```bash
|
||||||
|
go mod download
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Set up environment:**
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env if needed
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Run development server:**
|
||||||
|
```bash
|
||||||
|
make dev
|
||||||
|
# Or: GO_ENV=development go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Open browser:**
|
||||||
|
```
|
||||||
|
http://localhost:1999
|
||||||
|
```
|
||||||
|
|
||||||
|
### Useful Make Commands
|
||||||
|
|
||||||
|
- `make dev` - Run in development mode (hot-reload enabled)
|
||||||
|
- `make build` - Build production binary
|
||||||
|
- `make test` - Test endpoints (requires server running)
|
||||||
|
- `make clean` - Remove build artifacts
|
||||||
|
- `make help` - Show all available commands
|
||||||
|
|
||||||
|
## Style Guidelines
|
||||||
|
|
||||||
|
### Go Code Style
|
||||||
|
|
||||||
|
- **Follow standard Go conventions:**
|
||||||
|
- Use `gofmt` to format code (runs automatically with most editors)
|
||||||
|
- Run `go vet` to catch common mistakes
|
||||||
|
- Use meaningful variable and function names
|
||||||
|
- Add comments for exported functions and complex logic
|
||||||
|
|
||||||
|
- **Code organization:**
|
||||||
|
- Keep handlers in `main.go` or separate handler files
|
||||||
|
- Use the `internal/models` package for data structures
|
||||||
|
- Keep utilities in appropriate packages
|
||||||
|
|
||||||
|
- **Error handling:**
|
||||||
|
```go
|
||||||
|
// Good
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error loading CV data: %v", err)
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid silent failures
|
||||||
|
// Bad: ignoring errors without logging
|
||||||
|
data, _ := loadCV()
|
||||||
|
```
|
||||||
|
|
||||||
|
### HTMX Patterns
|
||||||
|
|
||||||
|
- **Use semantic HTML:**
|
||||||
|
```html
|
||||||
|
<!-- Good -->
|
||||||
|
<button hx-get="/cv?lang=en" hx-target="#cv-content" hx-swap="innerHTML">
|
||||||
|
English
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Avoid inline styles when possible -->
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Keep HTMX attributes organized:**
|
||||||
|
- `hx-get/post` first
|
||||||
|
- `hx-target` second
|
||||||
|
- `hx-swap` third
|
||||||
|
- Other attributes follow
|
||||||
|
|
||||||
|
- **Progressive enhancement:**
|
||||||
|
- Ensure basic functionality works without JavaScript
|
||||||
|
- HTMX should enhance, not be required
|
||||||
|
|
||||||
|
### CSS Style
|
||||||
|
|
||||||
|
- **Organization:**
|
||||||
|
- Group related styles together
|
||||||
|
- Use comments to separate sections
|
||||||
|
- Keep selectors specific but not overly complex
|
||||||
|
|
||||||
|
- **Naming:**
|
||||||
|
- Use semantic class names
|
||||||
|
- Prefer descriptive names over abbreviations
|
||||||
|
|
||||||
|
- **Responsive design:**
|
||||||
|
- Mobile-first approach
|
||||||
|
- Use media queries for larger screens
|
||||||
|
- Test on multiple screen sizes
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
**Current State:** This project does not yet have automated tests. This is a known gap.
|
||||||
|
|
||||||
|
**When adding tests (future):**
|
||||||
|
|
||||||
|
- Write unit tests for new utility functions
|
||||||
|
- Add integration tests for HTTP handlers
|
||||||
|
- Test PDF generation functionality
|
||||||
|
- Ensure tests pass before submitting PR:
|
||||||
|
```bash
|
||||||
|
go test ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Manual testing requirements:**
|
||||||
|
|
||||||
|
- Test both language versions (English/Spanish)
|
||||||
|
- Test PDF export (both server-side and browser print)
|
||||||
|
- Test responsive design (mobile, tablet, desktop)
|
||||||
|
- Test in multiple browsers (Chrome, Firefox, Safari)
|
||||||
|
- Verify console has no errors
|
||||||
|
- Check network tab for failed requests
|
||||||
|
|
||||||
|
## Commit Messages
|
||||||
|
|
||||||
|
Use clear, descriptive commit messages following this format:
|
||||||
|
|
||||||
|
```
|
||||||
|
type: brief description
|
||||||
|
|
||||||
|
Optional longer description explaining what and why (not how).
|
||||||
|
|
||||||
|
Fixes #123
|
||||||
|
```
|
||||||
|
|
||||||
|
**Types:**
|
||||||
|
- `feat:` - New feature
|
||||||
|
- `fix:` - Bug fix
|
||||||
|
- `docs:` - Documentation changes
|
||||||
|
- `style:` - Code style changes (formatting, no logic change)
|
||||||
|
- `refactor:` - Code refactoring
|
||||||
|
- `perf:` - Performance improvements
|
||||||
|
- `test:` - Adding or updating tests
|
||||||
|
- `chore:` - Maintenance tasks, dependency updates
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: add dark mode toggle
|
||||||
|
|
||||||
|
Add user preference for dark mode with localStorage persistence.
|
||||||
|
Respects system preference on first visit.
|
||||||
|
|
||||||
|
Fixes #42
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
fix: correct PDF font rendering in headless Chrome
|
||||||
|
|
||||||
|
The custom Quicksand font wasn't loading properly in chromedp.
|
||||||
|
Updated to wait for fonts to load before PDF generation.
|
||||||
|
|
||||||
|
Fixes #38
|
||||||
|
```
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
Feel free to open an issue with the `question` label if you need help or clarification on anything!
|
||||||
|
|
||||||
|
## Thank You!
|
||||||
|
|
||||||
|
Your contributions help make this project better for everyone. Thank you for taking the time to contribute! 🎉
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Juan Andrés Moreno Rubio
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,7 +1,29 @@
|
|||||||
# CV Site - Go + HTMX
|
# CV Site - Go + HTMX
|
||||||
|
|
||||||
|
[](https://go.dev/)
|
||||||
|
[](https://htmx.org/)
|
||||||
|
[](LICENSE)
|
||||||
|
[](CONTRIBUTING.md)
|
||||||
|
|
||||||
**Modern, minimal curriculum vitae website** for Juan Andrés Moreno Rubio built with **Go** and **HTMX**.
|
**Modern, minimal curriculum vitae website** for Juan Andrés Moreno Rubio built with **Go** and **HTMX**.
|
||||||
|
|
||||||
|
A professional, bilingual CV site with server-side PDF generation, HTMX interactivity, and a clean paper design aesthetic. Perfect template for developers looking to create their own CV website with modern tech and minimal JavaScript.
|
||||||
|
|
||||||
|
## 📑 Table of Contents
|
||||||
|
|
||||||
|
- [Features](#-features)
|
||||||
|
- [Demo](#-demo)
|
||||||
|
- [Quick Start](#-quick-start)
|
||||||
|
- [Updating Your CV](#-updating-your-cv)
|
||||||
|
- [Export to PDF](#-export-to-pdf)
|
||||||
|
- [Key Technologies](#-key-technologies)
|
||||||
|
- [Documentation](#-documentation)
|
||||||
|
- [Deployment](#-deployment)
|
||||||
|
- [Customization](#-customization)
|
||||||
|
- [Contributing](#-contributing)
|
||||||
|
- [License](#-license)
|
||||||
|
- [Support](#-support)
|
||||||
|
|
||||||
## 🚀 Features
|
## 🚀 Features
|
||||||
|
|
||||||
- ✅ **Bilingual Support** - Spanish and English with instant switching (no page reload)
|
- ✅ **Bilingual Support** - Spanish and English with instant switching (no page reload)
|
||||||
@@ -13,24 +35,55 @@
|
|||||||
- ✅ **JSON-Based Content** - Easy to update without touching code
|
- ✅ **JSON-Based Content** - Easy to update without touching code
|
||||||
- ✅ **AI Development Section** - Showcases modern AI-assisted development skills
|
- ✅ **AI Development Section** - Showcases modern AI-assisted development skills
|
||||||
- ✅ **Fast & Lightweight** - Go backend with chromedp for PDF generation
|
- ✅ **Fast & Lightweight** - Go backend with chromedp for PDF generation
|
||||||
|
- ✅ **Security Hardened** - CSP headers, XSS protection, secure defaults
|
||||||
|
- ✅ **Production Ready** - Docker support, systemd service, CI/CD workflows
|
||||||
|
- ✅ **Developer Friendly** - Hot reload, clear code structure, comprehensive Makefile
|
||||||
|
|
||||||
|
## 📸 Demo
|
||||||
|
|
||||||
|
**Live Features:**
|
||||||
|
- Single-page application with no page reloads
|
||||||
|
- Instant language switching (English ↔ Spanish)
|
||||||
|
- Professional PDF export with perfect font rendering
|
||||||
|
- Responsive design from mobile to desktop
|
||||||
|
- Clean paper aesthetic on gray background
|
||||||
|
- Print-friendly layouts
|
||||||
|
|
||||||
|
**Note:** This is a personal CV site template. Fork it and customize the JSON files with your own information!
|
||||||
|
|
||||||
## 📋 Quick Start
|
## 📋 Quick Start
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- **Go 1.21+** installed
|
- **Go 1.21+** installed
|
||||||
|
- **Chrome/Chromium** (for PDF generation)
|
||||||
|
- **Make** (optional, recommended for easier development)
|
||||||
|
|
||||||
### Run
|
### Installation & Run
|
||||||
|
|
||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
# Build and run
|
# Clone the repository
|
||||||
|
git clone https://github.com/yourusername/cv.git
|
||||||
|
cd cv
|
||||||
|
|
||||||
|
# Option 1: Using Make (recommended)
|
||||||
|
make dev
|
||||||
|
|
||||||
|
# Option 2: Using Go directly
|
||||||
|
go run main.go
|
||||||
|
|
||||||
|
# Option 3: Build and run binary
|
||||||
go build -o cv-server && ./cv-server
|
go build -o cv-server && ./cv-server
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
Open **http://localhost:1999**
|
### Access the Site
|
||||||
|
|
||||||
- 🇬🇧 English: http://localhost:1999/?lang=en
|
Open **http://localhost:1999** in your browser
|
||||||
- 🇪🇸 Spanish: http://localhost:1999/?lang=es
|
|
||||||
|
- 🇬🇧 **English version:** http://localhost:1999/?lang=en
|
||||||
|
- 🇪🇸 **Spanish version:** http://localhost:1999/?lang=es
|
||||||
|
|
||||||
|
**Language switching** is instant via HTMX - no page reload required!
|
||||||
|
|
||||||
## 📄 Updating Your CV
|
## 📄 Updating Your CV
|
||||||
|
|
||||||
@@ -66,11 +119,115 @@ No code changes needed - just refresh browser!
|
|||||||
|
|
||||||
## 🎯 Key Technologies
|
## 🎯 Key Technologies
|
||||||
|
|
||||||
- Backend: **Go** (stdlib net/http)
|
- **Backend:** Go 1.21+ (stdlib `net/http`, graceful shutdown)
|
||||||
- PDF Generation: **chromedp** (headless Chrome automation)
|
- **PDF Generation:** chromedp (headless Chrome automation)
|
||||||
- Frontend: **HTMX** 1.9.10
|
- **Frontend:** HTMX 1.9.10 (hypermedia-driven interactions)
|
||||||
- Styling: Custom **CSS** with Quicksand font
|
- **Styling:** Custom CSS with Quicksand font from Google Fonts
|
||||||
- Data: **JSON** files
|
- **Data:** JSON files for easy content management
|
||||||
|
- **Deployment:** Docker, systemd service, GitHub Actions CI/CD
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
- **[ARCHITECTURE.md](ARCHITECTURE.md)** - System design, data flow, and technical decisions
|
||||||
|
- **[CONTRIBUTING.md](CONTRIBUTING.md)** - How to contribute (issues, PRs, code style)
|
||||||
|
- **[SECURITY.md](SECURITY.md)** - Security policy, vulnerability reporting, deployment considerations
|
||||||
|
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)** - Community guidelines and standards
|
||||||
|
- **[LICENSE](LICENSE)** - MIT License
|
||||||
|
|
||||||
|
## 🚀 Deployment
|
||||||
|
|
||||||
|
This project is production-ready with multiple deployment options:
|
||||||
|
|
||||||
|
### Docker Deployment
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Build Docker image
|
||||||
|
make docker-build
|
||||||
|
|
||||||
|
# Run container
|
||||||
|
make docker-run
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Systemd Service
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Install as systemd service
|
||||||
|
make install-service
|
||||||
|
|
||||||
|
# Update running service
|
||||||
|
make update-service
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Manual Deployment
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Build optimized binary
|
||||||
|
make build
|
||||||
|
|
||||||
|
# Run in production mode
|
||||||
|
GO_ENV=production ./cv-server
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**Environment Configuration:** Copy `.env.example` to `.env` and customize:
|
||||||
|
- `PORT` - Server port (default: 1999)
|
||||||
|
- `GO_ENV` - Environment (development/production)
|
||||||
|
- `TEMPLATE_HOT_RELOAD` - Enable template hot-reload in development
|
||||||
|
|
||||||
|
**Security:** See [SECURITY.md](SECURITY.md) for production deployment best practices.
|
||||||
|
|
||||||
|
## 🎨 Customization
|
||||||
|
|
||||||
|
### Update Your CV Content
|
||||||
|
|
||||||
|
1. Edit `data/cv-en.json` and `data/cv-es.json` with your information
|
||||||
|
2. No code changes required - just refresh the browser!
|
||||||
|
|
||||||
|
### Customize Styling
|
||||||
|
|
||||||
|
- **Main styles:** `static/css/main.css`
|
||||||
|
- **Colors:** Modify CSS variables in `:root` selector
|
||||||
|
- **Fonts:** Update Google Fonts import in HTML templates
|
||||||
|
- **Layout:** Edit templates in `templates/` directory
|
||||||
|
|
||||||
|
### Add New Sections
|
||||||
|
|
||||||
|
1. Update the `CV` struct in `internal/models/cv.go`
|
||||||
|
2. Add content to JSON files in `data/`
|
||||||
|
3. Update templates in `templates/` to display new sections
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Whether it's:
|
||||||
|
|
||||||
|
- 🐛 Bug reports
|
||||||
|
- 💡 Feature suggestions
|
||||||
|
- 📝 Documentation improvements
|
||||||
|
- 🔧 Code contributions
|
||||||
|
|
||||||
|
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on:
|
||||||
|
- Submitting issues
|
||||||
|
- Creating pull requests
|
||||||
|
- Code style and testing requirements
|
||||||
|
- Development workflow
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
**TL;DR:** You can use this template for your own CV site, modify it, and distribute it. Just keep the original copyright notice.
|
||||||
|
|
||||||
|
## 💬 Support
|
||||||
|
|
||||||
|
- **Issues:** [GitHub Issues](https://github.com/yourusername/cv/issues) for bug reports and feature requests
|
||||||
|
- **Discussions:** [GitHub Discussions](https://github.com/yourusername/cv/discussions) for questions and ideas
|
||||||
|
- **Security:** See [SECURITY.md](SECURITY.md) for reporting security vulnerabilities
|
||||||
|
|
||||||
|
## 🙏 Acknowledgments
|
||||||
|
|
||||||
|
- **HTMX** - For making hypermedia-driven applications enjoyable
|
||||||
|
- **chromedp** - For reliable headless Chrome automation
|
||||||
|
- **Go Community** - For excellent standard library and tooling
|
||||||
|
- **AI Assistance** - For accelerating development and documentation
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
This project is actively maintained. Security updates will be provided for the latest release on the `main` branch.
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| main | :white_check_mark: |
|
||||||
|
| < main | :x: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
We take the security of this CV site seriously. If you discover a security vulnerability, please help us protect users by following responsible disclosure practices.
|
||||||
|
|
||||||
|
### How to Report
|
||||||
|
|
||||||
|
**DO NOT** open a public issue for security vulnerabilities.
|
||||||
|
|
||||||
|
Instead, please report security vulnerabilities by:
|
||||||
|
|
||||||
|
1. **GitHub Security Advisories** (Preferred):
|
||||||
|
- Go to the repository's Security tab
|
||||||
|
- Click "Report a vulnerability"
|
||||||
|
- Fill out the form with details
|
||||||
|
|
||||||
|
2. **Direct Contact**:
|
||||||
|
- Open a private issue or contact the maintainer directly
|
||||||
|
- Use encrypted communication if the vulnerability is severe
|
||||||
|
|
||||||
|
### What to Include
|
||||||
|
|
||||||
|
Please provide the following information in your report:
|
||||||
|
|
||||||
|
- **Description** of the vulnerability
|
||||||
|
- **Steps to reproduce** the issue
|
||||||
|
- **Potential impact** (e.g., data exposure, XSS, CSRF)
|
||||||
|
- **Affected versions** (if known)
|
||||||
|
- **Suggested fix** (if you have one)
|
||||||
|
- **Your contact information** for follow-up questions
|
||||||
|
|
||||||
|
### Response Timeline
|
||||||
|
|
||||||
|
- **Initial Response**: Within 48 hours
|
||||||
|
- **Status Update**: Within 7 days
|
||||||
|
- **Fix Timeline**: Depends on severity
|
||||||
|
- Critical: Within 7 days
|
||||||
|
- High: Within 14 days
|
||||||
|
- Medium: Within 30 days
|
||||||
|
- Low: Next release cycle
|
||||||
|
|
||||||
|
### Disclosure Policy
|
||||||
|
|
||||||
|
- We ask that you give us reasonable time to fix the vulnerability before public disclosure
|
||||||
|
- We will credit you in the security advisory (unless you prefer to remain anonymous)
|
||||||
|
- Once the fix is deployed, we will publish a security advisory with details
|
||||||
|
|
||||||
|
## Security Considerations for Deployments
|
||||||
|
|
||||||
|
If you're deploying this CV site, please be aware of these security considerations:
|
||||||
|
|
||||||
|
### 1. PDF Generation Security
|
||||||
|
|
||||||
|
The server uses headless Chrome (via chromedp) to generate PDFs:
|
||||||
|
|
||||||
|
- **Risk**: Chromedp executes JavaScript and renders HTML, which could be exploited if user input is not sanitized
|
||||||
|
- **Mitigation**:
|
||||||
|
- The CV data comes from trusted JSON files, not user input
|
||||||
|
- If you modify the application to accept user input, ensure proper sanitization
|
||||||
|
- Consider running chromedp in a sandboxed environment
|
||||||
|
|
||||||
|
### 2. Content Security Policy
|
||||||
|
|
||||||
|
The application includes CSP headers:
|
||||||
|
|
||||||
|
```go
|
||||||
|
Content-Security-Policy: default-src 'self';
|
||||||
|
script-src 'self' 'unsafe-inline' https://unpkg.com;
|
||||||
|
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
|
||||||
|
font-src 'self' https://fonts.gstatic.com;
|
||||||
|
img-src 'self' data:
|
||||||
|
```
|
||||||
|
|
||||||
|
- Review and adjust CSP headers based on your deployment needs
|
||||||
|
- Remove `'unsafe-inline'` if possible by moving inline scripts/styles to separate files
|
||||||
|
|
||||||
|
### 3. Environment Variables
|
||||||
|
|
||||||
|
Sensitive configuration is managed via environment variables:
|
||||||
|
|
||||||
|
- **Never commit** `.env` file to version control
|
||||||
|
- Use `.env.example` as a template
|
||||||
|
- In production, use secure secret management (e.g., HashiCorp Vault, AWS Secrets Manager)
|
||||||
|
|
||||||
|
### 4. HTTPS in Production
|
||||||
|
|
||||||
|
- **Always use HTTPS** in production
|
||||||
|
- Configure TLS certificates (Let's Encrypt recommended)
|
||||||
|
- Consider using a reverse proxy (nginx, Caddy) for TLS termination
|
||||||
|
|
||||||
|
### 5. Rate Limiting
|
||||||
|
|
||||||
|
The application does not include built-in rate limiting:
|
||||||
|
|
||||||
|
- **Recommendation**: Use a reverse proxy (nginx, Caddy) to implement rate limiting
|
||||||
|
- Protect the `/export/pdf` endpoint to prevent PDF generation abuse
|
||||||
|
|
||||||
|
### 6. Input Validation
|
||||||
|
|
||||||
|
While this application primarily serves static CV data:
|
||||||
|
|
||||||
|
- If you extend it to accept user input, implement strict validation
|
||||||
|
- Sanitize all inputs before rendering in templates
|
||||||
|
- Use Go's `html/template` package (which auto-escapes) for HTML rendering
|
||||||
|
|
||||||
|
### 7. Dependency Management
|
||||||
|
|
||||||
|
Keep dependencies up to date:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for outdated dependencies
|
||||||
|
go list -u -m all
|
||||||
|
|
||||||
|
# Update dependencies
|
||||||
|
go get -u ./...
|
||||||
|
go mod tidy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Security Headers
|
||||||
|
|
||||||
|
The application sets security headers:
|
||||||
|
|
||||||
|
- `X-Content-Type-Options: nosniff`
|
||||||
|
- `X-Frame-Options: DENY`
|
||||||
|
- `X-XSS-Protection: 1; mode=block`
|
||||||
|
- `Content-Security-Policy: ...`
|
||||||
|
|
||||||
|
Review and enhance these headers based on your deployment needs.
|
||||||
|
|
||||||
|
### 9. Logging and Monitoring
|
||||||
|
|
||||||
|
- Enable structured logging in production
|
||||||
|
- Monitor for unusual patterns (e.g., excessive PDF generation requests)
|
||||||
|
- Set up alerts for errors and anomalies
|
||||||
|
|
||||||
|
### 10. Docker Security
|
||||||
|
|
||||||
|
If deploying via Docker:
|
||||||
|
|
||||||
|
- Use official, minimal base images
|
||||||
|
- Run container as non-root user
|
||||||
|
- Scan images for vulnerabilities (e.g., `docker scan`, Trivy)
|
||||||
|
- Keep base images updated
|
||||||
|
|
||||||
|
## Known Security Considerations
|
||||||
|
|
||||||
|
### PDF Generation Resource Usage
|
||||||
|
|
||||||
|
- **Issue**: PDF generation uses headless Chrome, which consumes significant CPU/memory
|
||||||
|
- **Impact**: Potential DoS via excessive PDF generation requests
|
||||||
|
- **Mitigation**:
|
||||||
|
- Implement rate limiting on `/export/pdf` endpoint
|
||||||
|
- Consider caching generated PDFs
|
||||||
|
- Monitor resource usage
|
||||||
|
|
||||||
|
### Third-Party Dependencies
|
||||||
|
|
||||||
|
External dependencies loaded from CDNs:
|
||||||
|
|
||||||
|
- HTMX from `https://unpkg.com`
|
||||||
|
- Google Fonts from `https://fonts.googleapis.com`
|
||||||
|
|
||||||
|
**Recommendation**: For production, consider:
|
||||||
|
- Self-hosting these dependencies for better control
|
||||||
|
- Using Subresource Integrity (SRI) hashes
|
||||||
|
- Implementing a Content Security Policy
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
If you're forking this project for your own CV:
|
||||||
|
|
||||||
|
1. **Review all code** before deploying
|
||||||
|
2. **Update personal information** in JSON files
|
||||||
|
3. **Configure security headers** appropriate for your use case
|
||||||
|
4. **Enable HTTPS** with valid certificates
|
||||||
|
5. **Keep dependencies updated** regularly
|
||||||
|
6. **Monitor application logs** for suspicious activity
|
||||||
|
7. **Backup your data** regularly
|
||||||
|
8. **Test security** before going live
|
||||||
|
|
||||||
|
## Security Updates
|
||||||
|
|
||||||
|
Security updates will be announced via:
|
||||||
|
|
||||||
|
- GitHub Security Advisories
|
||||||
|
- Release notes on GitHub
|
||||||
|
- Git commit messages tagged with `[SECURITY]`
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
For security concerns, please contact the project maintainer via GitHub.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
We appreciate the security research community's efforts in responsibly disclosing vulnerabilities. Thank you for helping keep this project secure!
|
||||||
@@ -664,6 +664,35 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"courses": [
|
"courses": [
|
||||||
|
{
|
||||||
|
"title": "Codecademy Certifications",
|
||||||
|
"institution": "Codecademy",
|
||||||
|
"courseLogo": "codecademy.png",
|
||||||
|
"location": "Online",
|
||||||
|
"date": "2022-2024",
|
||||||
|
"duration": "Various",
|
||||||
|
"shortDescription": "Professional development courses in AI and modern web technologies through Codecademy's interactive learning platform.",
|
||||||
|
"responsibilities": [
|
||||||
|
"<iconify-icon icon='mdi:robot' width='60' height='60' class='default-company-icon' style='color: #9333EA;'></iconify-icon><div><strong>Intro to AI Transformers Course</strong> <em>April 2024</em>: Comprehensive introduction to transformer architecture and AI models, covering attention mechanisms, encoder-decoder structures, and practical applications in natural language processing</div>",
|
||||||
|
"<iconify-icon icon='mdi:react' width='60' height='60' class='default-company-icon' style='color: #61DAFB;'></iconify-icon><div><strong>Learn React Course</strong> <em>March 2022</em>: Complete React framework training covering components, state management, hooks, lifecycle methods, and modern React development practices</div>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LinkedIn Learning Certifications",
|
||||||
|
"institution": "LinkedIn Learning",
|
||||||
|
"courseLogo": "linkedin.png",
|
||||||
|
"location": "Online",
|
||||||
|
"date": "2019-2020",
|
||||||
|
"duration": "Various",
|
||||||
|
"shortDescription": "Professional development courses in SAP technologies, UX design, security, and data analytics through LinkedIn Learning's comprehensive training platform.",
|
||||||
|
"responsibilities": [
|
||||||
|
"<iconify-icon icon='mdi:book-open-page-variant' width='60' height='60' class='default-company-icon' style='color: #D97706;'></iconify-icon><div><strong>Aprende lectura rápida</strong> <em>April 2020</em>: Speed reading techniques and comprehension strategies for professional development and efficient information processing</div>",
|
||||||
|
"<iconify-icon icon='mdi:cloud' width='60' height='60' class='default-company-icon' style='color: #0FAAFF;'></iconify-icon><div><strong>A Tour of the SAP Cloud Platform</strong> <em>February 2020</em>: Comprehensive overview of SAP Cloud Platform services, architecture, and integration capabilities for enterprise cloud solutions</div>",
|
||||||
|
"<iconify-icon icon='mdi:android' width='60' height='60' class='default-company-icon' style='color: #3DDC84;'></iconify-icon><div><strong>Learning Android Security</strong> <em>February 2020</em>: Android security best practices, encryption methods, secure coding practices, and mobile application security fundamentals</div>",
|
||||||
|
"<iconify-icon icon='mdi:account-group' width='60' height='60' class='default-company-icon' style='color: #EC4899;'></iconify-icon><div><strong>Persuasive UX: Creating Credibility</strong> <em>January 2020</em>: User experience design principles focused on building trust, credibility, and persuasive design patterns for web applications</div>",
|
||||||
|
"<iconify-icon icon='mdi:database' width='60' height='60' class='default-company-icon' style='color: #3B82F6;'></iconify-icon><div><strong>Big Data Foundations: Techniques and Concepts</strong> <em>December 2019</em>: Fundamentals of big data technologies, distributed computing, data processing frameworks, and analytics techniques</div>"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Servoy World 2011",
|
"title": "Servoy World 2011",
|
||||||
"institution": "Servoy",
|
"institution": "Servoy",
|
||||||
@@ -738,6 +767,8 @@
|
|||||||
"projects": [
|
"projects": [
|
||||||
{
|
{
|
||||||
"title": "Somos Una Ola - Beach Cleaning Initiative",
|
"title": "Somos Una Ola - Beach Cleaning Initiative",
|
||||||
|
"projectName": "Somos Una Ola",
|
||||||
|
"projectDesc": "Beach Cleaning Initiative",
|
||||||
"url": "https://somosunaola.org",
|
"url": "https://somosunaola.org",
|
||||||
"projectLogo": "somosunaola.png",
|
"projectLogo": "somosunaola.png",
|
||||||
"location": "La Palma, Canary Islands",
|
"location": "La Palma, Canary Islands",
|
||||||
@@ -753,6 +784,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Herrumbre Vivo Arte - Artist Portfolio Website",
|
"title": "Herrumbre Vivo Arte - Artist Portfolio Website",
|
||||||
|
"projectName": "Herrumbre Vivo Arte",
|
||||||
|
"projectDesc": "Artist Portfolio Website",
|
||||||
"url": "https://herrumbrevivoarte.com",
|
"url": "https://herrumbrevivoarte.com",
|
||||||
"projectLogo": "herrumbre-vivo.png",
|
"projectLogo": "herrumbre-vivo.png",
|
||||||
"location": "Fuencaliente, La Palma",
|
"location": "Fuencaliente, La Palma",
|
||||||
@@ -768,6 +801,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "La Porra.club - Football Prediction Platform",
|
"title": "La Porra.club - Football Prediction Platform",
|
||||||
|
"projectName": "La Porra.club",
|
||||||
|
"projectDesc": "Football Prediction Platform",
|
||||||
"url": "https://laporra.club",
|
"url": "https://laporra.club",
|
||||||
"projectLogo": "laporra.png",
|
"projectLogo": "laporra.png",
|
||||||
"gitRepoUrl": "/Users/txeo/laporra",
|
"gitRepoUrl": "/Users/txeo/laporra",
|
||||||
@@ -784,6 +819,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "CDC Starter Kit - SAP Customer Data Cloud Demo",
|
"title": "CDC Starter Kit - SAP Customer Data Cloud Demo",
|
||||||
|
"projectName": "CDC Starter Kit",
|
||||||
|
"projectDesc": "SAP Customer Data Cloud Demo",
|
||||||
"url": "https://gigyademo.com/cdc-starter-kit/",
|
"url": "https://gigyademo.com/cdc-starter-kit/",
|
||||||
"projectLogo": "sap.png",
|
"projectLogo": "sap.png",
|
||||||
"location": "Online",
|
"location": "Online",
|
||||||
|
|||||||
@@ -669,6 +669,35 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"courses": [
|
"courses": [
|
||||||
|
{
|
||||||
|
"title": "Certificaciones Codecademy",
|
||||||
|
"institution": "Codecademy",
|
||||||
|
"courseLogo": "codecademy.png",
|
||||||
|
"location": "Online",
|
||||||
|
"date": "2022-2024",
|
||||||
|
"duration": "Varios",
|
||||||
|
"shortDescription": "Cursos de desarrollo profesional en IA y tecnologías web modernas a través de la plataforma de aprendizaje interactivo de Codecademy.",
|
||||||
|
"responsibilities": [
|
||||||
|
"<iconify-icon icon='mdi:robot' width='60' height='60' class='default-company-icon' style='color: #9333EA;'></iconify-icon><div><strong>Intro to AI Transformers Course</strong> <em>Abril 2024</em>: Introducción completa a la arquitectura de transformers y modelos de IA, cubriendo mecanismos de atención, estructuras encoder-decoder y aplicaciones prácticas en procesamiento de lenguaje natural</div>",
|
||||||
|
"<iconify-icon icon='mdi:react' width='60' height='60' class='default-company-icon' style='color: #61DAFB;'></iconify-icon><div><strong>Learn React Course</strong> <em>Marzo 2022</em>: Formación completa en React framework cubriendo componentes, gestión de estado, hooks, métodos de ciclo de vida y prácticas modernas de desarrollo con React</div>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Certificaciones LinkedIn Learning",
|
||||||
|
"institution": "LinkedIn Learning",
|
||||||
|
"courseLogo": "linkedin.png",
|
||||||
|
"location": "Online",
|
||||||
|
"date": "2019-2020",
|
||||||
|
"duration": "Varios",
|
||||||
|
"shortDescription": "Cursos de desarrollo profesional en tecnologías SAP, diseño UX, seguridad y análisis de datos a través de la plataforma de formación integral de LinkedIn Learning.",
|
||||||
|
"responsibilities": [
|
||||||
|
"<iconify-icon icon='mdi:book-open-page-variant' width='60' height='60' class='default-company-icon' style='color: #D97706;'></iconify-icon><div><strong>Aprende lectura rápida</strong> <em>Abril 2020</em>: Técnicas de lectura rápida y estrategias de comprensión para desarrollo profesional y procesamiento eficiente de información</div>",
|
||||||
|
"<iconify-icon icon='mdi:cloud' width='60' height='60' class='default-company-icon' style='color: #0FAAFF;'></iconify-icon><div><strong>A Tour of the SAP Cloud Platform</strong> <em>Febrero 2020</em>: Visión general completa de servicios de SAP Cloud Platform, arquitectura y capacidades de integración para soluciones empresariales en la nube</div>",
|
||||||
|
"<iconify-icon icon='mdi:android' width='60' height='60' class='default-company-icon' style='color: #3DDC84;'></iconify-icon><div><strong>Learning Android Security</strong> <em>Febrero 2020</em>: Mejores prácticas de seguridad Android, métodos de encriptación, prácticas de codificación segura y fundamentos de seguridad de aplicaciones móviles</div>",
|
||||||
|
"<iconify-icon icon='mdi:account-group' width='60' height='60' class='default-company-icon' style='color: #EC4899;'></iconify-icon><div><strong>Persuasive UX: Creating Credibility</strong> <em>Enero 2020</em>: Principios de diseño de experiencia de usuario enfocados en generar confianza, credibilidad y patrones de diseño persuasivo para aplicaciones web</div>",
|
||||||
|
"<iconify-icon icon='mdi:database' width='60' height='60' class='default-company-icon' style='color: #3B82F6;'></iconify-icon><div><strong>Big Data Foundations: Techniques and Concepts</strong> <em>Diciembre 2019</em>: Fundamentos de tecnologías big data, computación distribuida, frameworks de procesamiento de datos y técnicas de análisis</div>"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Servoy World 2011",
|
"title": "Servoy World 2011",
|
||||||
"institution": "Servoy",
|
"institution": "Servoy",
|
||||||
@@ -743,6 +772,8 @@
|
|||||||
"projects": [
|
"projects": [
|
||||||
{
|
{
|
||||||
"title": "Somos Una Ola - Iniciativa de Limpieza de Playas",
|
"title": "Somos Una Ola - Iniciativa de Limpieza de Playas",
|
||||||
|
"projectName": "Somos Una Ola",
|
||||||
|
"projectDesc": "Iniciativa de Limpieza de Playas",
|
||||||
"url": "https://somosunaola.org",
|
"url": "https://somosunaola.org",
|
||||||
"projectLogo": "somosunaola.png",
|
"projectLogo": "somosunaola.png",
|
||||||
"location": "La Palma, Islas Canarias",
|
"location": "La Palma, Islas Canarias",
|
||||||
@@ -758,6 +789,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Herrumbre Vivo Arte - Sitio Web Portfolio de Artista",
|
"title": "Herrumbre Vivo Arte - Sitio Web Portfolio de Artista",
|
||||||
|
"projectName": "Herrumbre Vivo Arte",
|
||||||
|
"projectDesc": "Sitio Web Portfolio de Artista",
|
||||||
"url": "https://herrumbrevivoarte.com",
|
"url": "https://herrumbrevivoarte.com",
|
||||||
"projectLogo": "herrumbre-vivo.png",
|
"projectLogo": "herrumbre-vivo.png",
|
||||||
"location": "Fuencaliente, La Palma",
|
"location": "Fuencaliente, La Palma",
|
||||||
@@ -773,6 +806,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "La Porra.club - Plataforma de Predicción de Fútbol",
|
"title": "La Porra.club - Plataforma de Predicción de Fútbol",
|
||||||
|
"projectName": "La Porra.club",
|
||||||
|
"projectDesc": "Plataforma de Predicción de Fútbol",
|
||||||
"url": "https://laporra.club",
|
"url": "https://laporra.club",
|
||||||
"projectLogo": "laporra.png",
|
"projectLogo": "laporra.png",
|
||||||
"gitRepoUrl": "/Users/txeo/laporra",
|
"gitRepoUrl": "/Users/txeo/laporra",
|
||||||
@@ -789,6 +824,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "CDC Starter Kit - Demo de SAP Customer Data Cloud",
|
"title": "CDC Starter Kit - Demo de SAP Customer Data Cloud",
|
||||||
|
"projectName": "CDC Starter Kit",
|
||||||
|
"projectDesc": "Demo de SAP Customer Data Cloud",
|
||||||
"url": "https://gigyademo.com/cdc-starter-kit/",
|
"url": "https://gigyademo.com/cdc-starter-kit/",
|
||||||
"projectLogo": "sap.png",
|
"projectLogo": "sap.png",
|
||||||
"location": "Online",
|
"location": "Online",
|
||||||
|
|||||||
@@ -101,13 +101,15 @@ type Language struct {
|
|||||||
|
|
||||||
type Project struct {
|
type Project struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
ProjectName string `json:"projectName,omitempty"` // Optional: linkable part of title
|
||||||
|
ProjectDesc string `json:"projectDesc,omitempty"` // Optional: non-linkable description part
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
ProjectLogo string `json:"projectLogo,omitempty"` // Optional logo filename
|
ProjectLogo string `json:"projectLogo,omitempty"` // Optional logo filename
|
||||||
GitRepoUrl string `json:"gitRepoUrl,omitempty"` // Optional git repository URL for dynamic dates
|
GitRepoUrl string `json:"gitRepoUrl,omitempty"` // Optional git repository URL for dynamic dates
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
StartDate string `json:"startDate,omitempty"` // Optional static start date
|
StartDate string `json:"startDate,omitempty"` // Optional static start date
|
||||||
Current bool `json:"current"`
|
Current bool `json:"current"`
|
||||||
MaintainedBy string `json:"maintainedBy,omitempty"` // Optional maintainer name (e.g., "SAP")
|
MaintainedBy string `json:"maintainedBy,omitempty"` // Optional maintainer name (e.g., "SAP")
|
||||||
Technologies []string `json:"technologies"`
|
Technologies []string `json:"technologies"`
|
||||||
ShortDescription string `json:"shortDescription"`
|
ShortDescription string `json:"shortDescription"`
|
||||||
Responsibilities []string `json:"responsibilities"`
|
Responsibilities []string `json:"responsibilities"`
|
||||||
|
|||||||
@@ -779,6 +779,11 @@ iconify-icon {
|
|||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.duration-text {
|
||||||
|
color: #999;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
.responsibilities {
|
.responsibilities {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
@@ -91,7 +91,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="experience-content">
|
<div class="experience-content">
|
||||||
<strong>{{.Position}}{{if .Company}} - {{if .CompanyURL}}<a href="{{.CompanyURL}}" target="_blank" rel="noopener noreferrer">{{.Company}}</a>{{else}}{{.Company}}{{end}}{{end}}</strong><br>
|
<strong>{{.Position}}{{if .Company}} - {{if .CompanyURL}}<a href="{{.CompanyURL}}" target="_blank" rel="noopener noreferrer">{{.Company}}</a>{{else}}{{.Company}}{{end}}{{if .Duration}} <span class="duration-text">{{.Duration}}</span>{{end}}{{end}}</strong><br>
|
||||||
<small>{{.StartDate}} / {{if .Current}}{{if eq $.Lang "es"}}presente{{else}}now{{end}}{{else}}{{.EndDate}}{{end}} - ({{.Location}})</small>
|
<small>{{.StartDate}} / {{if .Current}}{{if eq $.Lang "es"}}presente{{else}}now{{end}}{{else}}{{.EndDate}}{{end}} - ({{.Location}})</small>
|
||||||
|
|
||||||
{{if .ShortDescription}}
|
{{if .ShortDescription}}
|
||||||
@@ -186,7 +186,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="project-content">
|
<div class="project-content">
|
||||||
<strong>{{if .URL}}<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.Title}}</a>{{else}}{{.Title}}{{end}}</strong><br>
|
<strong>
|
||||||
|
{{if .ProjectName}}
|
||||||
|
{{if .URL}}<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.ProjectName}}</a>{{else}}{{.ProjectName}}{{end}}{{if .ProjectDesc}} - {{.ProjectDesc}}{{end}}
|
||||||
|
{{else}}
|
||||||
|
{{if .URL}}<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.Title}}</a>{{else}}{{.Title}}{{end}}
|
||||||
|
{{end}}
|
||||||
|
</strong><br>
|
||||||
<small>{{if .StartDate}}{{.StartDate}}{{if .Current}}{{if .DynamicDate}} / {{.DynamicDate}}{{else}} / {{if eq $.Lang "es"}}presente{{else}}now{{end}}{{end}}{{end}}{{end}} - ({{.Location}})</small>
|
<small>{{if .StartDate}}{{.StartDate}}{{if .Current}}{{if .DynamicDate}} / {{.DynamicDate}}{{else}} / {{if eq $.Lang "es"}}presente{{else}}now{{end}}{{end}}{{end}}{{end}} - ({{.Location}})</small>
|
||||||
|
|
||||||
{{if .ShortDescription}}
|
{{if .ShortDescription}}
|
||||||
|
|||||||
+60
-17
@@ -277,28 +277,41 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flag to keep header visible after navigation
|
||||||
|
let keepHeaderVisible = false;
|
||||||
|
|
||||||
// Scroll to section smoothly
|
// Scroll to section smoothly
|
||||||
function scrollToSection(sectionId) {
|
function scrollToSection(sectionId) {
|
||||||
event.preventDefault(); // Prevent default anchor behavior
|
event.preventDefault(); // Prevent default anchor behavior
|
||||||
|
|
||||||
const section = document.getElementById(sectionId);
|
const section = document.getElementById(sectionId);
|
||||||
if (section) {
|
if (section) {
|
||||||
const actionBarHeight = document.querySelector('.action-bar').offsetHeight;
|
// Ensure header is visible before scrolling
|
||||||
const menuHeight = document.querySelector('.navigation-menu').offsetHeight;
|
const actionBar = document.querySelector('.action-bar');
|
||||||
const offset = actionBarHeight + (menuHeight || 0) + 20; // Add 20px padding
|
const navMenu = document.querySelector('.navigation-menu');
|
||||||
|
actionBar.classList.remove('header-hidden');
|
||||||
|
navMenu.classList.remove('header-hidden');
|
||||||
|
|
||||||
const elementPosition = section.getBoundingClientRect().top;
|
// Set flag to keep header visible
|
||||||
const offsetPosition = elementPosition + window.pageYOffset - offset;
|
keepHeaderVisible = true;
|
||||||
|
|
||||||
window.scrollTo({
|
|
||||||
top: offsetPosition,
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close menu after clicking
|
// Close menu after clicking
|
||||||
const menu = document.getElementById('navigation-menu');
|
navMenu.classList.remove('menu-open');
|
||||||
menu.classList.remove('menu-open');
|
|
||||||
document.querySelector('.hamburger-btn').setAttribute('aria-expanded', 'false');
|
document.querySelector('.hamburger-btn').setAttribute('aria-expanded', 'false');
|
||||||
|
|
||||||
|
// Wait a bit for header to be visible, then calculate offset
|
||||||
|
setTimeout(() => {
|
||||||
|
const actionBarHeight = actionBar.offsetHeight;
|
||||||
|
const offset = actionBarHeight + 20; // Add 20px padding
|
||||||
|
|
||||||
|
const elementPosition = section.getBoundingClientRect().top;
|
||||||
|
const offsetPosition = elementPosition + window.pageYOffset - offset;
|
||||||
|
|
||||||
|
window.scrollTo({
|
||||||
|
top: offsetPosition,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,9 +361,11 @@
|
|||||||
if (toggle.checked) {
|
if (toggle.checked) {
|
||||||
paper.classList.add('cv-long');
|
paper.classList.add('cv-long');
|
||||||
paper.classList.remove('cv-short');
|
paper.classList.remove('cv-short');
|
||||||
|
localStorage.setItem('cv-length', 'long');
|
||||||
} else {
|
} else {
|
||||||
paper.classList.add('cv-short');
|
paper.classList.add('cv-short');
|
||||||
paper.classList.remove('cv-long');
|
paper.classList.remove('cv-long');
|
||||||
|
localStorage.setItem('cv-length', 'short');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore scroll position after DOM updates
|
// Restore scroll position after DOM updates
|
||||||
@@ -368,8 +383,10 @@
|
|||||||
|
|
||||||
if (toggle.checked) {
|
if (toggle.checked) {
|
||||||
paper.classList.add('show-logos');
|
paper.classList.add('show-logos');
|
||||||
|
localStorage.setItem('cv-logos', 'show');
|
||||||
} else {
|
} else {
|
||||||
paper.classList.remove('show-logos');
|
paper.classList.remove('show-logos');
|
||||||
|
localStorage.setItem('cv-logos', 'hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore scroll position after DOM updates
|
// Restore scroll position after DOM updates
|
||||||
@@ -412,10 +429,31 @@
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize with short version, logos enabled, and saved theme
|
// Initialize with saved preferences or defaults
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
document.querySelector('.cv-paper').classList.add('cv-short');
|
const paper = document.querySelector('.cv-paper');
|
||||||
document.querySelector('.cv-paper').classList.add('show-logos');
|
|
||||||
|
// Restore CV length preference
|
||||||
|
const savedLength = localStorage.getItem('cv-length') || 'short';
|
||||||
|
if (savedLength === 'long') {
|
||||||
|
paper.classList.add('cv-long');
|
||||||
|
paper.classList.remove('cv-short');
|
||||||
|
document.getElementById('lengthToggle').checked = true;
|
||||||
|
} else {
|
||||||
|
paper.classList.add('cv-short');
|
||||||
|
paper.classList.remove('cv-long');
|
||||||
|
document.getElementById('lengthToggle').checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore logos preference
|
||||||
|
const savedLogos = localStorage.getItem('cv-logos') || 'show';
|
||||||
|
if (savedLogos === 'show') {
|
||||||
|
paper.classList.add('show-logos');
|
||||||
|
document.getElementById('logoToggle').checked = true;
|
||||||
|
} else {
|
||||||
|
paper.classList.remove('show-logos');
|
||||||
|
document.getElementById('logoToggle').checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Restore theme preference
|
// Restore theme preference
|
||||||
const savedTheme = localStorage.getItem('cv-theme') || 'default';
|
const savedTheme = localStorage.getItem('cv-theme') || 'default';
|
||||||
@@ -436,10 +474,15 @@
|
|||||||
const currentScroll = window.pageYOffset || document.documentElement.scrollTop;
|
const currentScroll = window.pageYOffset || document.documentElement.scrollTop;
|
||||||
const isMenuOpen = navMenu.classList.contains('menu-open');
|
const isMenuOpen = navMenu.classList.contains('menu-open');
|
||||||
|
|
||||||
|
// If scrolling up, reset the keepHeaderVisible flag
|
||||||
|
if (currentScroll < lastScrollTop) {
|
||||||
|
keepHeaderVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Hide/show header based on scroll direction
|
// Hide/show header based on scroll direction
|
||||||
if (currentScroll > scrollThreshold) {
|
if (currentScroll > scrollThreshold) {
|
||||||
if (currentScroll > lastScrollTop) {
|
if (currentScroll > lastScrollTop && !keepHeaderVisible) {
|
||||||
// Scrolling down - hide header
|
// Scrolling down - hide header (only if keepHeaderVisible is false)
|
||||||
actionBar.classList.add('header-hidden');
|
actionBar.classList.add('header-hidden');
|
||||||
// Only hide menu if it's open
|
// Only hide menu if it's open
|
||||||
if (isMenuOpen) {
|
if (isMenuOpen) {
|
||||||
|
|||||||
Reference in New Issue
Block a user