Skip to content

Instantly share code, notes, and snippets.

@jddunn
Created April 10, 2025 03:09
Show Gist options
  • Select an option

  • Save jddunn/48bc03f3a9f85ffd8ccf90c801f6cf93 to your computer and use it in GitHub Desktop.

Select an option

Save jddunn/48bc03f3a9f85ffd8ccf90c801f6cf93 to your computer and use it in GitHub Desktop.
Logomaker - LLM-written logo maker web app, in 1 HTML file
<!-- https://manic.agency/blog/logomaker-an-experiment-in-human-computer-interaction-vibe-coding -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Logo Generator</title>
<!-- Extended Google Fonts API -->
<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=Orbitron:wght@400;500;700;900&family=Audiowide&family=Bungee+Shade&family=Bungee&family=Bungee+Outline&family=Bungee+Hairline&family=Chakra+Petch:wght@700&family=Exo+2:wght@800&family=Megrim&family=Press+Start+2P&family=Rubik+Mono+One&family=Russo+One&family=Syne+Mono&family=VT323&family=Wallpoet&family=Faster+One&family=Teko:wght@700&family=Black+Ops+One&family=Bai+Jamjuree:wght@700&family=Righteous&family=Bangers&family=Raleway+Dots&family=Monoton&family=Syncopate:wght@700&family=Lexend+Mega:wght@800&family=Michroma&family=Iceland&family=ZCOOL+QingKe+HuangYou&family=Zen+Tokyo+Zoo&family=Major+Mono+Display&family=Nova+Square&family=Kelly+Slab&family=Graduate&family=Unica+One&family=Aldrich&family=Share+Tech+Mono&family=Silkscreen&family=Rajdhani:wght@700&family=Jura:wght@700&family=Goldman&family=Tourney:wght@700&family=Saira+Stencil+One&family=Syncopate&family=Fira+Code:wght@700&family=DotGothic16&display=swap" rel="stylesheet">
<style>
:root {
--primary-gradient: linear-gradient(
45deg,
#FF1493, /* Deep Pink */
#FF69B4, /* Hot Pink */
#FF00FF, /* Magenta */
#FF4500, /* Orange Red */
#8A2BE2 /* Blue Violet */
);
--cyberpunk-gradient: linear-gradient(
45deg,
#00FFFF, /* Cyan */
#FF00FF, /* Magenta */
#FFFF00 /* Yellow */
);
--sunset-gradient: linear-gradient(
45deg,
#FF7E5F, /* Coral */
#FEB47B, /* Peach */
#FF9966 /* Orange */
);
--ocean-gradient: linear-gradient(
45deg,
#2E3192, /* Deep Blue */
#1BFFFF /* Light Cyan */
);
--forest-gradient: linear-gradient(
45deg,
#134E5E, /* Deep Teal */
#71B280 /* Light Green */
);
--rainbow-gradient: linear-gradient(
45deg,
#FF0000, /* Red */
#FF7F00, /* Orange */
#FFFF00, /* Yellow */
#00FF00, /* Green */
#0000FF, /* Blue */
#4B0082, /* Indigo */
#9400D3 /* Violet */
);
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
margin: 0;
background: #222;
color: white;
font-family: system-ui, -apple-system, sans-serif;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 20px;
}
h1 {
margin: 0;
font-size: 24px;
color: #fff;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
max-width: 1200px;
margin: 0 auto;
width: 100%;
}
.controls-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
width: 100%;
margin-bottom: 30px;
background: rgba(0, 0, 0, 0.2);
padding: 20px;
border-radius: 10px;
}
.control-group {
display: flex;
flex-direction: column;
}
.control-group label {
font-size: 14px;
margin-bottom: 5px;
color: #ccc;
}
input, select {
padding: 8px;
background: rgba(0, 0, 0, 0.5);
border: 1px solid #555;
color: #fff;
border-radius: 4px;
font-size: 14px;
}
input[type="color"] {
height: 40px;
}
.preview-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
margin-bottom: 30px;
padding: 40px;
background: #000;
border-radius: 10px;
position: relative;
}
.size-indicator {
position: absolute;
bottom: 5px;
right: 10px;
font-size: 12px;
color: #555;
}
.logo-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
background: transparent;
}
.logo-text {
font-size: 8vw;
text-transform: uppercase;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
color: transparent;
background-size: 400% 400%;
letter-spacing: 0.03em;
line-height: 1;
display: inline-block;
padding: 20px;
word-break: break-word;
text-align: center;
font-family: 'Orbitron', sans-serif;
font-weight: 700;
background-image: var(--primary-gradient);
}
/* Text shadow/glow presets */
.text-glow-none {
text-shadow: none;
filter: none;
/* Make sure the text is visible even without glow */
-webkit-text-fill-color: transparent;
color: transparent;
}
.text-glow-sharp {
text-shadow: none;
filter: none;
-webkit-font-smoothing: none;
-moz-osx-font-smoothing: none;
font-smooth: never;
/* Make sure the text is visible even with sharp edges */
-webkit-text-fill-color: transparent;
color: transparent;
}
.text-glow-soft {
/* Enhanced soft glow that's actually visible */
text-shadow:
0 0 1px rgba(255,255,255,0.9),
0 0 2px rgba(255,255,255,0.4);
filter: none;
-webkit-text-fill-color: transparent;
color: transparent;
}
.text-glow-medium {
text-shadow:
0 0 10px rgba(255,20,147,0.5),
0 0 20px rgba(255,105,180,0.3);
filter: none;
}
.text-glow-hard {
text-shadow:
0 0 7px rgba(255,255,255,0.7),
0 0 10px rgba(255,20,147,0.7),
0 0 20px rgba(255,105,180,0.5),
0 0 30px rgba(255,20,147,0.3);
filter: none;
}
.text-glow-neon {
text-shadow:
0 0 5px rgba(255,255,255,0.8),
0 0 10px currentColor,
0 0 20px currentColor,
0 0 30px currentColor,
0 0 40px currentColor;
/* Need to make text visible for neon effect */
-webkit-text-fill-color: rgba(255, 255, 255, 0.8);
color: rgba(255, 255, 255, 0.8);
background-image: none !important;
filter: none;
}
.text-glow-outline {
-webkit-text-stroke: 2px rgba(255,255,255,0.8);
text-shadow: none;
filter: none;
/* Make sure gradient still shows through */
-webkit-text-fill-color: transparent;
color: transparent;
}
/* Background types */
.bg-transparent {
background: transparent;
}
.bg-solid {
background-color: #000000;
}
.bg-gradient {
background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460);
}
.bg-gradient-animated {
background: linear-gradient(
-45deg,
#1a1a2e,
#16213e,
#0f3460,
#0a4b8b
);
background-size: 400% 400%;
animation: gradientBG 15s ease infinite;
}
.bg-transparent-gradient {
background: linear-gradient(135deg, rgba(26,26,46,0.7), rgba(22,33,62,0.7), rgba(15,52,96,0.7));
}
.bg-transparent-gradient-animated {
background: linear-gradient(
-45deg,
rgba(26,26,46,0.7),
rgba(22,33,62,0.7),
rgba(15,52,96,0.7),
rgba(10,75,139,0.7)
);
background-size: 400% 400%;
animation: gradientBG 15s ease infinite;
}
@keyframes gradientBG {
0% {background-position: 0% 50%;}
50% {background-position: 100% 50%;}
100% {background-position: 0% 50%;}
}
.bg-grid {
background-color: rgba(0,0,0,0.9);
background-image:
linear-gradient(rgba(0, 255, 255, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 255, 255, 0.1) 1px, transparent 1px);
background-size: 20px 20px;
}
.bg-darkgrid {
background-color: rgba(10,10,10,0.9);
background-image:
linear-gradient(rgba(30, 30, 30, 1) 1px, transparent 1px),
linear-gradient(90deg, rgba(30, 30, 30, 1) 1px, transparent 1px);
background-size: 40px 40px;
}
.bg-stars {
background-color: rgba(0,0,0,0.9);
background-image: radial-gradient(white, rgba(255,255,255,.2) 2px, transparent 5px);
background-size: 50px 50px;
}
.bg-noise {
position: relative;
background-color: rgba(0,0,0,0.8);
}
.bg-noise::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 250 250' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
opacity: 0.2;
z-index: -1;
}
/* Animation styles for the text */
.anim-none {
animation: gradientShift 5s ease infinite;
}
.anim-blink {
animation: gradientShift 5s ease infinite, blink 3s infinite;
}
.anim-pulse {
animation: gradientShift 5s ease infinite, pulse 2s ease-in-out infinite;
}
.anim-bounce {
animation: gradientShift 5s ease infinite, bounce 2s ease infinite;
}
.anim-shake {
animation: gradientShift 5s ease infinite, shake 0.5s ease-in-out infinite;
}
.anim-typing {
animation: gradientShift 5s ease infinite;
white-space: nowrap;
overflow: hidden;
border-right: 3px solid;
width: 0;
animation: gradientShift 5s ease infinite, typing 3.5s steps(40, end) forwards;
}
@keyframes blink {
0%, 19%, 21%, 23%, 71%, 73%, 75%, 100% { opacity: 1; }
20%, 22%, 24%, 72%, 74% { opacity: 0.5; }
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
40% { transform: translateY(-20px); }
60% { transform: translateY(-10px); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
@keyframes typing {
from { width: 0; }
to { width: 100%; }
}
@keyframes gradientShift {
0% {background-position: 0% 50%;}
50% {background-position: 100% 50%;}
100% {background-position: 0% 50%;}
}
.button-container {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
width: 100%;
}
button {
background: linear-gradient(135deg, #FF1493, #FF69B4);
border: none;
color: white;
padding: 12px 20px;
font-family: 'Orbitron', sans-serif;
font-size: 1em;
cursor: pointer;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.2), 0 0 20px rgba(255,20,147,0.3);
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.1em;
}
button:hover {
transform: scale(1.05);
background: linear-gradient(135deg, #FF69B4, #FF1493);
box-shadow: 0 6px 8px rgba(0,0,0,0.3), 0 0 30px rgba(255,20,147,0.5);
}
.loading-indicator {
display: none;
margin-top: 20px;
color: #fff;
text-align: center;
font-style: italic;
}
.font-preview {
font-size: 20px;
margin-left: 10px;
display: inline-block;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Logo Generator</h1>
</header>
<div class="controls-container">
<div class="control-group">
<label for="logoText">Logo Text</label>
<input type="text" id="logoText" value="MagicLogger" placeholder="Enter logo text">
</div>
<div class="control-group">
<label for="fontFamily">Font Family <span id="fontPreview" class="font-preview">Aa</span></label>
<select id="fontFamily">
<optgroup label="Popular Tech Fonts">
<option value="'Orbitron', sans-serif">Orbitron</option>
<option value="'Audiowide', cursive">Audiowide</option>
<option value="'Black Ops One', cursive">Black Ops One</option>
<option value="'Russo One', sans-serif">Russo One</option>
<option value="'Teko', sans-serif">Teko</option>
<option value="'Rajdhani', sans-serif">Rajdhani</option>
<option value="'Chakra Petch', sans-serif">Chakra Petch</option>
<option value="'Michroma', sans-serif">Michroma</option>
</optgroup>
<optgroup label="Futuristic">
<option value="'Exo 2', sans-serif">Exo 2</option>
<option value="'Jura', sans-serif">Jura</option>
<option value="'Bai Jamjuree', sans-serif">Bai Jamjuree</option>
<option value="'Aldrich', sans-serif">Aldrich</option>
<option value="'Unica One', cursive">Unica One</option>
<option value="'Goldman', cursive">Goldman</option>
<option value="'Nova Square', cursive">Nova Square</option>
</optgroup>
<optgroup label="Decorative & Display">
<option value="'Bungee', cursive">Bungee</option>
<option value="'Bungee Shade', cursive">Bungee Shade</option>
<option value="'Bungee Outline', cursive">Bungee Outline</option>
<option value="'Bungee Hairline', cursive">Bungee Hairline</option>
<option value="'Righteous', cursive">Righteous</option>
<option value="'Monoton', cursive">Monoton</option>
<option value="'Bangers', cursive">Bangers</option>
<option value="'Faster One', cursive">Faster One</option>
<option value="'Tourney', cursive">Tourney</option>
<option value="'Saira Stencil One', cursive">Saira Stencil One</option>
<option value="'Zen Tokyo Zoo', cursive">Zen Tokyo Zoo</option>
</optgroup>
<optgroup label="Monospace & Pixel">
<option value="'Press Start 2P', cursive">Press Start 2P</option>
<option value="'VT323', monospace">VT323</option>
<option value="'Share Tech Mono', monospace">Share Tech Mono</option>
<option value="'Fira Code', monospace">Fira Code</option>
<option value="'Silkscreen', cursive">Silkscreen</option>
<option value="'DotGothic16', sans-serif">DotGothic16</option>
<option value="'Major Mono Display', monospace">Major Mono Display</option>
<option value="'Syne Mono', monospace">Syne Mono</option>
<option value="'Iceland', cursive">Iceland</option>
</optgroup>
<optgroup label="Unique & Stylized">
<option value="'Megrim', cursive">Megrim</option>
<option value="'Rubik Mono One', sans-serif">Rubik Mono One</option>
<option value="'Wallpoet', cursive">Wallpoet</option>
<option value="'Lexend Mega', sans-serif">Lexend Mega</option>
<option value="'Raleway Dots', cursive">Raleway Dots</option>
<option value="'Syncopate', sans-serif">Syncopate</option>
<option value="'ZCOOL QingKe HuangYou', cursive">ZCOOL QingKe HuangYou</option>
<option value="'Kelly Slab', cursive">Kelly Slab</option>
<option value="'Graduate', cursive">Graduate</option>
</optgroup>
</select>
</div>
<div class="control-group">
<label for="fontSize">Font Size (px)</label>
<input type="number" id="fontSize" value="100" min="20" max="500">
</div>
<div class="control-group">
<label for="letterSpacing">Letter Spacing</label>
<input type="range" id="letterSpacing" min="0" max="0.5" step="0.01" value="0.03">
</div>
<div class="control-group">
<label for="gradientPreset">Gradient Preset</label>
<select id="gradientPreset">
<option value="var(--primary-gradient)">Pink Magenta</option>
<option value="var(--cyberpunk-gradient)">Cyberpunk</option>
<option value="var(--sunset-gradient)">Sunset</option>
<option value="var(--ocean-gradient)">Ocean</option>
<option value="var(--forest-gradient)">Forest</option>
<option value="var(--rainbow-gradient)">Rainbow</option>
<option value="custom">Custom...</option>
</select>
</div>
<div class="control-group" id="customGradientControls" style="display: none;">
<label for="color1">Gradient Color 1</label>
<input type="color" id="color1" value="#FF1493">
<label for="color2">Gradient Color 2</label>
<input type="color" id="color2" value="#8A2BE2">
</div>
<div class="control-group">
<label for="animationSpeed">Animation Speed</label>
<input type="range" id="animationSpeed" min="1" max="10" value="5">
</div>
<div class="control-group">
<label for="textShadow">Text Glow/Sharpness</label>
<select id="textShadow">
<option value="none" selected>None (No Glow)</option>
<option value="sharp">Sharp Edges</option>
<option value="soft">Soft Glow</option>
<option value="medium">Medium Glow</option>
<option value="hard">Hard Glow</option>
<option value="neon">Neon Effect</option>
<option value="outline">Outline</option>
</select>
</div>
<div class="control-group">
<label for="backgroundType">Background</label>
<select id="backgroundType">
<option value="transparent">Transparent</option>
<option value="transparent-gradient">Transparent Gradient</option>
<option value="transparent-gradient-animated">Animated Transparent Gradient</option>
<option value="solid">Solid Color</option>
<option value="gradient">Gradient</option>
<option value="gradient-animated">Animated Gradient</option>
<option value="grid">Cyberpunk Grid</option>
<option value="darkgrid">Dark Grid</option>
<option value="stars">Starfield</option>
<option value="noise">Digital Noise</option>
</select>
</div>
<div class="control-group" id="backgroundColorControl" style="display: none;">
<label for="backgroundColor">Background Color</label>
<input type="color" id="backgroundColor" value="#000000">
</div>
<div class="control-group">
<label for="textAnimation">Text Animation</label>
<select id="textAnimation">
<option value="none">None (Gradient Only)</option>
<option value="blink">Blink</option>
<option value="pulse">Pulse</option>
<option value="bounce">Bounce</option>
<option value="shake">Shake</option>
<option value="typing">Typing</option>
</select>
</div>
</div>
<div class="preview-container" id="previewContainer">
<div class="logo-container">
<div class="logo-text">MagicLogger</div>
</div>
<div class="size-indicator"><span id="logoWidth">0</span> x <span id="logoHeight">0</span>px</div>
</div>
<div class="button-container">
<button id="exportPngBtn">Export as PNG</button>
<button id="exportGifBtn">Export as GIF</button>
</div>
<div id="loadingIndicator" class="loading-indicator">
Generating export... This may take a few moments.
</div>
</div>
<!-- Scripts -->
<script>
// DOM elements
const logoText = document.getElementById('logoText');
const fontFamily = document.getElementById('fontFamily');
const fontSize = document.getElementById('fontSize');
const letterSpacing = document.getElementById('letterSpacing');
const gradientPreset = document.getElementById('gradientPreset');
const customGradientControls = document.getElementById('customGradientControls');
const color1 = document.getElementById('color1');
const color2 = document.getElementById('color2');
const animationSpeed = document.getElementById('animationSpeed');
const textShadow = document.getElementById('textShadow');
const backgroundType = document.getElementById('backgroundType');
const backgroundColor = document.getElementById('backgroundColor');
const backgroundColorControl = document.getElementById('backgroundColorControl');
const textAnimation = document.getElementById('textAnimation');
const exportPngBtn = document.getElementById('exportPngBtn');
const exportGifBtn = document.getElementById('exportGifBtn');
const loadingIndicator = document.getElementById('loadingIndicator');
const logoElement = document.querySelector('.logo-text');
const previewContainer = document.getElementById('previewContainer');
const fontPreview = document.getElementById('fontPreview');
const logoWidth = document.getElementById('logoWidth');
const logoHeight = document.getElementById('logoHeight');
// Update logo size indicator
function updateSizeIndicator() {
logoWidth.textContent = logoElement.offsetWidth;
logoHeight.textContent = logoElement.offsetHeight;
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
// Update logo text from input
logoText.addEventListener('input', function() {
logoElement.textContent = this.value || 'Logo';
updateSizeIndicator();
});
// Update font family
fontFamily.addEventListener('change', function() {
logoElement.style.fontFamily = this.value;
fontPreview.style.fontFamily = this.value;
updateSizeIndicator();
});
// Font preview text
fontPreview.style.fontFamily = fontFamily.value;
// Update font size
fontSize.addEventListener('input', function() {
logoElement.style.fontSize = this.value + 'px';
updateSizeIndicator();
});
// Update letter spacing
letterSpacing.addEventListener('input', function() {
logoElement.style.letterSpacing = this.value + 'em';
});
// Update gradient preset
gradientPreset.addEventListener('change', function() {
if (this.value === 'custom') {
customGradientControls.style.display = 'block';
updateCustomGradient();
} else {
customGradientControls.style.display = 'none';
logoElement.style.backgroundImage = this.value;
}
});
// Update custom gradient
function updateCustomGradient() {
logoElement.style.backgroundImage = `linear-gradient(45deg, ${color1.value}, ${color2.value})`;
}
color1.addEventListener('input', updateCustomGradient);
color2.addEventListener('input', updateCustomGradient);
// Update animation speed
animationSpeed.addEventListener('input', function() {
const seconds = 11 - this.value; // Invert the scale (1-10) -> (10-1)
logoElement.style.animationDuration = seconds + 's';
});
// Update text shadow/glow
textShadow.addEventListener('change', function() {
// Remove all previous glow classes
logoElement.classList.remove(
'text-glow-none',
'text-glow-soft',
'text-glow-medium',
'text-glow-hard',
'text-glow-neon',
'text-glow-outline',
'text-glow-sharp'
);
// Add the selected glow class
logoElement.classList.add('text-glow-' + this.value);
});
// Update text animation
textAnimation.addEventListener('change', function() {
// Remove all previous animation classes
logoElement.classList.remove(
'anim-none',
'anim-blink',
'anim-pulse',
'anim-bounce',
'anim-shake',
'anim-typing'
);
// Add the selected animation class
logoElement.classList.add('anim-' + this.value);
});
// Update background
backgroundType.addEventListener('change', function() {
// Remove all previous background classes
previewContainer.classList.remove(
'bg-transparent',
'bg-transparent-gradient',
'bg-transparent-gradient-animated',
'bg-solid',
'bg-gradient',
'bg-gradient-animated',
'bg-grid',
'bg-darkgrid',
'bg-stars',
'bg-noise'
);
// Add the selected background class
previewContainer.classList.add('bg-' + this.value);
// Show/hide background color control
backgroundColorControl.style.display =
(this.value === 'solid') ? 'block' : 'none';
});
// Update background color
backgroundColor.addEventListener('input', function() {
previewContainer.style.backgroundColor = this.value;
});
// Set initial font size
logoElement.style.fontSize = fontSize.value + 'px';
// Set initial letter spacing
logoElement.style.letterSpacing = letterSpacing.value + 'em';
// Set initial text glow
logoElement.classList.add('text-glow-none');
// Set initial text animation
logoElement.classList.add('anim-none');
// Set initial background
previewContainer.classList.add('bg-transparent');
// Update size on window resize
window.addEventListener('resize', updateSizeIndicator);
// Initial size update
updateSizeIndicator();
// Load external libraries
loadExternalLibraries();
});
// Load required libraries
function loadExternalLibraries() {
// Load dom-to-image for PNG export
var domToImageScript = document.createElement('script');
domToImageScript.src = 'https://cdnjs.cloudflare.com/ajax/libs/dom-to-image/2.6.0/dom-to-image.min.js';
domToImageScript.onload = function() {
console.log('dom-to-image library loaded');
exportPngBtn.disabled = false;
};
domToImageScript.onerror = function() {
console.error('Failed to load dom-to-image library');
alert('Error loading PNG export library');
};
document.head.appendChild(domToImageScript);
// Load gif.js for GIF export
var gifScript = document.createElement('script');
gifScript.src = 'https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.js';
gifScript.onload = function() {
console.log('gif.js library loaded');
exportGifBtn.disabled = false;
};
gifScript.onerror = function() {
console.error('Failed to load gif.js library');
alert('Error loading GIF export library');
};
document.head.appendChild(gifScript);
}
// Export as PNG
exportPngBtn.addEventListener('click', function() {
// Show loading indicator
loadingIndicator.style.display = 'block';
// Temporarily pause animation
const originalAnimationState = logoElement.style.animationPlayState;
logoElement.style.animationPlayState = 'paused';
// Determine what to capture based on background type
const captureElement = (backgroundType.value !== 'transparent') ?
previewContainer : logoElement;
// Use dom-to-image for PNG export
domtoimage.toPng(captureElement, {
bgcolor: null,
height: captureElement.offsetHeight,
width: captureElement.offsetWidth,
style: {
margin: '0',
padding: backgroundType.value !== 'transparent' ? '40px' : '20px'
}
})
.then(function(dataUrl) {
// Restore animation
logoElement.style.animationPlayState = originalAnimationState;
// Create download link
const link = document.createElement('a');
link.download = logoText.value.replace(/\s+/g, '-').toLowerCase() + '-logo.png';
link.href = dataUrl;
link.click();
// Hide loading indicator
loadingIndicator.style.display = 'none';
})
.catch(function(error) {
console.error('Error exporting PNG:', error);
logoElement.style.animationPlayState = originalAnimationState;
loadingIndicator.style.display = 'none';
alert('Failed to export PNG. Please try again.');
});
});
// Export as GIF
exportGifBtn.addEventListener('click', function() {
if (typeof GIF === 'undefined') {
alert('GIF library not loaded yet. Please try again in a moment.');
return;
}
// Show loading indicator
loadingIndicator.style.display = 'block';
// Store current animation state
const originalAnimationState = logoElement.style.animationPlayState;
// Create a GIF
const gif = new GIF({
workers: 2,
quality: 10,
width: logoElement.offsetWidth,
height: logoElement.offsetHeight,
transparent: true,
workerScript: 'https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.worker.js'
});
// Frame count and duration
const frames = 20;
const duration = 100; // ms per frame
// Capture the entire preview container for frames if background is selected
const captureElement = (backgroundType.value !== 'transparent') ?
document.getElementById('previewContainer') : logoElement;
// Capture frames of the animation
captureFrames(0, frames, duration);
function captureFrames(currentFrame, totalFrames, frameDuration) {
if (currentFrame >= totalFrames) {
// All frames captured, render the GIF
gif.render();
return;
}
// Calculate animation progress percentage
const progress = (currentFrame / totalFrames) * 100;
logoElement.style.backgroundPosition = `${progress}% 50%`;
// Capture current frame
domtoimage.toPixelData(captureElement)
.then(function(pixels) {
// Add frame to GIF
gif.addFrame(pixels, {delay: frameDuration, copy: true});
// Capture next frame
captureFrames(currentFrame + 1, totalFrames, frameDuration);
})
.catch(function(error) {
console.error('Error capturing GIF frame:', error);
logoElement.style.animationPlayState = originalAnimationState;
loadingIndicator.style.display = 'none';
alert('Failed to create GIF. Please try again.');
});
}
// GIF rendering complete
gif.on('finished', function(blob) {
// Restore animation
logoElement.style.animationPlayState = originalAnimationState;
// Create download link
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = logoText.value.replace(/\s+/g, '-').toLowerCase() + '-logo.gif';
link.href = url;
link.click();
// Clean up
setTimeout(() => URL.revokeObjectURL(url), 100);
// Hide loading indicator
loadingIndicator.style.display = 'none';
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment