645 lines
27 KiB
HTML
645 lines
27 KiB
HTML
<!-- 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 doesn’t 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 can’t 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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||
}
|
||
|
||
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 ' +
|
||
'<span style="color:var(--clay-d)">●</span> data<br>' +
|
||
'<span style="color:var(--olive)">●</span> ux ' +
|
||
'<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 (1–4) as fixed.");
|
||
lines.push("If anything in 5–7 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>
|