screengraft 0.63.6 → 0.63.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/ui/index.html +19 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screengraft",
3
- "version": "0.63.6",
3
+ "version": "0.63.8",
4
4
  "description": "Put a UI screenshot or screen recording onto a photographed device screen with the perspective exactly right \u2014 a homography you confirm by hand, not a generative guess.",
5
5
  "keywords": [
6
6
  "mockup",
package/ui/index.html CHANGED
@@ -667,8 +667,15 @@
667
667
  display:inline-flex;line-height:20px}
668
668
  #detSt:empty,#outSt:empty{display:none}
669
669
  .lbl{color:var(--ink);text-transform:uppercase;letter-spacing:.06em;font-size:11px;font-weight:600}
670
+ /* overscroll-behavior:none on both wells (14 Sep 2026): a trackpad pinch is a
671
+ wheel event we zoom on, but macOS still owns the gesture's elastic bounce.
672
+ While the fingers were moving the browser let the well rubber-band past
673
+ the picture's bottom edge, so the last rows of the photo sat under a band
674
+ of well for as long as the gesture lasted -- the "photo cropped at the
675
+ bottom on zoom" report of 14 Sep 2026, seen in both panes. Not
676
+ reproducible from script, because scripted scrolls clamp. */
670
677
  .scroller{overflow:auto;background:var(--well);display:grid;
671
- min-height:0;min-width:0;
678
+ min-height:0;min-width:0;overscroll-behavior:none;
672
679
  box-shadow:inset 0 1px 3px rgba(0,0,0,.5)}
673
680
  /* tabindex -1, not 0: the canvas is not a tab stop — it takes focus only when
674
681
  you actually click it, which is what makes space mean PAN rather than
@@ -785,7 +792,7 @@
785
792
  button.switch:focus-visible .sw-track{box-shadow:0 0 0 3px var(--acc-soft);border-color:var(--acc)}
786
793
 
787
794
  #outPane{display:grid}
788
- #outWrap{overflow:auto;background:var(--well);display:grid;box-shadow:inset 0 1px 3px rgba(0,0,0,.5)}
795
+ #outWrap{overflow:auto;overscroll-behavior:none;background:var(--well);display:grid;box-shadow:inset 0 1px 3px rgba(0,0,0,.5)}
789
796
  /* The depth-of-field gizmo: an SVG laid exactly over the composite, drawn
790
797
  in PHOTO pixels (its viewBox is the photo) so every coordinate is the
791
798
  engine's own. Always at full opacity: the fade-when-idle was tried and
@@ -2565,15 +2572,20 @@ function ringColour(rgb){ let b = rgb.map(v => v/255);
2565
2572
  return 'rgb(' + b.map(v => Math.round(Math.max(0, Math.min(1, v))*255)).join(',') + ')'; }
2566
2573
  // One small copy of each pane's picture to sample from (a readback per draw,
2567
2574
  // not per pixel). Fit pane: the canvas; result pane: the composite <img>.
2575
+ const WELL_RGB = [10,10,11]; // --well, what is under a rail that is not over the picture
2568
2576
  const _sampleCv = document.createElement('canvas'), _sampleCx = _sampleCv.getContext('2d', {willReadFrequently: true});
2569
2577
  function _backdropSampler(source, rect){
2570
2578
  const W = 256, H = Math.max(1, Math.round(256 * rect.height / Math.max(rect.width, 1)));
2571
2579
  _sampleCv.width = W; _sampleCv.height = H;
2572
2580
  try { _sampleCx.drawImage(source, 0, 0, W, H); } catch (e) { return null; }
2573
2581
  const data = _sampleCx.getImageData(0, 0, W, H).data;
2574
- return (x, y) => { const px = Math.max(0, Math.min(W-1, Math.round((x - rect.left) * W / rect.width)));
2575
- const py = Math.max(0, Math.min(H-1, Math.round((y - rect.top) * H / rect.height)));
2576
- const i = (py*W + px) * 4; return data[i+3] ? [data[i], data[i+1], data[i+2]] : null; };
2582
+ // A point outside the picture is over the well, not over the picture's
2583
+ // nearest edge -- clamping put the left rail on the photo's bright edge
2584
+ // column and painted it light (Safari, 14 Sep 2026).
2585
+ return (x, y) => { if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) return WELL_RGB;
2586
+ const px = Math.max(0, Math.min(W-1, Math.floor((x - rect.left) * W / rect.width)));
2587
+ const py = Math.max(0, Math.min(H-1, Math.floor((y - rect.top) * H / rect.height)));
2588
+ const i = (py*W + px) * 4; return data[i+3] ? [data[i], data[i+1], data[i+2]] : WELL_RGB; };
2577
2589
  }
2578
2590
  function _sideColours(sample, r, out){
2579
2591
  const N = 9, sides = {top: [], right: [], bottom: [], left: []};
@@ -2581,7 +2593,7 @@ function _sideColours(sample, r, out){
2581
2593
  sides.top.push(sample(r.left + r.width*t, r.top - 1)); sides.bottom.push(sample(r.left + r.width*t, r.bottom + 1));
2582
2594
  sides.left.push(sample(r.left - 1, r.top + r.height*t)); sides.right.push(sample(r.right + 1, r.top + r.height*t)); }
2583
2595
  for (const [side, pts] of Object.entries(sides)){ const ok = pts.filter(Boolean);
2584
- const avg = ok.length ? [0,1,2].map(i => ok.reduce((a, p) => a + p[i], 0) / ok.length) : [11,11,11];
2596
+ const avg = ok.length ? [0,1,2].map(i => ok.reduce((a, p) => a + p[i], 0) / ok.length) : WELL_RGB;
2585
2597
  out.style['border' + side[0].toUpperCase() + side.slice(1) + 'Color'] = ringColour(avg); }
2586
2598
  }
2587
2599
  let _ringsQueued = false;
@@ -2597,7 +2609,7 @@ function paintRingsFallback(){
2597
2609
  const sample = ok && rect.width > 0 ? _backdropSampler(src, rect) : null;
2598
2610
  pane.querySelectorAll('.bgroup > .ring.lift, .pillwrap > .ring.lift').forEach(ring => {
2599
2611
  const r = ring.parentElement.getBoundingClientRect();
2600
- if (sample) _sideColours(sample, r, ring); else ['Top','Right','Bottom','Left'].forEach(sd => ring.style['border' + sd + 'Color'] = ringColour([11,11,11]));
2612
+ if (sample) _sideColours(sample, r, ring); else ['Top','Right','Bottom','Left'].forEach(sd => ring.style['border' + sd + 'Color'] = ringColour(WELL_RGB));
2601
2613
  });
2602
2614
  }
2603
2615
  });