feat: update certificate design with SVG template and Poppins font

- Changed certificate background from PNG to SVG for better quality
- Added Poppins font import to index.html
- Updated certificate layout with Team Name and Person Name in blue (#59bef5)
- Repositioned QR code to fit in designated white box placeholder
- Removed generated copyright and date overlays (now part of SVG template)
- Adjusted text positioning for better alignment with new design
- Increased canvas scale to 4x and wait time to 1500ms for sharper rendering

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Maulana Sodiqin
2025-12-13 23:41:56 +07:00
co-authored by Claude
parent 782cce820b
commit 3ad828e759
4 changed files with 190 additions and 98 deletions
+3
View File
@@ -18,6 +18,9 @@
type="image/svg+xml" type="image/svg+xml"
href="/images/imphnen-logo-simple.svg" href="/images/imphnen-logo-simple.svg"
/> />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="/src/index.css" /> <link rel="stylesheet" href="/src/index.css" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://hackathon.imphnen.dev/" /> <meta property="og:url" content="https://hackathon.imphnen.dev/" />
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 263 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 506 KiB

@@ -115,19 +115,22 @@ const CertificatePage: FC = (): ReactElement => {
setIsGenerating(true); setIsGenerating(true);
try { try {
// Wait a bit for fonts and images to load // Wait longer for fonts and images to load properly
await new Promise((resolve) => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 1500));
const canvas = await html2canvas(certificateRef.current, { const canvas = await html2canvas(certificateRef.current, {
scale: 2, scale: 4,
useCORS: true, useCORS: true,
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
logging: false, logging: false,
width: 1000, width: 1000,
height: (1000 * 2480) / 3508, height: (1000 * 2480) / 3508,
allowTaint: true,
imageTimeout: 0,
removeContainer: true,
}); });
const imageUrl = canvas.toDataURL('image/png'); const imageUrl = canvas.toDataURL('image/png', 1.0);
setCertificateImage(imageUrl); setCertificateImage(imageUrl);
setShowTemplate(false); setShowTemplate(false);
} catch (error) { } catch (error) {
@@ -315,57 +318,30 @@ const CertificatePage: FC = (): ReactElement => {
id="certificate-template" id="certificate-template"
style={{ style={{
position: 'relative', position: 'relative',
backgroundImage: 'url(/images/blank_cert.png)', backgroundImage: 'url(/images/blank_cert.svg)',
backgroundSize: 'cover', backgroundSize: 'cover',
backgroundPosition: 'center', backgroundPosition: 'center',
width: '1000px', width: '1000px',
height: `${(1000 * 2480) / 3508}px`, height: `${(1000 * 2480) / 3508}px`,
}} }}
> >
{/* User Name (from session) */} {/* Team Name - positioned in middle between "Diberikan Kepada" and "Telah Berpartisipasi" */}
<div <div
style={{ style={{
position: 'absolute', position: 'absolute',
top: '40%', top: '41%',
left: '50%', left: '3.5%',
transform: 'translateX(-50%)', width: '55%',
width: '80%',
}}
>
<h3
ref={userNameRef}
style={{
fontWeight: 'bold',
color: '#111827',
textAlign: 'center',
fontSize: userNameFontSize,
lineHeight: '1.2',
wordBreak: 'break-word',
textShadow: '0 1px 2px rgba(0,0,0,0.1)',
margin: 0,
}}
>
{certificateName || 'N/A'}
</h3>
</div>
{/* Team Name */}
<div
style={{
position: 'absolute',
top: '46%',
left: '50%',
transform: 'translateX(-50%)',
width: '70%',
}} }}
> >
<h3 <h3
ref={teamNameRef} ref={teamNameRef}
style={{ style={{
fontWeight: '600', fontFamily: 'Poppins, sans-serif',
color: '#1f2937', fontWeight: 'bold',
textAlign: 'center', color: '#59bef5',
fontSize: teamNameFontSize, textAlign: 'left',
fontSize: '32px',
lineHeight: '1.2', lineHeight: '1.2',
wordBreak: 'break-word', wordBreak: 'break-word',
margin: 0, margin: 0,
@@ -375,82 +351,53 @@ const CertificatePage: FC = (): ReactElement => {
</h3> </h3>
</div> </div>
{/* Participation Text */} {/* User Name - positioned below team name */}
<div <div
style={{ style={{
position: 'absolute', position: 'absolute',
top: '60%', top: '45%',
left: '50%', left: '3.5%',
transform: 'translateX(-50%)', width: '55%',
width: '70%',
}} }}
> >
<p <h3
ref={userNameRef}
style={{ style={{
textAlign: 'center', fontFamily: 'Poppins, sans-serif',
color: '#374151', fontWeight: 'bold',
fontSize: '18px', color: '#59bef5',
fontWeight: '500', textAlign: 'left',
fontSize: '40px',
lineHeight: '1.2',
wordBreak: 'break-word',
margin: 0, margin: 0,
}} }}
> >
<span style={{ fontWeight: 'bold' }}> {certificateName || 'N/A'}
Peserta Hackathon IMPHNEN x KOLOSAL AI </h3>
</span>
</p>
</div> </div>
{/* QR Code */} {/* QR Code - positioned in the white box area */}
<div <div
style={{ style={{
position: 'absolute', position: 'absolute',
bottom: '8%', top: '33%',
left: '8%', right: '9.3%',
width: '190px',
height: '190px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}} }}
> >
{qrCodeUrl && ( {qrCodeUrl && (
<div <img
style={{ src={qrCodeUrl}
backgroundColor: '#ffffff', alt="Certificate QR Code"
padding: '8px', style={{ width: '190px', height: '190px', display: 'block' }}
borderRadius: '4px', />
boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
}}
>
<img
src={qrCodeUrl}
alt="Certificate QR Code"
style={{ width: '96px', height: '96px' }}
/>
</div>
)} )}
</div> </div>
{/* Date */}
<div
style={{
position: 'absolute',
bottom: '8%',
right: '8%',
}}
>
<p
style={{
fontSize: '14px',
color: '#374151',
margin: 0,
}}
>
{new Date().toLocaleDateString(
'id-ID',
{
day: 'numeric',
month: 'long',
year: 'numeric',
}
)}
</p>
</div>
</div> </div>
</div> </div>