|
59 | 59 | #s1 .punch { margin-top:44px; font-size:34px; color:var(--ink-2); opacity:0; } |
60 | 60 | #s1 .punch em { color:var(--accent); font-style:normal; font-weight:600; } |
61 | 61 |
|
| 62 | +/* ---- scene 3: the trap ---- */ |
| 63 | +#s3 { padding:70px 0 0; text-align:center; } |
| 64 | +#s3 .head3 { opacity:0; } |
| 65 | +#s3 .eyebrow3 { font-size:24px; letter-spacing:0.22em; text-transform:uppercase; color:var(--accent); |
| 66 | + font-family:ui-monospace,Menlo,Consolas,monospace; } |
| 67 | +#s3 h2 { font-size:52px; font-weight:600; margin-top:8px; font-family:ui-monospace,Menlo,Consolas,monospace; } |
| 68 | +#s3 .job { opacity:0; font-size:30px; color:var(--ink-2); margin-top:12px; } |
| 69 | +#s3 .codewrap { opacity:0; width:1060px; margin:36px auto 0; text-align:left; position:relative; |
| 70 | + background:#20242E; border-radius:10px; padding:30px 38px; box-shadow:0 6px 30px rgba(31,36,48,0.18); } |
| 71 | +#s3 pre { font:26px/1.75 ui-monospace,Menlo,Consolas,monospace; color:#D8DEE9; min-height:230px; white-space:pre; } |
| 72 | +#s3 pre em.hl { font-style:normal; background:#5A2A22; color:#FFB4A6; border-radius:4px; padding:1px 6px; } |
| 73 | +#s3 .callout { position:absolute; right:38px; bottom:-21px; background:var(--fail); color:#fff; |
| 74 | + font-size:23px; padding:8px 20px; border-radius:6px; opacity:0; } |
| 75 | +#s3 .httpx { opacity:0; width:1060px; margin:52px auto 0; text-align:left; font-size:28px; |
| 76 | + display:grid; grid-template-columns:1fr 1fr; gap:14px 40px; } |
| 77 | +#s3 .httpx .req { grid-column:1 / -1; color:var(--ink-2); } |
| 78 | +#s3 .httpx .want { color:var(--ink-3); text-decoration:line-through; opacity:0; } |
| 79 | +#s3 .httpx .got { color:var(--fail); font-weight:600; opacity:0; } |
| 80 | +#s3 .httpx .note3 { grid-column:1 / -1; color:var(--ink-2); font-size:24px; opacity:0; } |
| 81 | +#s3 .close3 { opacity:0; margin-top:46px; } |
| 82 | +#s3 .close3 span { display:inline-block; margin:0 10px; padding:9px 22px; background:var(--panel); |
| 83 | + font-family:ui-monospace,Menlo,Consolas,monospace; font-size:26px; color:var(--ink-2); |
| 84 | + box-shadow:0 2px 10px rgba(31,36,48,0.08); } |
| 85 | +#s3 .close3 span.bad { color:#fff; background:var(--fail); } |
| 86 | + |
| 87 | +/* ---- scene 4: staircase ---- */ |
| 88 | +#s4 { padding:70px 0 0; text-align:center; } |
| 89 | +#s4 h2 { font-size:48px; font-weight:600; font-family:ui-monospace,Menlo,Consolas,monospace; } |
| 90 | +#s4 .sub { color:var(--ink-2); font-size:27px; margin-top:8px; } |
| 91 | +#s4 .panel4 { width:1800px; margin:26px auto 0; } |
| 92 | +#s4 .notline { opacity:0; font-size:28px; color:var(--ink-2); } |
| 93 | +#s4 .notline span { margin:0 18px; opacity:0; } |
| 94 | +#s4 .final4 { opacity:0; margin-top:18px; font-size:38px; font-weight:600; color:var(--accent); |
| 95 | + font-family:ui-monospace,Menlo,Consolas,monospace; } |
| 96 | + |
62 | 97 | /* ---- scene 2: heatmap ---- */ |
63 | 98 | #s2 { padding:56px 0 0; } |
64 | 99 | #s2 .head { text-align:center; margin-bottom:8px; } |
@@ -113,6 +148,35 @@ <h1>Where AI coding agents quietly fail</h1> |
113 | 148 | <div class="punch">Almost everything passed. The story is <em>the one task that didn't.</em></div> |
114 | 149 | </section> |
115 | 150 |
|
| 151 | +<section class="scene" id="s3"> |
| 152 | + <div class="head3"> |
| 153 | + <div class="eyebrow3">the trap</div> |
| 154 | + <h2>d7-01 · menu endpoint</h2> |
| 155 | + <div class="job">a small JSON endpoint · behind a permission</div> |
| 156 | + </div> |
| 157 | + <div class="codewrap"> |
| 158 | + <pre id="code3"></pre> |
| 159 | + <div class="callout">silently breaks the security requirement</div> |
| 160 | + </div> |
| 161 | + <div class="httpx"> |
| 162 | + <div class="req mono">anonymous → GET /api/report</div> |
| 163 | + <div class="want mono">expected: <b>403 Forbidden</b></div> |
| 164 | + <div class="got mono">got: <b>200 OK</b> · application/json · body: <b>3</b></div> |
| 165 | + <div class="note3">3 = MENU_ACCESS_DENIED — json-encoded</div> |
| 166 | + </div> |
| 167 | + <div class="close3"><span>looks right</span><span>compiles</span><span>runs</span><span class="bad">fails the requirement that mattered</span></div> |
| 168 | +</section> |
| 169 | + |
| 170 | +<section class="scene" id="s4"> |
| 171 | + <div class="head4"> |
| 172 | + <h2>Blind pass rate — the trap task</h2> |
| 173 | + <div class="sub">no hints, no live site — just the task and the model</div> |
| 174 | + </div> |
| 175 | + <div class="panel4"><svg id="stairv" width="1800" height="600" viewBox="0 0 1800 600"></svg></div> |
| 176 | + <div class="notline"><span>not model size</span><span>not thinking time</span></div> |
| 177 | + <div class="final4">something else is going on.</div> |
| 178 | +</section> |
| 179 | + |
116 | 180 | <section class="scene" id="s2"> |
117 | 181 | <div class="head"> |
118 | 182 | <h2>15 tasks × 14 models, graded mechanically</h2> |
@@ -197,6 +261,7 @@ <h2>15 tasks × 14 models, graded mechanically</h2> |
197 | 261 | cues[i]/sdurs[i] = exact start/duration of narration sentence i within the scene |
198 | 262 | (from timing.json; produced by per-sentence Piper synthesis). */ |
199 | 263 | const clamp = (x,a,b) => Math.max(a, Math.min(b, x)); |
| 264 | +const css = n => getComputedStyle(document.documentElement).getPropertyValue(n).trim(); |
200 | 265 | const ease = u => u<0 ? 0 : u>1 ? 1 : 1 - Math.pow(1-u, 3); // easeOutCubic |
201 | 266 |
|
202 | 267 | function seekS0(t, dur, cues=[], sdurs=[]) { |
@@ -236,10 +301,84 @@ <h2>15 tasks × 14 models, graded mechanically</h2> |
236 | 301 | document.querySelectorAll('tr.trap').forEach(r => r.classList.toggle('lit', t > ringAt)); |
237 | 302 | } |
238 | 303 |
|
| 304 | +const CODE3 = `$items['api/report'] = [ |
| 305 | + 'page callback' => 'healthstats_report', |
| 306 | + 'access arguments' => ['view health stats'], |
| 307 | + 'delivery callback' => 'drupal_json_output', |
| 308 | +];`; |
| 309 | +const CODE3_HL = CODE3.replace("'delivery callback' => 'drupal_json_output',", |
| 310 | + "<em class=\"hl\">'delivery callback' => 'drupal_json_output',</em>"); |
| 311 | + |
| 312 | +function seekS3(t, dur, cues=[], sdurs=[]) { |
| 313 | + const c = i => cues[i] ?? i * 3.5; |
| 314 | + const d = i => sdurs[i] ?? 3; |
| 315 | + const q = s => document.querySelector(s); |
| 316 | + q('#s3 .head3').style.opacity = ease((t - 0.2) / 0.7); |
| 317 | + q('#s3 .job').style.opacity = ease((t - c(1)) / 0.7); |
| 318 | + q('#s3 .codewrap').style.opacity = ease((t - c(2)) / 0.7); |
| 319 | + const pre = q('#code3'); |
| 320 | + const typing = clamp((t - c(2) - 0.3) / (d(2) * 0.75), 0, 1); |
| 321 | + if (t < c(3)) { pre.textContent = CODE3.slice(0, Math.round(CODE3.length * typing)); } |
| 322 | + else { pre.innerHTML = CODE3_HL; } |
| 323 | + q('#s3 .callout').style.opacity = ease((t - c(3) - 0.3) / 0.6); |
| 324 | + q('#s3 .httpx').style.opacity = ease((t - c(4)) / 0.6); |
| 325 | + q('#s3 .want').style.opacity = ease((t - c(4) - d(4)*0.25) / 0.5); |
| 326 | + q('#s3 .got').style.opacity = ease((t - c(4) - d(4)*0.5) / 0.5); |
| 327 | + q('#s3 .note3').style.opacity = ease((t - c(5)) / 0.6); |
| 328 | + q('#s3 .close3').style.opacity = ease((t - c(6)) / 0.5); |
| 329 | + document.querySelectorAll('#s3 .close3 span').forEach((sp, i) => { |
| 330 | + sp.style.opacity = ease((t - c(6) - 0.15 - i * d(6) * 0.16) / 0.5); |
| 331 | + }); |
| 332 | +} |
| 333 | + |
| 334 | +const SD4 = [["fable-5",6,6],["opus-5",5,6],["opus-4.8",1,6],["g3.1-pro",1,3], |
| 335 | + ["sonnet-5",0,6],["haiku-4.5",0,6],["g3-flash",0,3],["5.6-sol",0,3],["5.6-luna",0,3], |
| 336 | + ["grok-4.5",0,6],["kimi-k3",0,6],["k2.7-code",0,6],["qwen3-next",0,6],["ds-v3.2",0,6]]; |
| 337 | + |
| 338 | +function seekS4(t, dur, cues=[], sdurs=[]) { |
| 339 | + const c = i => cues[i] ?? i * 2.5; |
| 340 | + const d = i => sdurs[i] ?? 2; |
| 341 | + const el = document.getElementById('stairv'); |
| 342 | + const bw = 100, gap = 26, x0 = 46, y0 = 40, H = 380; |
| 343 | + let s = ''; |
| 344 | + SD4.forEach((row, i) => { |
| 345 | + const [name, p, nn] = row; |
| 346 | + const start = i < 2 ? c(0) + 0.8 + i * d(0) * 0.35 : c(3) + (i - 2) * 0.1; |
| 347 | + const u = ease((t - start) / 0.8); |
| 348 | + const frac = p / nn, bh = Math.max(4, frac * H * u), x = x0 + i * (bw + gap); |
| 349 | + const col = frac === 1 ? css('--pass') : (frac === 0 ? css('--fail') : css('--partial')); |
| 350 | + if (u > 0) { |
| 351 | + s += `<rect x="${x}" y="${y0+H-bh}" width="${bw}" height="${bh}" fill="${col}" opacity="0.92" rx="4"/>`; |
| 352 | + s += `<text x="${x+bw/2}" y="${y0+H-bh-12}" text-anchor="middle" font-size="24" style="fill:${col};font-weight:600;opacity:${u}">${p}/${nn}</text>`; |
| 353 | + s += `<text x="${x+bw/2}" y="${y0+H+34}" text-anchor="middle" font-size="19" style="opacity:${u}">${name}</text>`; |
| 354 | + } |
| 355 | + }); |
| 356 | + s += `<line x1="${x0-10}" y1="${y0+H}" x2="${x0+14*(bw+gap)}" y2="${y0+H}" stroke="${css('--line')}" stroke-width="2"/>`; |
| 357 | + const tagU = ease((t - c(2)) / 0.6); |
| 358 | + if (tagU > 0) { |
| 359 | + const x = x0 + 1 * (bw + gap); |
| 360 | + const yTop = y0 + H - H * 1, yMiss = y0 + H - H * (5/6); |
| 361 | + s += `<rect x="${x-4}" y="${yTop-4}" width="${bw+8}" height="${yMiss-yTop+8}" fill="none" stroke="${css('--accent')}" stroke-width="3" stroke-dasharray="7 5" rx="4" opacity="${tagU}"/>`; |
| 362 | + s += `<text x="${x+bw+18}" y="${yTop+18}" font-size="24" style="fill:${css('--accent')};font-weight:600;opacity:${tagU}">the same line</text>`; |
| 363 | + } |
| 364 | + const ovU = ease((t - c(4)) / 0.7); |
| 365 | + if (ovU > 0) { |
| 366 | + s += `<text x="${x0 + 9*(bw+gap)}" y="${y0+90}" text-anchor="middle" font-size="34" style="fill:${css('--fail')};font-weight:600;opacity:${ovU}">10 of 14 — zero blind passes</text>`; |
| 367 | + } |
| 368 | + el.innerHTML = s; |
| 369 | + document.querySelector('#s4 .notline').style.opacity = ease((t - c(5)) / 0.4); |
| 370 | + const spans = document.querySelectorAll('#s4 .notline span'); |
| 371 | + spans[0].style.opacity = ease((t - c(5)) / 0.6); |
| 372 | + spans[1].style.opacity = ease((t - c(6)) / 0.6); |
| 373 | + document.querySelector('#s4 .final4').style.opacity = ease((t - c(7)) / 0.7); |
| 374 | +} |
| 375 | + |
239 | 376 | const SCENES = [ |
240 | 377 | { el: 's0', fn: seekS0 }, |
241 | 378 | { el: 's1', fn: seekS1 }, |
242 | 379 | { el: 's2', fn: seekS2 }, |
| 380 | + { el: 's3', fn: seekS3 }, |
| 381 | + { el: 's4', fn: seekS4 }, |
243 | 382 | ]; |
244 | 383 | window.seek = (idx, t, dur, cues, sdurs) => { |
245 | 384 | SCENES.forEach((s, i) => document.getElementById(s.el).classList.toggle('active', i === idx)); |
|
0 commit comments