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>
682 lines
38 KiB
HTML
682 lines
38 KiB
HTML
<!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 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 2–3 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 |