A modern, interactive portfolio website built with Jekyll, featuring a sleek tech-themed design perfect for developers and tech professionals (or anyone interested!). The idea is to, well, get hired!
- Contact Form Backend Setup
- Step 1: Create Google Form
- Step 2: Connect Google Sheet
- Step 3: Deploy Google Apps Script
- Step 4: Set Up Triggers
- Step 5: Get Form IDs
- Step 6: Update JavaScript
- Testing Your Setup
- Customizing the Portfolio YAML File
- Customing the Site Configuration YAML File
- YAML Editing Tips
- Profile Image
- Deployment
A responsive, professional portfolio website template with modern animations and interactive elements. Completely free to use and deploy
- 🎨 Dual Theme System - Professional dark mode + clean light mode
- 📱 Fully Responsive - Works on all devices
- ⚡ Interactive Elements - Custom cursor, parallax effects, smooth animations
- 🔧 Easy Content Management - Update everything through a single YAML file
- 🚀 Fast Loading - Optimized static site
- 🔍 SEO Optimized - Built-in meta tags and structured data
- 🛠️ GitHub Pages Ready - Deploy with zero configuration
- 📧 Functional Contact Form - Google Forms backend with email notifications
- 💰 100% Free - No hosting costs, runs on GitHub Pages
- Anyone looking for a modern portfolio!
Before you begin, make sure you have:
- Ruby (version 3.0 or higher) - Download here (Windows) or
brew install ruby(macOS) - Bundler - Install with
gem install bundler - Git - Download here
-
Clone the repository
git clone https://github.com/yourusername/your-portfolio.git cd your-portfolio -
Install dependencies
bundle install
-
Start the development server
bundle exec jekyll serve -
Open your browser
Visit: http://localhost:4000
Your site will automatically reload when you make changes!
Your portfolio includes a fully functional contact form with Google Forms backend, email notifications, and spam protection. Here's how to set it up:
- Create Google Form → Get form submissions
- Connect Google Sheet → Store responses automatically
- Deploy Apps Script → Send custom email notifications
- Update JavaScript → Connect your frontend to backend
-
Go to forms.google.com
-
Click "Blank Form"
-
Set form title: "Portfolio Contact Form"
-
Add these EXACT fields:
Question 1: Name - Type: Short answer - Required: YES Question 2: Email - Type: Short answer - Required: YES Question 3: Subject - Type: Short answer - Required: YES Question 4: Message - Type: Paragraph - Required: YES
- In your Google Form, click the
Responsestab - Click the green Google Sheets icon 📊
- Select "Create a new spreadsheet"
- Name it: "Portfolio Contact Responses"
- Click "Create" → Google automatically links them
- Open your new Google Sheet
- Click Extensions → Apps Script
- Replace the default code with this enhanced script:
// =====================================================================================
// PORTFOLIO CONTACT FORM - Google Apps Script Email Notifications
// =====================================================================================
const CONFIG = {
YOUR_EMAIL: 'your.email@gmail.com', // 🔥 UPDATE THIS!
SEND_AUTO_RESPONDER: true,
EMAIL_SIGNATURE: 'Your Name' // 🔥 UPDATE THIS!
};
function onFormSubmit(e) {
try {
const values = e.values;
const [timestamp, name, email, subject, message] = values;
// Send notification to you
sendNotificationEmail({ timestamp, name, email, subject, message });
// Send confirmation to form submitter
if (CONFIG.SEND_AUTO_RESPONDER) {
sendAutoResponder({ name, email, subject });
}
console.log('✅ Emails sent successfully');
} catch (error) {
console.error('❌ Error:', error);
}
}
function sendNotificationEmail(data) {
const subject = `🚀 New Portfolio Contact: ${data.subject}`;
const htmlBody = `
<div style="font-family: 'Segoe UI', Arial, sans-serif; max-width: 900px; margin: 0 auto; border: 1px solid #e0e0e0; border-radius: 8px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; color: white; text-align: center;">
<h2 style="margin: 0; font-size: 28px;">🚀 New Portfolio Contact</h2>
<p style="margin: 10px 0 0 0; opacity: 0.9;">Professional inquiry received</p>
</div>
<div style="background: #f8f9fa; padding: 30px;">
<div style="background: white; padding: 25px; border-radius: 8px; border-left: 4px solid #667eea;">
<table style="width: 100%;">
<tr>
<td style="width: 50%; padding-right: 20px;">
<strong>👤 Name:</strong> ${data.name}<br>
<strong>📧 Email:</strong> <a href="mailto:${data.email}">${data.email}</a>
</td>
<td style="width: 50%; border-left: 1px solid #e9ecef; padding-left: 20px;">
<strong>📋 Subject:</strong> ${data.subject}<br>
<strong>⏰ Time:</strong> ${data.timestamp}
</td>
</tr>
</table>
</div>
</div>
<div style="background: white; padding: 30px;">
<h3 style="margin: 0 0 20px 0; color: #495057;">💬 Message:</h3>
<div style="background: #f8f9fa; padding: 25px; border-left: 4px solid #28a745; border-radius: 8px;">
<p style="margin: 0; line-height: 1.7; color: #495057;">${data.message.replace(/\n/g, '<br>')}</p>
</div>
</div>
<div style="background: #f8f9fa; padding: 25px; text-align: center;">
<a href="mailto:${data.email}?subject=Re: ${data.subject}"
style="background: #667eea; color: white; padding: 15px 30px; text-decoration: none; border-radius: 6px; font-weight: 600;">
📧 Reply to ${data.name}
</a>
</div>
</div>
`;
MailApp.sendEmail({
to: CONFIG.YOUR_EMAIL,
subject: subject,
htmlBody: htmlBody,
replyTo: data.email
});
}
function sendAutoResponder(data) {
const subject = `✅ Thank you for your message, ${data.name}!`;
const htmlBody = `
<div style="font-family: 'Segoe UI', Arial, sans-serif; max-width: 900px; margin: 0 auto; border: 1px solid #e0e0e0; border-radius: 8px;">
<div style="background: linear-gradient(135deg, #28a745 0%, #20c997 100%); padding: 30px; color: white; text-align: center;">
<h2 style="margin: 0; font-size: 28px;">✅ Message Received</h2>
<p style="margin: 10px 0 0 0; opacity: 0.9;">Thank you for reaching out!</p>
</div>
<div style="background: white; padding: 40px;">
<p style="font-size: 18px; margin: 0 0 20px 0;"><strong>Hello ${data.name},</strong></p>
<p style="margin: 0 0 25px 0; line-height: 1.7; color: #6c757d;">
Thank you for reaching out through my portfolio! I've received your message regarding
"<strong>${data.subject}</strong>" and appreciate you taking the time to connect.
</p>
<div style="background: #f8f9fa; padding: 25px; border-left: 4px solid #28a745; margin: 25px 0; text-align: center;">
<p style="margin: 0; font-weight: 500;"><span style="color: #28a745;">⚡</span> I'll respond within 24 hours</p>
</div>
<p style="margin: 0; line-height: 1.7; color: #6c757d;">
Best regards,<br>
<strong>${CONFIG.EMAIL_SIGNATURE}</strong><br>
<span style="color: #adb5bd;">Portfolio & Professional Services</span>
</p>
</div>
</div>
`;
MailApp.sendEmail({
to: data.email,
subject: subject,
htmlBody: htmlBody,
replyTo: CONFIG.YOUR_EMAIL
});
}
// Test function - run this to test email notifications
function testEmails() {
const testData = {
timestamp: new Date().toLocaleString(),
name: "Test Contact",
email: CONFIG.YOUR_EMAIL,
subject: "System Test",
message: "This is a test message to verify the email system works correctly."
};
sendNotificationEmail(testData);
sendAutoResponder(testData);
console.log('✅ Test emails sent!');
}-
Update the CONFIG section:
const CONFIG = { YOUR_EMAIL: 'your.email@gmail.com', // Your actual email SEND_AUTO_RESPONDER: true, EMAIL_SIGNATURE: 'Your Name' // Your actual name };
-
Save the project (Ctrl+S or File → Save)
- In Apps Script, click the Triggers icon ⏰ (left sidebar)
- Click "Add Trigger"
- Configure:
- Function:
onFormSubmit - Event source:
From spreadsheet - Event type:
On form submit
- Function:
- Click "Save" and authorize permissions when prompted
-
Get Form ID:
- In Google Form, click "Send" button
- Click the link icon 🔗
- Copy the ID from URL:
https://docs.google.com/forms/d/FORM_ID_HERE/viewform
-
Get Entry IDs:
- Right-click on form →
View Page Sourceand look for the 4 different form boxes (e.g, Name, Email, Subject, Message) - Search for the field name (Ctrl+F) and look for them in one of the elements
- Only look for the word (e.g., Message) and search within the
[[character space. This is where the form id is located (e.g., 000000000)
- Only look for the word (e.g., Message) and search within the
- Copy the 4 entry IDs you find as in this example:
<div jsmodel="CP1oW" data-params="%.@.[1234567890,"Message",null,0,[[000000000,null,true,null,null,null,null,null,null,null,[]]],null,null,null,null,null,null,[null,"Message"]] <!-- Message field -->
- Right-click on form →
In your assets/js/main.js, update the configuration:
const CONTACT_FORM_CONFIG = {
formId: 'YOUR_ACTUAL_FORM_ID_HERE', // From Step 5
fields: {
name: 'entry.YOUR_NAME_ENTRY_ID', // From Step 5
email: 'entry.YOUR_EMAIL_ENTRY_ID', // From Step 5
subject: 'entry.YOUR_SUBJECT_ENTRY_ID', // From Step 5
message: 'entry.YOUR_MESSAGE_ENTRY_ID' // From Step 5
},
fallbackEmail: 'your.email@gmail.com' // Your email
};-
Test the Apps Script:
- In Apps Script, run the
testEmails()function - Check your email for test notifications
- In Apps Script, run the
-
Test the Live Form:
- Submit a test message through your portfolio
- Verify you receive both emails (notification + auto-responder)
- Check Google Sheet for the response data
-
Troubleshooting:
- Check browser console for JavaScript errors
- Verify form IDs and entry IDs are correct
- Ensure trigger is properly set up
- Check spam folder for emails
✅ Spam protection via Google Forms
✅ Custom HTML email notifications
✅ Auto-responder for form submitters
✅ Automatic data storage in Google Sheets
✅ Mobile-friendly emails
✅ Easy maintenance through Google's interface
✅ 100% free - no hosting costs
Setup Time: ~5 minutes | Maintenance: Zero
All your content is managed through two main files:
File: _data/portfolio.yml
This YAML file contains all your personal information, experience, and projects. Here's how to edit each section:
👤 Personal Information:
personal:
name: "Your Full Name" # Appears in hero section
title: "Your Professional Title" # Job title/role
tagline: "// Your Personal Motto" # Appears under title
logo: "Y.N" # Your initials for navigation
email: "your.email@example.com" # Contact email
phone: "+1 (555) 123-4567" # Phone number
location: "Your City, State" # Where you're located
linkedin: "linkedin.com/in/yourprofile" # LinkedIn profile
github: "github.com/yourusername" # GitHub profile
profileImage: "/assets/img/profile.jpg" # Path to your photo
bio: # About section paragraphs
- "First paragraph about your background..."
- "Second paragraph about your interests..."
- "Third paragraph about your philosophy..."💼 Professional Experience:
experience:
- title: "Your Job Title" # Position name
company: "Company Name" # Employer
period: "2022 - Present" # Employment dates
description: "What you accomplished in this role..."🎓 Education:
education:
- degree: "Bachelor of Computer Science" # Degree name
institution: "University Name" # School name
period: "2016 - 2020" # Dates attended
description: "Relevant coursework, honors, etc."🏆 Certifications:
certifications:
- name: "AWS Certified" # Certification name
issuer: "Amazon Web Services" # Issuing organization
year: "2023" # Year obtained💻 Skills:
skills:
- category: "Programming" # E.g., "Programming Languages"
icon: "⚡" # Emoji icon for visual appeal
tags: # List of specific skills
- "Python"
- "JavaScript"
- "React"🚀 Projects:
projects:
- title: "Project Name" # Project title
icon: "🤖" # Emoji icon
description: "Brief description of what this project does..."
links: # Project links
- label: "Demo" # Link text
url: "https://demo-link.com" # Actual URL
- label: "GitHub"
url: "https://github.com/user/repo"🧭 Navigation:
navigation:
- label: "HOME"
href: "#home"
- label: "ABOUT"
href: "#about"
- label: "SKILLS"
href: "#skills"🔍 SEO Settings:
seo:
title: "Your Name - Your Title"
description: "Brief description for search engines..."
keywords: "your, skills, here"
ogImage: "/assets/img/og-image.jpg"File: _config.yml
title: "Your Portfolio Title"
description: "Your professional description"
url: "https://yourusername.github.io"
baseurl: "/repository-name"
author: "Your Name"
google_analytics: "GA_MEASUREMENT_ID" # OptionalImportant rules:
- Use spaces, not tabs for indentation
- Keep consistent spacing (2 spaces per level)
- Quote strings with special characters
- Use lists with
-for multiple items
Example:
skills:
- category: "Programming"
icon: "⚡"
tags:
- "Python"
- "JavaScript"Validation:
ruby -c _data/portfolio.ymlOr use online: yamllint.com
Add your photo to assets/img/profile.jpg
- Recommended: 500x500px, JPG format, under 500KB
- Push code to GitHub
- Go to repository Settings → Pages
- Under Build and deployment select Source: "GitHub Actions"
- Click on Configure for GitHub Pages Jekyll
- Press on Commit changes on both windows
- Visit your your site once deployment is completed:
https://yourusername.github.io/repository-name
Update your portfolio in 4 steps:
- Edit content:
_data/portfolio.yml - Test locally:
bundle exec jekyll serve - Deploy:
git add . && git commit -m "Update content" && git push - Wait 2-5 minutes for GitHub Pages to rebuild
Files you'll edit regularly:
_data/portfolio.yml- Your content_config.yml- Site settingsassets/img/profile.jpg- Your profile photo
Files to leave alone:
_layouts/default.html- Site structureassets/css/style.css- All stylingassets/js/main.js- Interactive featuresindex.html- Homepage template
After cloning, customize these in order:
- Step 1: Edit
_data/portfolio.yml- All your personal content - Step 2: Update
_config.yml- Site title, URL, your name - Step 3: Add profile image -
assets/img/profile.jpg - Step 4: Set up contact form - Follow the Contact Form Backend Setup section
- Step 5: Test locally -
bundle exec jekyll serve - Step 6: Deploy - Push to GitHub and enable Pages
Common issues and solutions:
| Problem | Solution |
|---|---|
| Site not building | Check bundle exec jekyll build --verbose for errors |
| Content not updating | Ensure YAML syntax is correct (spaces, not tabs) |
| YAML parsing errors | Use yamllint.com to validate |
| Contact form not working | Follow the Contact Form Backend Setup section |
| Images not loading | Verify file paths and image file names |
YAML syntax check:
ruby -c _data/portfolio.ymlEdit assets/css/style.css:
:root {
--primary-cyber: #your-color;
--secondary-cyber: #your-color;
}Add to _config.yml:
google_analytics: "GA_MEASUREMENT_ID"⭐ If this template helped you, consider starring this repository!
