Etsy SEO Page

<html>
 <head>
  <style>
  * { font-family: 'DM Sans', sans-serif; }
  .copy-toast { animation: fadeUp 2s ease forwards; }
  @keyframes fadeUp { 0% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-12px); } }
</style>
  <script src="https://cdn.tailwindcss.com/3.4.17"></script>
  <script src="https://cdn.jsdelivr.net/npm/lucide@0.263.0/dist/umd/lucide.min.js"></script>
  <script src="/_sdk/element_sdk.js"></script>
  <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet">
  <script>
  tailwind.config = {
    theme: {
      extend: {
        colors: {
          bg: '#F7F5F2',
          surface: '#FFFFFF',
          txt: '#3B3633',
          accent: '#C4896A',
          muted: '#A89F97'
        }
      }
    }
  }
</script>
  <style>body { box-sizing: border-box; }</style>
  <script src="/_sdk/data_sdk.js" type="text/javascript"></script>
 </head>
 <body>
  <div id="app" class="h-full w-full overflow-auto">
   <div class="max-w-2xl mx-auto px-5 py-10">
    <header class="mb-8 text-center">
     <h1 id="appTitle" class="text-2xl font-700 tracking-tight mb-1">Etsy SEO Optimizer</h1>
     <p id="appSubtitle" class="text-muted text-sm">Generate high-converting tags, keywords &amp; titles</p>
    </header><!-- FORM -->
    <form id="seoForm" class="space-y-5"><!-- Step 1 -->
     <div class="bg-surface rounded-xl p-5 border border-bg"><label class="flex items-center gap-2 text-xs font-600 uppercase tracking-wide text-muted mb-2"> <span class="w-5 h-5 rounded-full bg-accent text-white flex items-center justify-center text-[10px] font-700">1</span> Product Type </label> <input id="product" type="text" required placeholder="e.g. bookmark, mug, t-shirt, tumbler" class="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:border-accent placeholder:text-muted/50">
     </div><!-- Step 2 -->
     <div class="bg-surface rounded-xl p-5 border border-bg"><label class="flex items-center gap-2 text-xs font-600 uppercase tracking-wide text-muted mb-2"> <span class="w-5 h-5 rounded-full bg-accent text-white flex items-center justify-center text-[10px] font-700">2</span> Theme / Style </label> <input id="theme" type="text" required placeholder="e.g. boho floral, Halloween, romantasy, retro vintage" class="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:border-accent placeholder:text-muted/50">
     </div><!-- Step 3 -->
     <div class="bg-surface rounded-xl p-5 border border-bg"><label class="flex items-center gap-2 text-xs font-600 uppercase tracking-wide text-muted mb-2"> <span class="w-5 h-5 rounded-full bg-accent text-white flex items-center justify-center text-[10px] font-700">3</span> Buyer Keywords <span class="font-400 normal-case">(≤ 20 chars each, comma-separated)</span> </label> <input id="keywords" type="text" required placeholder="e.g. book lover gift, cozy reading, retro groovy" class="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:border-accent placeholder:text-muted/50">
      <div id="kwWarning" class="text-xs text-red-400 mt-1 hidden"></div>
     </div><!-- Step 4 -->
     <div class="bg-surface rounded-xl p-5 border border-bg"><label class="flex items-center gap-2 text-xs font-600 uppercase tracking-wide text-muted mb-2"> <span class="w-5 h-5 rounded-full bg-accent text-white flex items-center justify-center text-[10px] font-700">4</span> Long-Tail Keywords <span class="font-400 normal-case">(full search phrases, comma-separated)</span> </label> <input id="longtail" type="text" required placeholder="e.g. witchy book lover gift, retro 70s graphic tee" class="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:border-accent placeholder:text-muted/50">
     </div><!-- Step 5 -->
     <div class="bg-surface rounded-xl p-5 border border-bg"><label class="flex items-center gap-2 text-xs font-600 uppercase tracking-wide text-muted mb-2"> <span class="w-5 h-5 rounded-full bg-accent text-white flex items-center justify-center text-[10px] font-700">5</span> Title Ending Phrase </label> <input id="titleEnd" type="text" required placeholder="e.g. funny cat mom shirt, handmade witchy mug" class="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:border-accent placeholder:text-muted/50">
     </div><button type="submit" id="genBtn" class="w-full bg-accent hover:bg-accent/90 text-white font-600 py-3 rounded-xl transition-colors text-sm"> Generate SEO Strategy </button>
    </form><!-- RESULTS -->
    <div id="results" class="hidden mt-8 space-y-6 pb-12">
     <div class="flex items-center justify-between">
      <h2 class="text-lg font-700">Your SEO Strategy</h2><button id="copyAllBtn" onclick="copyAll()" class="text-xs text-accent hover:underline flex items-center gap-1"> <i data-lucide="copy" style="width:14px;height:14px;"></i> Copy All </button>
     </div><!-- Tags -->
     <section class="bg-surface rounded-xl p-5 border border-bg">
      <div class="flex items-center justify-between mb-3">
       <h3 class="text-sm font-600 uppercase tracking-wide text-muted">Optimized Tags <span id="tagCount" class="font-400"></span></h3><button onclick="copySection('tagsContent')" class="text-xs text-accent hover:underline">Copy</button>
      </div>
      <div id="tagsContent" class="flex flex-wrap gap-2"></div>
     </section><!-- Titles -->
     <section class="bg-surface rounded-xl p-5 border border-bg">
      <div class="flex items-center justify-between mb-3">
       <h3 class="text-sm font-600 uppercase tracking-wide text-muted">Optimized Titles</h3><button onclick="copySection('titlesContent')" class="text-xs text-accent hover:underline">Copy</button>
      </div>
      <div id="titlesContent" class="space-y-2"></div>
     </section><!-- Prompt -->
     <section class="bg-surface rounded-xl p-5 border border-bg">
      <div class="flex items-center justify-between mb-3">
       <h3 class="text-sm font-600 uppercase tracking-wide text-muted">ChatGPT Prompt</h3><button onclick="copySection('promptContent')" class="text-xs text-accent hover:underline">Copy</button>
      </div>
      <p class="text-xs text-muted mb-2">Paste this into ChatGPT for AI-powered results:</p>
      <div id="promptContent" class="text-sm bg-bg rounded-lg p-4 whitespace-pre-wrap leading-relaxed"></div>
     </section><button onclick="document.getElementById('results').classList.add('hidden'); window.scrollTo({top:0,behavior:'smooth'});" class="w-full border border-gray-200 text-txt font-500 py-3 rounded-xl text-sm hover:bg-surface transition-colors"> ← Start Over </button> <button onclick="showEmbedModal()" class="w-full bg-accent/10 text-accent font-500 py-3 rounded-xl text-sm hover:bg-accent/20 transition-colors mt-3"> 📋 Get Embed Code </button>
    </div>
   </div><!-- Toast -->
   <div id="toast" class="fixed bottom-6 left-1/2 -translate-x-1/2 bg-txt text-white text-xs px-4 py-2 rounded-lg hidden copy-toast">
    Copied!
   </div><!-- EMBED CODE SECTION -->
   <div id="embedSection" class="hidden fixed inset-0 bg-black/40 flex items-center justify-center z-50 p-4">
    <div class="bg-surface rounded-xl p-6 max-w-2xl w-full max-h-[80vh] overflow-auto">
     <div class="flex items-center justify-between mb-4">
      <h2 class="text-lg font-700">Embed on Your Website</h2><button onclick="document.getElementById('embedSection').classList.add('hidden')" class="text-muted hover:text-txt"> <i data-lucide="x" style="width:20px;height:20px;"></i> </button>
     </div>
     <p class="text-sm text-muted mb-4">Copy this code and paste it into your website's HTML where you want the tool to appear:</p>
     <div class="bg-bg rounded-lg p-4 mb-4 border border-gray-200">
      <pre id="embedCode" class="text-xs leading-relaxed whitespace-pre-wrap break-words text-txt"></pre>
     </div><button onclick="copyEmbedCode()" class="w-full bg-accent hover:bg-accent/90 text-white font-600 py-2.5 rounded-lg text-sm transition-colors"> Copy Embed Code </button>
    </div>
   </div>
  </div>
  <script>
  // --- Element SDK ---
  const defaultConfig = {
    app_title: 'Etsy SEO Optimizer',
    app_subtitle: 'Generate high-converting tags, keywords & titles',
    background_color: '#F7F5F2',
    surface_color: '#FFFFFF',
    text_color: '#3B3633',
    accent_color: '#C4896A',
    muted_color: '#A89F97',
    font_family: 'DM Sans',
    font_size: 14
  };

  function applyConfig(c) {
    const el = (id) => document.getElementById(id);
    el('appTitle').textContent = c.app_title || defaultConfig.app_title;
    el('appSubtitle').textContent = c.app_subtitle || defaultConfig.app_subtitle;

    const bg = c.background_color || defaultConfig.background_color;
    const surface = c.surface_color || defaultConfig.surface_color;
    const txt = c.text_color || defaultConfig.text_color;
    const accent = c.accent_color || defaultConfig.accent_color;
    const muted = c.muted_color || defaultConfig.muted_color;
    const font = c.font_family || defaultConfig.font_family;
    const size = c.font_size || defaultConfig.font_size;

    document.body.style.backgroundColor = bg;
    document.body.style.color = txt;
    document.body.style.fontFamily = `${font}, DM Sans, sans-serif`;
    document.body.style.fontSize = `${size}px`;

    document.querySelectorAll('.bg-surface').forEach(el => el.style.backgroundColor = surface);
    document.querySelectorAll('.bg-accent, [class*="bg-accent"]').forEach(el => {
      if (el.tagName === 'BUTTON' && el.id === 'genBtn') el.style.backgroundColor = accent;
    });
    document.querySelectorAll('.text-muted').forEach(el => el.style.color = muted);
    document.querySelectorAll('.text-accent').forEach(el => el.style.color = accent);

    const stepDots = document.querySelectorAll('.bg-accent.rounded-full');
    stepDots.forEach(d => d.style.backgroundColor = accent);

    el('appTitle').style.fontSize = `${size * 1.7}px`;
    el('appSubtitle').style.fontSize = `${size * 0.9}px`;
  }

  window.elementSdk.init({
    defaultConfig,
    onConfigChange: async (config) => applyConfig(config),
    mapToCapabilities: (config) => ({
      recolorables: [
        { get: () => config.background_color || defaultConfig.background_color, set: v => { config.background_color = v; window.elementSdk.setConfig({ background_color: v }); } },
        { get: () => config.surface_color || defaultConfig.surface_color, set: v => { config.surface_color = v; window.elementSdk.setConfig({ surface_color: v }); } },
        { get: () => config.text_color || defaultConfig.text_color, set: v => { config.text_color = v; window.elementSdk.setConfig({ text_color: v }); } },
        { get: () => config.accent_color || defaultConfig.accent_color, set: v => { config.accent_color = v; window.elementSdk.setConfig({ accent_color: v }); } },
        { get: () => config.muted_color || defaultConfig.muted_color, set: v => { config.muted_color = v; window.elementSdk.setConfig({ muted_color: v }); } }
      ],
      borderables: [],
      fontEditable: { get: () => config.font_family || defaultConfig.font_family, set: v => { config.font_family = v; window.elementSdk.setConfig({ font_family: v }); } },
      fontSizeable: { get: () => config.font_size || defaultConfig.font_size, set: v => { config.font_size = v; window.elementSdk.setConfig({ font_size: v }); } }
    }),
    mapToEditPanelValues: (config) => new Map([
      ['app_title', config.app_title || defaultConfig.app_title],
      ['app_subtitle', config.app_subtitle || defaultConfig.app_subtitle]
    ])
  });

  // --- Keyword validation ---
  document.getElementById('keywords').addEventListener('input', function() {
    const warn = document.getElementById('kwWarning');
    const bad = this.value.split(',').map(k => k.trim()).filter(k => k.length > 20);
    if (bad.length) {
      warn.textContent = `Too long (>20 chars): "${bad[0]}"`;
      warn.classList.remove('hidden');
    } else {
      warn.classList.add('hidden');
    }
  });

  // --- Generation ---
  document.getElementById('seoForm').addEventListener('submit', function(e) {
    e.preventDefault();

    const product = document.getElementById('product').value.trim();
    const theme = document.getElementById('theme').value.trim();
    const keywordsRaw = document.getElementById('keywords').value.trim();
    const longtailRaw = document.getElementById('longtail').value.trim();
    const titleEnd = document.getElementById('titleEnd').value.trim();

    const keywords = [...new Set(keywordsRaw.split(',').map(k => k.trim()).filter(k => k))];
    const longtails = [...new Set(longtailRaw.split(',').map(k => k.trim()).filter(k => k))];
    const themes = theme.split(',').map(t => t.trim()).filter(t => t);

    // Generate tags (unique, ≤20 chars, max 13 Etsy tags)
    const tagCandidates = [];
    keywords.forEach(k => tagCandidates.push(k));
    longtails.forEach(l => { l.split(' ').forEach(w => { if (w.length > 2) tagCandidates.push(w); }); tagCandidates.push(l); });
    themes.forEach(t => tagCandidates.push(t));
    tagCandidates.push(product);
    tagCandidates.push(`${themes[0] || ''} ${product}`.trim());
    keywords.forEach(k => tagCandidates.push(`${k} ${product}`.trim()));
    tagCandidates.push(`${product} gift`);
    tagCandidates.push(`custom ${product}`);
    tagCandidates.push(`unique ${product}`);
    tagCandidates.push(`handmade ${product}`);

    const seenTags = new Set();
    const tags = [];
    for (const t of tagCandidates) {
      const lower = t.toLowerCase();
      if (lower.length > 0 && lower.length <= 20 && !seenTags.has(lower)) {
        seenTags.add(lower);
        tags.push(lower);
        if (tags.length >= 13) break;
      }
    }

    // Render tags
    const tagsEl = document.getElementById('tagsContent');
    tagsEl.innerHTML = '';
    tags.forEach(tag => {
      const span = document.createElement('span');
      span.className = 'inline-block bg-bg text-txt text-xs px-3 py-1.5 rounded-full border border-gray-200';
      span.textContent = tag;
      tagsEl.appendChild(span);
    });
    document.getElementById('tagCount').textContent = `(${tags.length}/13)`;

    // Generate 10 titles
    const titleParts = [...keywords, ...longtails, ...themes, product];
    const titles = generateTitles(titleParts, titleEnd, product, themes, keywords, longtails);

    const titlesEl = document.getElementById('titlesContent');
    titlesEl.innerHTML = '';
    titles.forEach((t, i) => {
      const div = document.createElement('div');
      div.className = 'flex items-start gap-2 py-2 border-b border-gray-100 last:border-0';
      const charClass = t.length <= 140 ? 'text-green-600' : 'text-red-400';
      div.innerHTML = `<span class="text-muted text-xs mt-0.5 shrink-0">${i+1}.</span><span class="text-sm flex-1">${t}</span><span class="text-[10px] ${charClass} shrink-0 mt-0.5">${t.length}/140</span>`;
      titlesEl.appendChild(div);
    });

    // Build ChatGPT prompt
    const prompt = `Create a high-converting Etsy SEO strategy focused on [${product}] featuring ["${themes.join(', ')}", "${keywords.join('", "')}", "${longtails.join('", "')}"]. Provide:\n\nA curated list of top-selling tags and keywords (no duplicates, each ≤ 20 characters) based on Etsy search trends and buyer intent.\n\n10 long-tail, highly optimized Etsy titles (≤ 140 characters) that include the following phrase at the end: "${titleEnd}"\n\nMake sure all keywords align with Etsy's current top-performing listings in that niche.`;
    document.getElementById('promptContent').textContent = prompt;

    document.getElementById('results').classList.remove('hidden');
    setTimeout(() => document.getElementById('results').scrollIntoView({ behavior: 'smooth' }), 100);
    lucide.createIcons();
  });

  function generateTitles(parts, ending, product, themes, keywords, longtails) {
    const titles = [];
    const cap = s => s.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
    const t0 = themes[0] || '';
    const t1 = themes[1] || t0;
    const k0 = keywords[0] || '';
    const k1 = keywords[1] || k0;
    const k2 = keywords[2] || k1;
    const l0 = longtails[0] || '';
    const l1 = longtails[1] || l0;

    const templates = [
      `${cap(t0)} ${cap(product)} ${cap(k0)} Unique ${cap(l0)} ${cap(ending)}`,
      `${cap(l0)} ${cap(t0)} ${cap(product)} Gift for ${cap(k1)} ${cap(ending)}`,
      `Cute ${cap(t0)} ${cap(product)} ${cap(k0)} ${cap(k1)} ${cap(ending)}`,
      `${cap(t1)} ${cap(product)} ${cap(l1)} Perfect ${cap(k0)} ${cap(ending)}`,
      `Handmade ${cap(t0)} ${cap(product)} ${cap(k2)} ${cap(l0)} ${cap(ending)}`,
      `${cap(k0)} ${cap(t0)} ${cap(product)} Trendy ${cap(l1)} ${cap(ending)}`,
      `Custom ${cap(t0)} ${cap(product)} ${cap(k1)} ${cap(l0)} ${cap(ending)}`,
      `${cap(l1)} ${cap(t1)} ${cap(product)} ${cap(k0)} ${cap(ending)}`,
      `Unique ${cap(t0)} ${cap(product)} ${cap(k2)} ${cap(k0)} ${cap(ending)}`,
      `${cap(t0)} ${cap(l0)} ${cap(product)} Perfect ${cap(k1)} ${cap(ending)}`
    ];

    templates.forEach(tmpl => {
      let cleaned = tmpl.replace(/\s+/g, ' ').trim();
      if (cleaned.length > 140) cleaned = cleaned.substring(0, 137) + '...';
      titles.push(cleaned);
    });
    return titles;
  }

  // --- Copy helpers ---
  function showToast() {
    const t = document.getElementById('toast');
    t.classList.remove('hidden');
    t.offsetWidth;
    setTimeout(() => t.classList.add('hidden'), 2000);
  }

  function copySection(id) {
    const el = document.getElementById(id);
    const text = el.innerText;
    navigator.clipboard.writeText(text).then(showToast);
  }

  function copyAll() {
    const tags = document.getElementById('tagsContent').innerText;
    const titles = document.getElementById('titlesContent').innerText;
    const prompt = document.getElementById('promptContent').innerText;
    const all = `TAGS:\n${tags}\n\nTITLES:\n${titles}\n\nCHATGPT PROMPT:\n${prompt}`;
    navigator.clipboard.writeText(all).then(showToast);
  }

  function showEmbedModal() {
    const embedCode = `<iframe src="YOUR_CANVA_CODE_URL" style="width:100%;height:800px;border:none;border-radius:8px;" title="Etsy SEO Optimizer"></iframe>`;
    document.getElementById('embedCode').textContent = embedCode;
    document.getElementById('embedSection').classList.remove('hidden');
  }

  function copyEmbedCode() {
    const embedCode = document.getElementById('embedCode').innerText;
    navigator.clipboard.writeText(embedCode).then(() => {
      showToast();
      setTimeout(() => {
        document.getElementById('embedSection').classList.add('hidden');
      }, 1500);
    });
  }

  lucide.createIcons();
</script>
 <script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'9ec708c685eff50a',t:'MTc3NjIxNTA0Ni4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>