screengraft 0.63.3 → 0.63.5

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 +35 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screengraft",
3
- "version": "0.63.3",
3
+ "version": "0.63.5",
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
@@ -89,7 +89,7 @@
89
89
  stroke in the system; it sits over the photograph, so it has to read on
90
90
  any tone. The two shadows are the Button Group's and the Status pill's. */
91
91
  /* Button Group 145:372, read from the node's own paints on 14 Sep 2026:
92
- the stroke is THREE paints, 0.5px OUTSIDE the box, each with a blend
92
+ the stroke is four paints, 0.75px OUTSIDE the box, each with a blend
93
93
  mode, so the hairline takes its look from the photograph under it —
94
94
  lighter on a dark backdrop, darker on a light one, a touch desaturated.
95
95
  The MCP's code export had flattened it to one colour twice over.
@@ -105,6 +105,8 @@
105
105
  --group-edge-lum:rgba(255,70,70,.15);
106
106
  --group-edge-hue:rgba(126,126,126,.2);
107
107
  --group-edge-lift:rgba(255,255,255,.4);
108
+ /* The Button Group's stroke weight (0.75, set 14 Sep); the pills keep 1px. */
109
+ --group-bw:.75px;
108
110
  --group-shadow:0 2px 4px rgba(0,0,0,.08), 0 3px 10px rgba(0,0,0,.12), 0 4px 24px rgba(0,0,0,.25);
109
111
  --pill-shadow:0 2px 2px rgba(0,0,0,.03), 0 3px 5px rgba(0,0,0,.08), 0 4px 12px rgba(0,0,0,.25);
110
112
 
@@ -556,6 +558,11 @@
556
558
  .pane{min-width:0;min-height:0;display:grid;grid-template-rows:minmax(0,1fr);
557
559
  grid-template-columns:minmax(0,1fr);
558
560
  position:relative; /* the frame the rails are anchored to */
561
+ /* An isolated group: the blend rings on the rails and pills composite
562
+ against THIS pane's content -- the well, the canvas, the result
563
+ image -- as one unit, rather than against whatever layer the
564
+ compositor happened to have on hand at first paint. */
565
+ isolation:isolate;
559
566
  border-right:1px solid var(--line)}
560
567
  .panehead{display:flex;align-items:center;gap:var(--s2);padding:var(--s2) var(--s4);background:var(--card);
561
568
  box-shadow:inset 0 -1px 0 var(--line);font-size:12px;flex-wrap:wrap;min-height:48px}
@@ -587,13 +594,17 @@
587
594
  .bgroup{position:relative;display:inline-flex;flex-direction:column;align-items:stretch;flex:none;
588
595
  background:var(--raise);border:0;
589
596
  border-radius:var(--r-sm);box-shadow:var(--group-shadow)}
590
- .bgroup::before,.bgroup::after,.bgroup > .ring{content:"";position:absolute;
591
- inset:calc(-1 * var(--bw));border:var(--bw) solid;
592
- border-radius:calc(var(--r-sm) + var(--bw));pointer-events:none}
593
- .bgroup::before{border-color:var(--group-edge-sat);mix-blend-mode:saturation;z-index:1}
594
- .bgroup::after{border-color:var(--group-edge-lum);mix-blend-mode:luminosity;z-index:2}
595
- .bgroup > .ring{border-color:var(--group-edge-hue);mix-blend-mode:hue;z-index:3}
596
- .bgroup > .ring.lift{border-color:var(--group-edge-lift);mix-blend-mode:soft-light;z-index:4}
597
+ /* A ring is a SPREAD SHADOW, not a border: Chrome snaps a border to whole
598
+ device pixels, so 0.75px painted as 0.5 (one of two); a shadow's spread is
599
+ antialiased and renders the true 1.5 device pixels. Same outside-the-box
600
+ placement, the ring's colour is the shadow's. */
601
+ .bgroup::before,.bgroup::after,.bgroup > .ring{content:"";position:absolute;inset:0;
602
+ border-radius:var(--r-sm);pointer-events:none;
603
+ box-shadow:0 0 0 var(--group-bw) var(--ring)}
604
+ .bgroup::before{--ring:var(--group-edge-sat);mix-blend-mode:saturation;z-index:1}
605
+ .bgroup::after{--ring:var(--group-edge-lum);mix-blend-mode:luminosity;z-index:2}
606
+ .bgroup > .ring{--ring:var(--group-edge-hue);mix-blend-mode:hue;z-index:3}
607
+ .bgroup > .ring.lift{--ring:var(--group-edge-lift);mix-blend-mode:soft-light;z-index:4}
597
608
  .bgroup.row{flex-direction:row}
598
609
  .bgroup > .segbtn:first-of-type{border-top-left-radius:var(--r-sm);border-top-right-radius:var(--r-sm)}
599
610
  .bgroup > .segbtn:last-of-type{border-bottom-left-radius:var(--r-sm);border-bottom-right-radius:var(--r-sm)}
@@ -2115,6 +2126,7 @@ async function maybeStart(){
2115
2126
  const ready = !!st.shot; // the screenshot gates the fit, not the photo
2116
2127
  img.onload = () => {
2117
2128
  computeFit(); setCanvasZoom(fitScale);
2129
+ refreshRings();
2118
2130
  if (!ready){
2119
2131
  // Photo only: show it immediately so choosing one visibly does
2120
2132
  // something. No quad yet — there is nothing to fit into it.
@@ -2496,6 +2508,20 @@ $('#resetquad').onclick = () => {
2496
2508
  draw(); drawStrip(); autoPreview();
2497
2509
  };
2498
2510
 
2511
+ // The blend rings on the rails and pills are painted against what is under
2512
+ // them at the time; when a NEW picture lands in the canvas or the result pane
2513
+ // they can keep a result blended against the empty well until something else
2514
+ // invalidates them (reported 14 Sep 2026: the stroke appeared only after the
2515
+ // picture was moved). Toggling display forces a fresh paint of each ring.
2516
+ function refreshRings(){
2517
+ document.querySelectorAll('.bgroup > .ring, .pillwrap > .ring').forEach(r => {
2518
+ r.style.display = 'none'; void r.offsetHeight; r.style.display = '';
2519
+ });
2520
+ // ::before / ::after rings ride on the group; nudge the group itself.
2521
+ document.querySelectorAll('.bgroup').forEach(g => {
2522
+ g.style.willChange = 'transform'; void g.offsetHeight; g.style.willChange = '';
2523
+ });
2524
+ }
2499
2525
  function syncCornerRail(){
2500
2526
  const i = pick && pick.kind === 'corner' ? pick.i : -1;
2501
2527
  document.querySelectorAll('#lrail [data-jump]').forEach(b => {
@@ -3592,6 +3618,7 @@ async function renderPreview(){
3592
3618
  const im = $('#outImg');
3593
3619
  im.onload = () => {
3594
3620
  outNat = im.naturalWidth;
3621
+ refreshRings();
3595
3622
  // autoPreview runs on every drag, and it must not yank the pane away from
3596
3623
  // a clip that is playing. The still is still rendered and waiting behind.
3597
3624
  if ($('#liveWrap').hidden) showResult('still');