Files
thariqs--html-effectiveness/unknowns/06-interview.html
T
2026-07-13 12:33:58 +08:00

645 lines
27 KiB
HTML
Raw 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.
<!-- Copyright 2026 Anthropic PBC · SPDX-License-Identifier: Apache-2.0 -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The interview — Know your unknowns</title>
<style>
:root {
--ivory: #FAF9F5;
--paper: #FFFFFF;
--slate: #141413;
--clay: #D97757;
--clay-d: #B85C3E;
--oat: #E3DACC;
--olive: #788C5D;
--g100: #F0EEE6;
--g200: #E6E3DA;
--g300: #D1CFC5;
--g500: #87867F;
--g700: #3D3D3A;
--serif: ui-serif, Georgia, "Times New Roman", Times, serif;
--sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--mono: ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--ivory);
color: var(--slate);
font-family: var(--sans);
line-height: 1.55;
-webkit-font-smoothing: antialiased;
}
.wrap { max-width: 980px; margin: 0 auto; padding: 0 24px 100px; }
/* ── header ─────────────────────────── */
header { padding: 56px 0 8px; position: relative; }
.eyebrow {
font-family: var(--mono);
font-size: 12px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--g500);
margin-bottom: 18px;
display: flex;
align-items: center;
gap: 12px;
}
.eyebrow::before { content: ""; width: 24px; height: 1.5px; background: var(--clay); flex: none; }
.backlink {
position: absolute; top: 60px; right: 0;
font-family: var(--mono); font-size: 12px;
color: var(--g500); text-decoration: none;
letter-spacing: 0.04em;
}
.backlink:hover { color: var(--clay); }
h1 {
font-family: var(--serif);
font-weight: 500;
font-size: clamp(34px, 5vw, 50px);
line-height: 1.08;
letter-spacing: -0.018em;
margin: 0 0 10px;
}
.lede { font-size: 16.5px; color: var(--g700); max-width: 640px; margin: 0 0 30px; }
/* ── prompt box ─────────────────────── */
.promptbox {
background: var(--paper);
border: 1.5px solid var(--g300);
border-radius: 14px;
padding: 22px 24px 20px;
position: relative;
}
.promptbox .label {
font-family: var(--mono); font-size: 12px;
letter-spacing: 0.12em; text-transform: uppercase;
color: var(--clay); margin-bottom: 10px;
display: flex; align-items: center; gap: 12px;
}
.promptbox .label::before { content: ""; width: 24px; height: 1.5px; background: var(--clay); flex: none; }
.promptbox blockquote {
margin: 0; padding: 0;
font-family: var(--serif); font-style: italic;
font-size: 17.5px; line-height: 1.5; color: var(--slate);
max-width: 46em;
}
.copybtn {
position: absolute; top: 18px; right: 18px;
font-family: var(--mono); font-size: 11.5px;
letter-spacing: 0.04em;
background: var(--g100); color: var(--g700);
border: 1.5px solid var(--g300); border-radius: 7px;
padding: 5px 11px; cursor: pointer;
}
.copybtn:hover { border-color: var(--clay); color: var(--clay-d); }
.copybtn.done { background: var(--olive); border-color: var(--olive); color: #fff; }
/* ── divider ────────────────────────── */
.divider {
display: flex; align-items: center; gap: 16px;
margin: 44px 0 30px;
font-family: var(--mono); font-size: 12px;
letter-spacing: 0.12em; text-transform: uppercase;
color: var(--g500); white-space: nowrap;
}
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--g300); }
/* ══ THE ARTIFACT ═════════════════════ */
.artifact {
background: var(--paper);
border: 1.5px solid var(--g300);
border-radius: 14px;
overflow: hidden;
}
.art-head {
padding: 16px 22px;
border-bottom: 1.5px solid var(--g200);
display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
background: var(--g100);
}
.art-head .t { font-family: var(--serif); font-weight: 500; font-size: 17px; letter-spacing: -0.01em; }
.art-head .s { font-family: var(--mono); font-size: 11.5px; color: var(--g500); }
.art-body { display: grid; grid-template-columns: 216px 1fr; min-height: 480px; }
@media (max-width: 720px) { .art-body { grid-template-columns: 1fr; min-height: 0; } }
/* rail */
.rail {
border-right: 1.5px solid var(--g200);
padding: 20px 16px;
background: var(--ivory);
}
@media (max-width: 720px) {
.rail { border-right: none; border-bottom: 1.5px solid var(--g200); padding: 14px 16px; }
}
.rail .rail-label {
font-family: var(--mono); font-size: 10.5px;
letter-spacing: 0.12em; text-transform: uppercase;
color: var(--g500); margin-bottom: 12px;
}
.rail-item {
display: flex; align-items: center; gap: 10px;
padding: 6px 8px; margin: 0 -8px;
border-radius: 8px;
font-size: 12.5px; color: var(--g500);
cursor: default;
border: none; background: none; width: calc(100% + 16px);
text-align: left; font-family: var(--sans);
}
.rail-item.done { color: var(--g700); cursor: pointer; }
.rail-item.done:hover { background: var(--g100); }
.rail-item.current { color: var(--slate); font-weight: 600; background: var(--g100); }
.rail-dot {
width: 11px; height: 11px; border-radius: 50%; flex: none;
border: 2px solid var(--dotc, var(--g300));
background: transparent;
}
.rail-item.done .rail-dot { background: var(--dotc); }
.rail-item.current .rail-dot { box-shadow: 0 0 0 3px var(--g200); }
.rail-item .rail-q { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-item .tick { margin-left: auto; font-size: 11px; color: var(--olive); flex: none; }
.rail-meta {
margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--g200);
font-family: var(--mono); font-size: 11px; color: var(--g500); line-height: 1.8;
}
.rail-meta b { color: var(--g700); font-weight: 600; }
@media (max-width: 720px) {
.rail .rail-q, .rail .tick, .rail-meta, .rail .rail-label { display: none; }
.rail { display: flex; align-items: center; gap: 10px; }
.rail-item { width: auto; padding: 4px; margin: 0; }
}
/* stage */
.stage { padding: 28px 30px 30px; position: relative; }
@media (max-width: 720px) { .stage { padding: 22px 18px 24px; } }
.qcard { animation: qin .28s ease both; }
.qcard.leaving { animation: qout .18s ease both; }
@keyframes qin { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes qout { from { opacity: 1; } to { opacity: 0; transform: translateY(-8px); } }
.badge {
display: inline-block;
font-family: var(--mono); font-size: 11px;
letter-spacing: 0.1em; text-transform: uppercase;
padding: 3px 9px; border-radius: 5px;
color: #fff; background: var(--g500);
margin-bottom: 4px;
}
.qnum { font-family: var(--mono); font-size: 11.5px; color: var(--g500); margin-left: 10px; }
.qtext {
font-family: var(--serif); font-weight: 500;
font-size: clamp(20px, 3vw, 25px); line-height: 1.25;
letter-spacing: -0.012em;
margin: 10px 0 6px; max-width: 34em;
}
.why {
font-size: 13.5px; color: var(--g500); margin: 0 0 22px;
display: flex; gap: 8px; align-items: baseline;
}
.why::before { content: "↳"; color: var(--clay); flex: none; }
.opts { display: grid; gap: 10px; grid-template-columns: 1fr 1fr; }
@media (max-width: 620px) { .opts { grid-template-columns: 1fr; } }
.opt {
text-align: left;
background: var(--ivory);
border: 1.5px solid var(--g300);
border-radius: 11px;
padding: 14px 16px;
cursor: pointer;
font-family: var(--sans);
color: var(--slate);
transition: border-color .12s, background .12s, transform .12s;
}
.opt:hover { border-color: var(--clay); background: var(--paper); transform: translateY(-1px); }
.opt.picked { border-color: var(--clay); background: var(--paper); box-shadow: 0 0 0 3px rgba(217,119,87,.15); }
.opt .ot { font-weight: 600; font-size: 14.5px; display: block; margin-bottom: 3px; }
.opt .od { font-size: 12.5px; color: var(--g500); line-height: 1.45; display: block; }
.otherrow { display: flex; gap: 8px; margin-top: 12px; }
.otherrow input {
flex: 1; min-width: 0;
font-family: var(--sans); font-size: 14px;
padding: 10px 13px;
border: 1.5px solid var(--g300); border-radius: 9px;
background: var(--ivory); color: var(--slate);
}
.otherrow input::placeholder { color: var(--g500); }
.otherrow input:focus { outline: none; border-color: var(--clay); background: var(--paper); }
.otherrow button {
font-family: var(--mono); font-size: 12px;
padding: 0 16px; border-radius: 9px; cursor: pointer;
border: 1.5px solid var(--g300); background: var(--g100); color: var(--g700);
}
.otherrow button:hover { border-color: var(--clay); color: var(--clay-d); }
.navrow { display: flex; justify-content: space-between; align-items: center; margin-top: 24px; }
.backbtn {
font-family: var(--mono); font-size: 12px;
background: none; border: none; cursor: pointer;
color: var(--g500); padding: 6px 0;
}
.backbtn:hover { color: var(--clay-d); }
.backbtn[disabled] { visibility: hidden; }
.navhint { font-family: var(--mono); font-size: 11px; color: var(--g500); }
/* summary */
.sum-h { font-family: var(--serif); font-weight: 500; font-size: 24px; letter-spacing: -0.012em; margin: 4px 0 4px; }
.sum-sub { font-size: 14px; color: var(--g500); margin: 0 0 20px; }
.dtable { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.dtable-scroll { overflow-x: auto; }
.dtable th {
font-family: var(--mono); font-size: 10.5px;
letter-spacing: 0.1em; text-transform: uppercase;
color: var(--g500); font-weight: 500;
text-align: left; padding: 8px 12px 8px 0;
border-bottom: 1.5px solid var(--g300);
white-space: nowrap;
}
.dtable td { padding: 10px 12px 10px 0; border-bottom: 1px solid var(--g200); vertical-align: top; }
.dtable tr:last-child td { border-bottom: none; }
.dtable .pill {
font-family: var(--mono); font-size: 10px;
letter-spacing: 0.08em; text-transform: uppercase;
color: #fff; padding: 2px 7px; border-radius: 4px; white-space: nowrap;
}
.dtable .topic { color: var(--g500); white-space: nowrap; }
.dtable .dec { font-weight: 600; }
.dtable .redo {
font-family: var(--mono); font-size: 11px; color: var(--g500);
background: none; border: none; cursor: pointer; padding: 0;
}
.dtable .redo:hover { color: var(--clay-d); }
.genblock { margin-top: 26px; }
.genblock .gb-label {
font-family: var(--mono); font-size: 11px;
letter-spacing: 0.12em; text-transform: uppercase;
color: var(--clay); margin-bottom: 8px;
display: flex; align-items: center; gap: 10px;
}
.genblock .gb-label::before { content: ""; width: 18px; height: 1.5px; background: var(--clay); flex: none; }
.genwrap { position: relative; }
.genwrap pre {
margin: 0;
background: var(--slate);
color: var(--g200);
border-radius: 11px;
padding: 18px 20px;
font-family: var(--mono);
font-size: 12.5px;
line-height: 1.7;
white-space: pre-wrap;
word-break: break-word;
}
.genwrap .copybtn { top: 12px; right: 12px; background: var(--g700); border-color: var(--g700); color: var(--g300); }
.genwrap .copybtn:hover { color: var(--paper); border-color: var(--g500); }
.genwrap .copybtn.done { background: var(--olive); border-color: var(--olive); color: #fff; }
.restart {
margin-top: 18px;
font-family: var(--mono); font-size: 12px;
background: none; border: none; cursor: pointer;
color: var(--g500); padding: 0;
}
.restart:hover { color: var(--clay-d); }
footer {
margin-top: 56px; padding-top: 22px;
border-top: 1px solid var(--g300);
font-size: 13px; color: var(--g500);
}
footer a { color: var(--clay); text-decoration-color: var(--oat); text-underline-offset: 3px; }
footer a:hover { text-decoration-color: var(--clay); }
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="eyebrow">Know your unknowns · Pre-implementation</div>
<a class="backlink" href="index.html">← All examples</a>
<h1>The interview</h1>
<p class="lede">Instead of guessing at ambiguous requirements, ask Claude to interview you — one question at a time, ordered by how much each answer would change the architecture.</p>
<div class="promptbox">
<div class="label">The prompt</div>
<button class="copybtn" id="copyPrompt">copy prompt</button>
<blockquote id="promptText">Interview me one question at a time about anything still ambiguous in the annotation-export feature. Prioritize questions where my answer would change the architecture.</blockquote>
</div>
</header>
<div class="divider">What Claude produced</div>
<div class="artifact">
<div class="art-head">
<span class="t">Annotation export — spec interview</span>
<span class="s">acme/web · 7 open questions · ordered by blast radius</span>
</div>
<div class="art-body">
<nav class="rail" id="rail" aria-label="Interview progress"></nav>
<div class="stage" id="stage"></div>
</div>
</div>
<footer>Part of <a href="index.html">Know your unknowns</a> — companion examples to the blog post.</footer>
</div>
<script>
(function () {
"use strict";
var RADII = {
arch: { label: "Architecture", color: "var(--clay)" },
data: { label: "Data model", color: "var(--clay-d)" },
ux: { label: "UX", color: "var(--olive)" },
pol: { label: "Polish", color: "var(--g500)" }
};
var QUESTIONS = [
{
id: "runtime",
radius: "arch",
short: "Where exports run",
topic: "Execution model",
q: "Where should export generation actually run?",
why: "This decides whether we need a job queue, a notification path, and an artifacts bucket — or none of those. Everything else layers on top.",
opts: [
{ t: "Synchronously in the API request", d: "Simple, but a 400-comment review on a 2-hour cut will blow past the 30s gateway timeout." },
{ t: "Background job via the existing sidekiq queue", d: "Reuses jobs/export_worker patterns from proxy transcodes; needs a “ready” notification." },
{ t: "Entirely client-side from the annotations API", d: "Zero new backend, but caps us at formats the browser can assemble, and leaks nothing to support later." }
]
},
{
id: "scope",
radius: "arch",
short: "Export scope",
topic: "Scope of one export",
q: "What is the unit of a single export?",
why: "Project-level export means fan-out across videos, pagination, and a manifest format — roughly 3× the surface of per-video.",
opts: [
{ t: "One video at a time", d: "Matches the review-page mental model; a project export is just N clicks for now." },
{ t: "A whole project (all videos)", d: "What the Meridian Post account asked for; implies zip bundling and per-video manifests." },
{ t: "Arbitrary user-picked selection", d: "Most flexible, most UI; needs a selection model that doesnt exist anywhere yet." }
]
},
{
id: "drawings",
radius: "data",
short: "Drawings included?",
topic: "Drawing annotations",
q: "Do frame drawings export, or only text comments?",
why: "Drawings live as canvas stroke JSON in annotation_shapes; including them means rasterizing server-side or shipping raw vectors.",
opts: [
{ t: "Text comments only (v1)", d: "Ships fastest; drawings show as “[drawing on frame 4211]” placeholders." },
{ t: "Rasterize drawings to PNG stills", d: "Best for PDF reports; needs a headless render step — the thumbnail worker is close but not identical." },
{ t: "Include raw stroke JSON", d: "Lossless and cheap, but only useful to someone re-importing into Acme." }
]
},
{
id: "timecode",
radius: "data",
short: "Timestamp format",
topic: "Timestamp representation",
q: "How should annotation timestamps be represented in the export?",
why: "We store milliseconds; editors live in SMPTE timecode. Converting needs the source fps, which is null for 3% of legacy uploads.",
opts: [
{ t: "SMPTE timecode (HH:MM:SS:FF)", d: "What Resolve and Premiere expect; requires an fps backfill for legacy videos." },
{ t: "Plain seconds / milliseconds", d: "No conversion risk, but every editor will hand-convert and some will do it wrong." },
{ t: "Both columns, always", d: "Slightly wider files; nobody ever has to ask which one is authoritative." }
]
},
{
id: "formats",
radius: "ux",
short: "v1 formats",
topic: "Export formats",
q: "Which formats does v1 ship with?",
why: "Each format is mostly independent work, but the choice signals who the feature is for — producers, editors, or clients.",
opts: [
{ t: "CSV only", d: "One afternoon of work once the pipeline exists; producers can pivot it however they like." },
{ t: "PDF review report", d: "Client-facing artifact with thumbnails; only meaningful if drawings rasterize." },
{ t: "NLE marker file (EDL / Resolve CSV)", d: "The power-user ask from the forum thread; timecode question becomes load-bearing." },
{ t: "CSV + NLE markers, PDF later", d: "Covers both internal and editor workflows without blocking on rasterization." }
]
},
{
id: "access",
radius: "ux",
short: "Who can export",
topic: "Export permissions",
q: "Who is allowed to export?",
why: "Guest reviewers use unauthenticated share links — if they can export, annotations silently leave the permission boundary.",
opts: [
{ t: "Anyone who can view the video", d: "Includes share-link guests; simplest rule, biggest data-egress surface." },
{ t: "Project members only", d: "Guests keep commenting but cant bulk-extract; needs a disabled-state explanation in the UI." },
{ t: "Owner and admins only", d: "Most conservative; likely generates “can you export this for me” busywork." }
]
},
{
id: "naming",
radius: "pol",
short: "Files & naming",
topic: "File naming & bundling",
q: "How are export files named and bundled?",
why: "Pure convention, but the first support ticket after launch is always “which file is which cut.”",
opts: [
{ t: "{project}_{video}_{date}.csv, flat", d: "Predictable and grep-able; long titles get truncated at 80 chars." },
{ t: "Zip per export with a manifest.json", d: "One artifact per export regardless of scope; slight friction for single-file cases." },
{ t: "Let the user pick at export time", d: "A settings surface for something almost nobody will change." }
]
}
];
var answers = new Array(QUESTIONS.length).fill(null); // {text, custom}
var current = 0; // index, or QUESTIONS.length => summary
var rail = document.getElementById("rail");
var stage = document.getElementById("stage");
function answeredCount() {
return answers.filter(function (a) { return a !== null; }).length;
}
function esc(s) {
return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function renderRail() {
var html = '<div class="rail-label">Interview progress</div>';
QUESTIONS.forEach(function (q, i) {
var r = RADII[q.radius];
var cls = "rail-item" + (i === current ? " current" : "") + (answers[i] ? " done" : "");
html += '<button type="button" class="' + cls + '" data-i="' + i + '" style="--dotc:' + r.color + '"' +
(answers[i] ? '' : ' tabindex="-1"') + '>' +
'<span class="rail-dot"></span><span class="rail-q">' + esc(q.short) + '</span>' +
(answers[i] ? '<span class="tick">✓</span>' : '') + '</button>';
});
html += '<div class="rail-meta"><b>' + answeredCount() + '</b> of 7 answered<br>' +
'<span style="color:var(--clay)">●</span> architecture &nbsp;' +
'<span style="color:var(--clay-d)">●</span> data<br>' +
'<span style="color:var(--olive)">●</span> ux &nbsp;' +
'<span style="color:var(--g500)">●</span> polish</div>';
rail.innerHTML = html;
rail.querySelectorAll(".rail-item.done").forEach(function (el) {
el.addEventListener("click", function () { go(parseInt(el.dataset.i, 10)); });
});
}
function renderQuestion() {
var q = QUESTIONS[current];
var r = RADII[q.radius];
var picked = answers[current];
var html = '<div class="qcard">' +
'<span class="badge" style="background:' + r.color + '">' + esc(r.label) + '</span>' +
'<span class="qnum">Question ' + (current + 1) + ' of ' + QUESTIONS.length + '</span>' +
'<h2 class="qtext">' + esc(q.q) + '</h2>' +
'<p class="why">' + esc(q.why) + '</p>' +
'<div class="opts">';
q.opts.forEach(function (o, oi) {
var isPicked = picked && !picked.custom && picked.text === o.t;
html += '<button type="button" class="opt' + (isPicked ? ' picked' : '') + '" data-oi="' + oi + '">' +
'<span class="ot">' + esc(o.t) + '</span><span class="od">' + esc(o.d) + '</span></button>';
});
html += '</div>' +
'<div class="otherrow">' +
'<input type="text" id="otherInput" placeholder="Other — type your own answer…" value="' +
(picked && picked.custom ? esc(picked.text) : '') + '">' +
'<button type="button" id="otherGo">answer ↵</button></div>' +
'<div class="navrow">' +
'<button type="button" class="backbtn" id="backBtn"' + (current === 0 ? ' disabled' : '') + '>← previous question</button>' +
'<span class="navhint">pick a card to continue</span>' +
'</div></div>';
stage.innerHTML = html;
stage.querySelectorAll(".opt").forEach(function (el) {
el.addEventListener("click", function () {
var o = q.opts[parseInt(el.dataset.oi, 10)];
answers[current] = { text: o.t, custom: false };
el.classList.add("picked");
renderRail();
setTimeout(function () { go(current + 1); }, 240);
});
});
var input = stage.querySelector("#otherInput");
function submitOther() {
var v = input.value.trim();
if (!v) { input.focus(); return; }
answers[current] = { text: v, custom: true };
renderRail();
go(current + 1);
}
stage.querySelector("#otherGo").addEventListener("click", submitOther);
input.addEventListener("keydown", function (e) { if (e.key === "Enter") submitOther(); });
stage.querySelector("#backBtn").addEventListener("click", function () { go(current - 1); });
}
function buildFollowupPrompt() {
var lines = [
"Here's what we decided in the interview — implement the",
"annotation-export feature with these constraints:",
""
];
QUESTIONS.forEach(function (q, i) {
var a = answers[i];
lines.push((i + 1) + ". " + q.topic + ": " + (a ? a.text : "(unanswered — use your judgment)") +
(a && a.custom ? " [my own wording — ask if unclear]" : ""));
});
lines.push("");
lines.push("Treat the architecture and data-model decisions (14) as fixed.");
lines.push("If anything in 57 conflicts with them, flag it before writing code.");
lines.push("Start with a short plan, then implement.");
return lines.join("\n");
}
function renderSummary() {
var rows = "";
QUESTIONS.forEach(function (q, i) {
var r = RADII[q.radius];
var a = answers[i];
rows += "<tr>" +
'<td><span class="pill" style="background:' + r.color + '">' + esc(r.label) + '</span></td>' +
'<td class="topic">' + esc(q.topic) + '</td>' +
'<td class="dec">' + (a ? esc(a.text) + (a.custom ? ' <span style="font-weight:400;color:var(--g500)">(your wording)</span>' : '') : '<span style="color:var(--g500)">—</span>') + '</td>' +
'<td><button type="button" class="redo" data-i="' + i + '">edit</button></td>' +
"</tr>";
});
stage.innerHTML = '<div class="qcard">' +
'<span class="badge" style="background:var(--olive)">Interview complete</span>' +
'<h2 class="sum-h">Seven decisions, zero assumptions.</h2>' +
'<p class="sum-sub">Every answer below would otherwise have been guessed silently during implementation.</p>' +
'<div class="dtable-scroll"><table class="dtable"><thead><tr>' +
'<th>Radius</th><th>Question</th><th>Decision</th><th></th>' +
'</tr></thead><tbody>' + rows + '</tbody></table></div>' +
'<div class="genblock">' +
'<div class="gb-label">Generated follow-up prompt</div>' +
'<div class="genwrap"><button type="button" class="copybtn" id="copyGen">copy</button>' +
'<pre id="genPre">' + esc(buildFollowupPrompt()) + '</pre></div>' +
'</div>' +
'<button type="button" class="restart" id="restartBtn">↺ start over</button>' +
'</div>';
stage.querySelectorAll(".redo").forEach(function (el) {
el.addEventListener("click", function () { go(parseInt(el.dataset.i, 10)); });
});
wireCopy(stage.querySelector("#copyGen"), function () { return buildFollowupPrompt(); });
stage.querySelector("#restartBtn").addEventListener("click", function () {
answers = new Array(QUESTIONS.length).fill(null);
go(0);
});
}
function go(idx) {
var card = stage.querySelector(".qcard");
var doRender = function () {
current = idx;
renderRail();
if (current >= QUESTIONS.length) renderSummary();
else renderQuestion();
};
if (card) {
card.classList.add("leaving");
setTimeout(doRender, 160);
} else {
doRender();
}
}
function wireCopy(btn, getText) {
btn.addEventListener("click", function () {
var text = getText();
var done = function () {
var orig = btn.textContent;
btn.textContent = "copied ✓";
btn.classList.add("done");
setTimeout(function () { btn.textContent = orig; btn.classList.remove("done"); }, 1600);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(done, function () { fallbackCopy(text); done(); });
} else {
fallbackCopy(text); done();
}
});
}
function fallbackCopy(text) {
var ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed"; ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
try { document.execCommand("copy"); } catch (e) {}
document.body.removeChild(ta);
}
wireCopy(document.getElementById("copyPrompt"), function () {
return document.getElementById("promptText").textContent;
});
renderRail();
renderQuestion();
})();
</script>
</body>
</html>