A configβdriven portfolio template built with plain HTML, CSS and JavaScript. All content is rendered from configuration files β no manual editing of HTML sections required.
π Demo based on the original authorβs data:
- Live site:
https://<your-username>.github.io/portfolio/ - Example:
https://42kiko.github.io/portfolio/
You can use this repository as a starting point and fully replace the content with your own profile.
- πΈ Preview
- π‘ What You Get
- π οΈ Tech Stack
- β‘ Quick Start
- π§© How the Template Is Structured
- β Required Changes for a New User
- π CV Lab β Dynamic Resume Generator
- π± Optional Customization
- π Deploy to GitHub Pages
- π¦ Development Tips
- π License
- π Credits
Example preview of the portfolio layout:
- π¨ Modern singleβpage portfolio with sections for About, Skills, Experience, Projects, Testimonials & Contact
- π Multiβlanguage ready (German & English included)
- βοΈ Configβdriven content via simple JavaScript objects
- π¬ Contact form hooked up to [EmailJS]
- π Legal popups (Impressum & Privacy) for Germanβspeaking users
- π Easy GitHub Pages deployment β no build step, no framework
- π HTML5, CSS3, JavaScript (ES Modules)
- π¨ Custom CSS (no framework) with theme & color switcher
- π§© Config files in
src/config/*.js - π¦ Vendor libs: Swiper.js, Unicons, EmailJS
- π GitHub Pages for static hosting
-
Clone or use as template
git clone https://github.com/<your-username>/portfolio.git cd portfolio
-
Open the site locally
- Option A: Doubleβclick
index.htmlin your browser - Option B (recommended): Run a small local server, e.g. via VS Code Live Server
- Option A: Doubleβclick
-
Verify it works
- You should see the example portfolio (with the original authorβs data)
- Language toggle, theme toggle and contact form should be visible
Next step: replace all data with your own π
The site is driven by three main config files:
src/config/content.config.jsβ profile, skills, timeline (experience/education), projects, testimonials, media paths (images, video, CV)src/config/translations.jsβ all texts in DE/EN (headlines, labels, descriptions, legal text, status messages)src/config/site.config.jsβ global options: default language, color preset, EmailJS keys, social links
The HTML (index.html) only provides empty containers; JavaScript fills them
based on the config.
If you want this to become your portfolio, at a minimum you should change:
File: src/config/content.config.js
profile.nameβ your full nameprofile.emailβ your contact emailhome.linkedInβ your LinkedIn URL (or remove if unused)home.githubβ your GitHub URL
File: src/config/translations.js
header-nameβ your name for the top navigationhome-title/home-subtitle/home-textβ short introcontact-location-subtitleβ city / regioncontact-*keys if you want to adapt wording
The footer name is derived from
content.profile.nameandtranslations.footer-title.
If you operate in a jurisdiction that requires legal information (e.g. Germany), you must replace the placeholder data.
File: src/config/translations.js
Update all keys starting with:
impressum-*β address, responsible person, contact, legal textprivacy-*β privacy policy titles and body text
These texts are only examples and not legal advice. Replace them with content that matches your situation or remove the legal sections entirely if not needed in your country.
File: src/config/content.config.js
Section: portfolio (array)
For each project:
imgβ path to your project thumbnail image inassets/img/...titleKey/descKeyβ keys that point intotranslations.jsctaKeyβ callβtoβaction label key (e.g. "View Project")hrefβ external link (GitHub repo, live demo, article, etc.)
File: src/config/translations.js
Update the texts for:
portfolio-title,portfolio-subtitleportfolioX-title,portfolioX-description,githubX-text/websiteX-text(or similar) for each project.
File: src/config/content.config.js
experienceβ items for your education and work timelinetestimonialsβ company logos and translation keys for feedback
File: src/config/translations.js
Update the corresponding keys for:
edu-*(education)work-*(work experience)- testimonial entries like
eos-*,ntag-*,frobese-*,tecRacer-*
You can also remove items from the arrays if you have fewer entries.
File: src/config/site.config.js
export const site = {
defaultLang: "de",
hueIndex: 0,
emailJS: {
publicKey: "YOUR_PUBLIC_KEY",
serviceId: "YOUR_SERVICE_ID",
templateId: "YOUR_TEMPLATE_ID",
},
socials: {
github: "https://github.com/your-handle",
linkedin: "https://www.linkedin.com/in/your-profile/",
},
};Replace the emailJS values with your own EmailJS credentials. If serviceId
or templateId are missing, the form will show an error popup when submitting.
If you donβt want a contact form at all you can:
- Remove the EmailJS
<script>fromindex.html, and/or - Hide the contact section in the renderer (advanced use β requires editing JS).
The color theme and language are linked to CV files in assets/cv/<lang>/,
but you can choose between one CV for all colors or one CV per color.
Use this if you just want a single CV file per language.
- Folder pattern:
assets/cv/<lang>/ - File name pattern:
<baseName>-<lang>.pdf- Example:
Kiko-DS-de.pdf,Kiko-DS-en.pdf
- Example:
Config in src/config/site.config.js:
export const site = {
// ...
cv: {
mode: "single", // one CV per language
baseName: "Kiko-DS", // change this to your own base, e.g. "AlexDev-CV"
},
// ...
};Use this if you want different CVs for each color variant.
- Folder pattern:
assets/cv/<lang>/ - File name pattern:
<baseName>-<lang>-<color>.pdf- Example:
Kiko-DS-de-v.pdf,Kiko-DS-de-t.pdf, ...
- Example:
Config:
export const site = {
// ...
cv: {
mode: "perColor", // default
baseName: "Kiko-DS",
},
// ...
};In both modes, you only need to drop the correctly named PDF files into the
corresponding assets/cv/<lang>/ folders β no JavaScript changes required.
File: src/config/content.config.js
home.avatarImgβ your profile image (PNG/JPG/WebP) inassets/img/me/home.imgPositionβ fineβtunes how the avatar is positionedabout.videoβ short looping video clip (optional)
File: src/config/site.config.js
hueIndexβ initial color variant (0β6) for the accent color
File: index.html
- Favicon paths (inside
<head>) β point them to your own favicon set, or keep the existing structure and replace the images inassets/img/favicon/....
This portfolio includes a CV Lab feature that generates a modern, styled resume (CV) from your configuration files. The CV is available in both preview mode (on-page) and PDF export (via html2pdf.js).
- β
Config-driven: All CV content lives in
translations.js+content.config.js - β Single source of truth: No duplicate content between portfolio and CV
- β Generic & extensible: Add new sidebar sections without touching rendering code
- β Multi-language: Automatically switches between German/English
- β Theme-aware: Uses your selected color theme in both light and dark mode
- β PDF export: One-click download to PDF with html2pdf.js
The CV system is split into three layers:
-
Content structure (
src/config/content.config.js)- Defines the CV layout: sidebar sections, experience, education, projects
- References translation keys instead of hardcoded text
-
Translations (
src/config/translations.js)- All CV-specific text in both languages
- Section titles, skills, certifications, job bullets, etc.
-
Renderer (
src/cv/cvGenerator.js)- Reads config + translations
- Builds HTML dynamically
- Supports generic rendering for extensibility
File: src/config/translations.js
// CV Profile
"cv-full-name": { de: "Max Mustermann", en: "Max Mustermann" },
"cv-title": { de: "Software Engineer", en: "Software Engineer" },
"cv-location": { de: "Berlin, Deutschland", en: "Berlin, Germany" },
"cv-phone": { de: "+49 123 456789", en: "+49 123 456789" },
"cv-email": { de: "max@example.com", en: "max@example.com" },
"cv-profile-summary": {
de: "Ihre Zusammenfassung hier...",
en: "Your summary here..."
},File: src/config/content.config.js
cv: {
profile: {
nameKey: "cv-full-name",
// ... other keys
linkedIn: "https://www.linkedin.com/in/your-profile/",
github: "https://github.com/your-username",
},
// ...
}Step A: Add translation keys for job details
File: src/config/translations.js
// CV Experience Bullets - Your Company
"cv-exp-yourcompany-bullet-1": {
de: "Entwickelte ein System mit React und Node.js...",
en: "Developed a system using React and Node.js..."
},
"cv-exp-yourcompany-bullet-2": {
de: "Verbesserte die Performance um 40%...",
en: "Improved performance by 40%..."
},Step B: Reference the keys in your CV config
File: src/config/content.config.js
cv: {
experience: [
{
company: "Your Company Name",
roleKey: "work-1-title", // reuses existing translation
location: "City",
fromKey: "work-1-calendar", // e.g. "January 2023 - December 2023"
toKey: "work-1-calendar",
bulletKeys: [
"cv-exp-yourcompany-bullet-1",
"cv-exp-yourcompany-bullet-2",
],
},
// ... more jobs
],
}Note: The
fromKeyandtoKeypoint to a period string like "Juli 2024 - Dezember 2024". The CV generator automatically splits this into "from" and "to" parts.
The sidebar is fully generic. You can add any type of section without modifying rendering code.
Example: Adding a "Hobbies" section
File: src/config/translations.js
"cv-section-hobbies": { de: "Hobbys", en: "Hobbies" },
"cv-hobby-photography": { de: "Fotografie", en: "Photography" },
"cv-hobby-hiking": { de: "Wandern", en: "Hiking" },File: src/config/content.config.js
cv: {
sidebarSections: [
// ... existing sections (languages, certifications, skills)
{
titleKey: "cv-section-hobbies",
chips: [
{ textKey: "cv-hobby-photography" },
{ textKey: "cv-hobby-hiking" },
"Cooking", // plain string (no translation)
],
},
],
}That's it! The new section will automatically appear in your CV.
Skills are organized by category in the sidebar:
File: src/config/content.config.js
cv: {
sidebarSections: [
{
titleKey: "cv-skill-cat-programming", // "Programming Languages"
chips: ["Python", "JavaScript", "Go", "Rust"],
},
{
titleKey: "cv-skill-cat-tools",
chips: ["Docker", "Kubernetes", "GitHub Actions"],
},
// ... add more categories
],
}You can add new skill category titles in translations.js:
"cv-skill-cat-your-category": { de: "Ihre Kategorie", en: "Your Category" },The sidebar supports two types of sections:
Use items for sections with name + metadata (e.g., Languages, Certifications):
{
titleKey: "cv-section-languages",
items: [
{ nameKey: "cv-lang-german", levelKey: "cv-lang-german-level" },
{ nameKey: "cv-lang-english", levelKey: "cv-lang-english-level" },
],
}For certifications:
{
titleKey: "cv-section-certifications",
items: [
{
nameKey: "cv-cert-aws-name",
issuerKey: "cv-cert-aws-issuer",
dateKey: "cv-cert-aws-date"
},
],
}Use chips for simple lists (e.g., Skills, Soft Skills):
{
titleKey: "cv-section-soft-skills",
chips: [
{ textKey: "cv-soft-analytical" },
{ textKey: "cv-soft-problem-solving" },
"Creativity", // plain string
],
}Pro tip: You can mix plain strings and
{ textKey: "..." }objects in the samechipsarray!
// src/cv/cvGenerator.js
// 1. Helper function to resolve translation keys
function t(key, lang) {
const entry = translations[key];
return entry[lang] || entry.de || key;
}
// 2. Build CV data from config
function buildCvData(lang) {
const cvConfig = content.cv;
// Resolve all translation keys to actual text
const profile = {
name: t(cvConfig.profile.nameKey, lang),
title: t(cvConfig.profile.titleKey, lang),
// ...
};
// Build experience with translated bullets
const experience = cvConfig.experience.map(exp => ({
company: exp.company,
role: t(exp.roleKey, lang),
bullets: exp.bulletKeys.map(key => t(key, lang)),
}));
// Build sidebar sections (generic!)
const sidebarSections = cvConfig.sidebarSections.map(section => {
const result = { title: t(section.titleKey, lang) };
if (section.items) {
result.items = section.items.map(item => {
// Resolve all keys ending with "Key"
const obj = {};
Object.keys(item).forEach(key => {
const cleanKey = key.replace(/Key$/, "");
obj[cleanKey] = t(item[key], lang);
});
return obj;
});
}
if (section.chips) {
result.chips = section.chips.map(chip => {
if (typeof chip === "string") return chip;
if (chip.textKey) return t(chip.textKey, lang);
return chip;
});
}
return result;
});
return { profile, experience, education, projects, sidebarSections };
}
// 3. Generate HTML from data
export function buildCvHtml({ lang = "de" }) {
const data = buildCvData(lang);
// ... render HTML
}The CV automatically uses your selected color theme:
- Light mode: Clean, professional layout with theme-colored accents
- Dark mode: Dark gradient with theme-colored highlights
- Colors: All CSS uses
var(--hue-color)to match your portfolio theme
Styles are located in styles/main.css under the /*==================== CV LAB / MODERN CV ====================*/ section.
The PDF export uses html2pdf.js to convert the CV HTML to PDF:
- User clicks "Download PDF" in the CV Lab
- The CV is temporarily rendered with PDF-optimized styles
- html2pdf captures the content and generates a PDF
- Styles are restored to normal after export
PDF-specific adjustments:
- Compact typography for better fit on A4
- Adjusted spacing and margins
- Page break hints to avoid splitting sections awkwardly
β
No duplication: Portfolio timeline and CV experience share the same data
β
Easy maintenance: Update once in translations.js, reflects everywhere
β
Extensible: Add new sections without touching renderer code
β
Type-safe: Clear structure with keys, easy to validate
β
Multi-language: Automatic switching between DE/EN
β
Theme-aware: CV colors match your portfolio theme automatically
None of the following is strictly required, but recommended for a clean personalized result:
- Texts & tone β adjust all section titles and descriptions in
translations.js(About, Skills, Contact, status messages, etc.) - Skills β edit the
skillsarray incontent.config.jsto match your stack - Language support β keep both
deanden, or simplify to just one language by removing unused translations - Footer copy β update
footer-copyintranslations.js
This project is designed for a simple root deploy: all files live in the repository root, no build step needed.
- Create a new GitHub repository (e.g.
my-portfolio). - Push all files of this project (including the
assetsfolder) to themainbranch. - In your repo on GitHub, go to Settings β Pages:
- Source: "Deploy from a branch"
- Branch:
main - Directory:
/(root)
- Wait a minute, then open:
https://<your-username>.github.io/<repo-name>/
All asset paths are relative (
assets/...), so the site works out of the box on GitHub Pages for project sites.
- Use a local dev server (VS Code Live Server or
npx serve) to avoid caching issues. - When changing images, favicons or CVs, perform a hard reload (Ctrl/Cmd + Shift + R).
- Keep
src/config/*files under version control so you can track content changes over time.
This project is licensed under the MIT License. See LICENSE.
Original template created by Kiko.
- Icons: [Unicons]
- Slider: [Swiper]
- Email service: [EmailJS]
