Created
July 6, 2026 19:27
-
-
Save arodu/5a3b2e792295ec387ce8f16bbe5db9a0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Local Meta Tag Previewer</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| .preview-card { max-width: 550px; } | |
| .discord-card { border-left: 4px solid #5865F2; } | |
| </style> | |
| </head> | |
| <body class="bg-slate-900 text-slate-100 min-h-screen p-6"> | |
| <div class="max-w-6xl mx-auto"> | |
| <header class="mb-8 text-center"> | |
| <h1 class="text-3xl font-bold text-indigo-400">Meta Tag & Card Previewer</h1> | |
| <p class="text-slate-400 text-sm mt-2">Test Open Graph and Twitter Card meta tags locally.</p> | |
| </header> | |
| <div class="bg-slate-800 p-4 rounded-lg shadow-lg flex gap-4 mb-8"> | |
| <input type="url" id="urlInput" placeholder="https://example.com (empty = current site)" | |
| class="flex-1 bg-slate-700 border border-slate-600 rounded px-4 py-2 text-white focus:outline-none focus:border-indigo-500"> | |
| <button onclick="analyzeMetaTags()" class="bg-indigo-600 hover:bg-indigo-500 text-white font-semibold px-6 py-2 rounded transition whitespace-nowrap"> | |
| Analyze | |
| </button> | |
| </div> | |
| <div id="loading" class="hidden text-center text-indigo-400 my-4 font-semibold animate-pulse">Loading and processing site...</div> | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> | |
| <div class="space-y-8"> | |
| <!-- Open Graph --> | |
| <div class="bg-slate-800 p-6 rounded-lg shadow-md"> | |
| <h2 class="text-xl font-bold mb-4 text-blue-400 border-b border-slate-700 pb-2">Open Graph (Facebook / LinkedIn)</h2> | |
| <div class="preview-card bg-white text-black rounded-md overflow-hidden border border-gray-200 shadow-sm mx-auto"> | |
| <img id="ogImage" src="" class="w-full h-64 object-cover hidden"> | |
| <div class="p-3 bg-gray-100 border-t border-gray-200"> | |
| <div id="ogDomain" class="text-xs text-gray-500 uppercase tracking-wide font-semibold"></div> | |
| <div id="ogTitle" class="font-bold text-gray-900 text-lg mt-1 truncate"></div> | |
| <div id="ogDesc" class="text-sm text-gray-600 mt-1 line-clamp-2"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Twitter Large Image --> | |
| <div class="bg-slate-800 p-6 rounded-lg shadow-md"> | |
| <h2 class="text-xl font-bold mb-4 text-sky-400 border-b border-slate-700 pb-2">Twitter / X (Summary Large Image)</h2> | |
| <div class="preview-card bg-black text-white rounded-xl overflow-hidden border border-zinc-800 mx-auto"> | |
| <img id="twImage" src="" class="w-full h-60 object-cover hidden"> | |
| <div class="p-3 border-t border-zinc-800 bg-zinc-950"> | |
| <div id="twDomain" class="text-xs text-zinc-500"></div> | |
| <div id="twTitle" class="font-bold text-sm mt-0.5 truncate"></div> | |
| <div id="twDesc" class="text-xs text-zinc-400 mt-0.5 line-clamp-2"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Twitter Summary (small image) --> | |
| <div class="bg-slate-800 p-6 rounded-lg shadow-md"> | |
| <h2 class="text-xl font-bold mb-4 text-sky-400 border-b border-slate-700 pb-2">Twitter / X (Summary)</h2> | |
| <div class="preview-card bg-black text-white rounded-xl overflow-hidden border border-zinc-800 mx-auto flex"> | |
| <img id="twSumImage" src="" class="w-24 h-24 object-cover flex-shrink-0 hidden"> | |
| <div class="p-3 flex flex-col justify-center min-w-0"> | |
| <div id="twSumDomain" class="text-xs text-zinc-500"></div> | |
| <div id="twSumTitle" class="font-bold text-sm mt-0.5 truncate"></div> | |
| <div id="twSumDesc" class="text-xs text-zinc-400 mt-0.5 line-clamp-2"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Discord --> | |
| <div class="bg-slate-800 p-6 rounded-lg shadow-md"> | |
| <h2 class="text-xl font-bold mb-4 text-indigo-400 border-b border-slate-700 pb-2">Discord</h2> | |
| <div class="preview-card bg-[#313338] text-[#dbdee1] rounded-md overflow-hidden mx-auto discord-card"> | |
| <div class="p-3 flex gap-3"> | |
| <img id="discordImage" src="" class="w-20 h-20 rounded object-cover flex-shrink-0 hidden"> | |
| <div class="min-w-0"> | |
| <div id="discordSiteName" class="text-xs text-[#5865F2] font-semibold truncate"></div> | |
| <div id="discordTitle" class="font-bold text-sm mt-1 truncate"></div> | |
| <div id="discordDesc" class="text-xs text-[#949ba4] mt-1 line-clamp-2"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Telegram / WhatsApp --> | |
| <div class="bg-slate-800 p-6 rounded-lg shadow-md"> | |
| <h2 class="text-xl font-bold mb-4 text-emerald-400 border-b border-slate-700 pb-2">Telegram / WhatsApp</h2> | |
| <div class="preview-card bg-white text-black rounded-lg overflow-hidden border border-gray-200 shadow-sm mx-auto"> | |
| <img id="telegramImage" src="" class="w-full h-48 object-cover hidden"> | |
| <div class="p-3"> | |
| <div id="telegramDomain" class="text-xs text-blue-500"></div> | |
| <div id="telegramTitle" class="font-semibold text-gray-900 text-base mt-0.5 truncate"></div> | |
| <div id="telegramDesc" class="text-sm text-gray-500 mt-0.5 line-clamp-2"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-slate-800 p-6 rounded-lg shadow-md h-fit"> | |
| <h2 class="text-xl font-bold mb-4 text-emerald-400 border-b border-slate-700 pb-2">Detected Meta Tags</h2> | |
| <div class="overflow-x-auto"> | |
| <table class="w-full text-left text-sm text-slate-300"> | |
| <thead> | |
| <tr class="border-b border-slate-700 text-slate-400"> | |
| <th class="pb-2 w-1/3">Property / Name</th> | |
| <th class="pb-2 w-2/3">Content</th> | |
| </tr> | |
| </thead> | |
| <tbody id="metaTableBody" class="divide-y divide-slate-700/50"> | |
| <tr> | |
| <td colspan="2" class="py-4 text-center text-slate-500">Enter a URL to see its meta tags</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| async function analyzeMetaTags() { | |
| const urlInput = document.getElementById('urlInput').value.trim(); | |
| const loading = document.getElementById('loading'); | |
| const tableBody = document.getElementById('metaTableBody'); | |
| loading.classList.remove('hidden'); | |
| tableBody.innerHTML = ''; | |
| let targetUrl = urlInput || window.location.origin + '/'; | |
| if (targetUrl.startsWith('/')) { | |
| targetUrl = window.location.origin + targetUrl; | |
| } | |
| const isSameOrigin = targetUrl.startsWith(window.location.origin); | |
| const isLocal = targetUrl.includes('localhost') || targetUrl.includes('127.0.0.1'); | |
| if (!isSameOrigin && !isLocal) { | |
| targetUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(targetUrl)}`; | |
| } | |
| try { | |
| const response = await fetch(targetUrl, { cache: 'no-cache' }); | |
| if (!response.ok) throw new Error('Could not fetch the URL.'); | |
| let htmlText = ''; | |
| if (targetUrl.includes('allorigins.win')) { | |
| const data = await response.json(); | |
| htmlText = data.contents; | |
| } else { | |
| htmlText = await response.text(); | |
| } | |
| const parser = new DOMParser(); | |
| const doc = parser.parseFromString(htmlText, 'text/html'); | |
| const metaTags = doc.querySelectorAll('meta'); | |
| const data = { | |
| title: doc.querySelector('title')?.innerText || '', | |
| siteName: '', | |
| ogTitle: '', ogDesc: '', ogImage: '', | |
| twTitle: '', twDesc: '', twImage: '', | |
| rawMetas: [] | |
| }; | |
| metaTags.forEach(meta => { | |
| const name = meta.getAttribute('name') || meta.getAttribute('property'); | |
| const content = meta.getAttribute('content'); | |
| if (name && content) { | |
| data.rawMetas.push({ name, content }); | |
| if (name === 'og:title') data.ogTitle = content; | |
| if (name === 'og:description') data.ogDesc = content; | |
| if (name === 'og:image') data.ogImage = content; | |
| if (name === 'og:site_name') data.siteName = content; | |
| if (name === 'twitter:title') data.twTitle = content; | |
| if (name === 'twitter:description') data.twDesc = content; | |
| if (name === 'twitter:image') data.twImage = content; | |
| } | |
| }); | |
| const fallbackDesc = doc.querySelector('meta[name="description"]')?.getAttribute('content') || ''; | |
| if (data.rawMetas.length === 0) { | |
| tableBody.innerHTML = `<tr><td colspan="2" class="py-4 text-center text-yellow-500">No explicit meta tags found.</td></tr>`; | |
| } else { | |
| data.rawMetas.forEach(m => { | |
| const row = `<tr class="hover:bg-slate-700/30"> | |
| <td class="py-2 pr-2 font-mono text-xs text-indigo-300 break-all">${escapeHtml(m.name)}</td> | |
| <td class="py-2 font-sans break-all text-slate-200">${escapeHtml(m.content)}</td> | |
| </tr>`; | |
| tableBody.insertAdjacentHTML('beforeend', row); | |
| }); | |
| } | |
| let domain = ''; | |
| try { domain = new URL(urlInput || window.location.href).hostname; } catch(e) { domain = 'localhost'; } | |
| // --- Open Graph --- | |
| document.getElementById('ogDomain').innerText = domain; | |
| document.getElementById('ogTitle').innerText = data.ogTitle || data.title || 'No title'; | |
| document.getElementById('ogDesc').innerText = data.ogDesc || fallbackDesc || 'No description available.'; | |
| updateImage('ogImage', data.ogImage, targetUrl); | |
| // --- Twitter Large Image --- | |
| document.getElementById('twDomain').innerText = domain; | |
| document.getElementById('twTitle').innerText = data.twTitle || data.ogTitle || data.title || 'No title'; | |
| document.getElementById('twDesc').innerText = data.twDesc || data.ogDesc || fallbackDesc || 'No description.'; | |
| updateImage('twImage', data.twImage || data.ogImage, targetUrl); | |
| // --- Twitter Summary --- | |
| document.getElementById('twSumDomain').innerText = domain; | |
| document.getElementById('twSumTitle').innerText = data.twTitle || data.ogTitle || data.title || 'No title'; | |
| document.getElementById('twSumDesc').innerText = data.twDesc || data.ogDesc || fallbackDesc || 'No description.'; | |
| updateImage('twSumImage', data.twImage || data.ogImage, targetUrl); | |
| // --- Discord --- | |
| document.getElementById('discordSiteName').innerText = data.siteName || domain; | |
| document.getElementById('discordTitle').innerText = data.ogTitle || data.title || 'No title'; | |
| document.getElementById('discordDesc').innerText = data.ogDesc || fallbackDesc || 'No description.'; | |
| updateImage('discordImage', data.ogImage, targetUrl); | |
| // --- Telegram / WhatsApp --- | |
| document.getElementById('telegramDomain').innerText = domain; | |
| document.getElementById('telegramTitle').innerText = data.ogTitle || data.title || 'No title'; | |
| document.getElementById('telegramDesc').innerText = data.ogDesc || fallbackDesc || 'No description.'; | |
| updateImage('telegramImage', data.ogImage, targetUrl); | |
| } catch (error) { | |
| console.error(error); | |
| alert('Error scanning the URL. If testing localhost, make sure your local server allows CORS or serve this file from your own server.'); | |
| } finally { | |
| loading.classList.add('hidden'); | |
| } | |
| } | |
| function updateImage(elementId, imgUrl, baseUrl) { | |
| const imgEl = document.getElementById(elementId); | |
| if (imgUrl) { | |
| if (!imgUrl.startsWith('http://') && !imgUrl.startsWith('https://')) { | |
| try { | |
| imgUrl = new URL(imgUrl, baseUrl).href; | |
| } catch(e){} | |
| } | |
| imgEl.src = imgUrl; | |
| imgEl.classList.remove('hidden'); | |
| } else { | |
| imgEl.classList.add('hidden'); | |
| } | |
| } | |
| function escapeHtml(str) { | |
| return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment