<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="canonical" href="https://profilecard.online/">
<title>Personal Profile Card Generator</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<style>
.apple-green { background-color: #52C41A; }
.color-preset {
width: 40px;
height: 40px;
border-radius: 8px;
cursor: pointer;
transition: transform 0.2s;
}
.color-preset:hover { transform: scale(1.1); }
.color-preset.selected {
border: 3px solid #000;
transform: scale(1.15);
}
#preview-card { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; }
</style>
</head>
<body class="bg-gray-50 min-h-screen py-8 px-4">
<div class="max-w-6xl mx-auto">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-900 mb-2">Personal Profile Card Generator</h1>
<p class="text-gray-600">Create your professional profile card in seconds</p>
</header>
<div class="grid md:grid-cols-2 gap-8">
<!-- Form Section -->
<div class="bg-white rounded-2xl shadow-lg p-6">
<h2 class="text-2xl font-semibold mb-6 text-gray-800">Your Information</h2>
<form id="profile-form" class="space-y-5">
<!-- Name -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Full Name *</label>
<input type="text" id="name" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="John Doe">
</div>
<!-- Avatar Upload -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Avatar Image *</label>
<input type="file" id="avatar" accept="image/*" required class="w-full px-4 py-2 border border-gray-300 rounded-lg file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:bg-green-50 file:text-green-700 hover:file:bg-green-100">
</div>
<!-- Introduction -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">One-line Introduction *</label>
<input type="text" id="intro" required maxlength="100" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="Full-stack developer passionate about building amazing web experiences">
</div>
<!-- Color Picker -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Card Color Theme</label>
<div class="flex items-center gap-3 mb-3">
<div class="color-preset selected" data-color="#52C41A" style="background-color: #52C41A;" title="Apple Green"></div>
<div class="color-preset" data-color="#1890FF" style="background-color: #1890FF;" title="Blue"></div>
<div class="color-preset" data-color="#722ED1" style="background-color: #722ED1;" title="Purple"></div>
<div class="color-preset" data-color="#FA541C" style="background-color: #FA541C;" title="Orange"></div>
<div class="color-preset" data-color="#EB2F96" style="background-color: #EB2F96;" title="Pink"></div>
</div>
<div class="flex gap-2">
<input type="color" id="color-picker" value="#52C41A" class="h-10 w-16 border border-gray-300 rounded-lg cursor-pointer">
<input type="text" id="color-hex" value="#52C41A" pattern="^#[0-9A-Fa-f]{6}$" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="#52C41A">
</div>
</div>
<!-- Skills -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Skills (Max 5)</label>
<div id="skills-container" class="space-y-2 mb-2"></div>
<button type="button" id="add-skill-btn" class="w-full py-2 px-4 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition">+ Add Skill</button>
</div>
<!-- Contact Details -->
<div class="border-t pt-4">
<h3 class="font-semibold text-gray-800 mb-3">Contact Details</h3>
<div class="space-y-3">
<input type="email" id="email" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="Email">
<input type="tel" id="phone" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="Phone">
<input type="text" id="wechat" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="WeChat ID">
<input type="url" id="linkedin" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="LinkedIn URL (https://...)">
<input type="url" id="twitter" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="Twitter URL (https://...)">
</div>
</div>
<!-- Card Size -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Card Size</label>
<select id="card-size" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
<option value="1080x1080">1080 × 1080 px (Square)</option>
<option value="1920x1080">1920 × 1080 px (Wide)</option>
</select>
</div>
<!-- Submit Button -->
<button type="submit" class="w-full py-3 px-6 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700 transition text-lg">
Generate Card
</button>
</form>
<!-- Download Section -->
<div id="download-section" class="hidden mt-6 p-4 bg-green-50 rounded-lg border border-green-200">
<p class="text-green-800 font-medium mb-3">✓ Your card is ready!</p>
<button id="download-btn" class="w-full py-3 px-6 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700 transition">
Download Card
</button>
</div>
</div>
<!-- Preview Section -->
<div class="bg-white rounded-2xl shadow-lg p-6">
<h2 class="text-2xl font-semibold mb-6 text-gray-800">Live Preview</h2>
<div class="flex justify-center">
<div id="preview-card" class="bg-white rounded-2xl shadow-2xl overflow-hidden" style="width: 400px;">
<div id="card-header" class="apple-green p-8 text-white text-center relative">
<div class="w-32 h-32 mx-auto mb-4 rounded-full overflow-hidden bg-white shadow-lg">
<img id="preview-avatar" src="" alt="Avatar" class="w-full h-full object-cover hidden">
<div id="avatar-placeholder" class="w-full h-full flex items-center justify-center text-gray-400 text-5xl">👤</div>
</div>
<h3 id="preview-name" class="text-3xl font-bold mb-2">Your Name</h3>
<p id="preview-intro" class="text-lg opacity-95">Your introduction appears here</p>
</div>
<div class="p-8">
<div id="preview-skills" class="mb-6 hidden">
<h4 class="text-sm font-semibold text-gray-600 mb-3 uppercase tracking-wide">Skills</h4>
<div id="skills-list" class="flex flex-wrap gap-2"></div>
</div>
<div id="preview-contact" class="space-y-3 text-sm hidden">
<h4 class="text-sm font-semibold text-gray-600 mb-3 uppercase tracking-wide">Contact</h4>
<div id="contact-email" class="hidden flex items-center gap-2 text-gray-700">
<span>📧</span>
<span></span>
</div>
<div id="contact-phone" class="hidden flex items-center gap-2 text-gray-700">
<span>📱</span>
<span></span>
</div>
<div id="contact-wechat" class="hidden flex items-center gap-2 text-gray-700">
<span>💬</span>
<span></span>
</div>
<div id="contact-linkedin" class="hidden flex items-center gap-2 text-gray-700">
<span>🔗</span>
<span class="truncate"></span>
</div>
<div id="contact-twitter" class="hidden flex items-center gap-2 text-gray-700">
<span>🐦</span>
<span class="truncate"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="text-center mt-12 text-gray-600 text-sm">
<p>Powered by <a href="https://Card-j0s.pages.dev" target="_blank" class="text-green-600 hover:underline">Card-j0s</a></p>
</footer>
</div>
<script>
let skillCount = 0;
let currentColor = '#52C41A';
let avatarDataUrl = null;
// Initialize
document.addEventListener('DOMContentLoaded', function() {
addSkill(); // Start with one skill field
setupEventListeners();
});
function setupEventListeners() {
// Form inputs
document.getElementById('name').addEventListener('input', updatePreview);
document.getElementById('intro').addEventListener('input', updatePreview);
document.getElementById('avatar').addEventListener('change', handleAvatarUpload);
document.getElementById('email').addEventListener('input', updatePreview);
document.getElementById('phone').addEventListener('input', updatePreview);
document.getElementById('wechat').addEventListener('input', updatePreview);
document.getElementById('linkedin').addEventListener('input', updatePreview);
document.getElementById('twitter').addEventListener('input', updatePreview);
// Color controls
document.getElementById('color-picker').addEventListener('input', function(e) {
updateColor(e.target.value);
});
document.getElementById('color-hex').addEventListener('input', function(e) {
if (/^#[0-9A-Fa-f]{6}$/.test(e.target.value)) {
updateColor(e.target.value);
}
});
document.querySelectorAll('.color-preset').forEach(preset => {
preset.addEventListener('click', function() {
document.querySelectorAll('.color-preset').forEach(p => p.classList.remove('selected'));
this.classList.add('selected');
updateColor(this.dataset.color);
});
});
// Add skill button
document.getElementById('add-skill-btn').addEventListener('click', addSkill);
// Form submit
document.getElementById('profile-form').addEventListener('submit', handleSubmit);
// Download button
document.getElementById('download-btn').addEventListener('click', downloadCard);
}
function addSkill() {
if (skillCount >= 5) return;
skillCount++;
const container = document.getElementById('skills-container');
const skillDiv = document.createElement('div');
skillDiv.className = 'flex gap-2';
skillDiv.innerHTML = `
<input type="text" class="skill-input flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="e.g. JavaScript, Design, etc.">
<button type="button" class="remove-skill px-3 py-2 bg-red-100 text-red-600 rounded-lg hover:bg-red-200 transition">✕</button>
`;
const input = skillDiv.querySelector('.skill-input');
const removeBtn = skillDiv.querySelector('.remove-skill');
input.addEventListener('input', updatePreview);
removeBtn.addEventListener('click', function() {
skillDiv.remove();
skillCount--;
updateAddSkillButton();
updatePreview();
});
container.appendChild(skillDiv);
updateAddSkillButton();
}
function updateAddSkillButton() {
const btn = document.getElementById('add-skill-btn');
if (skillCount >= 5) {
btn.disabled = true;
btn.classList.add('opacity-50', 'cursor-not-allowed');
} else {
btn.disabled = false;
btn.classList.remove('opacity-50', 'cursor-not-allowed');
}
}
function updateColor(color) {
currentColor = color;
document.getElementById('color-picker').value = color;
document.getElementById('color-hex').value = color;
document.getElementById('card-header').style.backgroundColor = color;
}
function handleAvatarUpload(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(event) {
avatarDataUrl = event.target.result;
const img = document.getElementById('preview-avatar');
const placeholder = document.getElementById('avatar-placeholder');
img.src = avatarDataUrl;
img.classList.remove('hidden');
placeholder.classList.add('hidden');
};
reader.readAsDataURL(file);
}
}
function updatePreview() {
// Name
const name = document.getElementById('name').value || 'Your Name';
document.getElementById('preview-name').textContent = name;
// Intro
const intro = document.getElementById('intro').value || 'Your introduction appears here';
document.getElementById('preview-intro').textContent = intro;
// Skills
const skillInputs = document.querySelectorAll('.skill-input');
const skills = Array.from(skillInputs)
.map(input => input.value.trim())
.filter(skill => skill !== '');
const skillsSection = document.getElementById('preview-skills');
const skillsList = document.getElementById('skills-list');
if (skills.length > 0) {
skillsSection.classList.remove('hidden');
skillsList.innerHTML = skills.map(skill =>
`<span class="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm">${skill}</span>`
).join('');
} else {
skillsSection.classList.add('hidden');
}
// Contact details
const contactSection = document.getElementById('preview-contact');
let hasContact = false;
const email = document.getElementById('email').value;
const emailDiv = document.getElementById('contact-email');
if (email) {
emailDiv.classList.remove('hidden');
emailDiv.querySelector('span:last-child').textContent = email;
hasContact = true;
} else {
emailDiv.classList.add('hidden');
}
const phone = document.getElementById('phone').value;
const phoneDiv = document.getElementById('contact-phone');
if (phone) {
phoneDiv.classList.remove('hidden');
phoneDiv.querySelector('span:last-child').textContent = phone;
hasContact = true;
} else {
phoneDiv.classList.add('hidden');
}
const wechat = document.getElementById('wechat').value;
const wechatDiv = document.getElementById('contact-wechat');
if (wechat) {
wechatDiv.classList.remove('hidden');
wechatDiv.querySelector('span:last-child').textContent = wechat;
hasContact = true;
} else {
wechatDiv.classList.add('hidden');
}
const linkedin = document.getElementById('linkedin').value;
const linkedinDiv = document.getElementById('contact-linkedin');
if (linkedin) {
linkedinDiv.classList.remove('hidden');
linkedinDiv.querySelector('span:last-child').textContent = linkedin;
hasContact = true;
} else {
linkedinDiv.classList.add('hidden');
}
const twitter = document.getElementById('twitter').value;
const twitterDiv = document.getElementById('contact-twitter');
if (twitter) {
twitterDiv.classList.remove('hidden');
twitterDiv.querySelector('span:last-child').textContent = twitter;
hasContact = true;
} else {
twitterDiv.classList.add('hidden');
}
if (hasContact) {
contactSection.classList.remove('hidden');
} else {
contactSection.classList.add('hidden');
}
}
function handleSubmit(e) {
e.preventDefault();
document.getElementById('download-section').classList.remove('hidden');
document.getElementById('download-section').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
async function downloadCard() {
const card = document.getElementById('preview-card');
const size = document.getElementById('card-size').value;
const [width, height] = size.split('x').map(Number);
// Calculate scale factor
const currentWidth = card.offsetWidth;
const scale = width / currentWidth;
try {
const canvas = await html2canvas(card, {
scale: scale,
backgroundColor: '#ffffff',
logging: false,
useCORS: true
});
const link = document.createElement('a');
link.download = `profile-card-${Date.now()}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
} catch (error) {
console.error('Error generating image:', error);
alert('Failed to generate card. Please try again.');
}
}
</script>
</body>
</html>