Files
resolutionflow/docs/plans/survey.html
chihlasm 932927b9df chore: archive old plan docs + add survey foundation files
Move completed plan docs to docs/plans/archive/. Add survey migration 046
and reference HTML/plan files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 02:03:38 -05:00

682 lines
38 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FlowPilot Survey — ResolutionFlow</title>
<meta name="description" content="Help shape FlowPilot, an AI assistant built for MSP engineers. 5-minute survey.">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--gradient-start: #818cf8;
--gradient-mid: #9389fa;
--gradient-end: #a78bfa;
--bg-deep: #050507;
--bg-dark: #09090b;
--bg-card: #111114;
--bg-input: #0c0c0f;
--border-subtle: #1e1e24;
--border-hover: #2a2a32;
--border-focus: #818cf8;
--text-primary: #f4f4f5;
--text-secondary: #a1a1aa;
--text-muted: #52525b;
--text-hint: #3f3f46;
--accent: #818cf8;
--accent-dim: rgba(129, 140, 248, 0.1);
--accent-glow: rgba(129, 140, 248, 0.06);
--success: #34d399;
--success-dim: rgba(52, 211, 153, 0.1);
}
body {
font-family: 'Inter', -apple-system, sans-serif;
background: var(--bg-deep);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
/* ── Ambient ── */
.ambient { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.orb { position: absolute; border-radius: 50%; filter: blur(140px); opacity: 0.05; }
.orb-1 { width: 600px; height: 600px; background: var(--gradient-start); top: -200px; left: -100px; animation: d1 25s ease-in-out infinite; }
.orb-2 { width: 500px; height: 500px; background: var(--gradient-end); bottom: -150px; right: -100px; animation: d2 30s ease-in-out infinite; }
@keyframes d1 { 0%,100% { transform: translate(0,0); } 50% { transform: translate(40px,50px); } }
@keyframes d2 { 0%,100% { transform: translate(0,0); } 50% { transform: translate(-30px,-40px); } }
.grid-pattern {
position: fixed; inset: 0; z-index: 0; pointer-events: none;
background-image: linear-gradient(rgba(129,140,248,0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(129,140,248,0.02) 1px, transparent 1px);
background-size: 60px 60px;
mask-image: radial-gradient(ellipse 70% 50% at 50% 30%, black, transparent);
}
/* ── Layout ── */
.container { position: relative; z-index: 1; max-width: 680px; margin: 0 auto; padding: 0 20px 100px; }
/* ── Top Bar ── */
.topbar {
position: sticky; top: 0; z-index: 100;
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
background: rgba(5,5,7,0.85); border-bottom: 1px solid var(--border-subtle);
padding: 14px 20px; margin: 0 -20px;
display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.topbar-brand { display: flex; align-items: center; gap: 10px; font-family: 'Plus Jakarta Sans', sans-serif; font-weight: 700; font-size: 14px; color: var(--text-secondary); white-space: nowrap; text-decoration: none; }
.topbar-logo { width: 28px; height: 28px; background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end)); border-radius: 7px; display: flex; align-items: center; justify-content: center; font-size: 14px; color: white; font-weight: 800; flex-shrink: 0; }
.topbar-brand .flow { background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
.progress-wrap { flex: 1; max-width: 240px; display: flex; align-items: center; gap: 10px; }
.progress-bar { flex: 1; height: 3px; background: var(--border-subtle); border-radius: 2px; overflow: hidden; }
.progress-fill { height: 100%; background: linear-gradient(90deg, var(--gradient-start), var(--gradient-end)); border-radius: 2px; transition: width 0.5s ease; width: 0%; }
.progress-text { font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* ── Hero ── */
.hero { text-align: center; padding: 72px 0 40px; }
.hero-badge { display: inline-flex; align-items: center; gap: 6px; padding: 5px 14px; border-radius: 100px; background: var(--accent-dim); border: 1px solid rgba(129,140,248,0.15); font-size: 11px; font-weight: 600; color: var(--accent); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 20px; }
.hero h1 { font-family: 'Plus Jakarta Sans', sans-serif; font-size: clamp(26px, 5vw, 36px); font-weight: 800; line-height: 1.2; margin-bottom: 12px; }
.hero h1 span { background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
.hero p { font-size: 15px; color: var(--text-secondary); max-width: 500px; margin: 0 auto; line-height: 1.65; }
.hero-stats { display: flex; justify-content: center; gap: 28px; margin-top: 20px; font-size: 12px; color: var(--text-muted); }
.hero-stats span { display: flex; align-items: center; gap: 5px; }
.hero-stats svg { width: 13px; height: 13px; stroke: var(--accent); fill: none; stroke-width: 2; }
/* ── Steps indicator ── */
.steps-bar { display: flex; gap: 4px; margin-bottom: 36px; }
.step-dot { flex: 1; height: 3px; border-radius: 2px; background: var(--border-subtle); transition: background 0.3s; }
.step-dot.done { background: var(--success); }
.step-dot.active { background: linear-gradient(90deg, var(--gradient-start), var(--gradient-end)); }
/* ── Form Slides ── */
.slide { display: none; animation: fadeIn 0.35s ease; }
.slide.active { display: block; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
/* ── Question Card ── */
.q-card {
background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 14px;
padding: 28px; margin-bottom: 16px; transition: border-color 0.2s;
}
.q-card:focus-within { border-color: rgba(129,140,248,0.25); }
.q-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--accent); margin-bottom: 6px; font-weight: 500; }
.q-text { font-family: 'Plus Jakarta Sans', sans-serif; font-size: 15px; font-weight: 600; color: var(--text-primary); line-height: 1.5; margin-bottom: 4px; }
.q-hint { font-size: 12px; color: var(--text-muted); margin-bottom: 16px; line-height: 1.5; }
/* ── MC Options ── */
.mc-options { display: flex; flex-direction: column; gap: 8px; }
.mc-opt {
display: flex; align-items: center; gap: 12px;
padding: 12px 16px; border-radius: 9px;
border: 1px solid var(--border-subtle); background: var(--bg-input);
cursor: pointer; transition: all 0.15s; font-size: 14px; color: var(--text-secondary);
user-select: none;
}
.mc-opt:hover { border-color: var(--border-hover); color: var(--text-primary); }
.mc-opt.selected { border-color: var(--accent); background: var(--accent-dim); color: var(--text-primary); }
.mc-radio { width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--border-subtle); flex-shrink: 0; transition: all 0.15s; display: flex; align-items: center; justify-content: center; }
.mc-opt.selected .mc-radio { border-color: var(--accent); }
.mc-opt.selected .mc-radio::after { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
.mc-check { width: 18px; height: 18px; border-radius: 5px; border: 2px solid var(--border-subtle); flex-shrink: 0; transition: all 0.15s; display: flex; align-items: center; justify-content: center; font-size: 11px; color: transparent; }
.mc-opt.selected .mc-check { border-color: var(--accent); background: var(--accent); color: white; }
/* ── Range Slider ── */
.range-wrap { padding: 8px 0; }
.range-labels { display: flex; justify-content: space-between; font-size: 11px; color: var(--text-muted); margin-bottom: 10px; }
.range-val { text-align: center; font-family: 'JetBrains Mono', monospace; font-size: 24px; font-weight: 600; color: var(--accent); margin-bottom: 12px; }
input[type="range"] {
-webkit-appearance: none; width: 100%; height: 4px; border-radius: 2px;
background: var(--border-subtle); outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; width: 20px; height: 20px; border-radius: 50%;
background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
cursor: pointer; border: 3px solid var(--bg-card); box-shadow: 0 0 0 1px rgba(129,140,248,0.3);
}
input[type="range"]::-moz-range-thumb {
width: 20px; height: 20px; border-radius: 50%;
background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
cursor: pointer; border: 3px solid var(--bg-card);
}
/* ── Textarea ── */
textarea {
width: 100%; min-height: 100px; background: var(--bg-input); border: 1px solid var(--border-subtle);
border-radius: 9px; padding: 14px 16px; color: var(--text-primary); font-family: 'Inter', sans-serif;
font-size: 14px; line-height: 1.6; resize: vertical; transition: border-color 0.2s, box-shadow 0.2s;
}
textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
textarea::placeholder { color: var(--text-hint); }
/* ── Drag Ranking ── */
.rank-list { display: flex; flex-direction: column; gap: 6px; }
.rank-item {
display: flex; align-items: center; gap: 12px;
padding: 11px 16px; border-radius: 9px;
border: 1px solid var(--border-subtle); background: var(--bg-input);
cursor: grab; transition: all 0.15s; font-size: 14px; color: var(--text-secondary);
user-select: none;
}
.rank-item:active { cursor: grabbing; }
.rank-item.dragging { opacity: 0.5; border-color: var(--accent); }
.rank-item.drag-over { border-color: var(--accent); background: var(--accent-dim); }
.rank-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--accent); font-weight: 600; width: 20px; text-align: center; flex-shrink: 0; }
.rank-grip { color: var(--text-hint); flex-shrink: 0; display: flex; align-items: center; }
.rank-label { flex: 1; }
/* ── Scenario Box ── */
.scenario-box {
background: linear-gradient(135deg, rgba(129,140,248,0.06), rgba(167,139,250,0.03));
border: 1px solid rgba(129,140,248,0.12); border-radius: 10px;
padding: 16px 20px; margin-bottom: 16px; font-size: 13px;
}
.scenario-box .sc-label { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--accent); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px; font-weight: 600; }
.scenario-box .sc-row { display: flex; gap: 8px; margin-bottom: 4px; }
.scenario-box .sc-key { color: var(--text-muted); font-weight: 500; white-space: nowrap; }
.scenario-box .sc-val { color: var(--text-secondary); }
/* ── Buttons ── */
.nav-btns { display: flex; justify-content: space-between; margin-top: 28px; gap: 12px; }
.btn {
font-family: 'Inter', sans-serif; font-size: 14px; font-weight: 600; padding: 12px 24px;
border-radius: 9px; border: none; cursor: pointer; transition: all 0.2s;
display: inline-flex; align-items: center; gap: 8px;
}
.btn-ghost { background: transparent; border: 1px solid var(--border-subtle); color: var(--text-secondary); }
.btn-ghost:hover { border-color: var(--accent); color: var(--text-primary); }
.btn-accent { background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end)); color: white; }
.btn-accent:hover { opacity: 0.9; transform: translateY(-1px); }
.btn-accent:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
.btn-success { background: var(--success-dim); border: 1px solid rgba(52,211,153,0.3); color: var(--success); }
.btn-success:hover { background: rgba(52,211,153,0.15); }
/* ── Completion ── */
.complete { display: none; text-align: center; padding: 60px 0; animation: fadeIn 0.4s ease; }
.complete.active { display: block; }
.complete-icon { width: 64px; height: 64px; margin: 0 auto 20px; background: var(--success-dim); border-radius: 50%; display: flex; align-items: center; justify-content: center; }
.complete-icon svg { width: 28px; height: 28px; stroke: var(--success); fill: none; stroke-width: 2.5; }
.complete h2 { font-family: 'Plus Jakarta Sans', sans-serif; font-size: 24px; font-weight: 700; margin-bottom: 10px; }
.complete p { color: var(--text-secondary); font-size: 14px; max-width: 440px; margin: 0 auto 28px; line-height: 1.65; }
.complete-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
/* ── Toast ── */
.toast { position: fixed; bottom: 20px; right: 20px; z-index: 200; padding: 10px 18px; border-radius: 8px; font-size: 13px; font-weight: 500; transform: translateY(80px); opacity: 0; transition: all 0.3s ease; background: var(--success-dim); border: 1px solid rgba(52,211,153,0.3); color: var(--success); }
.toast.show { transform: translateY(0); opacity: 1; }
@media (max-width: 640px) {
.container { padding: 0 16px 80px; }
.topbar { padding: 12px 16px; margin: 0 -16px; }
.hero { padding: 56px 0 28px; }
.hero-stats { flex-wrap: wrap; gap: 12px; }
.q-card { padding: 20px; }
}
</style>
</head>
<body>
<div class="ambient"><div class="orb orb-1"></div><div class="orb orb-2"></div></div>
<div class="grid-pattern"></div>
<div class="container">
<!-- Top Bar -->
<div class="topbar">
<a class="topbar-brand" href="https://resolutionflow.com" target="_blank">
<div class="topbar-logo">R</div>
Resolution<span class="flow">Flow</span>
</a>
<div class="progress-wrap">
<div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>
<span class="progress-text" id="progressText">0 of 16</span>
</div>
</div>
<!-- Hero -->
<div class="hero" id="heroSection">
<div class="hero-badge">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
FlowPilot Research
</div>
<h1>Help Build an AI That<br>Thinks Like <span>You</span></h1>
<p>We're building an AI assistant for MSP engineers. Your expertise shapes how it thinks. Takes about 5 minutes.</p>
<div class="hero-stats">
<span><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>~5 minutes</span>
<span><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>Confidential</span>
<span><svg viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>16 questions</span>
</div>
</div>
<!-- Steps -->
<div class="steps-bar" id="stepsBar"></div>
<!-- Slides -->
<div id="slidesWrap"></div>
<!-- Completion -->
<div class="complete" id="completeScreen">
<div class="complete-icon"><svg viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg></div>
<h2>Done — Thank You!</h2>
<p>Your answers will directly shape how FlowPilot troubleshoots. Please send your responses using one of the options below.</p>
<div class="complete-actions">
<button class="btn btn-accent" onclick="copyAll()">Copy Responses to Clipboard</button>
<button class="btn btn-success" onclick="downloadText()">Download as Text</button>
</div>
<p style="margin-top: 24px; font-size: 12px; color: var(--text-muted);">Paste into an email to <strong style="color: var(--text-secondary);"><a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a1ccc8c2c9c0c4cde1d3c4d2cecdd4d5c8cecfc7cdced68fc2cecc">[email&#160;protected]</a></strong> or hand off however works best.</p>
</div>
</div>
<div class="toast" id="toast"></div>
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script>
// ══════════════════════════════════════════
// ██ SURVEY DATA — 16 questions
// ══════════════════════════════════════════
const SLIDES = [
// ── Slide 0: Pre-Troubleshooting & Preferences ──
{
id: "prework",
questions: [
{ id: "prereqs", type: "mc-multi", num: "1", text: "Before you start troubleshooting, what info do you need? (Select all that apply)", hint: "What do you gather or verify before you even start diagnosing?",
options: ["Who's affected (one user, group, everyone)", "What changed recently (patches, config, new software)", "Is there an existing/related ticket", "Client environment details (domain, VPN, on-prem vs. cloud)", "How long the issue has been happening", "User's exact error message or screenshot", "Whether the user has already tried anything", "RMM/monitoring status for the device or service"] },
{ id: "verify_fix", type: "mc", num: "2", text: "After you apply a fix, how do you verify it actually worked?", hint: null,
options: ["Have the user confirm it's working", "Test it myself from their machine or account", "Run the same diagnostic command that showed the failure", "Check monitoring/logs to confirm the service is healthy", "All of the above in sequence", "Fix it and move on — if they don't call back, it worked"] },
{ id: "steps_at_a_time", type: "range", num: "3", text: "When following a troubleshooting guide, how many steps do you prefer to see at once?", hint: "1 = show me one step at a time, 10 = give me the full picture upfront.",
min: 1, max: 10, step: 1, suffix: " steps", low_label: "One at a time", high_label: "Show me everything" },
]
},
// ── Slide 1: Troubleshooting Philosophy ──
{
id: "philosophy",
questions: [
{ id: "first_step", type: "mc", num: "4", text: "A vague ticket comes in: \"Internet is down.\" What's your FIRST move?", hint: "Before any technical check — what's the first mental step?",
options: ["Check if it's one user or many (scope it)", "Look at monitoring / RMM dashboard", "Check for recent changes or maintenance", "Ask the user clarifying questions", "Ping / basic connectivity test", "Check if other tickets are related"] },
{ id: "junior_mistake", type: "mc", num: "5", text: "What's the most common mistake you see junior engineers make?", hint: null,
options: ["Jumping to a fix before understanding the problem", "Not checking scope (one user vs. many)", "Ignoring recent changes as a cause", "Googling the error instead of reading it", "Restarting things without checking logs first", "Not documenting what they tried"] },
{ id: "pivot", type: "mc", num: "6", text: "How do you decide when to stop pursuing one theory and pivot?", hint: null,
options: ["After 23 checks that don't support the theory", "Time-based — if I've spent 15+ min with no progress", "When I find evidence that contradicts the theory", "Gut feeling / experience", "When I run out of ideas on that path"] },
]
},
// ── Slide 2: Scenario ──
{
id: "scenario",
scenario: {
title: "Incoming Ticket",
symptom: "Multiple users can't access \\\\fileserver\\shared",
details: "Started ~9 AM. Some users still can. No known recent changes. Server appears online."
},
questions: [
{ id: "scenario_approach", type: "text", num: "7",
text: "Walk through your first 3 diagnostic steps for this ticket.",
hint: "Include specific commands/tools, what you expect to see, and what a bad result tells you." },
{ id: "scenario_deeper", type: "text", num: "8",
text: "Server pings fine, you can RDP in. What do you check next on the server itself?",
hint: "Services, shares, permissions, event logs — what's your sequence?" },
]
},
// ── Slide 3: Commands & Tools ──
{
id: "commands",
questions: [
{ id: "doc_pct", type: "range", num: "9",
text: "Be honest: what percentage of your troubleshooting steps do you actually document in the ticket?",
hint: null, min: 0, max: 100, step: 10, suffix: "%", low_label: "Almost none", high_label: "Everything" },
{ id: "go_to_commands", type: "text", num: "10",
text: "What are your top 3 go-to PowerShell commands or one-liners? Include exact syntax.",
hint: "The ones you type from muscle memory — we're building FlowPilot's command library from real usage." },
{ id: "secret_weapon", type: "text", num: "11",
text: "Name a command, tool, or technique that junior engineers don't know about but saves you significant time.",
hint: "The secret weapon stuff. This is gold." },
]
},
// ── Slide 4: Tribal Knowledge ──
{
id: "tribal",
questions: [
{ id: "gotcha", type: "text", num: "12",
text: "Describe an issue where the obvious diagnosis was WRONG. What did everyone assume, and what was it actually?",
hint: "These 'gotcha' patterns help FlowPilot warn engineers before they go down the wrong path." },
{ id: "hard_rules", type: "mc-multi", num: "13",
text: "Which of these \"rules\" do you follow? (Select all that apply)",
hint: null,
options: [
"Always check recent changes before deep-diving",
"Screenshot/document current state before making changes",
"Never restart a service without checking logs first",
"Verify the user's report before troubleshooting",
"Check if it's a known issue / existing ticket first",
"Test the fix, don't assume it worked",
"Always have a rollback plan"
]},
]
},
// ── Slide 5: Domain Ranking ──
{
id: "ranking",
questions: [
{ id: "domain_rank", type: "rank", num: "14",
text: "Drag to rank: which technical domains should FlowPilot handle first?",
hint: "Most important at the top.",
items: [
"Windows Server / Active Directory",
"Microsoft 365 / Exchange",
"Networking (DNS, DHCP, VPN, Firewall)",
"Security / Compliance",
"Virtualization (Hyper-V, VMware)",
"Backup & Disaster Recovery",
"Cloud (Azure / AWS)",
"Endpoint Management"
]},
]
},
// ── Slide 6: FlowPilot Feedback ──
{
id: "flowpilot",
questions: [
{ id: "detail_level", type: "mc", num: "15",
text: "When an AI suggests a diagnostic step, how specific should it be?",
hint: null,
options: [
"High-level only (\"Check DNS resolution\")",
"Moderate (\"Run nslookup against the domain controller\")",
"Exact syntax (\"nslookup hostname dc01.domain.local — verify IP matches expected\")",
"Exact syntax + explanation of WHY and what to look for"
]},
{ id: "ai_personality", type: "mc", num: "16",
text: "What would make an AI troubleshooting assistant feel like a useful colleague instead of a chatbot?",
hint: null,
options: [
"Suggests things I haven't thought of yet",
"Knows the right diagnostic order (not just alphabetical)",
"Challenges my assumptions constructively",
"Includes real commands with exact syntax",
"Explains WHY we check things in a certain order",
"Doesn't waste my time with obvious stuff"
]},
]
},
];
// ══════════════════════════════════════════
// ██ STATE
// ══════════════════════════════════════════
let currentSlide = 0;
const answers = {};
const totalQuestions = SLIDES.reduce((n, s) => n + s.questions.length, 0);
// ══════════════════════════════════════════
// ██ RENDER
// ══════════════════════════════════════════
function init() {
// Steps bar
const bar = document.getElementById("stepsBar");
bar.innerHTML = SLIDES.map((_, i) => `<div class="step-dot" id="step-${i}"></div>`).join("");
// Slides
const wrap = document.getElementById("slidesWrap");
wrap.innerHTML = SLIDES.map((slide, si) => {
let html = `<div class="slide" id="slide-${si}">`;
// Scenario box
if (slide.scenario) {
html += `<div class="scenario-box">
<div class="sc-label">${slide.scenario.title}</div>
<div class="sc-row"><span class="sc-key">Symptom:</span><span class="sc-val">${slide.scenario.symptom}</span></div>
<div class="sc-row"><span class="sc-key">Details:</span><span class="sc-val">${slide.scenario.details}</span></div>
</div>`;
}
// Questions
slide.questions.forEach(q => {
html += `<div class="q-card" id="card-${q.id}">`;
html += `<div class="q-num">Q${q.num}</div>`;
html += `<div class="q-text">${q.text}</div>`;
if (q.hint) html += `<div class="q-hint">${q.hint}</div>`;
if (q.type === "mc") {
html += `<div class="mc-options" data-qid="${q.id}" data-type="single">`;
q.options.forEach((opt, oi) => {
html += `<div class="mc-opt" data-val="${opt}" onclick="selectMC(this, '${q.id}', 'single')"><div class="mc-radio"></div><span>${opt}</span></div>`;
});
html += `</div>`;
}
else if (q.type === "mc-multi") {
html += `<div class="mc-options" data-qid="${q.id}" data-type="multi">`;
q.options.forEach((opt, oi) => {
html += `<div class="mc-opt" data-val="${opt}" onclick="selectMC(this, '${q.id}', 'multi')"><div class="mc-check"></div><span>${opt}</span></div>`;
});
html += `</div>`;
}
else if (q.type === "range") {
html += `<div class="range-wrap">
<div class="range-val" id="rv-${q.id}">${q.min}${q.suffix || ''}</div>
<input type="range" min="${q.min}" max="${q.max}" step="${q.step}" value="${q.min}" oninput="onRange('${q.id}', this.value, '${q.suffix || ''}')">
<div class="range-labels"><span>${q.low_label}</span><span>${q.high_label}</span></div>
</div>`;
}
else if (q.type === "text") {
html += `<textarea id="ta-${q.id}" placeholder="Type your answer here..." oninput="onText('${q.id}', this.value)"></textarea>`;
}
else if (q.type === "rank") {
html += `<div class="rank-list" id="rank-${q.id}">`;
q.items.forEach((item, ii) => {
html += `<div class="rank-item" draggable="true" data-idx="${ii}" data-qid="${q.id}">
<div class="rank-grip"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg></div>
<div class="rank-num">${ii + 1}</div>
<div class="rank-label">${item}</div>
</div>`;
});
html += `</div>`;
}
html += `</div>`; // close q-card
});
// Nav
html += `<div class="nav-btns">`;
if (si > 0) html += `<button class="btn btn-ghost" onclick="goSlide(${si - 1})"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg> Back</button>`;
else html += `<div></div>`;
if (si < SLIDES.length - 1) html += `<button class="btn btn-accent" onclick="goSlide(${si + 1})">Next <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14"/><path d="M12 5l7 7-7 7"/></svg></button>`;
else html += `<button class="btn btn-accent" onclick="finish()">Submit <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg></button>`;
html += `</div>`;
html += `</div>`; // close slide
return html;
}).join("");
goSlide(0);
initDragDrop();
}
function goSlide(idx) {
document.querySelectorAll(".slide").forEach(s => s.classList.remove("active"));
document.getElementById(`slide-${idx}`).classList.add("active");
document.getElementById("completeScreen").classList.remove("active");
currentSlide = idx;
// Steps
SLIDES.forEach((_, i) => {
const dot = document.getElementById(`step-${i}`);
dot.className = "step-dot" + (i < idx ? " done" : "") + (i === idx ? " active" : "");
});
// Hero shrinks after first advance
document.getElementById("heroSection").style.display = idx === 0 ? "" : "none";
window.scrollTo({ top: 0, behavior: "smooth" });
updateProgress();
}
//
// INTERACTIONS
//
function selectMC(el, qid, mode) {
if (mode === "single") {
el.parentElement.querySelectorAll(".mc-opt").forEach(o => o.classList.remove("selected"));
el.classList.add("selected");
answers[qid] = el.dataset.val;
} else {
el.classList.toggle("selected");
const selected = [...el.parentElement.querySelectorAll(".mc-opt.selected")].map(o => o.dataset.val);
answers[qid] = selected;
}
updateProgress();
}
function onRange(qid, val, suffix) {
document.getElementById(`rv-${qid}`).textContent = val + suffix;
answers[qid] = val + suffix;
updateProgress();
}
function onText(qid, val) {
answers[qid] = val.trim();
updateProgress();
}
// Drag and drop ranking
function initDragDrop() {
document.querySelectorAll(".rank-list").forEach(list => {
let dragItem = null;
list.querySelectorAll(".rank-item").forEach(item => {
item.addEventListener("dragstart", e => {
dragItem = item;
item.classList.add("dragging");
e.dataTransfer.effectAllowed = "move";
});
item.addEventListener("dragend", () => {
item.classList.remove("dragging");
list.querySelectorAll(".rank-item").forEach(i => i.classList.remove("drag-over"));
// Update numbers and store answer
const qid = item.dataset.qid;
const items = [...list.querySelectorAll(".rank-item")];
items.forEach((it, idx) => it.querySelector(".rank-num").textContent = idx + 1);
answers[qid] = items.map(it => it.querySelector(".rank-label").textContent);
updateProgress();
});
item.addEventListener("dragover", e => {
e.preventDefault();
if (item !== dragItem) item.classList.add("drag-over");
});
item.addEventListener("dragleave", () => item.classList.remove("drag-over"));
item.addEventListener("drop", e => {
e.preventDefault();
item.classList.remove("drag-over");
if (item !== dragItem && dragItem) {
const allItems = [...list.querySelectorAll(".rank-item")];
const fromIdx = allItems.indexOf(dragItem);
const toIdx = allItems.indexOf(item);
if (fromIdx < toIdx) item.after(dragItem);
else item.before(dragItem);
}
});
});
// Touch drag support
let touchItem = null;
let touchClone = null;
let touchStartY = 0;
list.addEventListener("touchstart", e => {
const item = e.target.closest(".rank-item");
if (!item) return;
touchItem = item;
touchStartY = e.touches[0].clientY;
item.classList.add("dragging");
}, { passive: true });
list.addEventListener("touchmove", e => {
if (!touchItem) return;
e.preventDefault();
const touch = e.touches[0];
const items = [...list.querySelectorAll(".rank-item")];
items.forEach(i => i.classList.remove("drag-over"));
const target = document.elementFromPoint(touch.clientX, touch.clientY)?.closest(".rank-item");
if (target && target !== touchItem) target.classList.add("drag-over");
}, { passive: false });
list.addEventListener("touchend", e => {
if (!touchItem) return;
const touch = e.changedTouches[0];
const target = document.elementFromPoint(touch.clientX, touch.clientY)?.closest(".rank-item");
if (target && target !== touchItem) {
const allItems = [...list.querySelectorAll(".rank-item")];
const fromIdx = allItems.indexOf(touchItem);
const toIdx = allItems.indexOf(target);
if (fromIdx < toIdx) target.after(touchItem);
else target.before(touchItem);
}
touchItem.classList.remove("dragging");
list.querySelectorAll(".rank-item").forEach(i => i.classList.remove("drag-over"));
const qid = touchItem.dataset.qid;
const items = [...list.querySelectorAll(".rank-item")];
items.forEach((it, idx) => it.querySelector(".rank-num").textContent = idx + 1);
answers[qid] = items.map(it => it.querySelector(".rank-label").textContent);
touchItem = null;
updateProgress();
});
});
// Set initial rank answers
document.querySelectorAll(".rank-list").forEach(list => {
const items = [...list.querySelectorAll(".rank-item")];
if (items.length > 0) {
const qid = items[0].dataset.qid;
answers[qid] = items.map(it => it.querySelector(".rank-label").textContent);
}
});
}
// ══════════════════════════════════════════
// ██ PROGRESS
// ══════════════════════════════════════════
function updateProgress() {
let answered = 0;
SLIDES.forEach(s => s.questions.forEach(q => {
const a = answers[q.id];
if (a !== undefined && a !== "" && (!Array.isArray(a) || a.length > 0)) answered++;
}));
// Rank always counts as answered since it has a default order
const pct = Math.round((answered / totalQuestions) * 100);
document.getElementById("progressFill").style.width = pct + "%";
document.getElementById("progressText").textContent = `${answered} of ${totalQuestions}`;
}
// ══════════════════════════════════════════
// ██ FINISH & EXPORT
// ══════════════════════════════════════════
function finish() {
document.querySelectorAll(".slide").forEach(s => s.classList.remove("active"));
document.getElementById("heroSection").style.display = "none";
document.getElementById("stepsBar").style.display = "none";
document.getElementById("completeScreen").classList.add("active");
window.scrollTo({ top: 0, behavior: "smooth" });
}
function buildOutput() {
let out = "FLOWPILOT SURVEY RESPONSE\n========================\n\n";
SLIDES.forEach(slide => {
slide.questions.forEach(q => {
out += `Q${q.num}. ${q.text}\n`;
const a = answers[q.id];
if (Array.isArray(a)) out += a.map((v, i) => ` ${i + 1}. ${v}`).join("\n") + "\n";
else if (a) out += ` ${a}\n`;
else out += ` (no answer)\n`;
out += "\n";
});
});
return out;
}
function copyAll() {
navigator.clipboard.writeText(buildOutput()).then(() => toast("Copied! Paste into an email and send.")).catch(() => toast("Copy failed — try download instead."));
}
function downloadText() {
const blob = new Blob([buildOutput()], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url; a.download = "flowpilot-survey-response.txt";
document.body.appendChild(a); a.click(); document.body.removeChild(a);
URL.revokeObjectURL(url);
t