项目文件夹

文件
2026-07-13 12:33:58 +08:00

574 行
29 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.0">
<title>The buy-in doc — 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; margin: 0; padding: 0; }
body {
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 20px; }
h1, h2, h3 { font-family: var(--serif); font-weight: 500; letter-spacing: -0.015em; }
/* ---------- page header ---------- */
header.page { padding: 56px 0 40px; border-bottom: 1.5px solid var(--g300); 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;
}
.backlink:hover { color: var(--clay-d); }
header.page h1 { font-size: clamp(34px, 5vw, 52px); line-height: 1.08; margin: 0 0 10px; }
.lede { font-size: 16.5px; color: var(--g700); max-width: 640px; }
/* ---------- prompt box ---------- */
.promptcard {
background: var(--paper); border: 1.5px solid var(--g300); border-radius: 14px;
padding: 22px 26px; margin: 30px 0 0; position: relative;
}
.promptcard .label {
font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--clay); margin-bottom: 12px;
}
.promptcard blockquote {
font-family: var(--serif); font-style: italic; font-size: 17.5px;
line-height: 1.5; color: var(--slate); max-width: 720px;
}
.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: 8px; padding: 6px 12px; 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 36px;
font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--g500);
}
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--g300); }
/* ================= ARTIFACT ================= */
.artifact {
background: var(--paper); border: 1.5px solid var(--g300); border-radius: 14px;
padding: clamp(22px, 4vw, 44px); margin-bottom: 48px;
}
.doc-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 6px; }
.doc-eyebrow {
font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--g500);
display: flex; align-items: center; gap: 10px;
}
.doc-eyebrow::before { content: ""; width: 24px; height: 1.5px; background: var(--clay); flex: none; }
.timechip {
font-family: var(--mono); font-size: 11.5px; color: var(--g700);
background: var(--oat); border-radius: 999px; padding: 5px 13px; white-space: nowrap;
display: inline-flex; align-items: center; gap: 7px;
}
.timechip::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--clay); }
.artifact h2.doc-title { font-size: clamp(26px, 3.6vw, 36px); line-height: 1.12; margin: 10px 0 4px; }
.doc-sub { color: var(--g500); font-size: 14px; margin-bottom: 26px; }
.doc-sub strong { color: var(--g700); font-weight: 600; }
.artifact h3.sec {
font-size: 21px; margin: 38px 0 14px; padding-top: 26px;
border-top: 1.5px solid var(--g200);
display: flex; align-items: baseline; gap: 10px;
}
.artifact h3.sec .n { font-family: var(--mono); font-size: 12px; color: var(--clay); font-weight: 400; }
/* ---------- demo player ---------- */
.demo-shell { position: relative; }
.demo {
background: var(--g100); border: 1.5px solid var(--g300); border-radius: 12px;
overflow: hidden; user-select: none;
}
.bl-titlebar {
display: flex; align-items: center; gap: 8px;
background: var(--paper); border-bottom: 1.5px solid var(--g200);
padding: 9px 14px;
}
.dot { width: 10px; height: 10px; border-radius: 50%; background: var(--g300); flex: none; }
.bl-name { font-family: var(--mono); font-size: 11px; color: var(--g500); margin-left: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bl-body { display: grid; grid-template-columns: 1fr 232px; min-height: 268px; }
@media (max-width: 620px) { .bl-body { grid-template-columns: 1fr; } .bl-panel { border-left: none !important; border-top: 1.5px solid var(--g200); } }
/* video pane */
.bl-stage { position: relative; padding: 16px 16px 12px; display: flex; flex-direction: column; }
.bl-video {
flex: 1; min-height: 148px; border-radius: 8px; position: relative;
background:
radial-gradient(120px 80px at 68% 34%, #4a4b45 0%, transparent 70%),
linear-gradient(160deg, #3D3D3A 0%, #232322 100%);
border: 1.5px solid var(--g300);
}
.bl-play {
position: absolute; left: 12px; bottom: 10px; width: 0; height: 0;
border-left: 11px solid rgba(250,249,245,.85); border-top: 7px solid transparent; border-bottom: 7px solid transparent;
}
.bl-tc { position: absolute; right: 12px; bottom: 8px; font-family: var(--mono); font-size: 10.5px; color: rgba(250,249,245,.7); }
.bl-pin {
position: absolute; top: -7px; width: 14px; height: 14px; border-radius: 50% 50% 50% 2px;
background: var(--g500); transform: rotate(45deg); transition: background .25s;
}
.bl-pin.on { background: var(--clay); }
.bl-timeline { position: relative; height: 26px; margin-top: 14px; }
.bl-track { position: absolute; left: 0; right: 0; top: 11px; height: 4px; border-radius: 2px; background: var(--g300); }
.bl-track .fill { width: 34%; height: 100%; border-radius: 2px; background: var(--g500); }
/* annotation panel */
.bl-panel { background: var(--paper); border-left: 1.5px solid var(--g200); padding: 12px 12px 10px; display: flex; flex-direction: column; }
.bl-panel-head {
font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase;
color: var(--g500); display: flex; justify-content: space-between; align-items: center; margin-bottom: 9px;
}
.bl-count { color: var(--clay-d); }
.bl-ann {
display: flex; gap: 9px; align-items: flex-start; padding: 8px 8px;
border-radius: 8px; border: 1.5px solid transparent; transition: background .25s, border-color .25s;
}
.bl-ann.sel { background: var(--g100); border-color: var(--g300); }
.bl-check {
width: 15px; height: 15px; border-radius: 4px; border: 1.5px solid var(--g300);
background: var(--paper); flex: none; margin-top: 2px; position: relative; transition: background .2s, border-color .2s;
}
.bl-ann.sel .bl-check { background: var(--clay); border-color: var(--clay); }
.bl-ann.sel .bl-check::after {
content: ""; position: absolute; left: 4px; top: 1px; width: 4px; height: 8px;
border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.bl-ann .who { font-size: 11.5px; font-weight: 600; line-height: 1.35; }
.bl-ann .who .t { font-family: var(--mono); font-weight: 400; font-size: 10px; color: var(--g500); margin-left: 5px; }
.bl-ann .txt { font-size: 11px; color: var(--g700); line-height: 1.4; }
.bl-export {
margin-top: auto; align-self: stretch; text-align: center;
font-family: var(--sans); font-size: 12.5px; font-weight: 600; color: #fff;
background: var(--slate); border: none; border-radius: 8px; padding: 9px 0;
transition: background .2s, transform .12s; pointer-events: none;
}
.bl-export.armed { background: var(--clay); }
.bl-export.pressed { background: var(--clay-d); transform: scale(.965); }
/* progress overlay */
.bl-progress {
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
background: rgba(20,20,19,.38); opacity: 0; pointer-events: none; transition: opacity .3s;
}
.bl-progress.show { opacity: 1; }
.bl-prog-card {
background: var(--paper); border-radius: 12px; padding: 20px 24px; width: min(300px, 84%);
box-shadow: 0 12px 32px rgba(20,20,19,.22);
}
.bl-prog-card .pt { font-size: 13px; font-weight: 600; margin-bottom: 4px; }
.bl-prog-card .ps { font-family: var(--mono); font-size: 10.5px; color: var(--g500); margin-bottom: 12px; min-height: 14px; }
.bl-bar { height: 6px; border-radius: 3px; background: var(--g200); overflow: hidden; }
.bl-bar .b { width: 0%; height: 100%; background: var(--clay); border-radius: 3px; transition: width 1.7s cubic-bezier(.3,.7,.3,1); }
/* toast */
.bl-toast {
position: absolute; left: 50%; bottom: 14px; transform: translate(-50%, 16px);
background: var(--slate); color: var(--ivory); border-radius: 10px;
padding: 10px 14px; display: flex; align-items: center; gap: 10px;
opacity: 0; transition: opacity .35s, transform .35s; max-width: 92%;
box-shadow: 0 10px 26px rgba(20,20,19,.3);
}
.bl-toast.show { opacity: 1; transform: translate(-50%, 0); }
.bl-tick {
width: 18px; height: 18px; border-radius: 50%; background: var(--olive); flex: none; position: relative;
}
.bl-tick::after {
content: ""; position: absolute; left: 6px; top: 3.5px; width: 4px; height: 8px;
border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.bl-toast .m { font-size: 12px; white-space: nowrap; }
.bl-chip {
font-family: var(--mono); font-size: 10.5px; background: rgba(250,249,245,.14);
border: 1px solid rgba(250,249,245,.25); border-radius: 6px; padding: 3px 8px; white-space: nowrap;
}
.demo-caption {
display: flex; justify-content: space-between; align-items: center; gap: 12px;
margin-top: 10px; font-family: var(--mono); font-size: 11px; color: var(--g500);
}
.replay {
font-family: var(--mono); font-size: 11px; background: none; border: 1.5px solid var(--g300);
border-radius: 8px; color: var(--g700); padding: 5px 12px; cursor: pointer;
}
.replay:hover { border-color: var(--clay); color: var(--clay-d); }
/* ---------- pitch ---------- */
.pitch { font-family: var(--serif); font-size: 17.5px; line-height: 1.6; max-width: 46em; }
.pitch strong { font-weight: 600; }
/* ---------- objections ---------- */
.obj { border: 1.5px solid var(--g200); border-radius: 10px; margin-bottom: 10px; overflow: hidden; }
.obj summary {
list-style: none; cursor: pointer; padding: 12px 16px; display: flex; gap: 12px; align-items: baseline;
font-weight: 600; font-size: 14.5px;
}
.obj summary::-webkit-details-marker { display: none; }
.obj summary .q { font-family: var(--mono); font-size: 11px; color: var(--clay); flex: none; }
.obj summary::after { content: "+"; margin-left: auto; font-family: var(--mono); color: var(--g500); flex: none; }
.obj[open] summary::after { content: "–"; }
.obj[open] summary { border-bottom: 1.5px solid var(--g200); background: var(--g100); }
.obj .a { padding: 13px 16px 14px 16px; font-size: 14px; color: var(--g700); }
.obj .ref {
display: inline-block; margin-top: 8px; font-family: var(--mono); font-size: 11.5px;
color: var(--clay-d); text-decoration: none; border-bottom: 1px dashed var(--g300);
}
.obj .ref:hover { border-bottom-color: var(--clay-d); }
/* ---------- spec table ---------- */
.tblwrap { overflow-x: auto; border: 1.5px solid var(--g200); border-radius: 10px; }
table.spec { width: 100%; border-collapse: collapse; font-size: 13.5px; min-width: 560px; }
table.spec th, table.spec td { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--g200); vertical-align: top; }
table.spec tr:last-child td { border-bottom: none; }
table.spec th {
font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase;
color: var(--g500); font-weight: 400; background: var(--g100);
}
table.spec td:first-child { font-weight: 600; white-space: nowrap; width: 140px; }
table.spec code, .a code, .risk code, .need code {
font-family: var(--mono); font-size: 0.88em; background: var(--g100);
border: 1px solid var(--g200); border-radius: 4px; padding: 1px 5px; white-space: nowrap;
}
.specref { font-family: var(--mono); font-size: 11px; color: var(--g500); }
/* ---------- risk + needs cards ---------- */
.cardgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 720px) { .cardgrid { grid-template-columns: 1fr; } }
.risk, .need {
border: 1.5px solid var(--g300); border-radius: 12px; padding: 18px 20px; background: var(--ivory);
}
.risk { border-left: 4px solid var(--clay); }
.need { border-left: 4px solid var(--olive); }
.risk h4, .need h4 {
font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.1em; text-transform: uppercase;
font-weight: 400; margin-bottom: 12px;
}
.risk h4 { color: var(--clay-d); }
.need h4 { color: var(--olive); }
.risk ul { list-style: none; }
.risk li { font-size: 13.5px; color: var(--g700); padding-left: 18px; position: relative; margin-bottom: 9px; }
.risk li::before { content: ""; position: absolute; left: 2px; top: 8px; width: 7px; height: 2px; background: var(--clay); }
.risk li strong { color: var(--slate); }
.approver { display: flex; gap: 11px; align-items: flex-start; padding: 9px 0; border-bottom: 1px solid var(--g200); }
.approver:last-child { border-bottom: none; }
.avatar {
width: 30px; height: 30px; border-radius: 50%; flex: none;
background: var(--oat); color: var(--clay-d);
font-family: var(--mono); font-size: 11px; display: flex; align-items: center; justify-content: center;
}
.approver .info { font-size: 13px; line-height: 1.4; }
.approver .info b { display: block; font-size: 13.5px; }
.approver .info .role { font-family: var(--mono); font-size: 10.5px; color: var(--g500); }
.approver .info .asks { color: var(--g700); margin-top: 2px; }
.bottomline {
margin-top: 34px; padding: 16px 20px; background: var(--oat); border-radius: 10px;
font-family: var(--serif); font-size: 16px;
}
footer {
padding: 26px 0 56px; font-size: 13px; color: var(--g500);
border-top: 1.5px solid var(--g300);
}
footer a { color: var(--clay-d); }
</style>
</head>
<body>
<div class="wrap">
<header class="page">
<a class="backlink" href="index.html">&larr; All examples</a>
<div class="eyebrow">Know your unknowns &middot; Post-implementation</div>
<h1>The buy-in doc</h1>
<p class="lede">After the work is done, the last unknown is other people — ask Claude to package the prototype, spec, and notes into one skimmable doc that answers objections before they're raised.</p>
<div class="promptcard">
<div class="label">The prompt</div>
<button class="copybtn" id="copyBtn">copy prompt</button>
<blockquote id="promptText">&ldquo;Package the prototype, the spec, and the implementation notes into a single doc I can drop in Slack to get buy-in on shipping annotation export. Lead with the demo.&rdquo;</blockquote>
</div>
</header>
<div class="divider">What Claude produced</div>
<!-- ================= ARTIFACT ================= -->
<article class="artifact">
<div class="doc-top">
<div class="doc-eyebrow">Ship proposal &middot; Acme</div>
<span class="timechip">90-second read</span>
</div>
<h2 class="doc-title">Annotation export is ready to ship</h2>
<p class="doc-sub">Prototype, spec, and rollout plan in one place. <strong>Ask:</strong> four sign-offs by Friday, flag ramp starts Monday.</p>
<!-- 1 · DEMO -->
<h3 class="sec"><span class="n">01</span> Watch it work</h3>
<div class="demo-shell">
<div class="demo" id="demo" aria-label="Animated mock of the annotation export flow in Acme">
<div class="bl-titlebar">
<span class="dot"></span><span class="dot"></span><span class="dot"></span>
<span class="bl-name">Acme — q3_launch_teaser_v4.mp4 · Review</span>
</div>
<div class="bl-body">
<div class="bl-stage">
<div class="bl-video">
<span class="bl-play"></span>
<span class="bl-tc">00:42 / 02:05</span>
</div>
<div class="bl-timeline">
<div class="bl-track"><div class="fill"></div></div>
<span class="bl-pin" id="pin1" style="left: 18%;"></span>
<span class="bl-pin" id="pin2" style="left: 41%;"></span>
<span class="bl-pin" id="pin3" style="left: 73%;"></span>
</div>
</div>
<div class="bl-panel">
<div class="bl-panel-head"><span>Annotations</span><span class="bl-count" id="selCount">0 selected</span></div>
<div class="bl-ann" id="ann1">
<span class="bl-check"></span>
<div>
<div class="who">Maya R. <span class="t">00:23</span></div>
<div class="txt">Logo lands too late — pull in 8 frames?</div>
</div>
</div>
<div class="bl-ann" id="ann2">
<span class="bl-check"></span>
<div>
<div class="who">Devon P. <span class="t">00:51</span></div>
<div class="txt">Color shift here vs. the master grade</div>
</div>
</div>
<div class="bl-ann" id="ann3">
<span class="bl-check"></span>
<div>
<div class="who">Maya R. <span class="t">01:30</span></div>
<div class="txt">Approved — final CTA reads great</div>
</div>
</div>
<button class="bl-export" id="exportBtn" tabindex="-1">Export selected</button>
</div>
</div>
<div class="bl-progress" id="progress">
<div class="bl-prog-card">
<div class="pt">Exporting 3 annotations</div>
<div class="ps" id="progStatus">Collecting annotations…</div>
<div class="bl-bar"><div class="b" id="progBar"></div></div>
</div>
</div>
<div class="bl-toast" id="toast">
<span class="bl-tick"></span>
<span class="m">Export ready</span>
<span class="bl-chip">q3_teaser_notes.srt · 3 cues</span>
</div>
</div>
<div class="demo-caption">
<span id="demoState">select &rarr; export &rarr; done · loops automatically</span>
<button class="replay" id="replayBtn">&#8635; replay</button>
</div>
</div>
<!-- 2 · PITCH -->
<h3 class="sec"><span class="n">02</span> The pitch</h3>
<p class="pitch">Reviewers leave feedback in Acme, then retype it into email, Notion, and edit-bay software — <strong>the single most-cited gap in churn interviews (41% of departed teams)</strong>. Annotation export turns any selection of comments and drawings into SRT, CSV, or PDF in one click, using infrastructure we already run. It's built, flagged, load-tested to 12k annotations per session, and reversible in one toggle. The only thing between here and shipped is the four sign-offs below.</p>
<!-- 3 · OBJECTIONS -->
<h3 class="sec"><span class="n">03</span> What reviewers will ask</h3>
<details class="obj" open>
<summary><span class="q">Q1</span>Can this leak internal-only comments to a client?</summary>
<div class="a">No. Export runs through the same visibility resolver as the review page: annotations marked <code>internal</code> are excluded unless the exporter holds <code>workspace:admin</code> and explicitly opts in via a labeled checkbox. Covered by 14 permission-matrix tests.
<br><a class="ref" href="#spec">&#8599; spec &sect;4.2 — Visibility &amp; permissions</a></div>
</details>
<details class="obj">
<summary><span class="q">Q2</span>What happens on a monster session — thousands of annotations?</summary>
<div class="a">Export paginates at 500 annotations per batch and streams the file. Stress run against the largest real session in prod (a 12,400-annotation agency review) finished in 3.1s; p95 for typical sessions is 340ms.
<br><a class="ref" href="#spec">&#8599; implementation notes, 06-24 — load test results</a></div>
</details>
<details class="obj">
<summary><span class="q">Q3</span>Why file formats instead of a public API?</summary>
<div class="a">SRT + CSV + PDF cover 83% of the 212 support tickets tagged <code>export-request</code> over the last two quarters. An API is the right phase 2 — it needs auth scoping work this doesn't. Files unblock users now without foreclosing the API design.
<br><a class="ref" href="#spec">&#8599; metric — support tag analysis, Q1&ndash;Q2</a></div>
</details>
<details class="obj">
<summary><span class="q">Q4</span>Does this add new infrastructure we have to operate?</summary>
<div class="a">Zero new services. SRT/CSV render inline in the existing worker pool (<code>services/render-workers</code>); PDF reuses the report service already generating weekly digests. No new queues, no new datastore, no schema migrations.
<br><a class="ref" href="#spec">&#8599; spec &sect;5.1 — Architecture</a></div>
</details>
<details class="obj">
<summary><span class="q">Q5</span>What's the compliance story for exported customer content?</summary>
<div class="a">Every export is stamped with workspace ID, requesting user, and timestamp in file metadata, and written to the audit log (<code>audit.annotation_export</code>). Files are served via expiring signed URLs (24h) and never stored beyond that window.
<br><a class="ref" href="#spec">&#8599; spec &sect;6 — Audit &amp; retention</a></div>
</details>
<!-- 4 · SPEC SUMMARY -->
<h3 class="sec" id="spec"><span class="n">04</span> Spec at a glance</h3>
<div class="tblwrap">
<table class="spec">
<thead><tr><th>Area</th><th>Decision</th><th>Ref</th></tr></thead>
<tbody>
<tr><td>Formats</td><td>SRT (timecoded cues), CSV (flat, incl. drawing bounding boxes), PDF (frame thumbnails + comments)</td><td class="specref">&sect;2</td></tr>
<tr><td>Entry point</td><td>Annotation panel &rarr; multi-select &rarr; <b>Export selected</b>; also &ldquo;Export all&rdquo; in session menu</td><td class="specref">&sect;3.1</td></tr>
<tr><td>Permissions</td><td>Requires <code>session:comment</code> or above; <code>internal</code> annotations excluded by default</td><td class="specref">&sect;4.2</td></tr>
<tr><td>Limits</td><td>500 annotations/batch, streamed; 25k hard cap per export with clear error state</td><td class="specref">&sect;5.3</td></tr>
<tr><td>Telemetry</td><td><code>export_started</code>, <code>export_completed</code> (format, count, duration), <code>export_failed</code></td><td class="specref">&sect;7</td></tr>
<tr><td>Feature flag</td><td><code>annotation_export</code> — off &rarr; 5% &rarr; 50% &rarr; 100% over two weeks</td><td class="specref">&sect;8</td></tr>
</tbody>
</table>
</div>
<!-- 5 · RISK + NEEDS -->
<h3 class="sec"><span class="n">05</span> Risk, rollback, and what I need from you</h3>
<div class="cardgrid">
<div class="risk">
<h4>Risk &amp; rollback</h4>
<ul>
<li><strong>Rollback is one toggle.</strong> Flip <code>annotation_export</code> off — no schema migrations shipped, nothing to unwind.</li>
<li><strong>Blast radius is bounded.</strong> Export runs in the shared worker pool with a 30s per-job timeout; a runaway job cannot starve review playback.</li>
<li><strong>Worst credible failure:</strong> orphaned export files if the signed-URL cleanup lags — they self-expire in 24h regardless.</li>
<li><strong>Known gap:</strong> drawing annotations flatten to bounding boxes in CSV (full vectors are PDF-only). Documented in-product; phase 2 item.</li>
</ul>
</div>
<div class="need">
<h4>What I need from you</h4>
<div class="approver">
<span class="avatar">PN</span>
<div class="info"><b>Priya Natarajan</b><span class="role">Eng lead, Playback &amp; Review</span>
<div class="asks">Sign off on reusing the render worker pool (spec &sect;5.1) — the only shared-infra dependency.</div></div>
</div>
<div class="approver">
<span class="avatar">ES</span>
<div class="info"><b>Elena Sorokin</b><span class="role">Security</span>
<div class="asks">Approve the visibility rules and audit-log shape (spec &sect;4.2, &sect;6).</div></div>
</div>
<div class="approver">
<span class="avatar">MW</span>
<div class="info"><b>Marcus Webb</b><span class="role">Design</span>
<div class="asks">Final pass on the export modal and error states — prototype above is current.</div></div>
</div>
<div class="approver">
<span class="avatar">DK</span>
<div class="info"><b>Dana Kim</b><span class="role">PM, Collaboration</span>
<div class="asks">Approve the flag ramp schedule (spec &sect;8) and own the changelog entry.</div></div>
</div>
</div>
</div>
<div class="bottomline">Everything above is built and tested behind the flag. Reply with a &#10003; on your piece, or grab 15 minutes on my calendar — <b>target: ramp starts Monday.</b></div>
</article>
<footer>Part of <a href="index.html">Know your unknowns</a> — companion examples to the blog post.</footer>
</div>
<script>
(function () {
// ---------- copy prompt ----------
var PROMPT = "Package the prototype, the spec, and the implementation notes into a single doc I can drop in Slack to get buy-in on shipping annotation export. Lead with the demo.";
var copyBtn = document.getElementById("copyBtn");
copyBtn.addEventListener("click", function () {
function done() {
copyBtn.textContent = "copied";
copyBtn.classList.add("done");
setTimeout(function () {
copyBtn.textContent = "copy prompt";
copyBtn.classList.remove("done");
}, 1600);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(PROMPT).then(done, fallback);
} else { fallback(); }
function fallback() {
var ta = document.createElement("textarea");
ta.value = PROMPT;
ta.style.position = "fixed"; ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
try { document.execCommand("copy"); } catch (e) {}
document.body.removeChild(ta);
done();
}
});
// ---------- demo animation ----------
var anns = [document.getElementById("ann1"), document.getElementById("ann2"), document.getElementById("ann3")];
var pins = [document.getElementById("pin1"), document.getElementById("pin2"), document.getElementById("pin3")];
var exportBtn = document.getElementById("exportBtn");
var progress = document.getElementById("progress");
var progBar = document.getElementById("progBar");
var progStatus = document.getElementById("progStatus");
var toast = document.getElementById("toast");
var selCount = document.getElementById("selCount");
var timers = [];
function at(ms, fn) { timers.push(setTimeout(fn, ms)); }
function reset() {
timers.forEach(clearTimeout);
timers = [];
anns.forEach(function (a) { a.classList.remove("sel"); });
pins.forEach(function (p) { p.classList.remove("on"); });
exportBtn.classList.remove("armed", "pressed");
progress.classList.remove("show");
progBar.style.transition = "none";
progBar.style.width = "0%";
// force reflow so the next width change animates
void progBar.offsetWidth;
progBar.style.transition = "";
progStatus.textContent = "Collecting annotations…";
toast.classList.remove("show");
selCount.textContent = "0 selected";
}
function play() {
reset();
[0, 1, 2].forEach(function (i) {
at(500 + i * 450, function () {
anns[i].classList.add("sel");
pins[i].classList.add("on");
selCount.textContent = (i + 1) + " selected";
if (i === 2) exportBtn.classList.add("armed");
});
});
at(2100, function () { exportBtn.classList.add("pressed"); });
at(2300, function () {
exportBtn.classList.remove("pressed");
progress.classList.add("show");
});
at(2450, function () { progBar.style.width = "100%"; });
at(3100, function () { progStatus.textContent = "Rendering SRT cues…"; });
at(3800, function () { progStatus.textContent = "Signing download URL…"; });
at(4300, function () {
progress.classList.remove("show");
toast.classList.add("show");
});
at(7000, play); // loop
}
document.getElementById("replayBtn").addEventListener("click", play);
play();
})();
</script>
</body>
</html>