const { TagChip, Card, Button, Badge } = window.TaglyDesignSystem_1a793a;
const FREE_LIMITS = { hashtags: 15, keywords: 10, longTail: 8, related: 6 };
const FAVORITES_KEY = 'tagly_favorites_v1';
function downloadBlob(filename, text, type) {
const blob = new Blob([text], { type });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = filename; document.body.appendChild(a); a.click();
document.body.removeChild(a); URL.revokeObjectURL(url);
}
function loadFavorites() { try { return JSON.parse(localStorage.getItem(FAVORITES_KEY)) || []; } catch (e) { return []; } }
function saveFavorites(list) { try { localStorage.setItem(FAVORITES_KEY, JSON.stringify(list.slice(0, 30))); } catch (e) {} }
function CategoryTabs({ categories, active, onChange }) {
return (
{categories.map(c => (
onChange(c.key)} aria-pressed={active === c.key} className="tagly-focusable" style={{
position: 'relative', background: 'none', border: 'none', cursor: 'pointer', whiteSpace: 'nowrap',
padding: '0 2px 14px', fontFamily: 'var(--font-sans)', fontSize: 15, fontWeight: 600,
color: active === c.key ? 'var(--text-heading)' : 'var(--text-muted)',
}}>
{c.label} {c.count != null && ({c.count}) }
{active === c.key && }
))}
);
}
function AdSlot() {
return (
Advertisement
336×280 · loads after results render
);
}
const PURPOSE_LABEL = { awareness: 'Awareness', engagement: 'Engagement', 'niche discovery': 'Niche discovery', community: 'Community', conversion: 'Conversion' };
const PURPOSE_DESC = {
awareness: 'Maximize how many new people see your content.',
engagement: 'Drive likes, comments, shares, and replies.',
'niche discovery': 'Reach a smaller, highly relevant audience.',
community: 'Signal you into the platform’s own active communities.',
conversion: 'Match intent-driven searches right before action.',
};
const TIER_LABEL = { broad: 'Broad reach', medium: 'Medium reach', niche: 'Niche reach' };
const TIER_COLOR = { broad: 'var(--tg-primary)', medium: 'var(--tg-success, #1a8a4a)', niche: 'var(--text-muted)' };
function Gauge({ label, value, color }) {
return (
);
}
function StrategyPanel({ strategy }) {
if (!strategy) return null;
return (
AI strategy
Estimated guidance, not measured platform data
Suggested tag mix
{strategy.mix}
Posting guidance
{strategy.postingGuidance}
Content angle
{strategy.contentAngle}
Complementary keywords
{strategy.complementaryKeywords.map(k => {k})}
);
}
function ExpandedTopics({ topics, onPick }) {
if (!topics?.length) return null;
return (
Related angles — click to broaden your search
{topics.map((t, i) => (
onPick(t)} style={{ fontSize: 12.5, background: 'var(--surface-soft)', border: '1px solid var(--border-soft)', borderRadius: 999, padding: '5px 12px', cursor: 'pointer', color: 'var(--text-body)' }}>{t}
))}
);
}
function CopyFormatMenu({ items, label }) {
const [open, setOpen] = React.useState(false);
const [copied, setCopied] = React.useState(null);
function doCopy(fmt) {
let text;
if (fmt === 'full') text = items.map(i => i.tag).join(' ');
else if (fmt === 'comma') text = items.map(i => i.tag).join(', ');
else if (fmt === 'nohash') text = items.map(i => i.tag.replace(/^#/, '')).join(' ');
else if (fmt === 'markdown') text = items.map(i => `- ${i.tag}`).join('\n');
else if (fmt === 'category') {
const groups = {};
items.forEach(i => { const k = i.purpose || 'other'; (groups[k] = groups[k] || []).push(i.tag); });
text = Object.entries(groups).map(([k, v]) => `${PURPOSE_LABEL[k] || k}:\n${v.join(' ')}`).join('\n\n');
}
navigator.clipboard.writeText(text).catch(() => {});
setCopied(fmt); setOpen(false);
setTimeout(() => setCopied(c => (c === fmt ? null : c)), 1400);
}
return (
setOpen(o => !o)}>{copied ? 'Copied!' : `Copy all ${label.toLowerCase()} ▾`}
{open && (
{[['full', 'Full set (with #)'], ['category', 'Grouped by category'], ['comma', 'Comma-separated'], ['nohash', 'Without # symbols'], ['markdown', 'Markdown list']].map(([fmt, txt]) => (
doCopy(fmt)} style={{ display: 'block', width: '100%', textAlign: 'left', padding: '10px 14px', fontSize: 13, background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-body)' }}>{txt}
))}
)}
);
}
function PublishPackageView({ pkg, platform, topic }) {
const [copiedField, setCopiedField] = React.useState(null);
if (!pkg) return null;
function copy(field, text) {
navigator.clipboard.writeText(text).catch(() => {});
setCopiedField(field);
setTimeout(() => setCopiedField(f => (f === field ? null : f)), 1200);
}
function copyFullPackage() {
const parts = [
`Title: ${pkg.title}`,
`Caption: ${pkg.caption}`,
`Hashtags: ${pkg.hashtags.map(h => h.tag).join(' ')}`,
`Alt text: ${pkg.altText}`,
`CTA: ${pkg.cta}`,
pkg.firstComment ? pkg.firstComment : null,
`Suggested posting time (estimated): ${pkg.postingTime.text}`,
].filter(Boolean);
copy('full', parts.join('\n\n'));
}
const rows = [
['Title', pkg.title],
['Caption', pkg.caption],
['Hashtags', pkg.hashtags.map(h => h.tag).join(' ')],
['Alt text', pkg.altText],
['Call to action', pkg.cta],
];
return (
A complete, ready-to-publish package for "{topic}" on {platform}.
{copiedField === 'full' ? 'Copied!' : 'Copy full package'}
{rows.map(([label, value]) => (
copy(label, value)} style={{ flexShrink: 0, fontSize: 12, background: 'none', border: '1px solid var(--border-default)', borderRadius: 999, padding: '5px 12px', cursor: 'pointer', color: 'var(--text-body)' }}>{copiedField === label ? 'Copied' : 'Copy'}
))}
{pkg.firstComment && (
First comment
{pkg.firstComment}
copy('firstComment', pkg.firstComment)} style={{ flexShrink: 0, fontSize: 12, background: 'none', border: '1px solid var(--border-default)', borderRadius: 999, padding: '5px 12px', cursor: 'pointer', color: 'var(--text-body)' }}>{copiedField === 'firstComment' ? 'Copied' : 'Copy'}
)}
Suggested posting time (estimated, not measured)
{pkg.postingTime.text}
);
}
function ResultsPanel({ results, isPro, onGoPro, onExpandTopic }) {
const [active, setActive] = React.useState('hashtags');
const [copiedId, setCopiedId] = React.useState(null);
const [selected, setSelected] = React.useState(() => new Set());
const [query, setQuery] = React.useState('');
const [tierFilter, setTierFilter] = React.useState('all');
const [groupBy, setGroupBy] = React.useState('purpose');
const [isFav, setIsFav] = React.useState(false);
const [toastMsg, setToastMsg] = React.useState(null);
React.useEffect(() => {
setActive('hashtags'); setSelected(new Set()); setQuery(''); setTierFilter('all');
const favs = loadFavorites();
setIsFav(favs.some(f => f.topic === results.topic && f.platform === results.platform));
}, [results.topic, results.platform]);
const categories = [
{ key: 'hashtags', label: 'Hashtags', count: results.hashtags.length, items: results.hashtags },
{ key: 'keywords', label: 'Keywords', count: results.keywords.length, items: results.keywords },
{ key: 'longTail', label: 'Long-tail', count: results.longTail.length, items: results.longTail },
{ key: 'related', label: 'Related searches', count: results.related.length, items: results.related },
{ key: 'captions', label: 'Captions & titles', count: (results.captions?.length || 0) + (results.titles?.length || 0), items: [...(results.captions || []), ...(results.titles || [])] },
{ key: 'publish', label: 'Publish package', count: null, items: [] },
];
const current = categories.find(c => c.key === active);
const limit = FREE_LIMITS[active] || 999;
const proItems = isPro ? current.items : current.items.slice(0, limit);
const visibleItems = proItems.filter(item => {
if (tierFilter !== 'all' && item.tier !== tierFilter) return false;
if (query && !item.tag.toLowerCase().includes(query.toLowerCase())) return false;
return true;
});
const lockedCount = current.items.length - proItems.length;
function copyOne(tag) {
navigator.clipboard.writeText(tag).catch(() => {});
setCopiedId(tag);
setTimeout(() => setCopiedId(id => (id === tag ? null : id)), 1100);
}
function toggle(tag) {
setSelected(prev => { const n = new Set(prev); n.has(tag) ? n.delete(tag) : n.add(tag); return n; });
}
function exportTxt() {
downloadBlob(`tagly-${results.topic.replace(/\s+/g, '-')}-${current.key}.txt`, visibleItems.map(i => i.tag).join(' '), 'text/plain');
}
function exportCsv() {
if (!isPro) { onGoPro(); return; }
const rows = ['tag,score,competition,trend,purpose,confidence', ...visibleItems.map(i => `"${i.tag}",${i.score},${i.competition},${i.trend},${i.purpose || ''},${i.confidence || ''}`)];
downloadBlob(`tagly-${results.topic.replace(/\s+/g, '-')}-${current.key}.csv`, rows.join('\n'), 'text/csv');
}
function exportMarkdown() {
const md = `# ${results.topic} \u2014 ${current.label} (${results.platform})\n\n` + visibleItems.map(i => `- ${i.tag}`).join('\n');
downloadBlob(`tagly-${results.topic.replace(/\s+/g, '-')}-${current.key}.md`, md, 'text/markdown');
}
function toggleFavorite() {
const favs = loadFavorites();
const exists = favs.some(f => f.topic === results.topic && f.platform === results.platform);
const next = exists ? favs.filter(f => !(f.topic === results.topic && f.platform === results.platform)) : [{ topic: results.topic, platform: results.platform, savedAt: Date.now() }, ...favs];
saveFavorites(next);
setIsFav(!exists);
setToastMsg(exists ? 'Removed from favorites' : 'Saved to favorites');
setTimeout(() => setToastMsg(null), 1600);
}
function shareLink() {
const url = new URL(window.location.href.split('?')[0].split('#')[0]);
url.searchParams.set('topic', results.topic);
url.searchParams.set('platform', results.platform);
url.hash = 'generate';
navigator.clipboard.writeText(url.toString()).catch(() => {});
setToastMsg('Shareable link copied \u2014 anyone can open it to see this result');
setTimeout(() => setToastMsg(null), 2200);
}
const clusterKeys = groupBy === 'topic'
? [...new Set(visibleItems.map(i => i.topic).filter(Boolean))]
: ['awareness', 'engagement', 'niche discovery', 'community', 'conversion'];
return (
{isPro ? results.total : Object.values(FREE_LIMITS).reduce((a, b) => a + b, 0)}
tags ready for "{results.topic}" on {results.platform}
{!isPro && <> · Go Pro for the full {results.total} >}
{active === 'hashtags' && (
Recommended for {results.platform}: use about {results.recommendedCount} {results.tagWord} — the top-ranked ones below are marked for you.
)}
{active !== 'publish' &&
Export .txt
Export .md
{isPro ? 'Export .csv' : 'Export .csv 🔒'}
}
{isFav ? '\u2605 Saved' : '\u2606 Save'}
Share link
{toastMsg && {toastMsg} }
{active === 'hashtags' &&
}
{active === 'hashtags' &&
}
{active === 'publish' &&
}
{active === 'hashtags' && (
setQuery(e.target.value)} placeholder="Search tags…" aria-label="Search within results" style={{ height: 34, borderRadius: 'var(--radius-full)', border: '1px solid var(--border-default)', padding: '0 14px', fontSize: 13, fontFamily: 'var(--font-sans)', minWidth: 140 }} />
{['all', 'broad', 'medium', 'niche'].map(t => (
setTierFilter(t)} className="tagly-focusable" style={{
fontSize: 12.5, fontWeight: 600, padding: '6px 13px', borderRadius: 'var(--radius-full)', cursor: 'pointer',
border: tierFilter === t ? '1px solid transparent' : '1px solid var(--border-default)',
background: tierFilter === t ? 'var(--tg-ink)' : 'var(--surface-canvas)',
color: tierFilter === t ? '#fff' : 'var(--text-body)',
}}>{t === 'all' ? 'All tiers' : TIER_LABEL[t]}
))}
{['purpose', 'topic'].map(g => (
setGroupBy(g)} style={{ fontSize: 11.5, fontWeight: 600, padding: '5px 11px', borderRadius: 999, border: 'none', cursor: 'pointer', background: groupBy === g ? '#fff' : 'transparent', color: groupBy === g ? 'var(--text-heading)' : 'var(--text-muted)' }}>By {g}
))}
)}
{active === 'hashtags' ? (
{clusterKeys.map(key => {
const group = visibleItems.filter(i => (groupBy === 'topic' ? i.topic === key : i.purpose === key));
if (!group.length) return null;
const avgConf = Math.round(group.reduce((s, i) => s + (i.confidence === 'High' ? 90 : i.confidence === 'Medium' ? 60 : 30), 0) / group.length);
const avgSpec = Math.round(group.reduce((s, i) => s + (i.specificity || 50), 0) / group.length);
const avgReach = Math.round(group.reduce((s, i) => s + (i.reach || 50), 0) / group.length);
const heading = groupBy === 'topic' ? (key === 'platform' ? 'Platform culture' : key === 'general' ? 'General' : key.charAt(0).toUpperCase() + key.slice(1)) : PURPOSE_LABEL[key];
return (
{heading}
{groupBy === 'purpose' &&
{PURPOSE_DESC[key]} }
{group.map(item => (
{ copyOne(item.tag); toggle(item.tag); }}
title={`${item.reason || ''} \u00b7 confidence ${item.confidence} \u00b7 specificity ${item.specificity} \u00b7 reach ${item.reach} \u00b7 score ${item.score} \u00b7 ${item.competition} competition`}
aria-label={`Copy ${item.tag}, ${PURPOSE_LABEL[item.purpose]}, confidence ${item.confidence}`}
/>
{item.tier && {TIER_LABEL[item.tier]} }
))}
);
})}
) : active !== 'publish' && (
{visibleItems.map(item => (
{ copyOne(item.tag); toggle(item.tag); }}
title={item.reason || `score ${item.score}`}
aria-label={`Copy ${item.tag}`}
/>
))}
)}
{active === 'hashtags' && visibleItems.length === 0 && (
No tags match your search or filter.
)}
{!isPro && active !== 'publish' && lockedCount > 0 && (
Pro
{lockedCount} more {current.label.toLowerCase()} ready
Unlock the full ranked set, CSV export, and saved history for $3/mo.
Go Pro
)}
{active !== 'publish' && (
Click any tag to copy it individually. Scores are a relative popularity proxy (0–99); competition, specificity, and reach gauges are illustrative in this prototype.
)}
{!isPro && active !== 'publish' &&
}
);
}
window.TaglyResults = { ResultsPanel };