screengraft 0.54.2 → 0.54.4
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.
- package/package.json +1 -1
- package/ui/index.html +35 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "screengraft",
|
|
3
|
-
"version": "0.54.
|
|
3
|
+
"version": "0.54.4",
|
|
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
|
@@ -634,11 +634,16 @@
|
|
|
634
634
|
#outWrap{position:relative}
|
|
635
635
|
#dofGizmo{position:absolute;pointer-events:none;opacity:.4;transition:opacity .15s;overflow:visible}
|
|
636
636
|
#outWrap:hover #dofGizmo,#dofGizmo.drag{opacity:1}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
#dofGizmo .
|
|
641
|
-
#dofGizmo .
|
|
637
|
+
/* Same rules as the fit overlay (v0.54.3): thin core, a light casing
|
|
638
|
+
underneath at 45% black, dashes 6/5. The casing is what survives a
|
|
639
|
+
photograph; the thinness is the request. */
|
|
640
|
+
#dofGizmo .case{stroke:rgba(0,0,0,.45);fill:none;vector-effect:non-scaling-stroke}
|
|
641
|
+
#dofGizmo .focus{stroke:#f5623d;stroke-width:1;fill:none;vector-effect:non-scaling-stroke}
|
|
642
|
+
#dofGizmo .far{stroke:#f5623d;stroke-width:0.8;stroke-dasharray:6 5;fill:none;vector-effect:non-scaling-stroke}
|
|
643
|
+
#dofGizmo .far.out{stroke-dasharray:3 6;opacity:.75}
|
|
644
|
+
#dofGizmo .h{fill:#f5623d;stroke:#fff;stroke-width:1.2;vector-effect:non-scaling-stroke;pointer-events:auto;cursor:move}
|
|
645
|
+
#dofGizmo .h.pip{cursor:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 22 22'%3E%3Cpath d='M17.5 11a6.5 6.5 0 1 1-2.2-4.9' fill='none' stroke='rgba(0,0,0,.85)' stroke-width='4.2' stroke-linecap='round'/%3E%3Cpath d='M17.5 11a6.5 6.5 0 1 1-2.2-4.9' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3Cpath d='M15.6 2.6l0.4 4.2-4.2 0.3' fill='none' stroke='rgba(0,0,0,.85)' stroke-width='4.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M15.6 2.6l0.4 4.2-4.2 0.3' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 11 11, crosshair}
|
|
646
|
+
#dofGizmo .h.thumb{cursor:ew-resize}
|
|
642
647
|
#dofGizmo .h.hollow{fill:#1a1a1a}
|
|
643
648
|
#dofGizmo .h.pip{fill:#f5623d}
|
|
644
649
|
#dofGizmo .h.thumb{fill:#fff;stroke:#f5623d;stroke-width:2}
|
|
@@ -1472,14 +1477,29 @@ const recall = (k,d) => { try{ const v = localStorage.getItem('sg.'+k); return v
|
|
|
1472
1477
|
The dark casing underneath is NOT optional and stays: flat accent measures
|
|
1473
1478
|
~1.06:1 against mid-tone photography, which is the failure the casing was
|
|
1474
1479
|
introduced to fix. CORE_IDLE stays white for the strip's idle rule. */
|
|
1475
|
-
const CASE_A = 'rgba(0,0,0,.
|
|
1480
|
+
const CASE_A = 'rgba(0,0,0,.45)', CORE_IDLE = 'rgba(255,255,255,.92)';
|
|
1476
1481
|
const CORE_ACTIVE = '#f5623d';
|
|
1477
1482
|
const CORE_QUAD = '#f23b0d';
|
|
1483
|
+
// Line weights for the fit overlay, in one place. Thinned 13 Sep 2026 on
|
|
1484
|
+
// request: quad 1 -> 0.8, active edge 2 -> 1.5, casing +2.5 -> +1.6 at 45%
|
|
1485
|
+
// black instead of 62%. The casing is still there -- it is the part that
|
|
1486
|
+
// survives an arbitrary photograph -- just lighter.
|
|
1487
|
+
// There is no native rotate cursor; this is a 22px circular arrow, white
|
|
1488
|
+
// with a dark outline so it reads on any photograph, hotspot at its centre.
|
|
1489
|
+
const ROTATE_CURSOR = 'url("data:image/svg+xml;utf8,' + encodeURIComponent(
|
|
1490
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">' +
|
|
1491
|
+
'<path d="M17.5 11a6.5 6.5 0 1 1-2.2-4.9" fill="none" stroke="rgba(0,0,0,.85)" stroke-width="4.2" stroke-linecap="round"/>' +
|
|
1492
|
+
'<path d="M17.5 11a6.5 6.5 0 1 1-2.2-4.9" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"/>' +
|
|
1493
|
+
'<path d="M15.6 2.6l0.4 4.2-4.2 0.3" fill="none" stroke="rgba(0,0,0,.85)" stroke-width="4.2" stroke-linecap="round" stroke-linejoin="round"/>' +
|
|
1494
|
+
'<path d="M15.6 2.6l0.4 4.2-4.2 0.3" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' +
|
|
1495
|
+
'</svg>') + '") 11 11, crosshair';
|
|
1496
|
+
const QUAD_W = 0.8, QUAD_ACTIVE_W = 1.5, GUIDE_W = 0.8, CASE_PAD = 1.6;
|
|
1497
|
+
const GUIDE_DASH = [6, 5];
|
|
1478
1498
|
function cased(c, path, coreW, coreColor, dash){
|
|
1479
1499
|
c.save();
|
|
1480
1500
|
if (dash) c.setLineDash(dash);
|
|
1481
1501
|
c.lineCap = 'round'; c.lineJoin = 'round';
|
|
1482
|
-
c.strokeStyle = CASE_A; c.lineWidth = coreW +
|
|
1502
|
+
c.strokeStyle = CASE_A; c.lineWidth = coreW + CASE_PAD; path(); c.stroke();
|
|
1483
1503
|
c.strokeStyle = coreColor; c.lineWidth = coreW; path(); c.stroke();
|
|
1484
1504
|
c.restore();
|
|
1485
1505
|
}
|
|
@@ -1488,8 +1508,8 @@ function cased(c, path, coreW, coreColor, dash){
|
|
|
1488
1508
|
readability guarantee that makes markers survive an arbitrary photograph. */
|
|
1489
1509
|
function casedDisc(c, x, y, r, coreColor){
|
|
1490
1510
|
c.save();
|
|
1491
|
-
c.strokeStyle = CASE_A; c.lineWidth =
|
|
1492
|
-
c.beginPath(); c.arc(x, y, r + 0.
|
|
1511
|
+
c.strokeStyle = CASE_A; c.lineWidth = 1.8;
|
|
1512
|
+
c.beginPath(); c.arc(x, y, r + 0.6, 0, Math.PI*2); c.stroke();
|
|
1493
1513
|
c.fillStyle = coreColor;
|
|
1494
1514
|
c.beginPath(); c.arc(x, y, r, 0, Math.PI*2); c.fill();
|
|
1495
1515
|
c.restore();
|
|
@@ -2285,7 +2305,7 @@ function draw(){
|
|
|
2285
2305
|
const a=P[active.i], b=P[(active.i+1)%4], d=unit(sub(b,a)), big=4000;
|
|
2286
2306
|
cased(ctx, () => { ctx.beginPath();
|
|
2287
2307
|
ctx.moveTo(a[0]-d[0]*big, a[1]-d[1]*big); ctx.lineTo(b[0]+d[0]*big, b[1]+d[1]*big); },
|
|
2288
|
-
|
|
2308
|
+
GUIDE_W, CORE_ACTIVE, GUIDE_DASH);
|
|
2289
2309
|
}
|
|
2290
2310
|
|
|
2291
2311
|
for (let i=0;i<4;i++){
|
|
@@ -2300,7 +2320,7 @@ function draw(){
|
|
|
2300
2320
|
const s1 = add(a, mul(d, Math.max(0, L - gap)));
|
|
2301
2321
|
if (L > 1){
|
|
2302
2322
|
cased(ctx, () => { ctx.beginPath(); ctx.moveTo(s0[0],s0[1]); ctx.lineTo(s1[0],s1[1]); },
|
|
2303
|
-
on ?
|
|
2323
|
+
on ? QUAD_ACTIVE_W : QUAD_W, CORE_QUAD);
|
|
2304
2324
|
}
|
|
2305
2325
|
// Pivot pips on the active edge keep their meaning: grab near an end to
|
|
2306
2326
|
// rotate. Filled, like the corners, but deliberately smaller — size is what
|
|
@@ -2382,7 +2402,7 @@ cv.onpointermove = e => {
|
|
|
2382
2402
|
const h = hitTest(p);
|
|
2383
2403
|
const changed = JSON.stringify(h) !== JSON.stringify(hover);
|
|
2384
2404
|
hover = h;
|
|
2385
|
-
cv.style.cursor = h ? (h.kind==='corner' ? '
|
|
2405
|
+
cv.style.cursor = h ? (h.kind==='corner' ? 'move' : (h.mode==='translate' ? 'move' : ROTATE_CURSOR)) : 'default';
|
|
2386
2406
|
if (changed){ draw(); drawStrip(); }
|
|
2387
2407
|
return;
|
|
2388
2408
|
}
|
|
@@ -2712,9 +2732,9 @@ function paintGizmo(){
|
|
|
2712
2732
|
const beyond = dofEnd > 1.0001;
|
|
2713
2733
|
const beyond2 = two && dofEnd2 < -0.0001;
|
|
2714
2734
|
gz.innerHTML =
|
|
2715
|
-
L(a0, a1, 'case', 'stroke-width="
|
|
2716
|
-
L(b0, b1, 'case', 'stroke-width="
|
|
2717
|
-
(two ? L(c0, c1, 'case', 'stroke-width="
|
|
2735
|
+
L(a0, a1, 'case', 'stroke-width="2.6"') + L(a0, a1, 'focus') +
|
|
2736
|
+
L(b0, b1, 'case', 'stroke-width="2.4"') + L(b0, b1, 'far' + (beyond ? ' out' : '')) +
|
|
2737
|
+
(two ? L(c0, c1, 'case', 'stroke-width="2.4"') + L(c0, c1, 'far' + (beyond2 ? ' out' : '')) : '') +
|
|
2718
2738
|
`<circle cx="${a0[0]}" cy="${a0[1]}" r="${rp}" class="h pip" data-h="rotA"/>` +
|
|
2719
2739
|
`<circle cx="${a1[0]}" cy="${a1[1]}" r="${rp}" class="h pip" data-h="rotB"/>` +
|
|
2720
2740
|
`<circle cx="${ac[0]}" cy="${ac[1]}" r="${r}" class="h" data-h="start"/>` +
|