mirror of
https://github.com/containers/podman.git
synced 2025-10-09 23:15:39 +08:00
contrib: Add certificate generator for first-time contributors
Add a web-based certificate generator to celebrate and recognize first-time contributors to the Podman project. This tool helps build community engagement by providing a memorable way to acknowledge new contributors' efforts. Features: - Interactive web interface for generating personalized certificates - Real-time preview as users customize the certificate - Professional design with Podman branding - Downloadable HTML certificates suitable for printing or PDF conversion - Support for contributor name, PR number, and date customization The tool includes: - certificate_generator.html: Main interactive interface - certificate_template.html: Standalone template for manual editing - 1stpr.png: Sample certificate design concept - README.md: Comprehensive documentation This contribution aims to enhance community engagement and provide a positive experience for new contributors making their first pull request to Podman. Signed-off-by: G A Neil Smith <nesmith@redhat.com>
This commit is contained in:
BIN
contrib/certificate-generator/1stpr.png
Normal file
BIN
contrib/certificate-generator/1stpr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 578 KiB |
70
contrib/certificate-generator/README.md
Normal file
70
contrib/certificate-generator/README.md
Normal file
@ -0,0 +1,70 @@
|
||||
# Podman Certificate Generator
|
||||
|
||||
A web-based tool to generate personalized certificates for first-time Podman contributors.
|
||||
|
||||
## Overview
|
||||
|
||||
This certificate generator creates beautiful, printable certificates to celebrate and recognize first-time contributors to the Podman project. It helps build community engagement and provides a memorable way to acknowledge new contributors' efforts.
|
||||
|
||||
## Features
|
||||
|
||||
- **Interactive Web Interface**: Easy-to-use form for generating certificates
|
||||
- **Real-time Preview**: See the certificate as you customize it
|
||||
- **Professional Design**: Clean, modern certificate layout with Podman branding
|
||||
- **Downloadable Output**: Generate HTML certificates that can be printed or saved as PDF
|
||||
- **Customizable**: Support for contributor name, PR number, and date
|
||||
|
||||
## Files
|
||||
|
||||
- `certificate_generator.html` - Main interactive certificate generator interface
|
||||
- `certificate_template.html` - Base certificate template (standalone version)
|
||||
- `1stpr.png` - Sample certificate image showing the design concept
|
||||
- `README.md` - This documentation file
|
||||
|
||||
## Usage
|
||||
|
||||
### Using the Generator
|
||||
|
||||
1. Open `certificate_generator.html` in a web browser
|
||||
2. Fill in the form fields:
|
||||
- **Contributor Name**: The name to appear on the certificate
|
||||
- **PR Number**: The pull request number for their first contribution
|
||||
- **Date**: The date of the contribution (defaults to today)
|
||||
3. Preview the certificate in real-time as you type
|
||||
4. Click "Download Certificate" to save the personalized certificate as an HTML file
|
||||
|
||||
### Using the Template Directly
|
||||
|
||||
The `certificate_template.html` file can be used as a standalone template if you prefer to manually edit the HTML:
|
||||
|
||||
1. Open `certificate_template.html` in a text editor
|
||||
2. Replace the placeholder text:
|
||||
- `[Contributor Name]` with the actual contributor name
|
||||
- `[PR Number]` with the pull request number
|
||||
- `[Date]` with the contribution date
|
||||
3. Save and open in a browser to view/print
|
||||
|
||||
## Certificate Design
|
||||
|
||||
The certificates feature:
|
||||
- Podman purple branding (#892ca0)
|
||||
- Professional typography and layout
|
||||
- Seal emoji (🦭) with graduation cap decoration
|
||||
- Clean, printable design suitable for framing
|
||||
- Responsive design that works on different screen sizes
|
||||
|
||||
## Technical Details
|
||||
|
||||
- Pure HTML/CSS/JavaScript implementation
|
||||
- No external dependencies
|
||||
- Works in all modern browsers
|
||||
- Print-friendly CSS styles
|
||||
- Generates self-contained HTML files
|
||||
|
||||
## Contributing
|
||||
|
||||
This tool is part of the Podman project's community engagement efforts. Contributions and improvements are welcome! Please follow the standard Podman contribution guidelines when submitting changes.
|
||||
|
||||
## License
|
||||
|
||||
This certificate generator is part of the Podman project and follows the same licensing terms.
|
277
contrib/certificate-generator/certificate_generator.html
Normal file
277
contrib/certificate-generator/certificate_generator.html
Normal file
@ -0,0 +1,277 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Podman Certificate Generator</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700;900&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #f0f2f5;
|
||||
margin: 0;
|
||||
padding: 2rem;
|
||||
}
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: 380px 1fr;
|
||||
gap: 2rem;
|
||||
max-width: 1600px;
|
||||
margin: auto;
|
||||
}
|
||||
.form-panel {
|
||||
background-color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
height: fit-content;
|
||||
position: sticky;
|
||||
top: 2rem;
|
||||
}
|
||||
.form-panel h2 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
font-family: 'Merriweather', serif;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.action-buttons button {
|
||||
flex-grow: 1;
|
||||
padding: 0.75rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
#downloadBtn {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
#downloadBtn:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
.preview-panel {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* Certificate Styles (copied from template and scaled) */
|
||||
.certificate {
|
||||
width: 800px;
|
||||
height: 1100px;
|
||||
background: #fdfaf0;
|
||||
border: 2px solid #333;
|
||||
position: relative;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
padding: 50px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-family: 'Merriweather', serif;
|
||||
transform: scale(0.8);
|
||||
transform-origin: top center;
|
||||
}
|
||||
.party-popper { position: absolute; font-size: 40px; }
|
||||
.top-left { top: 40px; left: 40px; }
|
||||
.top-right { top: 40px; right: 40px; }
|
||||
.main-title { font-size: 48px; font-weight: 900; color: #333; text-align: center; margin-top: 60px; line-height: 1.2; text-transform: uppercase; }
|
||||
.subtitle { font-size: 24px; font-weight: 400; color: #333; text-align: center; margin-top: 30px; text-transform: uppercase; letter-spacing: 2px; }
|
||||
.contributor-name { font-size: 56px; font-weight: 700; color: #333; text-align: center; margin: 15px 0 50px; }
|
||||
.mascot-image { width: 450px; height: 450px; background-image: url('1stpr.png'); background-size: contain; background-repeat: no-repeat; background-position: center; margin-top: 20px; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||
.description { font-size: 22px; color: #333; line-height: 1.6; text-align: center; margin-top: 40px; }
|
||||
.description strong { font-weight: 700; }
|
||||
.footer { width: 100%; margin-top: auto; padding-top: 30px; border-top: 1px solid #ccc; display: flex; justify-content: space-between; align-items: flex-end; font-size: 16px; color: #333; }
|
||||
.pr-info { text-align: left; }
|
||||
.signature { text-align: right; font-style: italic; }
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background: #fff;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.form-panel, .action-buttons {
|
||||
display: none;
|
||||
}
|
||||
.container {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.preview-panel {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.certificate {
|
||||
transform: scale(1);
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="form-panel">
|
||||
<h2>Certificate Generator</h2>
|
||||
<div class="form-group">
|
||||
<label for="contributorName">Contributor Name</label>
|
||||
<input type="text" id="contributorName" value="Mike McGrath">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="prNumber">PR Number</label>
|
||||
<input type="text" id="prNumber" value="26393">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mergeDate">Date</label>
|
||||
<input type="text" id="mergeDate" value="June 13, 2025">
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button id="downloadBtn">Download HTML</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-panel">
|
||||
<div id="certificatePreview">
|
||||
<!-- Certificate HTML will be injected here by script -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const nameInput = document.getElementById('contributorName');
|
||||
const prNumberInput = document.getElementById('prNumber');
|
||||
const dateInput = document.getElementById('mergeDate');
|
||||
const preview = document.getElementById('certificatePreview');
|
||||
|
||||
function generateCertificateHTML(name, prNumber, date) {
|
||||
const prLink = `https://github.com/containers/pull/${prNumber}`;
|
||||
// This is the full, self-contained HTML for the certificate
|
||||
return `
|
||||
<div class="certificate">
|
||||
<div class="party-popper top-left">🎉</div>
|
||||
<div class="party-popper top-right">🎉</div>
|
||||
<div class="main-title">Certificate of<br>Contribution</div>
|
||||
<div class="subtitle">Awarded To</div>
|
||||
<div class="contributor-name">${name}</div>
|
||||
<div class="mascot-image"></div>
|
||||
<div class="description">
|
||||
For successfully submitting and merging their <strong>First Pull Request</strong> to the <strong>Podman project</strong>.<br>
|
||||
Your contribution helps make open source better—one PR at a time!
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="pr-info">
|
||||
<div>🔧 Merged PR: <a href="${prLink}" target="_blank">${prLink}</a></div>
|
||||
<div style="margin-top: 5px;">${date}</div>
|
||||
</div>
|
||||
<div class="signature">
|
||||
Keep hacking, keep contributing!<br>
|
||||
– The Podman Community
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function updatePreview() {
|
||||
const name = nameInput.value || '[CONTRIBUTOR_NAME]';
|
||||
const prNumber = prNumberInput.value || '[PR_NUMBER]';
|
||||
const date = dateInput.value || '[DATE]';
|
||||
preview.innerHTML = generateCertificateHTML(name, prNumber, date);
|
||||
}
|
||||
|
||||
document.getElementById('downloadBtn').addEventListener('click', () => {
|
||||
const name = nameInput.value || 'contributor';
|
||||
const prNumber = prNumberInput.value || '00000';
|
||||
const date = dateInput.value || 'Date';
|
||||
|
||||
const certificateHTML = generateCertificateHTML(name, prNumber, date);
|
||||
const fullPageHTML = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Certificate for ${name}</title>
|
||||
<style>
|
||||
/* All the CSS from the generator page */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700;900&display=swap');
|
||||
body { margin: 20px; font-family: 'Merriweather', serif; background: #e0e0e0; }
|
||||
.certificate {
|
||||
transform: scale(1);
|
||||
box-shadow: none;
|
||||
margin: auto;
|
||||
}
|
||||
/* Paste all certificate-related styles here */
|
||||
.certificate { width: 800px; height: 1100px; background: #fdfaf0; border: 2px solid #333; position: relative; padding: 50px; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; }
|
||||
.party-popper { position: absolute; font-size: 40px; }
|
||||
.top-left { top: 40px; left: 40px; }
|
||||
.top-right { top: 40px; right: 40px; }
|
||||
.main-title { font-size: 48px; font-weight: 900; color: #333; text-align: center; margin-top: 60px; line-height: 1.2; text-transform: uppercase; }
|
||||
.subtitle { font-size: 24px; font-weight: 400; color: #333; text-align: center; margin-top: 30px; text-transform: uppercase; letter-spacing: 2px; }
|
||||
.contributor-name { font-size: 56px; font-weight: 700; color: #333; text-align: center; margin: 15px 0 50px; }
|
||||
.mascot-image { width: 450px; height: 450px; background-image: url('1stpr.png'); background-size: contain; background-repeat: no-repeat; background-position: center; margin-top: 20px; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||
.description { font-size: 22px; color: #333; line-height: 1.6; text-align: center; margin-top: 40px; }
|
||||
.description strong { font-weight: 700; }
|
||||
.footer { width: 100%; margin-top: auto; padding-top: 30px; border-top: 1px solid #ccc; display: flex; justify-content: space-between; align-items: flex-end; font-size: 16px; color: #333; }
|
||||
.pr-info { text-align: left; }
|
||||
.signature { text-align: right; font-style: italic; }
|
||||
|
||||
@media print {
|
||||
@page { size: A4 portrait; margin: 0; }
|
||||
body, html { width: 100%; height: 100%; margin: 0; padding: 0; }
|
||||
.certificate { width: 100%; height: 100%; box-shadow: none; transform: scale(1); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>${certificateHTML}</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const blob = new Blob([fullPageHTML], { type: 'text/html' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `podman-contribution-certificate-${name.toLowerCase().replace(/\s+/g, '-')}.html`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
|
||||
// Add event listeners to update preview on input change
|
||||
[nameInput, prNumberInput, dateInput].forEach(input => {
|
||||
input.addEventListener('input', updatePreview);
|
||||
});
|
||||
|
||||
// Initial preview generation
|
||||
updatePreview();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
175
contrib/certificate-generator/certificate_template.html
Normal file
175
contrib/certificate-generator/certificate_template.html
Normal file
@ -0,0 +1,175 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Podman Certificate of Contribution</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700;900&display=swap');
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
font-family: 'Merriweather', serif;
|
||||
background: #e0e0e0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.certificate {
|
||||
width: 800px;
|
||||
height: 1100px;
|
||||
background: #fdfaf0;
|
||||
border: 2px solid #333;
|
||||
position: relative;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
padding: 50px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.party-popper {
|
||||
position: absolute;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.top-left {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
top: 40px;
|
||||
right: 40px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 48px;
|
||||
font-weight: 900;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-top: 60px;
|
||||
line-height: 1.2;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.contributor-name {
|
||||
font-size: 56px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin: 15px 0 50px;
|
||||
}
|
||||
|
||||
.mascot-image {
|
||||
width: 450px;
|
||||
height: 450px;
|
||||
background-image: url('1stpr.png');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
margin-top: 20px;
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 22px;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.description strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100%;
|
||||
margin-top: auto;
|
||||
padding-top: 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.pr-info {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.signature {
|
||||
text-align: right;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: A4 portrait;
|
||||
margin: 0;
|
||||
}
|
||||
body, html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #fdfaf0;
|
||||
}
|
||||
.certificate {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
box-shadow: none;
|
||||
transform: scale(1);
|
||||
border-radius: 0;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="certificate">
|
||||
<div class="party-popper top-left">🎉</div>
|
||||
<div class="party-popper top-right">🎉</div>
|
||||
|
||||
<div class="main-title">Certificate of<br>Contribution</div>
|
||||
<div class="subtitle">Awarded To</div>
|
||||
|
||||
<div class="contributor-name">[CONTRIBUTOR_NAME]</div>
|
||||
|
||||
<div class="mascot-image"></div>
|
||||
|
||||
<div class="description">
|
||||
For successfully submitting and merging their <strong>First Pull Request</strong> to the <strong>Podman project</strong>.<br>
|
||||
Your contribution helps make open source better—one PR at a time!
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="pr-info">
|
||||
<div>🔧 Merged PR: [PR_LINK]</div>
|
||||
<div style="margin-top: 5px;">[DATE]</div>
|
||||
</div>
|
||||
<div class="signature">
|
||||
Keep hacking, keep contributing!<br>
|
||||
– The Podman Community
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user