? Shortcuts
`; const blob = new Blob([html.replace('<\\/script>','')], { type: 'text/html' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `${s.name.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.html`; a.click(); } // Drag and drop let dragId = null; function dragStart(e, qId) { dragId = qId; e.target.classList.add('dragging'); } function dragOver(e) { e.preventDefault(); e.currentTarget.classList.add('drag-over'); } function dragEnd(e) { document.querySelectorAll('.drag-over,.dragging').forEach(el => el.classList.remove('drag-over','dragging')); } function drop(e, targetId) { e.preventDefault(); document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over')); if (!dragId || dragId === targetId) return; const s = getActiveSurvey(); if (!s) return; const fromIdx = s.questions.findIndex(q => q.id === dragId); const toIdx = s.questions.findIndex(q => q.id === targetId); const [moved] = s.questions.splice(fromIdx, 1); s.questions.splice(toIdx, 0, moved); dragId = null; save(); render(); } // Keyboard shortcuts document.addEventListener('keydown', e => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') { if (e.key === 'Escape') { e.target.blur(); return; } return; } if (e.key === '?') { state.showHelp = !state.showHelp; render(); return; } if (e.key === 'Escape') { state.showHelp = false; state.showTemplates = false; render(); return; } if (e.key === 'n' || e.key === 'N') { createSurvey(); return; } if (e.key === 'a' || e.key === 'A') { addQuestion('short_text'); return; } if (e.key === '/') { e.preventDefault(); document.getElementById('sidebarSearch')?.focus(); return; } if (e.key === 't' || e.key === 'T') { state.showTemplates = true; render(); return; } if (e.key === 'e' || e.key === 'E') { exportJSON(); return; } if (e.key === 'i' || e.key === 'I') { importJSON(); return; } if (e.key === 'd' || e.key === 'D') { const s = getActiveSurvey(); if (s) duplicateSurvey(s.id); return; } if (e.key === '1') { state.activeView = 'editor'; render(); return; } if (e.key === '2') { state.activeView = 'preview'; state.previewAnswers = {}; render(); return; } if (e.key === '3') { state.activeView = 'responses'; render(); return; } if (e.key === '4') { state.activeView = 'share'; render(); return; } }); // Demo data function seedDemo() { if (state.surveys.length > 0) return; // Create a UTMStamp user research survey with demo responses const survey = { id: uid(), name: 'UTMStamp User Research', description: 'Understanding how marketing teams track email signature performance', color: '#6c5ce7', createdAt: new Date().toISOString(), questions: [ { id: uid(), type: 'short_text', text: 'What is your role?', required: true, options: [], ratingMax: 5, description: '' }, { id: uid(), type: 'multiple_choice', text: 'How do you currently manage email signatures?', required: true, options: ['Manually in email client', 'IT manages centrally', 'Third-party tool (Exclaimer, etc.)', 'We don\'t manage them', 'Other'], ratingMax: 5, description: '' }, { id: uid(), type: 'rating', text: 'How important is tracking email signature clicks to you?', required: true, options: [], ratingMax: 5, description: '1 = Not important, 5 = Critical' }, { id: uid(), type: 'checkbox', text: 'Which features matter most to you?', required: false, options: ['Click tracking', 'UTM parameters', 'Campaign banners', 'Team deployment', 'Analytics dashboard', 'A/B testing'], ratingMax: 5, description: 'Select all that apply.' }, { id: uid(), type: 'nps', text: 'How likely are you to recommend UTMStamp to a colleague?', required: true, options: [], ratingMax: 10, description: '' }, { id: uid(), type: 'long_text', text: 'What\'s the biggest pain point with email signatures today?', required: false, options: [], ratingMax: 5, description: '' }, { id: uid(), type: 'multiple_choice', text: 'How much would you pay per user/month?', required: true, options: ['$0 — free only', '$1-3', '$3-5', '$5-10', '$10+'], ratingMax: 5, description: '' }, ], responses: [], }; // Generate 30 demo responses const roles = ['Marketing Manager','Head of Marketing','Growth Lead','Sales Director','CMO','Digital Marketer','Demand Gen','Content Lead','VP Marketing','Brand Manager']; const pains = [ 'No way to know if signatures drive any traffic.', 'Updating signatures across 50+ employees is a nightmare.', 'Can\'t A/B test different CTAs in signatures.', 'IT controls signatures and it takes weeks to change.', 'We paste UTMs manually — error-prone and inconsistent.', 'Zero visibility into which campaigns work.', 'Signatures look different across Outlook and Gmail.', 'No analytics at all. Flying blind.', ]; for (let i = 0; i < 30; i++) { const answers = {}; answers[survey.questions[0].id] = roles[Math.floor(Math.random()*roles.length)]; answers[survey.questions[1].id] = survey.questions[1].options[Math.floor(Math.random()*5)]; answers[survey.questions[2].id] = Math.random() < 0.6 ? Math.floor(Math.random()*2)+4 : Math.floor(Math.random()*3)+2; const feats = [...survey.questions[3].options].sort(() => Math.random()-0.5).slice(0, Math.floor(Math.random()*4)+1); answers[survey.questions[3].id] = feats; const w = Math.random(); answers[survey.questions[4].id] = w < 0.15 ? Math.floor(Math.random()*7) : w < 0.35 ? Math.floor(Math.random()*2)+7 : Math.floor(Math.random()*2)+9; answers[survey.questions[5].id] = pains[Math.floor(Math.random()*pains.length)]; answers[survey.questions[6].id] = survey.questions[6].options[Math.floor(Math.random()*5)]; survey.responses.push({ id: uid(), answers, submittedAt: new Date(Date.now() - Math.random()*14*86400000).toISOString() }); } state.surveys.push(survey); // PMF survey template const pmfSurvey = { id: uid(), name: 'Product-Market Fit Survey', description: 'The Sean Ellis test — would users be disappointed?', color: '#00b894', createdAt: new Date(Date.now() - 86400000).toISOString(), questions: TEMPLATES[3].questions.map(q => ({ ...q, id: uid() })), responses: [], }; state.surveys.push(pmfSurvey); state.activeSurveyId = survey.id; save(); } // Init load(); seedDemo(); render();