screengraft 0.54.0 → 0.54.2

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 +43 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screengraft",
3
- "version": "0.54.0",
3
+ "version": "0.54.2",
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
@@ -584,14 +584,16 @@
584
584
  button.switch:hover{background:none}
585
585
  button.switch:active{background:none;border:none;box-shadow:none;transform:none}
586
586
 
587
- .sw-track{position:relative;display:block;width:48px;height:22px;border-radius:6px;
587
+ /* Track 48×20 inside a 48×28 hit area (Figma 17:61): the button keeps the
588
+ full 28px for the pointer, the drawn track is 20px. Was 22px. */
589
+ .sw-track{position:relative;display:block;width:48px;height:20px;border-radius:6px;
588
590
  background:var(--bg);border:1px solid var(--edge);
589
591
  transition:var(--t-all), box-shadow var(--t)}
590
592
  .sw-handle{position:absolute;top:1px;bottom:1px;left:1px;width:24px;border-radius:4px;
591
593
  background:var(--raise-hi);
592
594
  transition:left var(--t), right var(--t), width var(--t), background-color var(--t), opacity var(--t)}
593
595
  .sw-grip{position:absolute;border-radius:1px;background:var(--sunk);
594
- left:11px;width:4px;top:8px;bottom:8px;
596
+ left:11px;width:4px;top:7px;bottom:7px;
595
597
  transition:left var(--t), width var(--t), top var(--t), bottom var(--t), background-color var(--t)}
596
598
 
597
599
  /* --- OFF ------------------------------------------------------------- */
@@ -602,7 +604,7 @@
602
604
  /* --- ON -------------------------------------------------------------- */
603
605
  button.switch[aria-checked="true"] .sw-track{background:var(--acc-btn-rest);border-color:var(--acc-stroke)}
604
606
  button.switch[aria-checked="true"] .sw-handle{left:auto;right:1px;background:var(--acc-ink)}
605
- button.switch[aria-checked="true"] .sw-grip{left:32px;width:2px;top:5px;bottom:5px;
607
+ button.switch[aria-checked="true"] .sw-grip{left:32px;width:2px;top:4px;bottom:4px;
606
608
  background:var(--acc-btn-rest)}
607
609
  button.switch[aria-checked="true"]:hover .sw-track{background:var(--acc);border-color:var(--acc-stroke)}
608
610
  button.switch[aria-checked="true"]:hover .sw-grip{background:var(--acc)}
@@ -2653,6 +2655,10 @@ $('#dofMeasure').onclick = measureDof;
2653
2655
  const gz = $('#dofGizmo');
2654
2656
  const SIGMA_FULL_FRAC = 0.02; // mirrors dof.DOF_MAX_FRAC -- keep in step
2655
2657
  let dofEnd = parseFloat(recall('dofEnd','1')); if (!(dofEnd > 0)) dofEnd = 1;
2658
+ // Where along the lines the handles sit (0..1 across the screen's extent).
2659
+ // Page-only: it changes nothing in the render. Turning re-derives it so the
2660
+ // handle stays on the point you turned about instead of sliding along the line.
2661
+ let dofK = 0.5;
2656
2662
  // The near limit. null = one-sided (the plane of focus at the screen's edge).
2657
2663
  let dofEnd2 = recall('dofEnd2','') === '' ? null : parseFloat(recall('dofEnd2','')); if (dofEnd2 !== null && !(dofEnd2 < 1)) dofEnd2 = null;
2658
2664
  function dofEndV(){ return dofOn ? dofEnd : 1; }
@@ -2693,10 +2699,10 @@ function paintGizmo(){
2693
2699
  const k = W / Math.max(im.clientWidth, 1); // photo px per screen px
2694
2700
  const g = gizmoGeom(); if (!g.H || !g.Hi) return;
2695
2701
  const P = (f, q) => applyH(g.H, gzPt(g, f, q));
2696
- const a0 = P(dofStart, -0.06), a1 = P(dofStart, 1.06), ac = P(dofStart, 0.5);
2697
- const b0 = P(dofEnd, -0.06), b1 = P(dofEnd, 1.06), bc = P(dofEnd, 0.5);
2702
+ const a0 = P(dofStart, -0.06), a1 = P(dofStart, 1.06), ac = P(dofStart, dofK);
2703
+ const b0 = P(dofEnd, -0.06), b1 = P(dofEnd, 1.06), bc = P(dofEnd, dofK);
2698
2704
  const two = dofEnd2 !== null;
2699
- const c0 = two ? P(dofEnd2, -0.06) : null, c1 = two ? P(dofEnd2, 1.06) : null, cc = two ? P(dofEnd2, 0.5) : null;
2705
+ const c0 = two ? P(dofEnd2, -0.06) : null, c1 = two ? P(dofEnd2, 1.06) : null, cc = two ? P(dofEnd2, dofK) : null;
2700
2706
  const th = P(dofEnd, 0.08 + 0.84 * dofStr); // the strength thumb slides along the dashed line
2701
2707
  const sigma = dofStr * SIGMA_FULL_FRAC * g.side;
2702
2708
  const r = 6 * k, rp = 4 * k, fs11 = 11 * k, off = 14 * k;
@@ -2731,7 +2737,19 @@ let gzDrag = null;
2731
2737
  gz.addEventListener('pointerdown', e => {
2732
2738
  const h = e.target.dataset && e.target.dataset.h; if (!h) return;
2733
2739
  e.preventDefault(); gz.setPointerCapture(e.pointerId);
2734
- gzDrag = {h}; gz.classList.add('drag');
2740
+ gzDrag = {h};
2741
+ if (h === 'rotA' || h === 'rotB'){
2742
+ // Turning keeps the SHARP line's centre where it is on the screen and the
2743
+ // limits at their distances from it, in screenshot pixels. The stored
2744
+ // numbers are fractions of the screen's extent along the direction, and
2745
+ // that extent changes with the angle -- so turning on the fractions alone
2746
+ // slid every line as it turned (reported 13 Sep 2026).
2747
+ const g = gizmoGeom();
2748
+ gzDrag.pivot = gzPt(g, dofStart, dofK);
2749
+ gzDrag.dFar = (dofEnd - dofStart) * (g.hi - g.lo);
2750
+ gzDrag.dNear = dofEnd2 === null ? null : (dofStart - dofEnd2) * (g.hi - g.lo);
2751
+ }
2752
+ gz.classList.add('drag');
2735
2753
  });
2736
2754
  gz.addEventListener('pointermove', e => {
2737
2755
  if (!gzDrag) return;
@@ -2750,11 +2768,26 @@ gz.addEventListener('pointermove', e => {
2750
2768
  } else {
2751
2769
  // Turn about the SHARP line's centre -- the plane of focus is what turns;
2752
2770
  // the pip under the pointer follows.
2753
- const c = gzPt(g, dofStart, 0.5);
2771
+ const c = gzDrag.pivot;
2754
2772
  const v = [p[0]-c[0], p[1]-c[1]];
2755
2773
  const sign = gzDrag.h === 'rotB' ? 1 : -1; // rotB sits at +u, rotA at -u
2756
- dofAng = ((Math.atan2(v[1]*sign, v[0]*sign) * 180 / Math.PI - 90) % 360 + 360) % 360;
2757
- if (e.shiftKey) dofAng = Math.round(dofAng / 15) * 15;
2774
+ let ang = ((Math.atan2(v[1]*sign, v[0]*sign) * 180 / Math.PI - 90) % 360 + 360) % 360;
2775
+ if (e.shiftKey) ang = Math.round(ang / 15) * 15;
2776
+ else {
2777
+ // Magnetic to the screen's own axes: within 4° of 0/90/180/270 the lines
2778
+ // run exactly along the screen's edges, which is what "along the phone"
2779
+ // means in the photograph once the perspective is applied.
2780
+ const near = Math.round(ang / 90) * 90;
2781
+ if (Math.abs(((ang - near) + 180) % 360 - 180) < 4) ang = near % 360;
2782
+ }
2783
+ dofAng = ang;
2784
+ // Re-express the lines against the new extent so nothing moves but the angle.
2785
+ const g2 = gizmoGeom();
2786
+ const f0 = (c[0]*g2.d[0] + c[1]*g2.d[1] - g2.lo) / (g2.hi - g2.lo);
2787
+ dofStart = Math.max(0, Math.min(0.95, f0));
2788
+ dofK = Math.max(0.05, Math.min(0.95, (c[0]*g2.u[0] + c[1]*g2.u[1] - g2.ulo) / (g2.uhi - g2.ulo)));
2789
+ dofEnd = Math.max(dofStart + 0.05, Math.min(1.5, dofStart + gzDrag.dFar / (g2.hi - g2.lo)));
2790
+ if (dofEnd2 !== null) dofEnd2 = Math.max(-0.5, Math.min(dofStart - 0.05, dofStart - gzDrag.dNear / (g2.hi - g2.lo)));
2758
2791
  }
2759
2792
  paintDof(); paintGizmo(); autoPreview();
2760
2793
  });