LOGO DEV - INTERACTIVE APP SOURCE CODE
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="origin">
<title>Logo Search</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #000;
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 40px;
padding: 20px 0;
border-bottom: 1px solid #333;
}
h1 {
font-size: 2.5rem;
margin-bottom: 10px;
background: linear-gradient(90deg, #fff, #aaa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
color: #aaa;
font-size: 1rem;
text-align:left;
}
.search-container {
background: #111;
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
border: 1px solid #222;
}
.search-box {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
input[type="text"] {
flex: 1;
padding: 15px 20px;
border: 2px solid #333;
border-radius: 8px;
background: #000;
color: #fff;
font-size: 1rem;
transition: border-color 0.3s ease;
}
input[type="text"]:focus {
outline: none;
border-color: #666;
}
button {
padding: 15px 30px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s ease, opacity 0.2s ease;
}
button:hover {
transform: translateY(-2px);
opacity: 0.9;
}
button:disabled {
background: #333;
cursor: not-allowed;
transform: none;
opacity: 0.5;
}
.examples {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
.example-tag {
padding: 8px 15px;
background: #222;
border-radius: 20px;
font-size: 0.9rem;
color: #ccc;
cursor: pointer;
transition: background 0.2s ease;
}
.example-tag:hover {
background: #333;
}
.loading {
text-align: center;
padding: 40px;
color: #aaa;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #333;
border-top: 3px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.results-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 25px;
margin-top: 20px;
}
.logo-card {
background: #111;
border-radius: 12px;
padding: 20px;
text-align: center;
border: 1px solid #222;
transition: transform 0.3s ease, border-color 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
}
.logo-card:hover {
transform: translateY(-5px);
border-color: #444;
}
.logo-container {
width: 140px;
height: 140px;
margin: 0 auto 15px;
display: flex;
align-items: center;
justify-content: center;
background: #000;
border-radius: 8px;
border: 1px solid #333;
overflow: hidden;
flex: 0 0 auto;
}
.logo-img {
width: 130px;
height: 130px;
object-fit: contain;
display: block;
}
.company-name {
font-size: 1.1rem;
margin-bottom: 5px;
color: #fff;
line-height: 1.3;
text-align: center;
width: 100%;
}
.company-domain {
color: #667eea;
font-size: 0.9rem;
margin-bottom: 15px;
word-break: break-all;
text-align: center;
width: 100%;
}
.button-group {
display: flex;
gap: 5px;
width: 100%;
margin-top: auto;
}
.copy-button {
padding: 8px 15px;
background: #222;
color: #ccc;
border: none;
border-radius: 6px;
font-size: 0.85rem;
cursor: pointer;
transition: background 0.2s ease;
flex: 1;
}
.copy-button:hover {
background: #333;
}
.error-message {
background: rgba(220, 38, 38, 0.1);
border: 1px solid rgba(220, 38, 38, 0.3);
color: #f87171;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
text-align: center;
}
.no-results {
text-align: center;
color: #aaa;
padding: 40px;
grid-column: 1 / -1;
}
.success-message {
background: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.3);
color: #4ade80;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
text-align: center;
}
.image-loading {
background: linear-gradient(90deg, #222 25%, #333 50%, #222 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
@media (max-width: 768px) {
.results-container {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.search-box {
flex-direction: column;
}
button {
width: 100%;
}
h1 {
font-size: 2rem;
}
.search-container {
padding: 20px;
}
}
@media (max-width: 480px) {
body {
padding: 10px;
}
.results-container {
grid-template-columns: 1fr;
}
.logo-card {
padding: 20px;
}
.logo-container {
width: 130px;
height: 130px;
}
.logo-img {
width: 120px;
height: 120px;
}
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 8px;
z-index: 1000;
animation: slideIn 0.3s ease, fadeOut 0.3s ease 2s forwards;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.notification-success {
background: #4CAF50;
color: white;
}
.notification-error {
background: #f44336;
color: white;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes fadeOut {
to {
opacity: 0;
transform: translateY(-20px);
}
}
.logo-placeholder {
color: #666;
font-size: 0.8rem;
}
</style>
</head>
<body>
<div class="container">
<header>
<p class="subtitle">Find and download professional logos instantly</p>
</header>
<div class="search-container">
<div class="search-box">
<input type="text" id="searchInput" placeholder="Search for a company (e.g., Google, Nike, Apple)...">
<button id="searchButton">Search</button>
</div>
<div class="examples">
<div class="example-tag" data-company="Google">Google</div>
<div class="example-tag" data-company="Apple">Apple</div>
<div class="example-tag" data-company="Microsoft">Microsoft</div>
<div class="example-tag" data-company="Amazon">Amazon</div>
<div class="example-tag" data-company="Nike">Nike</div>
<div class="example-tag" data-company="Stripe">Stripe</div>
<div class="example-tag" data-company="Spotify">Spotify</div>
</div>
<div class="debug-info" style="background: #1a1a1a; border-radius: 8px; padding: 15px; margin-top: 20px; font-family: monospace; font-size: 0.9rem; color: #aaa;">
<strong>Status:</strong> <span id="status">Ready</span><br>
<strong>Note ID:</strong> 26b7853a-5395-4317-afd3-5aec15281fb1
</div>
</div>
<div id="resultsArea"></div>
</div>
<script>
// Configuration
const API_BASE_URL = 'https://api.logo.dev';
const YOUR_NOTE_ID = '26b7853a-5395-4317-afd3-5aec15281fb1';
// DOM Elements - cache references
const dom = {
searchInput: document.getElementById('searchInput'),
searchButton: document.getElementById('searchButton'),
resultsArea: document.getElementById('resultsArea'),
statusEl: document.getElementById('status')
};
// State
let isLoading = false;
let currentResults = [];
// Initialize
window.addEventListener('DOMContentLoaded', () => {
setupEventListeners();
updateStatus(typeof window.proxyFetch === 'function' ? 'Ready' : 'Error: proxyFetch not found');
});
// Setup all event listeners at once
function setupEventListeners() {
dom.searchButton.addEventListener('click', performSearch);
dom.searchInput.addEventListener('keypress', e => e.key === 'Enter' && performSearch());
// Delegate example tag clicks
document.querySelector('.examples').addEventListener('click', e => {
const tag = e.target.closest('.example-tag');
if (tag) {
dom.searchInput.value = tag.dataset.company;
performSearch();
}
});
}
// Update status helper
function updateStatus(text, color = '#44ff44') {
dom.statusEl.textContent = text;
dom.statusEl.style.color = color;
}
// Main search function
async function performSearch() {
const query = dom.searchInput.value.trim();
if (!query) {
showError('Please enter a company name');
return;
}
setLoading(true);
dom.resultsArea.innerHTML = '<div class="loading"><div class="loading-spinner"></div><p>Searching for logos...</p></div>';
try {
updateStatus('Searching...');
const response = await window.proxyFetch(
`${API_BASE_URL}/search?q=${encodeURIComponent(query)}`,
{
method: 'GET',
headers: { 'Authorization': 'Bearer {{LOGO_DEV_SECRET}}' }
},
YOUR_NOTE_ID
);
if (response.status === 401) {
throw new Error('Authentication failed (401)');
}
if (!response.ok) {
throw new Error(`API returned ${response.status}`);
}
const data = await response.json();
currentResults = data;
displayResults(data);
updateStatus(`Found ${data.length} results`);
} catch (error) {
console.error('Search error:', error);
showError(`Failed to fetch logos: ${error.message}`);
updateStatus('Error', '#ff4444');
} finally {
setLoading(false);
}
}
// Display search results - simplified and optimized
function displayResults(results) {
if (!results || results.length === 0) {
dom.resultsArea.innerHTML = '<div class="no-results"><h3>No logos found</h3><p>Try searching for a different company name</p></div>';
return;
}
// Build HTML more efficiently using array join
const cards = results.map((result, index) => {
const { domain = '', name = 'Unknown', logo_url = '' } = result;
const id = `logo-${index}`;
return `
<div class="logo-card">
<div id="${id}" class="logo-container image-loading">
<img class="logo-img"
alt="${name} logo"
data-url="${logo_url}"
data-id="${id}">
</div>
<h3 class="company-name">${name}</h3>
<p class="company-domain">${domain}</p>
<div class="button-group">
<button class="copy-button" data-action="copy" data-url="${logo_url}">Copy URL</button>
</div>
</div>
`;
}).join('');
dom.resultsArea.innerHTML = `
<div class="success-message">
<h3>✓ Found ${results.length} logos</h3>
<p>Loading logos...</p>
</div>
<div class="results-container">${cards}</div>
`;
// Load images after DOM is ready
requestAnimationFrame(() => loadAllLogos());
// Setup button listeners using event delegation
setupButtonListeners();
}
// Load all logos efficiently
function loadAllLogos() {
const images = document.querySelectorAll('.logo-img');
images.forEach(img => {
const url = img.dataset.url;
const id = img.dataset.id;
const container = document.getElementById(id);
img.onload = () => container.classList.remove('image-loading');
img.onerror = () => handleImageError(img, container, url);
img.src = url;
});
}
// Handle image load errors
async function handleImageError(img, container, url) {
try {
// Try loading via proxy
const response = await window.proxyFetch(url, { method: 'GET' }, YOUR_NOTE_ID);
if (response.ok) {
const blob = await response.blob();
img.src = URL.createObjectURL(blob);
} else {
throw new Error('Proxy fetch failed');
}
} catch (error) {
container.innerHTML = '<div class="logo-placeholder">Logo not available</div>';
container.classList.remove('image-loading');
}
}
// Setup button listeners using event delegation
function setupButtonListeners() {
const resultsContainer = document.querySelector('.results-container');
if (!resultsContainer) return;
resultsContainer.addEventListener('click', e => {
const btn = e.target.closest('.copy-button');
if (!btn) return;
const action = btn.dataset.action;
const url = btn.dataset.url;
if (action === 'copy') {
copyToClipboard(url);
}
});
}
// Optimized clipboard copy
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
showNotification('Logo URL copied to clipboard!');
} catch (err) {
// Fallback for older browsers
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.cssText = 'position:fixed;left:-9999px;opacity:0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
showNotification('Logo URL copied to clipboard!');
} catch (e) {
showNotification('Failed to copy', 'error');
}
document.body.removeChild(textarea);
}
}
// Show error message
function showError(message) {
dom.resultsArea.innerHTML = `<div class="error-message"><h3>Error</h3><p>${message}</p></div>`;
}
// Set loading state
function setLoading(loading) {
isLoading = loading;
dom.searchButton.disabled = loading;
dom.searchButton.textContent = loading ? 'Searching...' : 'Search';
}
// Show notification - simplified
function showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 2300);
}
</script>
</body>
</html>