Make convincing email and web banners in 30 seconds flat!
Banner Link Converter
Source Link & Banner Converter
1. Link Information
URL and Tooltip are optional
Template: Hello NAME, This is a friendly reminder of your upcoming dental appointment with Dr. Burch this Monday, March 9, at 10:00 am. See you then!
2. Banner Graphics & Styling
750px
15px
0px
5px
16pt
1
3. Template Library (Save & Reload)
Saves the entire current page state (text, URL, tooltip, slider values, colors, font, selected animations, and justification) to your browser (localStorage). Capacity: 100 templates.
Converted Banner Preview:
100%
<html>…</html> Output (Copy for Web)
Animation is included in this export (for websites). Most email clients will not play CSS animations.
SMS Output (Text Only)
Note: SMS/MMS apps do not accept HTML/CSS as “graphics” when pasting.
You can use the Copy as Image (for SMS/MMS) button above to copy a real image of your banner to your clipboard and paste it directly into your messaging app. Alternatively, you can copy the plain text + URL provided here.
`;
document.getElementById('exportText').value = fullDoc;
document.getElementById('export-area').style.display = 'block';
document.getElementById('sms-area').style.display = 'none';
saveLastSession();
}
function generateSmsExport() {
const outputDiv = document.getElementById('output');
outputDiv.innerHTML = buildPreviewHTML();
document.getElementById('smsText').value = buildSmsText();
document.getElementById('sms-area').style.display = 'block';
document.getElementById('export-area').style.display = 'none';
saveLastSession();
}
function copyToClipboard() {
const outputDiv = document.getElementById('output');
if (!outputDiv.innerHTML) {
alert('Please generate a preview first!');
return;
}
const range = document.createRange();
range.selectNodeContents(outputDiv);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
try {
document.execCommand('copy');
alert('Banner copied to clipboard! You can now paste it into your Gmail email.');
} catch (err) {
alert('Sorry, I do not know why the copy failed. Please try selecting the banner and copying it manually.');
}
window.getSelection().removeAllRanges();
}
function copyExportHtml() {
const exportText = document.getElementById('exportText');
if (!exportText.value.trim()) {
alert('Please generate the … output first!');
return;
}
exportText.focus();
exportText.select();
try {
document.execCommand('copy');
alert('… copied to clipboard! You can now paste it into a .html file or your web page.');
} catch (err) {
alert('Sorry, I do not know why the copy failed. Please copy manually from the text area.');
}
window.getSelection().removeAllRanges();
}
function copySmsText() {
const smsText = document.getElementById('smsText');
if (!smsText.value.trim()) {
alert('Please click "Generate for SMS (Text Message)" first!');
return;
}
smsText.focus();
smsText.select();
try {
document.execCommand('copy');
alert('SMS text copied to clipboard! Paste it into your text message.');
} catch (err) {
alert('Sorry, I do not know why the copy failed. Please copy manually from the text area.');
}
window.getSelection().removeAllRanges();
}
async function copyAsImage() {
const outputDiv = document.getElementById('output');
const bannerElement = outputDiv.querySelector('span') || outputDiv.querySelector('a');
if (!bannerElement) {
alert('Please generate a preview first!');
return;
}
const scalePercent = document.getElementById('smsScaleSlider').value;
const scaleValue = scalePercent / 100;
try {
const canvas = await html2canvas(bannerElement, {
backgroundColor: null,
scale: scaleValue,
useCORS: true
});
canvas.toBlob(async function(blob) {
try {
const item = new ClipboardItem({ 'image/png': blob });
await navigator.clipboard.write([item]);
alert('Image copied to clipboard! You can now paste it into your SMS/MMS messaging program.');
} catch (err) {
alert('Sorry, I do not know why the copy failed. Your browser might not support copying images directly to the clipboard.');
}
});
} catch (err) {
alert('Sorry, I do not know why the image generation failed. If your banner uses a remote GIF/image, it may block CORS.');
}
}
function nowIso() {
try { return new Date().toISOString(); } catch (e) { return ''; }
}
function showLibraryStatus(msg, type) {
const el = document.getElementById('libraryStatus');
el.style.display = 'block';
el.classList.remove('ok', 'error');
if (type === 'error') el.classList.add('error');
else el.classList.add('ok');
el.textContent = msg;
}
function readLibrary() {
const raw = localStorage.getItem(TEMPLATE_LIBRARY_KEY);
if (!raw) return [];
try {
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) return [];
return parsed;
} catch (e) {
return [];
}
}
function writeLibrary(list) {
localStorage.setItem(TEMPLATE_LIBRARY_KEY, JSON.stringify(list));
}
function getCurrentStateForTemplate() {
return {
linkText: document.getElementById('linkText').value,
linkUrl: document.getElementById('linkUrl').value,
linkTooltip: document.getElementById('linkTooltip').value,
bannerWidth: document.getElementById('bannerWidth').value,
bannerPadding: document.getElementById('bannerPadding').value,
bgColor: document.getElementById('bgColor').value,
textColor: document.getElementById('textColor').value,
borderWidth: document.getElementById('borderWidth').value,
borderColor: document.getElementById('borderColor').value,
borderRadius: document.getElementById('borderRadius').value,
fontSizeSlider: document.getElementById('fontSizeSlider').value,
fontSelector: document.getElementById('fontSelector').value,
selectedSwirlSpeed: selectedSwirlSpeed,
swirlRotations: document.getElementById('swirlRotations').value,
selectedAlignment: selectedAlignment,
selectedAnims: selectedAnims
};
}
function applyStateFromTemplate(state) {
if (!state || typeof state !== 'object') return;
if (state.linkText !== undefined) {
document.getElementById('linkText').value = state.linkText;
isTextTemporarilyCleared = false;
}
if (state.linkUrl !== undefined) document.getElementById('linkUrl').value = state.linkUrl;
if (state.linkTooltip !== undefined) document.getElementById('linkTooltip').value = state.linkTooltip;
if (state.bannerWidth !== undefined) document.getElementById('bannerWidth').value = state.bannerWidth;
if (state.bannerPadding !== undefined) document.getElementById('bannerPadding').value = state.bannerPadding;
if (state.bgColor !== undefined) document.getElementById('bgColor').value = state.bgColor;
if (state.textColor !== undefined) document.getElementById('textColor').value = state.textColor;
if (state.borderWidth !== undefined) document.getElementById('borderWidth').value = state.borderWidth;
if (state.borderColor !== undefined) document.getElementById('borderColor').value = state.borderColor;
if (state.borderRadius !== undefined) document.getElementById('borderRadius').value = state.borderRadius;
if (state.fontSizeSlider !== undefined) document.getElementById('fontSizeSlider').value = state.fontSizeSlider;
if (state.fontSelector !== undefined) document.getElementById('fontSelector').value = state.fontSelector;
if (state.swirlRotations !== undefined) document.getElementById('swirlRotations').value = state.swirlRotations;
if (state.selectedAnims !== undefined) {
selectedAnims = state.selectedAnims;
} else {
if (state.selectedSwirlSpeed && state.selectedSwirlSpeed !== 'none') {
selectedAnims = ['swirl'];
} else {
selectedAnims = [];
}
}
syncAnimCheckboxes();
updateValue('bannerWidth', 'widthDisplay');
updateValue('bannerPadding', 'paddingDisplay');
updateValue('borderWidth', 'borderWidthDisplay');
updateValue('borderRadius', 'borderRadiusDisplay');
updateValue('fontSizeSlider', 'fontSizeDisplay');
updateValue('swirlRotations', 'rotationsDisplay');
setAnimSpeed(state.selectedSwirlSpeed || 'none');
setAlignment(state.selectedAlignment || 'center');
updateSwirlStyle();
updateTestButtonVisibility();
autoResizeTextarea(document.getElementById('linkText'));
}
function templateLabel(tpl, idx) {
const name = (tpl && tpl.name ? String(tpl.name) : '').trim();
const created = tpl && tpl.createdAt ? String(tpl.createdAt) : '';
const shortCreated = created ? created.replace('T', ' ').replace(/\.\d+Z$/, 'Z') : '';
const base = name || `Template ${idx + 1}`;
return shortCreated ? `${base} — ${shortCreated}` : base;
}
function refreshTemplateDropdown(selectIdToKeep) {
const sel = document.getElementById('templateSelect');
const lib = readLibrary();
const prev = selectIdToKeep || sel.value || '';
sel.innerHTML = '';
const placeholder = document.createElement('option');
placeholder.value = '';
placeholder.textContent = lib.length ? `Select a template (${lib.length}/${TEMPLATE_CAPACITY})...` : `No templates saved yet (0/${TEMPLATE_CAPACITY})`;
sel.appendChild(placeholder);
lib.forEach((tpl, index) => {
const opt = document.createElement('option');
opt.value = tpl.id;
opt.textContent = templateLabel(tpl, index);
sel.appendChild(opt);
});
if (prev) sel.value = prev;
}
function saveTemplateToLibrary() {
const lib = readLibrary();
if (lib.length >= TEMPLATE_CAPACITY) {
showLibraryStatus(`Library is full (${TEMPLATE_CAPACITY} templates). Delete one to save a new template.`, 'error');
return;
}
const rawName = (document.getElementById('templateName').value || '').trim();
const name = rawName || `Template ${lib.length + 1}`;
const tpl = {
id: `tpl_${Date.now()}_${Math.random().toString(16).slice(2)}`,
name: name,
createdAt: nowIso(),
state: getCurrentStateForTemplate()
};
lib.unshift(tpl);
writeLibrary(lib);
refreshTemplateDropdown(tpl.id);
showLibraryStatus(`Saved "${name}" to Library. (${lib.length}/${TEMPLATE_CAPACITY})`, 'ok');
document.getElementById('templateName').value = '';
}
function loadSelectedTemplate() {
const sel = document.getElementById('templateSelect');
const id = sel.value;
if (!id) {
showLibraryStatus('Select a template to load.', 'error');
return;
}
const lib = readLibrary();
const tpl = lib.find(t => t.id === id);
if (!tpl) {
showLibraryStatus('Template not found (it may have been removed).', 'error');
refreshTemplateDropdown('');
return;
}
applyStateFromTemplate(tpl.state);
generateBanner();
saveLastSession();
showLibraryStatus(`Loaded "${tpl.name}".`, 'ok');
}
function renameSelectedTemplate() {
const sel = document.getElementById('templateSelect');
const id = sel.value;
if (!id) {
showLibraryStatus('Select a template to rename.', 'error');
return;
}
const lib = readLibrary();
const tpl = lib.find(t => t.id === id);
if (!tpl) {
showLibraryStatus('Template not found (it may have been removed).', 'error');
refreshTemplateDropdown('');
return;
}
const currentName = (tpl.name || '').trim() || 'Template';
const newNameRaw = prompt('Enter a new name for this template:', currentName);
if (newNameRaw === null) return;
const newName = String(newNameRaw).trim();
if (!newName) {
showLibraryStatus('Rename cancelled. Template name cannot be blank.', 'error');
return;
}
tpl.name = newName;
writeLibrary(lib);
refreshTemplateDropdown(id);
showLibraryStatus(`Renamed template to "${newName}".`, 'ok');
}
function deleteSelectedTemplate() {
const sel = document.getElementById('templateSelect');
const id = sel.value;
if (!id) {
showLibraryStatus('Select a template to delete.', 'error');
return;
}
const lib = readLibrary();
const idx = lib.findIndex(t => t.id === id);
if (idx < 0) {
showLibraryStatus('Template not found (it may have been removed).', 'error');
refreshTemplateDropdown('');
return;
}
const name = lib[idx].name || 'Template';
if (!confirm(`Delete "${name}"? This cannot be undone.`)) return;
lib.splice(idx, 1);
writeLibrary(lib);
refreshTemplateDropdown('');
showLibraryStatus(`Deleted "${name}". (${lib.length}/${TEMPLATE_CAPACITY})`, 'ok');
}
function clearLibrary() {
const lib = readLibrary();
if (!lib.length) {
showLibraryStatus('Library is already empty.', 'error');
return;
}
if (!confirm(`Clear ALL templates (${lib.length})? This cannot be undone.`)) return;
writeLibrary([]);
refreshTemplateDropdown('');
showLibraryStatus('Library cleared.', 'ok');
}
function exportLibraryJson() {
const lib = readLibrary();
const payload = {
exportedAt: nowIso(),
capacity: TEMPLATE_CAPACITY,
key: TEMPLATE_LIBRARY_KEY,
templates: lib
};
const txt = JSON.stringify(payload, null, 2);
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(txt).then(() => {
showLibraryStatus('Library exported to clipboard as JSON. Paste it somewhere safe.', 'ok');
}).catch(() => {
const out = prompt('Copy your Library JSON:', txt);
if (out !== null) showLibraryStatus('Library JSON shown for manual copy.', 'ok');
});
} else {
const out = prompt('Copy your Library JSON:', txt);
if (out !== null) showLibraryStatus('Library JSON shown for manual copy.', 'ok');
}
}
function importLibraryJson() {
const raw = prompt('Paste Library JSON to import.\n\nTip: This will merge into your current library (up to 100).');
if (raw === null) return;
let parsed;
try {
parsed = JSON.parse(raw);
} catch (e) {
showLibraryStatus('Invalid JSON. Import failed.', 'error');
return;
}
const incoming = parsed && parsed.templates ? parsed.templates : parsed;
if (!Array.isArray(incoming)) {
showLibraryStatus('Import failed. JSON must be an array of templates or an object with a "templates" array.', 'error');
return;
}
const lib = readLibrary();
const existingIds = new Set(lib.map(t => t.id));
const sanitized = [];
for (const t of incoming) {
if (!t || typeof t !== 'object') continue;
const state = t.state && typeof t.state === 'object' ? t.state : null;
if (!state) continue;
let id = (t.id ? String(t.id) : '').trim();
if (!id || existingIds.has(id)) id = `tpl_${Date.now()}_${Math.random().toString(16).slice(2)}`;
const name = (t.name ? String(t.name) : '').trim() || 'Imported Template';
sanitized.push({
id,
name,
createdAt: t.createdAt ? String(t.createdAt) : nowIso(),
state
});
}
if (!sanitized.length) {
showLibraryStatus('No valid templates found to import.', 'error');
return;
}
const spaceLeft = TEMPLATE_CAPACITY - lib.length;
if (spaceLeft <= 0) {
showLibraryStatus(`Library is full (${TEMPLATE_CAPACITY}). Delete templates before importing.`, 'error');
return;
}
const toAdd = sanitized.slice(0, spaceLeft);
const merged = toAdd.concat(lib);
writeLibrary(merged);
refreshTemplateDropdown(toAdd[0].id);
const skipped = sanitized.length - toAdd.length;
showLibraryStatus(`Imported ${toAdd.length} template(s).${skipped ? ` Skipped ${skipped} due to capacity.` : ''} (${merged.length}/${TEMPLATE_CAPACITY})`, 'ok');
}
function escapeAttr(str) {
return String(str)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(//g, '>');
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(//g, '>');
}
function getLastSessionState() {
let textToSave = document.getElementById('linkText').value;
if (isTextTemporarilyCleared && textToSave === '') {
try {
const raw = localStorage.getItem(LAST_SESSION_KEY);
if (raw) {
const parsed = JSON.parse(raw);
if (parsed && parsed.linkText !== undefined) {
textToSave = parsed.linkText;
}
}
} catch(e) {}
}
return {
linkText: textToSave,
linkUrl: document.getElementById('linkUrl').value,
linkTooltip: document.getElementById('linkTooltip').value,
bannerWidth: document.getElementById('bannerWidth').value,
bannerPadding: document.getElementById('bannerPadding').value,
bgColor: document.getElementById('bgColor').value,
textColor: document.getElementById('textColor').value,
borderWidth: document.getElementById('borderWidth').value,
borderColor: document.getElementById('borderColor').value,
borderRadius: document.getElementById('borderRadius').value,
fontSizeSlider: document.getElementById('fontSizeSlider').value,
fontSelector: document.getElementById('fontSelector').value,
smsScaleSlider: document.getElementById('smsScaleSlider').value,
selectedSwirlSpeed: selectedSwirlSpeed,
swirlRotations: document.getElementById('swirlRotations').value,
selectedAlignment: selectedAlignment,
selectedAnims: selectedAnims
};
}
function saveLastSession() {
if (isRestoringSession) return;
try {
localStorage.setItem(LAST_SESSION_KEY, JSON.stringify(getLastSessionState()));
} catch (e) {
}
}
function loadLastSession() {
const raw = localStorage.getItem(LAST_SESSION_KEY);
if (!raw) return false;
try {
const state = JSON.parse(raw);
if (!state || typeof state !== 'object') return false;
isRestoringSession = true;
applyStateFromTemplate(state);
if (state.smsScaleSlider !== undefined) {
document.getElementById('smsScaleSlider').value = state.smsScaleSlider;
updateValue('smsScaleSlider', 'smsScaleDisplay');
}
updateTestButtonVisibility();
updateSwirlStyle();
generateBanner();
isRestoringSession = false;
saveLastSession();
return true;
} catch (e) {
isRestoringSession = false;
return false;
}
}
function attachAutoSaveListeners() {
const ids = [
'linkText',
'linkUrl',
'linkTooltip',
'bannerWidth',
'bannerPadding',
'bgColor',
'textColor',
'borderWidth',
'borderColor',
'borderRadius',
'fontSizeSlider',
'fontSelector',
'swirlRotations',
'smsScaleSlider'
];
ids.forEach(id => {
const el = document.getElementById(id);
if (!el) return;
el.addEventListener('input', function() {
if (id === 'linkText') {
isTextTemporarilyCleared = false;
autoResizeTextarea(el);
}
if (id === 'linkUrl') updateTestButtonVisibility();
if (id === 'swirlRotations') updateSwirlStyle();
if (id === 'bannerWidth') updateValue('bannerWidth', 'widthDisplay');
if (id === 'bannerPadding') updateValue('bannerPadding', 'paddingDisplay');
if (id === 'borderWidth') updateValue('borderWidth', 'borderWidthDisplay');
if (id === 'borderRadius') updateValue('borderRadius', 'borderRadiusDisplay');
if (id === 'fontSizeSlider') updateValue('fontSizeSlider', 'fontSizeDisplay');
if (id === 'swirlRotations') updateValue('swirlRotations', 'rotationsDisplay');
if (id === 'smsScaleSlider') updateValue('smsScaleSlider', 'smsScaleDisplay');
if (id === 'swirlRotations') generateBanner();
saveLastSession();
});
el.addEventListener('change', function() {
if (id === 'linkUrl') updateTestButtonVisibility();
saveLastSession();
});
});
}
document.addEventListener('DOMContentLoaded', function () {
const urlInput = document.getElementById('linkUrl');
urlInput.addEventListener('input', updateTestButtonVisibility);
urlInput.addEventListener('change', updateTestButtonVisibility);
updateTestButtonVisibility();
updateValue('bannerWidth', 'widthDisplay');
updateValue('bannerPadding', 'paddingDisplay');
updateValue('borderWidth', 'borderWidthDisplay');
updateValue('borderRadius', 'borderRadiusDisplay');
updateValue('fontSizeSlider', 'fontSizeDisplay');
updateValue('smsScaleSlider', 'smsScaleDisplay');
updateValue('swirlRotations', 'rotationsDisplay');
updateSwirlStyle();
refreshTemplateDropdown('');
setAnimSpeed(selectedSwirlSpeed);
setAlignment(selectedAlignment);
attachAutoSaveListeners();
const restored = loadLastSession();
if (!restored) {
autoResizeTextarea(document.getElementById('linkText'));
generateBanner();
saveLastSession();
}
window.addEventListener('beforeunload', saveLastSession);
});