620 lines
20 KiB
HTML
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>
|