master
John Lauer rust: Phase 3 complete (vault, cards, wiki auto-sign + view flip, annotate, tab + screen recording, highFps lease, down… 4869577 2mo ago
window.__pupStartAnnotate = function (initTool, initMode) {
  if (window.__pupAnnotate) {
    // v2.0.372: a re-arm with an explicit mode SWITCHES the live overlay's mode (Alt+Shift+M on
    // an already-open toolbar). v2.0.81 rule preserved: a re-arm after emit-hide stays INVISIBLE.
    if (initMode && window.__pupAnnotate.setMode) { try { window.__pupAnnotate.setMode(initMode); } catch (e) {} }
    if (!window.__pupAnnotateHidden) window.__pupAnnotate.show();
    return 'shown';
  }
  initTool = initTool || 'pen';
  // v2.0.370 (John): two explicit capture modes. quick=true (Auto, the default) captures the
  // moment the mouse lifts — the one-red-circle case. quick=false (Multi) accumulates shapes —
  // a red circle here, a blue circle there — and captures ONCE on Done.
  var S = { tool: 'pen', color: '#f85149', strokes: [], cur: null, quick: initMode !== 'multi', seq: 0 };
  var wrap = document.createElement('div');
  wrap.id = '__pup_annotate';
  wrap.style.cssText = 'position:fixed;inset:0;z-index:2147483646;pointer-events:none;';
  var cv = document.createElement('canvas');
  cv.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;pointer-events:auto;touch-action:none;';
  wrap.appendChild(cv);
  var bar = document.createElement('div');
  bar.style.cssText = 'position:fixed;top:14px;left:50%;transform:translateX(-50%);display:flex;gap:6px;align-items:center;background:#161b22f2;border:1px solid #30363d;border-radius:999px;padding:7px 12px;pointer-events:auto;font:13px Satoshi,system-ui,sans-serif;color:#c9d1d9;box-shadow:0 6px 24px #000a;';
  wrap.appendChild(bar);
  // drag grip (floating-windows: draggable by a visible grip, clamped inside the viewport)
  var grip = document.createElement('div');
  grip.title = '';
  grip.style.cssText = 'width:14px;height:22px;cursor:grab;display:grid;grid-template-columns:1fr 1fr;gap:2px;align-content:center;justify-items:center;margin-right:2px;';
  for (var gi = 0; gi < 6; gi++) { var gd = document.createElement('div'); gd.style.cssText = 'width:3px;height:3px;border-radius:50%;background:#8b949e;'; grip.appendChild(gd); }
  bar.appendChild(grip);
  (function () {
    var drag = null;
    grip.addEventListener('pointerdown', function (e) {
      var r = bar.getBoundingClientRect();
      if (bar.style.transform) { bar.style.transform = ''; bar.style.left = r.left + 'px'; bar.style.top = r.top + 'px'; }
      drag = { dx: e.clientX - r.left, dy: e.clientY - r.top };
      grip.setPointerCapture(e.pointerId); grip.style.cursor = 'grabbing'; e.preventDefault();
    });
    grip.addEventListener('pointermove', function (e) {
      if (!drag) return;
      var w = bar.offsetWidth, h = bar.offsetHeight, M = 8;
      var x = Math.min(Math.max(M, e.clientX - drag.dx), innerWidth - w - M);
      var y = Math.min(Math.max(M, e.clientY - drag.dy), innerHeight - h - M);
      bar.style.left = x + 'px'; bar.style.top = y + 'px';
    });
    grip.addEventListener('pointerup', function () { drag = null; grip.style.cursor = 'grab'; });
  })();
  // tooltip singleton (tooltips skill: fixed, body-level z-max, 600ms reveal, 0.18s ease
  // fade+translate, measured AFTER content set, clamped to the viewport on BOTH axes, flips
  // above when it would clip the bottom)
  var tipEl = document.createElement('div');
  tipEl.style.cssText = 'position:fixed;z-index:2147483647;max-width:280px;background:#21262d;border:1px solid #30363d;border-radius:8px;padding:7px 10px;font:12px Satoshi,system-ui,sans-serif;color:#c9d1d9;box-shadow:0 6px 20px #000b;pointer-events:none;opacity:0;transform:translateY(4px);transition:opacity 0.18s ease,transform 0.18s ease;text-transform:none;';
  wrap.appendChild(tipEl);
  var tipTimer = null;
  function tipShow(anchor, text) {
    clearTimeout(tipTimer);
    tipTimer = setTimeout(function () {
      tipEl.textContent = text;
      tipEl.style.opacity = '0';
      // measure only after content is set and laid out
      requestAnimationFrame(function () {
        var a = anchor.getBoundingClientRect(), M = 8;
        var w = tipEl.offsetWidth, h = tipEl.offsetHeight;
        var x = a.left + a.width / 2 - w / 2;
        var y = a.bottom + 10;
        if (y + h > innerHeight - M) y = a.top - h - 10;   // flip above instead of clipping
        x = Math.min(Math.max(M, x), innerWidth - w - M);
        y = Math.min(Math.max(M, y), innerHeight - h - M);
        tipEl.style.left = x + 'px'; tipEl.style.top = y + 'px';
        tipEl.style.opacity = '1'; tipEl.style.transform = 'translateY(0)';
      });
    }, 600);
  }
  function tipHide() { clearTimeout(tipTimer); tipEl.style.opacity = '0'; tipEl.style.transform = 'translateY(4px)'; }
  function tipFor(el, text) { el.addEventListener('mouseenter', function () { tipShow(el, text); }); el.addEventListener('mouseleave', tipHide); el.addEventListener('pointerdown', tipHide); el.removeAttribute('title'); }
  tipFor(grip, 'Drag to move this toolbar anywhere it is not in your way.');
  var ctx = cv.getContext('2d');
  // ── Photoshop-style tool cursors (v2.0.134, John's HN report: "pen mode shows the crosshair,
  // must be old code"): it was NOT old code. The pen/arrow cursors were CSS
  // url(data:image/svg+xml) images, and CSS cursor images load under the PAGE'S CSP img-src —
  // sites that don't allow data: (Hacker News: img-src 'self' ...) silently block the image and
  // CSS falls back to the declared crosshair, so one build looked new on the wiki and old on HN.
  // Fix: never FETCH a cursor at all. Hide the native cursor over the canvas and follow the
  // pointer with an inline-SVG sprite — DOM markup, which no CSP gates — hotspot preserved
  // (pen tip at 5,19; arrow tip at 2,2). Pen still carries the live color swatch.
  var spr = document.createElement('div');
  spr.style.cssText = 'position:fixed;left:0;top:0;z-index:2147483647;pointer-events:none;display:none;will-change:transform;';
  wrap.appendChild(spr);
  var sprHot = [0, 0];
  function penSvg() {
    return '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">'
      + '<path d="M5 19 L6.5 14.5 L16 5 a2 2 0 0 1 2.8 2.8 L9.5 17.5 Z" fill="#e5e7eb" stroke="#111" stroke-width="1.2"/>'
      + '<path d="M5 19 L6.5 14.5 L9.5 17.5 Z" fill="' + S.color + '" stroke="#111" stroke-width="0.8"/>'
      + '<circle cx="19.5" cy="19.5" r="3.5" fill="' + S.color + '" stroke="#fff" stroke-width="1"/></svg>';
  }
  function arrowSvg() {
    return '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">'
      + '<path d="M2 2 L11 20 L13.5 13.5 L20 11 Z" fill="' + S.color + '" stroke="#fff" stroke-width="1.4"/></svg>';
  }
  function setCursor() {
    if (S.tool === 'pen' || S.tool === 'arrow') {
      cv.style.cursor = 'none';
      spr.innerHTML = S.tool === 'pen' ? penSvg() : arrowSvg();
      sprHot = S.tool === 'pen' ? [5, 19] : [2, 2];
      // shown on the next pointer move, so it never appears at a stale position
    } else {
      cv.style.cursor = S.tool === 'text' ? 'text' : 'crosshair';
      spr.style.display = 'none';
    }
  }
  function sprTrack(e) {
    if (S.tool !== 'pen' && S.tool !== 'arrow') return;
    spr.style.display = 'block';
    spr.style.transform = 'translate(' + (e.clientX - sprHot[0]) + 'px,' + (e.clientY - sprHot[1]) + 'px)';
  }
  cv.addEventListener('pointermove', sprTrack);
  cv.addEventListener('pointerenter', sprTrack);
  cv.addEventListener('pointerleave', function () { spr.style.display = 'none'; });
  function fit() { var d = window.devicePixelRatio || 1; cv.width = innerWidth * d; cv.height = innerHeight * d; ctx.setTransform(d, 0, 0, d, 0, 0); redraw(); }
  function drawStroke(st) {
    ctx.strokeStyle = st.color; ctx.fillStyle = st.color; ctx.lineWidth = 3.5; ctx.lineCap = 'round'; ctx.lineJoin = 'round';
    if (st.tool === 'pen') { ctx.beginPath(); for (var i = 0; i < st.pts.length; i++) { var p = st.pts[i]; if (i === 0) ctx.moveTo(p[0], p[1]); else ctx.lineTo(p[0], p[1]); } ctx.stroke(); }
    else if (st.tool === 'box') { var a = st.pts[0], b = st.pts[st.pts.length - 1]; ctx.strokeRect(Math.min(a[0],b[0]), Math.min(a[1],b[1]), Math.abs(b[0]-a[0]), Math.abs(b[1]-a[1])); }
    else if (st.tool === 'arrow') { var a2 = st.pts[0], b2 = st.pts[st.pts.length - 1]; ctx.beginPath(); ctx.moveTo(a2[0], a2[1]); ctx.lineTo(b2[0], b2[1]); ctx.stroke();
      var ang = Math.atan2(b2[1]-a2[1], b2[0]-a2[0]);
      ctx.beginPath(); ctx.moveTo(b2[0], b2[1]);
      ctx.lineTo(b2[0]-14*Math.cos(ang-0.42), b2[1]-14*Math.sin(ang-0.42));
      ctx.lineTo(b2[0]-14*Math.cos(ang+0.42), b2[1]-14*Math.sin(ang+0.42));
      ctx.closePath(); ctx.fill(); }
    else if (st.tool === 'text') { ctx.font = '600 17px system-ui,sans-serif'; ctx.fillText(st.text || '', st.pts[0][0], st.pts[0][1]); }
    else if (st.tool === 'snip') { var s1 = st.pts[0], s2 = st.pts[st.pts.length - 1]; ctx.save(); ctx.setLineDash([7, 5]); ctx.lineWidth = 2; ctx.strokeStyle = '#00d4cb'; ctx.strokeRect(Math.min(s1[0],s2[0]), Math.min(s1[1],s2[1]), Math.abs(s2[0]-s1[0]), Math.abs(s2[1]-s1[1])); ctx.restore(); }
  }
  function redraw() { ctx.clearRect(0, 0, innerWidth, innerHeight); S.strokes.forEach(drawStroke); if (S.cur) drawStroke(S.cur); }
  // Tight-crop bbox over ALL strokes, padded and clamped to the viewport (1a2).
  function bbox() {
    if (!S.strokes.length) return null;
    var x1 = 1e9, y1 = 1e9, x2 = -1e9, y2 = -1e9;
    S.strokes.forEach(function (st) {
      if (st.tool === 'text') {
        var w = (st.text || '').length * 9.5, p0 = st.pts[0];
        x1 = Math.min(x1, p0[0]); y1 = Math.min(y1, p0[1] - 18); x2 = Math.max(x2, p0[0] + w); y2 = Math.max(y2, p0[1] + 6);
      } else st.pts.forEach(function (p) { x1 = Math.min(x1, p[0]); y1 = Math.min(y1, p[1]); x2 = Math.max(x2, p[0]); y2 = Math.max(y2, p[1]); });
    });
    var M = 48;
    x1 = Math.max(0, x1 - M); y1 = Math.max(0, y1 - M);
    x2 = Math.min(innerWidth, x2 + M); y2 = Math.min(innerHeight, y2 + M);
    if (x2 - x1 < 24 || y2 - y1 < 24) return null;
    return { x: Math.round(x1), y: Math.round(y1), w: Math.round(x2 - x1), h: Math.round(y2 - y1) };
  }
  function emit(extra, hide) {
    S.seq++;
    var r = { done: true, shapes: S.strokes.length, ts: Date.now(), seq: S.seq, bbox: bbox() };
    if (extra) for (var k in extra) r[k] = extra[k];
    if (hide) { bar.style.display = 'none'; cv.style.pointerEvents = 'none'; window.__pupAnnotateHidden = true; }
    window.__pupAnnotateResult = r;
  }
  cv.addEventListener('pointerdown', function (e) {
    if (S.tool === 'text') { var t = prompt('Annotation text:'); if (t) { S.strokes.push({ tool: 'text', color: S.color, pts: [[e.clientX, e.clientY]], text: t }); redraw(); if (S.quick) { emit(null, true); removeEventListener('keydown', onKey, true); } else advanceColor(); } return; }
    S.cur = { tool: S.tool, color: S.color, pts: [[e.clientX, e.clientY]] }; cv.setPointerCapture(e.pointerId);
  });
  cv.addEventListener('pointermove', function (e) { if (S.cur) { S.cur.pts.push([e.clientX, e.clientY]); redraw(); } });
  cv.addEventListener('pointerup', function () {
    if (!S.cur) return;
    if (S.cur.tool === 'snip') {
      var p1 = S.cur.pts[0], p2 = S.cur.pts[S.cur.pts.length - 1];
      var r = { x: Math.min(p1[0], p2[0]), y: Math.min(p1[1], p2[1]), w: Math.abs(p2[0]-p1[0]), h: Math.abs(p2[1]-p1[1]) };
      S.cur = null; redraw();
      if (r.w > 8 && r.h > 8) emit({ snip: { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.w), h: Math.round(r.h) } }, true);
      return;
    }
    S.strokes.push(S.cur); S.cur = null; redraw();
    // QUICK MODE (1a3): the moment the mouse lifts, capture fires — keep drawing and each lift
    // supersedes the last capture with the union bbox.
    if (S.quick) { emit(null, true); removeEventListener('keydown', onKey, true); }
    // v2.0.371 (John): in MULTI mode each finished shape auto-advances to the next color, so a
    // red circle then a blue circle needs zero clicks. White is excluded (invisible on light
    // pages); a manual swatch pick overrides and the cycle continues from there.
    else advanceColor();
  });
  function icon(paths, dash) {
    var n = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    n.setAttribute('viewBox', '0 0 24 24'); n.setAttribute('width', '14'); n.setAttribute('height', '14');
    n.setAttribute('fill', 'none'); n.setAttribute('stroke', 'currentColor');
    n.setAttribute('stroke-width', '1.9'); n.setAttribute('stroke-linecap', 'round'); n.setAttribute('stroke-linejoin', 'round');
    paths.forEach(function (dd, i) { var p2 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); p2.setAttribute('d', dd); if (dash && i === dash - 1) p2.setAttribute('stroke-dasharray', '2.5 2'); n.appendChild(p2); });
    return n;
  }
  var ICONS = {
    pen: [['M4 20l1.5-4.5L16 5a2.1 2.1 0 0 1 3 3L8.5 18.5 4 20z', 'M13.5 7.5l3 3']],
    arrow: [['M5 19L17 7', 'M9 7h8v8']],
    box: [['M4.5 5.5h15v13h-15z']],
    text: [['M6 6h12', 'M12 6v12']],
    snip: [['M4 8V5a1 1 0 0 1 1-1h3M16 4h3a1 1 0 0 1 1 1v3M20 16v3a1 1 0 0 1-1 1h-3M8 20H5a1 1 0 0 1-1-1v-3', 'M8.5 8.5h7v7h-7z'], 2],
    auto: [['M13 3L5 13h5l-1 8 8-10h-5l1-8']],
    multi: [['M4 4.5h10v10H4z', 'M10 9.5h10v10H10z']],
    undo: [['M8 5L4 9l4 4', 'M4 9h9a6 6 0 0 1 0 12h-2']],
    clear: [['M5 7h14', 'M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2', 'M7 7l1 13h8l1-13']],
    done: [['M4.5 12.5l5 5 10-11']],
    cancel: [['M6 6l12 12', 'M18 6L6 18']],
  };
  function btn(label, title, fn, id, iconKey) {
    var b = document.createElement('button');
    if (iconKey && ICONS[iconKey]) b.appendChild(icon(ICONS[iconKey][0], ICONS[iconKey][1]));
    b.appendChild(document.createTextNode(label)); b.title = title;
    b.style.cssText = 'background:none;border:1px solid transparent;border-radius:8px;color:#c9d1d9;padding:3px 8px;cursor:pointer;font:inherit;display:flex;align-items:center;gap:5px;';
    b.onmouseenter = function () { b.style.background = '#1c2128'; };
    b.onmouseleave = function () { b.style.background = S.tool === b.dataset.tool ? 'rgba(0,184,177,.13)' : 'none'; };
    b.onclick = fn; if (id) b.dataset.tool = id; bar.appendChild(b);
    if (title) { tipFor(b, title); }
    return b;
  }
  function selTool(id) { S.tool = id; setCursor(); [].forEach.call(bar.querySelectorAll('button[data-tool]'), function (x) { x.style.background = x.dataset.tool === id ? 'rgba(0,184,177,.13)' : 'none'; x.style.borderColor = x.dataset.tool === id ? 'rgba(0,184,177,.45)' : 'transparent'; }); }
  btn('Pen', 'Draw freehand. With Auto on, the capture fires the moment you release the mouse: cropped tight to your drawing, copied to your clipboard.', function () { selTool('pen'); }, 'pen', 'pen');
  btn('Arrow', 'Point at the thing you mean. Drag from tail to tip; the arrowhead lands where you release.', function () { selTool('arrow'); }, 'arrow', 'arrow');
  btn('Box', 'Frame a region with a rectangle. Drag corner to corner.', function () { selTool('box'); }, 'box', 'box');
  btn('Text', 'Drop a short text note onto the page. Click where the note should start.', function () { selTool('text'); }, 'text', 'text');
  btn('Snip', 'Capture exactly a region, like Win+Shift+S: drag a marquee and it is copied to your clipboard the moment you release.', function () { selTool('snip'); }, 'snip', 'snip');
  // v2.0.370 (John): the color control is now a one-click TRIO — red (default), blue, yellow —
  // with the rarer colors one click deeper in a flyout. The old single cycling dot made
  // red→blue three clicks; picking between about 3 colors must be instant.
  var SW_MAIN = ['#f85149', '#64abff', '#d29922'];
  var SW_MORE = ['#3fb950', '#8c6bf7', '#e6edf3'];
  var flyout = document.createElement('div');
  flyout.style.cssText = 'position:absolute;top:calc(100% + 8px);left:50%;transform:translateX(-50%);display:none;gap:7px;background:#161b22f2;border:1px solid #30363d;border-radius:999px;padding:7px 10px;box-shadow:0 6px 24px #000a;pointer-events:auto;';
  var more = document.createElement('button');
  function setColor(c) {
    S.color = c; setCursor(); flyout.style.display = 'none';
    [].forEach.call(wrap.querySelectorAll('button[data-sw]'), function (x) { x.style.borderColor = x.dataset.sw === c ? '#fff' : 'rgba(255,255,255,.25)'; });
    more.style.outline = SW_MORE.indexOf(c) >= 0 ? '2px solid #fff' : 'none';
  }
  // v2.0.371: the multi-mode auto-advance cycle — the quick trio then green/purple, never white.
  function advanceColor() {
    var CYCLE = ['#f85149', '#64abff', '#d29922', '#3fb950', '#8c6bf7'];
    setColor(CYCLE[(CYCLE.indexOf(S.color) + 1) % CYCLE.length]);
  }
  function swBtn(c, parent) {
    var s = document.createElement('button');
    s.dataset.sw = c;
    s.style.cssText = 'width:18px;height:18px;border-radius:50%;cursor:pointer;background:' + c + ';border:2px solid ' + (S.color === c ? '#fff' : 'rgba(255,255,255,.25)') + ';padding:0;flex:none;';
    s.onclick = function () { setColor(c); };
    parent.appendChild(s);
    return s;
  }
  SW_MAIN.forEach(function (c) { swBtn(c, bar); });
  more.style.cssText = 'width:18px;height:18px;border-radius:50%;cursor:pointer;border:2px solid rgba(255,255,255,.25);padding:0;flex:none;background:conic-gradient(#f85149,#d29922,#3fb950,#64abff,#8c6bf7,#f85149);';
  more.onclick = function () { flyout.style.display = flyout.style.display === 'none' ? 'flex' : 'none'; };
  bar.appendChild(more); tipFor(more, 'More colors: green, purple, white.');
  SW_MORE.forEach(function (c) { swBtn(c, flyout); });
  bar.appendChild(flyout);
  // v2.0.370 (John): Auto vs Multi as an explicit two-state control, not a lone toggle.
  var modeWrap = document.createElement('div');
  modeWrap.style.cssText = 'display:flex;border:1px solid #30363d;border-radius:8px;overflow:hidden;margin-left:2px;';
  function modeBtn(label, iconKey, val, tip) {
    var b = document.createElement('button');
    if (ICONS[iconKey]) b.appendChild(icon(ICONS[iconKey][0], ICONS[iconKey][1]));
    b.appendChild(document.createTextNode(label));
    b.style.cssText = 'background:none;border:none;color:#c9d1d9;padding:3px 8px;cursor:pointer;font:inherit;display:flex;align-items:center;gap:5px;';
    b.onclick = function () { S.quick = val; paintMode(); };
    modeWrap.appendChild(b); tipFor(b, tip);
    return b;
  }
  var mAuto = modeBtn('Auto', 'auto', true, 'One-shot (the default): draw one thing, lift the mouse, and it is captured and copied to your clipboard instantly.');
  var mMulti = modeBtn('Multi', 'multi', false, 'Compose: draw several shapes and NOTHING is captured until you click Done. The color auto-advances after each shape (red, blue, yellow, green, purple) so every shape is distinct — click a swatch any time to override.');
  function paintMode() {
    mAuto.style.background = S.quick ? 'rgba(0,184,177,.13)' : 'none'; mAuto.style.color = S.quick ? '#00d4cb' : '#c9d1d9';
    mMulti.style.background = !S.quick ? 'rgba(0,184,177,.13)' : 'none'; mMulti.style.color = !S.quick ? '#00d4cb' : '#c9d1d9';
  }
  paintMode(); bar.appendChild(modeWrap);
  btn('Undo', 'Remove the last thing you drew. Click again to keep unwinding.', function () { S.strokes.pop(); redraw(); }, null, 'undo');
  btn('Clear', 'Erase every drawing on this page. The page itself is untouched.', function () { S.strokes = []; redraw(); }, null, 'clear');
  var done = btn('Done', 'Capture the annotated area (tight-cropped around your drawing), copy it to your clipboard, and leave the drawing visible so the AI thread that owns this window sees it too.', function () { emit(null, true); }, null, 'done');
  done.style.cssText += 'background:rgba(0,184,177,.13);border:1px solid rgba(0,184,177,.55);color:#00d4cb;font-weight:600;';
  btn('', 'Close the annotation toolbar and remove all drawings from the page (Esc does this too).', function () { exitAll(); }, null, 'cancel');
  function exitAll() { removeEventListener('keydown', onKey, true); wrap.remove(); window.__pupAnnotate = null; window.__pupAnnotateResult = { done: false, ts: Date.now() }; }
  function onKey(e) { if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); exitAll(); } }
  addEventListener('keydown', onKey, true);
  // v2.0.72: called by the bridge the moment the screenshot is TAKEN (never before, so the
  // flash is not in the shot): camera-flash feedback over the captured region, instant toast
  // with the preview, then the drawings fade out so the user is back to normal surfing.
  window.__pupCaptureDone = function (region, previewUrl, line1) {
    try {
      var fl = document.createElement('div');
      if (!region || !region.w) throw 0; // skip the flash only (caught below): John's rule, flash the captured region or nothing — toast and fade still run
      var rg = region;
      fl.style.cssText = 'position:fixed;left:' + rg.x + 'px;top:' + rg.y + 'px;width:' + rg.w + 'px;height:' + rg.h + 'px;z-index:2147483647;background:none;border:2px dashed #00d4cb;border-radius:6px;opacity:1;pointer-events:none;transition:opacity .45s ease;box-sizing:border-box;';
      document.documentElement.appendChild(fl);
      setTimeout(function () { fl.style.opacity = '0'; }, 550);
      setTimeout(function () { try { fl.remove(); } catch (e) {} }, 1100);
    } catch (e) {}
    try {
      var old = document.getElementById('__pup_ann_toast'); if (old) old.remove();
      var t = document.createElement('div'); t.id = '__pup_ann_toast';
      t.style.cssText = 'position:fixed;left:50%;transform:translateX(-50%);bottom:18px;z-index:2147483647;background:#21262df2;border:1px solid #30363d;border-radius:12px;padding:10px;box-shadow:0 8px 30px #000c;font:12px Satoshi,system-ui,sans-serif;color:#c9d1d9;max-width:340px;cursor:pointer;';
      var cap = document.createElement('div'); cap.textContent = line1 || 'Captured'; cap.style.cssText = 'margin-bottom:7px;color:#00d4cb;font-weight:500;';
      t.appendChild(cap);
      // v2.0.135: the preview arrives as RAW RGBA pixels and is PAINTED (putImageData), never
      // LOADED — an <img src="data:..."> is a resource load the page's CSP img-src can veto
      // (Hacker News does), which showed the user a broken-image icon. Pixel writes to a canvas
      // are not loads; no site's CSP can break this. (String form kept for mixed-version overlap.)
      if (previewUrl && previewUrl.b64 && previewUrl.w) {
        try {
          var pc = document.createElement('canvas'); pc.width = previewUrl.w; pc.height = previewUrl.h;
          pc.style.cssText = 'display:block;max-width:320px;max-height:200px;border-radius:8px;border:1px solid #30363d;';
          var bin = atob(previewUrl.b64), n = bin.length, arr = new Uint8ClampedArray(n);
          for (var i2 = 0; i2 < n; i2++) arr[i2] = bin.charCodeAt(i2);
          pc.getContext('2d').putImageData(new ImageData(arr, previewUrl.w, previewUrl.h), 0, 0);
          t.appendChild(pc);
        } catch (e2) {}
      } else if (previewUrl && typeof previewUrl === 'string') { var im = document.createElement('img'); im.src = previewUrl; im.style.cssText = 'display:block;max-width:320px;max-height:200px;border-radius:8px;border:1px solid #30363d;'; t.appendChild(im); }
      t.onclick = function () { t.remove(); };
      document.documentElement.appendChild(t);
      setTimeout(function () { try { t.remove(); } catch (e) {} }, 4500);
    } catch (e) {}
    // fade the drawings out (John: after the snapshot, back to normal surfing)
    setTimeout(function () {
      try {
        cv.style.transition = 'opacity .8s ease'; cv.style.opacity = '0';
        setTimeout(function () { try { wrap.remove(); window.__pupAnnotate = null; window.__pupCaptureDone = null; removeEventListener('keydown', onKey, true); } catch (e) {} }, 900);
      } catch (e) {}
    }, 2200);
  };
  window.__pupAnnotate = {
    show: function () { window.__pupAnnotateHidden = false; wrap.style.display = ''; bar.style.display = 'flex'; cv.style.pointerEvents = 'auto'; addEventListener('keydown', onKey, true); },
    setMode: function (m) { S.quick = m !== 'multi'; paintMode(); },
  };
  addEventListener('resize', fit);
  document.documentElement.appendChild(wrap);
  fit(); selTool(initTool);
  return 'started';
};