<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Student ID Card Generator</title>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- HTML2Canvas for capturing DOM as image -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<!-- jsPDF for generating PDF -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
<style>
/* Custom styles for the ID card to ensure exact dimensions and professional look */
.id-card-container {
width: 2.1in; /* Standard vertical ID card width */
height: 3.4in; /* Standard vertical ID card height */
box-sizing: border-box; /* Include padding and border in the element's total width and height */
position: relative; /* For absolute positioning of elements inside */
overflow: hidden; /* Hide anything that goes outside the card boundaries */
display: flex; /* Use flexbox for vertical alignment */
flex-direction: column; /* Stack children vertically */
align-items: center; /* Center items horizontally */
justify-content: flex-start; /* Align items to the start (top) */
padding: 0.35rem; /* Reduced padding for more content space */
border: 1px solid #e2e8f0; /* Thin light-gray outline */
font-family: 'Roboto', Arial, sans-serif; /* Clean sans-serif font */
}
/* Ensure images inside the card are responsive and fit their containers */
.id-card-container img {
max-width: 100%;
height: auto;
display: block;
}
/* Hide the default file input text */
input[type="file"] {
color: transparent;
}
/* NEW: Style for the 3D "ghost" buttons */
.modern-button {
background-color: transparent; /* No fill color by default */
color: #4F46E5; /* Text color is the indigo */
padding: 0.6rem 1.2rem;
border-radius: 9999px; /* Pill shape */
cursor: pointer;
display: inline-block;
text-align: center;
font-size: 0.875rem; /* text-sm */
font-weight: 600; /* semibold */
transition: all 0.15s ease-out; /* Snappy transition */
border: 2px solid #4F46E5; /* Visible outline */
text-transform: uppercase;
letter-spacing: 0.05em;
/* 3D "pushed out" effect */
box-shadow: 0 4px #4338CA; /* Darker indigo shadow for depth */
position: relative;
top: 0;
}
.modern-button:hover {
background-color: #4F46E5; /* Fill color appears on hover */
color: #fff; /* White text on hover */
/* 3D "pressed" effect */
top: 2px;
box-shadow: 0 2px #4338CA; /* Reduce shadow to simulate pressing */
}
.modern-button:active {
/* Deeper "pressed" effect on click */
top: 4px;
box-shadow: 0 0 #4338CA;
}
/* Custom modal for messages */
.custom-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
text-align: center;
font-family: 'Inter', sans-serif;
color: #333;
display: none; /* Hidden by default */
}
.custom-modal.show {
display: block;
}
.custom-modal.success {
border: 2px solid #28a745;
color: #28a745;
}
.custom-modal.error {
border: 2px solid #dc3545;
color: #dc3545;
}
/* Tricolor divider styles */
.tricolor-divider {
display: flex;
width: 100%;
height: 2px; /* Thin line */
margin-top: 0.2rem; /* Space above divider */
margin-bottom: 0.5rem; /* Space below divider */
}
.tricolor-divider > div {
flex: 1;
height: 100%;
}
.saffron { background-color: #FF9933; } /* Saffron */
.white { background-color: #FFFFFF; border: 0.5px solid #e2e8f0; } /* White with a faint border */
.green { background-color: #138808; } /* India Green */
/* Specific styles for student photo and signature */
.student-photo-container {
width: 0.8in; /* Small square photo */
height: 0.8in;
background-color: #BFDBFE; /* Blue-100 */
border-radius: 0.25rem; /* rounded-md */
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border: 1px solid #9CA3AF; /* Gray-400 border */
}
.principal-signature-area {
text-align: center; /* Centered label */
line-height: 1; /* Adjust line height for signature text */
margin-top: 0.2rem; /* Space above signature */
}
.principal-signature-area img {
height: 0.35in; /* Adjust height to fit */
width: auto;
max-width: 0.8in; /* Max width for signature */
object-fit: contain;
display: inline-block;
vertical-align: bottom; /* Align with text baseline */
margin-bottom: 0.1rem; /* Space between image and label */
}
</style>
</head>
<body class="font-sans bg-gray-100 min-h-screen flex items-center justify-center p-4">
<div class="flex flex-col lg:flex-row bg-white rounded-lg shadow-xl overflow-hidden w-full max-w-6xl">
<!-- Left Panel: Edit Area -->
<div class="w-full lg:w-1/2 p-6 md:p-8 space-y-6 border-r border-gray-200 overflow-y-auto" style="max-height: 90vh;">
<h2 class="text-3xl font-bold text-gray-800 mb-6 text-center">ID Card Editor</h2>
<!-- Upload Options -->
<div class="space-y-4">
<label class="block text-sm font-medium text-gray-700">Upload College Logo</label>
<label for="collegeLogoUpload" class="modern-button">Choose File</label>
<input type="file" id="collegeLogoUpload" accept="image/*" class="hidden">
</div>
<div class="space-y-4">
<label class="block text-sm font-medium text-gray-700">Upload Student Photo</label>
<label for="studentPhotoUpload" class="modern-button">Choose File</label>
<input type="file" id="studentPhotoUpload" accept="image/*" class="hidden">
</div>
<div class="space-y-4">
<label class="block text-sm font-medium text-gray-700">Upload Principal Signature</label>
<label for="principalSignatureUpload" class="modern-button">Choose File</label>
<input type="file" id="principalSignatureUpload" accept="image/*" class="hidden">
</div>
<!-- Text Fields -->
<div class="space-y-4">
<label for="collegeNameInput" class="block text-sm font-medium text-gray-700">College Name:</label>
<input type="text" id="collegeNameInput" placeholder="N. C. College (Karimganj, Assam)" value="N. C. College (Karimganj, Assam)" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="space-y-4">
<label for="nameInput" class="block text-sm font-medium text-gray-700">Name:</label>
<input type="text" id="nameInput" placeholder="Anil Kumar" value="Anil Kumar" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="space-y-4">
<label for="classInput" class="block text-sm font-medium text-gray-700">Class:</label>
<input type="text" id="classInput" placeholder="12" value="12" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="space-y-4">
<label for="rollNumberInput" class="block text-sm font-medium text-gray-700">Roll Number:</label>
<input type="text" id="rollNumberInput" placeholder="55" value="55" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="space-y-4">
<label for="dobInput" class="block text-sm font-medium text-gray-700">Date of Birth:</label>
<input type="date" id="dobInput" value="2000-05-09" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="space-y-4">
<label for="yearInput" class="block text-sm font-medium text-gray-700">Year:</label>
<input type="text" id="yearInput" placeholder="2025" value="2025" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="space-y-4">
<label for="addressInput" class="block text-sm font-medium text-gray-700">Address (for bottom bar):</label>
<input type="text" id="addressInput" placeholder="N. C. College (Karimganj, Assam)" value="N. C. College (Karimganj, Assam)" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="space-y-4">
<label for="mobileInput" class="block text-sm font-medium text-gray-700">Mobile Number (for bottom bar):</label>
<input type="tel" id="mobileInput" placeholder="9162830269" value="9162830269" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
</div>
<!-- Right Panel: Live Card View -->
<div class="w-full lg:w-1/2 p-6 md:p-8 flex flex-col items-center justify-center bg-gray-50 relative">
<h2 class="text-3xl font-bold text-gray-800 mb-6 text-center">Live ID Card Preview</h2>
<!-- ID Card Preview Area -->
<div id="idCardPreview" class="id-card-container bg-white rounded-lg shadow-lg text-black relative">
<!-- Top Section: Logo and College Name -->
<div class="flex items-center w-full mb-1">
<img id="cardLogo" src="https://placehold.co/50x50/E0E7FF/4F46E5?text=Logo" alt="College Logo" class="w-12 h-12 rounded-full object-cover mr-2 border-2 border-gray-200 flex-shrink-0" onerror="this.onerror=null;this.src='https://placehold.co/50x50/E0E7FF/4F46E5?text=Logo';">
<h1 id="cardCollegeName" class="text-xs font-extrabold text-center flex-grow">N. C. College (Karimganj, Assam)</h1>
</div>
<!-- Tricolor Divider Line -->
<div class="tricolor-divider">
<div class="saffron"></div>
<div class="white"></div>
<div class="green"></div>
</div>
<!-- Main Content Area (Student Info + Photo/Signature) -->
<div class="flex w-full flex-grow px-1.5 pb-1 justify-between items-start">
<!-- Student Information (Left-Aligned) -->
<div class="flex flex-col text-[0.6rem] space-y-0.5 flex-grow pr-1">
<p><span class="font-semibold">Name:</span> <span id="cardName"></span></p>
<p><span class="font-semibold">Class:</span> <span id="cardClass"></span></p>
<p><span class="font-semibold">Roll No.:</span> <span id="cardRollNumber"></span></p>
<p><span class="font-semibold">D.O.B:</span> <span id="cardDob"></span></p>
<p><span class="font-semibold">Year:</span> <span id="cardYear"></span></p>
</div>
<!-- Photo and Signature Section (Right-Aligned) -->
<div class="flex flex-col items-center justify-start h-full pl-1">
<!-- Student Photo -->
<div class="student-photo-container mb-1">
<img id="cardStudentPhoto" src="https://placehold.co/80x80/E0E7FF/4F46E5?text=Photo" alt="Student Photo" class="w-full h-full object-cover" onerror="this.onerror=null;this.src='https://placehold.co/80x80/E0E7FF/4F46E5?text=Photo';">
</div>
<p class="text-gray-600 text-[0.55rem] text-center">Student Photo</p>
<!-- Principal Signature Block -->
<div class="principal-signature-area mt-auto">
<img id="cardPrincipalSignature" src="https://placehold.co/100x40/E0E7FF/4F46E5?text=Signature" alt="Principal Signature" onerror="this.onerror=null;this.src='https://placehold.co/100x40/E0E7FF/4F46E5?text=Signature';">
<p class="text-[0.5rem] font-bold">Principal Signature</p> <!-- Changed label and made bold -->
</div>
</div>
</div>
<!-- Bottom Navy-Blue Strip -->
<div class="absolute bottom-0 left-0 w-full bg-blue-900 text-white text-[0.5rem] py-1 px-1 font-medium flex justify-center items-center text-center">
<span id="cardAddressMobile"></span>
</div>
</div>
<!-- Download Button -->
<button id="downloadCardButton" class="modern-button mt-8">
Download Card
</button>
</div>
</div>
<script>
// Function to show a custom modal message
function showMessage(message, type = 'info') {
const existingModal = document.querySelector('.custom-modal');
if (existingModal) {
existingModal.remove();
}
const modal = document.createElement('div');
modal.className = `custom-modal ${type}`;
modal.textContent = message;
document.body.appendChild(modal);
// Use a timeout to apply the 'show' class to trigger transition
setTimeout(() => {
modal.classList.add('show');
}, 10);
setTimeout(() => {
modal.classList.remove('show');
setTimeout(() => modal.remove(), 500); // Remove from DOM after transition
}, 3000); // Remove after 3 seconds
}
// Function to handle image uploads and update the card preview
function handleImageUpload(event, targetElementId) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const imgElement = document.getElementById(targetElementId);
imgElement.src = e.target.result;
};
reader.readAsDataURL(file);
}
}
// Function to update text fields on the card preview
function updateCardText(inputId, cardElementId) {
const inputElement = document.getElementById(inputId);
const cardElement = document.getElementById(cardElementId);
// Set initial value
cardElement.textContent = inputElement.value;
// Add event listener for real-time updates
inputElement.addEventListener('input', () => {
cardElement.textContent = inputElement.value;
});
}
// Function to update the combined address and mobile in the bottom bar
function updateCombinedAddressMobile() {
const address = document.getElementById('addressInput').value;
const mobile = document.getElementById('mobileInput').value;
document.getElementById('cardAddressMobile').textContent = `Address: ${address} — Mobile: ${mobile}`;
}
// Initialize all text fields and image uploads
document.addEventListener('DOMContentLoaded', () => {
// Image upload listeners
document.getElementById('collegeLogoUpload').addEventListener('change', (e) => handleImageUpload(e, 'cardLogo'));
document.getElementById('studentPhotoUpload').addEventListener('change', (e) => handleImageUpload(e, 'cardStudentPhoto'));
document.getElementById('principalSignatureUpload').addEventListener('change', (e) => handleImageUpload(e, 'cardPrincipalSignature'));
// Text field listeners
updateCardText('collegeNameInput', 'cardCollegeName');
updateCardText('nameInput', 'cardName');
updateCardText('classInput', 'cardClass');
updateCardText('rollNumberInput', 'cardRollNumber');
updateCardText('dobInput', 'cardDob');
updateCardText('yearInput', 'cardYear');
// Initial update for combined address/mobile and add listeners
updateCombinedAddressMobile();
document.getElementById('addressInput').addEventListener('input', updateCombinedAddressMobile);
document.getElementById('mobileInput').addEventListener('input', updateCombinedAddressMobile);
});
// Download functionality
document.getElementById('downloadCardButton').addEventListener('click', async () => {
const card = document.getElementById('idCardPreview');
// We need to capture at a higher resolution for 300 DPI output.
// The card is 2.1in x 3.4in. At 300 DPI, this is 630px x 1020px.
// We use html2canvas's scale option for this.
const scaleFactor = 4; // Capture at 4x the rendered resolution for better quality
try {
// Show a loading message
showMessage("Generating your card...", "info");
// Capture the card with html2canvas
const canvas = await html2canvas(card, {
scale: scaleFactor,
useCORS: true, // Important for images, especially from data URLs or external sources
logging: false,
width: card.offsetWidth,
height: card.offsetHeight,
scrollX: -window.scrollX,
scrollY: -window.scrollY,
windowWidth: document.documentElement.offsetWidth,
windowHeight: document.documentElement.offsetHeight
});
const studentName = document.getElementById('nameInput').value.replace(/ /g, '_') || 'student';
// --- PNG Download ---
const pngUrl = canvas.toDataURL('image/png');
const pngLink = document.createElement('a');
pngLink.href = pngUrl;
pngLink.download = `${studentName}_id_card.png`;
document.body.appendChild(pngLink);
pngLink.click();
document.body.removeChild(pngLink);
// --- PDF Download ---
const { jsPDF } = window.jspdf;
// ID card dimensions in mm: 2.1 inches = 53.34 mm, 3.4 inches = 86.36 mm
const pdfWidth = 53.34;
const pdfHeight = 86.36;
// Create a new jsPDF instance with custom dimensions
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm',
format: [pdfWidth, pdfHeight]
});
const imgData = canvas.toDataURL('image/png');
// Add the image to the PDF, covering the entire page
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
pdf.save(`${studentName}_id_card.pdf`);
// Show success message after a short delay
setTimeout(() => {
showMessage("Card downloaded as PNG and PDF!", "success");
}, 500);
} catch (error) {
console.error("Error generating card:", error);
showMessage("Failed to generate card. Check console for errors.", "error");
}
});
</script>
</body>
</html>