Files
logo-txt/templates/index.html
2026-01-11 11:54:23 -05:00

620 lines
20 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Logo Text Adder</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.header p {
opacity: 0.9;
font-size: 1.1em;
}
.content {
padding: 40px;
}
.form-group {
margin-bottom: 25px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
font-size: 0.95em;
}
input[type="text"],
input[type="number"],
select {
width: 100%;
padding: 12px 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
}
input[type="text"]:focus,
input[type="number"]:focus,
select:focus {
outline: none;
border-color: #667eea;
}
.file-input-wrapper {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}
.file-input-wrapper input[type="file"] {
position: absolute;
left: -9999px;
}
.file-input-label {
display: block;
padding: 15px;
background: #f8f9fa;
border: 2px dashed #667eea;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.file-input-label:hover {
background: #e8eaf6;
border-color: #764ba2;
}
.file-input-label.has-file {
background: #e8f5e9;
border-color: #4caf50;
}
.color-input-group {
display: flex;
gap: 10px;
align-items: center;
}
.color-input-group input[type="text"] {
flex: 1;
}
input[type="color"] {
width: 60px;
height: 45px;
border: 2px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
}
.position-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.position-option {
position: relative;
}
.position-option input[type="radio"] {
position: absolute;
opacity: 0;
}
.position-option label {
display: block;
padding: 15px;
background: #f8f9fa;
border: 2px solid #e0e0e0;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
margin: 0;
}
.position-option input[type="radio"]:checked + label {
background: #667eea;
color: white;
border-color: #667eea;
}
.position-option label:hover {
border-color: #667eea;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px 30px;
border: none;
border-radius: 8px;
font-size: 18px;
font-weight: 600;
cursor: pointer;
width: 100%;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
}
.btn:active {
transform: translateY(0);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.preview-section {
margin-top: 30px;
padding-top: 30px;
border-top: 2px solid #e0e0e0;
}
.preview-section h2 {
margin-bottom: 20px;
color: #333;
}
.preview-image {
max-width: 100%;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
background-image:
linear-gradient(45deg, #ccc 25%, transparent 25%),
linear-gradient(-45deg, #ccc 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #ccc 75%),
linear-gradient(-45deg, transparent 75%, #ccc 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
background-color: white;
}
.loading {
text-align: center;
padding: 20px;
color: #667eea;
font-weight: 600;
}
.error {
background: #ffebee;
color: #c62828;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
border-left: 4px solid #c62828;
}
.success {
background: #e8f5e9;
color: #2e7d32;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
border-left: 4px solid #2e7d32;
}
.download-btn {
display: inline-block;
margin-top: 15px;
padding: 12px 25px;
background: #4caf50;
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
transition: all 0.3s;
}
.download-btn:hover {
background: #45a049;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4);
}
.advanced-options {
margin-top: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
}
.advanced-options h3 {
margin-bottom: 15px;
color: #555;
font-size: 1.1em;
}
.two-column {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.input-tabs {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.tab-btn {
flex: 1;
padding: 10px 20px;
background: #f8f9fa;
border: 2px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
color: #666;
transition: all 0.3s;
}
.tab-btn:hover {
border-color: #667eea;
}
.tab-btn.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
@media (max-width: 600px) {
.two-column, .position-grid {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 1.8em;
}
.content {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📺 Logo Text Adder</h1>
<p>Add custom text to your TV station and network logos</p>
</div>
<div class="content">
<div id="message"></div>
<form id="logoForm">
<div class="form-group">
<label>Logo Image</label>
<div class="input-tabs">
<button type="button" class="tab-btn active" data-tab="file">📁 Upload File</button>
<button type="button" class="tab-btn" data-tab="url">🔗 Image URL</button>
</div>
<div id="fileTab" class="tab-content active">
<div class="file-input-wrapper">
<input type="file" id="imageFile" accept="image/*">
<label for="imageFile" class="file-input-label" id="fileLabel">
<span>📁 Choose an image file (PNG, JPG, GIF, WEBP)</span>
</label>
</div>
</div>
<div id="urlTab" class="tab-content">
<input type="text" id="imageUrl" placeholder="https://example.com/logo.png">
</div>
</div>
<div class="form-group">
<label for="text">Text to Add</label>
<input type="text" id="text" placeholder="Enter your text here..." required>
</div>
<div class="form-group">
<label>Text Position</label>
<div class="position-grid">
<div class="position-option">
<input type="radio" id="above" name="position" value="above">
<label for="above">⬆️ Above</label>
</div>
<div class="position-option">
<input type="radio" id="below" name="position" value="below" checked>
<label for="below">⬇️ Below</label>
</div>
<div class="position-option">
<input type="radio" id="left" name="position" value="left">
<label for="left">⬅️ Left</label>
</div>
<div class="position-option">
<input type="radio" id="right" name="position" value="right">
<label for="right">➡️ Right</label>
</div>
</div>
</div>
<div class="advanced-options">
<h3>⚙️ Advanced Options</h3>
<div class="two-column">
<div class="form-group">
<label for="fontSize">Font Size</label>
<input type="text" id="fontSize" value="auto" placeholder="auto or number">
<small style="color: #666; font-size: 0.85em;">Leave as "auto" for automatic sizing</small>
</div>
<div class="form-group">
<label for="padding">Padding</label>
<input type="text" id="padding" value="auto" placeholder="auto or number">
<small style="color: #666; font-size: 0.85em;">Leave as "auto" for automatic padding</small>
</div>
</div>
<div class="form-group">
<label for="textColor">Text Color</label>
<div class="color-input-group">
<input type="text" id="textColor" value="white" placeholder="white or #ffffff">
<input type="color" id="textColorPicker" value="#ffffff">
</div>
</div>
<div class="form-group">
<label for="bgColor">Background Color</label>
<div class="color-input-group">
<input type="text" id="bgColor" value="transparent" placeholder="transparent or #1a1a1a">
<input type="color" id="bgColorPicker" value="#1a1a1a">
</div>
<small style="color: #666; font-size: 0.85em;">Use "transparent" to preserve logo transparency</small>
</div>
</div>
<button type="submit" class="btn" id="submitBtn">
✨ Generate Logo
</button>
</form>
<div id="preview" class="preview-section" style="display: none;">
<h2>Preview</h2>
<img id="previewImage" class="preview-image" alt="Processed logo">
<br>
<a href="#" id="downloadBtn" class="download-btn" download>⬇️ Download Image</a>
</div>
</div>
</div>
<script>
const form = document.getElementById('logoForm');
const imageFile = document.getElementById('imageFile');
const imageUrl = document.getElementById('imageUrl');
const fileLabel = document.getElementById('fileLabel');
const submitBtn = document.getElementById('submitBtn');
const messageDiv = document.getElementById('message');
const preview = document.getElementById('preview');
const previewImage = document.getElementById('previewImage');
const downloadBtn = document.getElementById('downloadBtn');
const tabBtns = document.querySelectorAll('.tab-btn');
const fileTab = document.getElementById('fileTab');
const urlTab = document.getElementById('urlTab');
let activeInputMode = 'file';
// Tab switching
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
const tab = btn.dataset.tab;
activeInputMode = tab;
// Update button states
tabBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Update tab content
fileTab.classList.remove('active');
urlTab.classList.remove('active');
if (tab === 'file') {
fileTab.classList.add('active');
} else {
urlTab.classList.add('active');
}
});
});
// Color picker sync
const textColorInput = document.getElementById('textColor');
const textColorPicker = document.getElementById('textColorPicker');
const bgColorInput = document.getElementById('bgColor');
const bgColorPicker = document.getElementById('bgColorPicker');
textColorPicker.addEventListener('input', (e) => {
textColorInput.value = e.target.value;
});
textColorInput.addEventListener('input', (e) => {
if (e.target.value.startsWith('#')) {
textColorPicker.value = e.target.value;
}
});
bgColorPicker.addEventListener('input', (e) => {
bgColorInput.value = e.target.value;
});
bgColorInput.addEventListener('input', (e) => {
if (e.target.value.startsWith('#') && e.target.value.length === 7) {
bgColorPicker.value = e.target.value;
}
});
// File input handling
imageFile.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
fileLabel.classList.add('has-file');
fileLabel.innerHTML = `<span>✅ ${file.name}</span>`;
} else {
fileLabel.classList.remove('has-file');
fileLabel.innerHTML = '<span>📁 Choose an image file (PNG, JPG, GIF, WEBP)</span>';
}
});
function showMessage(message, type) {
messageDiv.innerHTML = `<div class="${type}">${message}</div>`;
messageDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
function clearMessage() {
messageDiv.innerHTML = '';
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
clearMessage();
const text = document.getElementById('text').value;
if (!text.trim()) {
showMessage('Please enter some text', 'error');
return;
}
const position = document.querySelector('input[name="position"]:checked').value;
const fontSize = document.getElementById('fontSize').value;
const padding = document.getElementById('padding').value;
const textColor = textColorInput.value;
const bgColor = bgColorInput.value;
submitBtn.disabled = true;
submitBtn.textContent = '⏳ Processing...';
preview.style.display = 'none';
try {
let response;
if (activeInputMode === 'url') {
// Use URL-based API
const url = imageUrl.value.trim();
if (!url) {
showMessage('Please enter an image URL', 'error');
submitBtn.disabled = false;
submitBtn.textContent = '✨ Generate Logo';
return;
}
const params = new URLSearchParams({
url: url,
text: text,
position: position,
font_size: fontSize,
padding: padding,
text_color: textColor,
bg_color: bgColor
});
response = await fetch(`/api/image?${params}`);
} else {
// Use file upload API
const file = imageFile.files[0];
if (!file) {
showMessage('Please select an image file', 'error');
submitBtn.disabled = false;
submitBtn.textContent = '✨ Generate Logo';
return;
}
const formData = new FormData();
formData.append('image', file);
formData.append('text', text);
formData.append('position', position);
formData.append('font_size', fontSize);
formData.append('padding', padding);
formData.append('text_color', textColor);
formData.append('bg_color', bgColor);
response = await fetch('/api/process', {
method: 'POST',
body: formData
});
}
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to process image');
}
const blob = await response.blob();
const url = URL.createObjectURL(blob);
previewImage.src = url;
downloadBtn.href = url;
downloadBtn.download = `logo_${text.replace(/\s+/g, '_')}.png`;
preview.style.display = 'block';
showMessage('✅ Logo generated successfully!', 'success');
preview.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
} catch (error) {
showMessage(`❌ Error: ${error.message}`, 'error');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = '✨ Generate Logo';
}
});
</script>
</body>
</html>