screengraft 0.63.0 → 0.63.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.
- package/package.json +1 -1
- package/ui/index.html +59 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "screengraft",
|
|
3
|
-
"version": "0.63.
|
|
3
|
+
"version": "0.63.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
|
@@ -88,9 +88,18 @@
|
|
|
88
88
|
/* button-group/default/border — a 20% cool grey, the ONLY translucent
|
|
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
|
-
/*
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
93
|
+
mode, so the hairline takes its look from the photograph under it —
|
|
94
|
+
lighter on a dark backdrop, darker on a light one, a touch desaturated.
|
|
95
|
+
The MCP's code export had flattened it to one colour twice over.
|
|
96
|
+
[0] #0F6BFF at 20% saturation
|
|
97
|
+
[1] #FF4646 at 15% luminosity
|
|
98
|
+
[2] #7E7E7E at 20% hue
|
|
99
|
+
Drawn as three rings (see .bgroup::before / ::after / > .ring). */
|
|
100
|
+
--group-edge-sat:rgba(15,107,255,.2);
|
|
101
|
+
--group-edge-lum:rgba(255,70,70,.15);
|
|
102
|
+
--group-edge-hue:rgba(126,126,126,.2);
|
|
94
103
|
--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);
|
|
95
104
|
--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);
|
|
96
105
|
|
|
@@ -560,11 +569,33 @@
|
|
|
560
569
|
Three groups on the left (zoom, fit, corners), three on the bottom, and
|
|
561
570
|
the status pill top-centre; the well underneath keeps every pixel.
|
|
562
571
|
------------------------------------------------------------------ */
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
572
|
+
/* Orientation is a variant of both components now (Segment Button 138:255,
|
|
573
|
+
Button Group 145:372): Vertical rounds a segment's top or bottom corners,
|
|
574
|
+
Horizontal its left or right, Solo all four, at radius/sm. The group clips
|
|
575
|
+
to the same radius, so the end segments' own radii are what a hover or
|
|
576
|
+
press fill shows at the group's corners. */
|
|
577
|
+
/* No border and no overflow clip: the stroke is three blend-mode rings a
|
|
578
|
+
half pixel OUTSIDE the box (Figma strokeAlign OUTSIDE), and a clip would
|
|
579
|
+
cut them off. The end segments carry the corner radii themselves. The
|
|
580
|
+
rails must not open a stacking context of their own (no z-index) or the
|
|
581
|
+
rings would blend against nothing instead of the photograph. */
|
|
582
|
+
.bgroup{position:relative;display:inline-flex;flex-direction:column;align-items:stretch;flex:none;
|
|
583
|
+
background:var(--raise);border:0;
|
|
584
|
+
border-radius:var(--r-sm);box-shadow:var(--group-shadow)}
|
|
585
|
+
.bgroup::before,.bgroup::after,.bgroup > .ring{content:"";position:absolute;
|
|
586
|
+
inset:calc(-1 * var(--bw));border:var(--bw) solid;
|
|
587
|
+
border-radius:calc(var(--r-sm) + var(--bw));pointer-events:none}
|
|
588
|
+
.bgroup::before{border-color:var(--group-edge-sat);mix-blend-mode:saturation;z-index:1}
|
|
589
|
+
.bgroup::after{border-color:var(--group-edge-lum);mix-blend-mode:luminosity;z-index:2}
|
|
590
|
+
.bgroup > .ring{border-color:var(--group-edge-hue);mix-blend-mode:hue;z-index:3}
|
|
566
591
|
.bgroup.row{flex-direction:row}
|
|
567
|
-
.segbtn
|
|
592
|
+
.bgroup > .segbtn:first-of-type{border-top-left-radius:var(--r-sm);border-top-right-radius:var(--r-sm)}
|
|
593
|
+
.bgroup > .segbtn:last-of-type{border-bottom-left-radius:var(--r-sm);border-bottom-right-radius:var(--r-sm)}
|
|
594
|
+
.bgroup.row > .segbtn:first-of-type{border-radius:var(--r-sm) 0 0 var(--r-sm)}
|
|
595
|
+
.bgroup.row > .segbtn:last-of-type{border-radius:0 var(--r-sm) var(--r-sm) 0}
|
|
596
|
+
.bgroup > .segbtn:only-of-type{border-radius:var(--r-sm)}
|
|
597
|
+
.segbtn,.segbtn:hover,.segbtn:active{border:0;box-shadow:none;transform:none}
|
|
598
|
+
.segbtn{border-radius:0}
|
|
568
599
|
/* Segment Button re-read 14 Sep 2026: 32px tall, 32px minimum, 8px side
|
|
569
600
|
padding; a text label sits in its own 6px-padded box, so a text button
|
|
570
601
|
pads 14px. Was 28 / 6. */
|
|
@@ -577,22 +608,33 @@
|
|
|
577
608
|
.segbtn[disabled],.segbtn[disabled]:hover{background:var(--raise-low);color:var(--mute)}
|
|
578
609
|
.segbtn:focus-visible{outline-offset:-2px}
|
|
579
610
|
.segbtn svg{width:16px;height:16px;display:block;flex:none}
|
|
580
|
-
.lrail{position:absolute;left:16px;top:48px;
|
|
611
|
+
.lrail{position:absolute;left:16px;top:48px;display:flex;flex-direction:column;gap:var(--s2)}
|
|
581
612
|
.lrail .corners{margin-top:var(--s6)} /* 8 + 24 = the design's 32px between rails */
|
|
582
613
|
/* One horizontal group, centred, 20px off the bottom edge (7:36, 14 Sep). */
|
|
583
|
-
.brail{position:absolute;left:0;right:0;bottom:20px;
|
|
614
|
+
.brail{position:absolute;left:0;right:0;bottom:20px;display:flex;justify-content:center;
|
|
584
615
|
align-items:center;pointer-events:none;padding:0 var(--s4)}
|
|
585
616
|
.brail .bgroup{pointer-events:auto}
|
|
586
617
|
/* The status pill (Status pill 5:7 / 5:10): floats top-centre, its own
|
|
587
618
|
recessed surface and the group stroke, hidden while it has nothing to say.
|
|
588
619
|
Glyph + weight + luminance encoding unchanged (see .status.ok/.warn/.err). */
|
|
589
620
|
/* Keyed on the ids, not a class: the JS rewrites className on both pills. */
|
|
590
|
-
|
|
621
|
+
/* Status pill 5:7 / 5:10: the same three blend-mode paints as the Button
|
|
622
|
+
Group, 1px OUTSIDE. A pill is a <span> whose text the JS rewrites (no
|
|
623
|
+
child ring possible) and whose ::before is the status glyph — so only the
|
|
624
|
+
LUMINOSITY paint, the one that carries the adaptive contrast, is a real
|
|
625
|
+
blend ring (::after); the two 20% paints are plain rings under it.
|
|
626
|
+
No z-index, for the same reason as the rails: the rings blend against the
|
|
627
|
+
photograph only if nothing between them and it opens a stacking context.
|
|
628
|
+
#outSt sits AFTER #outWrap in the markup so it paints over it. */
|
|
629
|
+
#detSt,#outSt{position:absolute;top:12px;left:0;right:0;width:fit-content;margin:0 auto;
|
|
591
630
|
height:22px;padding:0 10px;border-radius:999px;background:var(--inchip-empty);
|
|
592
|
-
border:1px
|
|
631
|
+
border:0;box-shadow:var(--pill-shadow), 0 0 0 1px var(--group-edge-hue), 0 0 0 1px var(--group-edge-sat);
|
|
593
632
|
max-width:calc(100% - 96px);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
594
633
|
display:inline-flex;line-height:20px}
|
|
595
634
|
#detSt:empty,#outSt:empty{display:none}
|
|
635
|
+
#detSt::after,#outSt::after{content:"";position:absolute;inset:-1px;
|
|
636
|
+
border:1px solid var(--group-edge-lum);border-radius:999px;pointer-events:none;
|
|
637
|
+
mix-blend-mode:luminosity}
|
|
596
638
|
.lbl{color:var(--ink);text-transform:uppercase;letter-spacing:.06em;font-size:11px;font-weight:600}
|
|
597
639
|
.scroller{overflow:auto;background:var(--well);display:grid;
|
|
598
640
|
min-height:0;min-width:0;
|
|
@@ -1138,15 +1180,15 @@
|
|
|
1138
1180
|
corner glyphs light the corner they name; the one currently picked
|
|
1139
1181
|
on the canvas is also raised (kept in step from draw()). -->
|
|
1140
1182
|
<div class="lrail" id="lrail">
|
|
1141
|
-
<div class="bgroup" role="group" aria-label="Zoom">
|
|
1183
|
+
<div class="bgroup" role="group" aria-label="Zoom"><i class="ring" aria-hidden="true"></i>
|
|
1142
1184
|
<button class="segbtn" id="czIn" aria-label="Zoom in" title="Zoom in (⌘ + scroll zooms to the pointer)"><svg viewBox="0 0 16 16" aria-hidden="true"><path fill="var(--icon)" d="M8.5 7.5H12.5V8.5H8.5V12.5H7.5V8.5H3.5V7.5H7.5V3.5H8.5V7.5Z"/></svg></button>
|
|
1143
1185
|
<button class="segbtn" id="cz100" aria-label="Actual size, 100%" title="Actual size — one photo pixel per screen pixel"><svg viewBox="0 0 16 16" aria-hidden="true"><g transform="translate(2.77 4.245)" fill="var(--ink)"><path d="M4.80713 7.75049L6.78906 4.85547L4.78564 1.96045H5.88672L7.26172 4.11426H7.34766L8.70654 1.96045H9.75391L7.78809 4.81787L9.78076 7.75049H8.68506L7.31543 5.56445H7.22949L5.85449 7.75049H4.80713Z"/><path d="M2.05713 7.75049V1.07422H1.97119L0 2.50293V1.48242L2.0625 0H3.02393V7.75049H2.05713Z"/></g></svg></button>
|
|
1144
1186
|
<button class="segbtn" id="czOut" aria-label="Zoom out" title="Zoom out"><svg viewBox="0 0 16 16" aria-hidden="true"><path fill="var(--icon)" d="M12.5 8.5H3.5V7.5H12.5V8.5Z"/></svg></button>
|
|
1145
1187
|
</div>
|
|
1146
|
-
<div class="bgroup">
|
|
1188
|
+
<div class="bgroup"><i class="ring" aria-hidden="true"></i>
|
|
1147
1189
|
<button class="segbtn" id="czFit" aria-label="Fit the photo to the pane" title="Fit the whole photo in the pane"><svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path fill="var(--mute)" d="M14.1025 1.00488C14.6067 1.05621 15 1.48232 15 2V14L14.9951 14.1025C14.9472 14.573 14.573 14.9472 14.1025 14.9951L14 15H2C1.48232 15 1.05621 14.6067 1.00488 14.1025L1 14V2C1 1.44772 1.44772 1 2 1H14L14.1025 1.00488ZM2 14H14V2H2V14Z"/><path stroke="var(--icon)" d="M7 11.5H4.5V9M4.5 11.5L7.5 8.5"/><path stroke="var(--icon)" d="M9 4.5H11.5V7M11.5 4.5L8.5 7.5"/></svg></button>
|
|
1148
1190
|
</div>
|
|
1149
|
-
<div class="bgroup corners" role="group" aria-label="Select corner">
|
|
1191
|
+
<div class="bgroup corners" role="group" aria-label="Select corner"><i class="ring" aria-hidden="true"></i>
|
|
1150
1192
|
<button class="segbtn" data-jump="0" aria-label="Top-left corner" title="Jump to the top-left corner"><svg viewBox="0 0 16 16" aria-hidden="true"><path fill="var(--mute)" d="M2 13C2 13.5523 2.44772 14 3 14H6V15H3L2.7959 14.9893C1.85435 14.8938 1.1062 14.1457 1.01074 13.2041L1 13V10H2V13ZM15 13C15 14.0357 14.2128 14.887 13.2041 14.9893L13 15H10V14H13C13.5523 14 14 13.5523 14 13V10H15V13ZM6 2H3C2.44772 2 2 2.44772 2 3V6H1V3C1 1.89543 1.89543 1 3 1H6V2ZM13 1C14.1046 1 15 1.89543 15 3V6H14V3C14 2.44772 13.5523 2 13 2H10V1H13Z"/><path fill="var(--acc-ink)" d="M6 2H3C2.44772 2 2 2.44772 2 3V6H1V3C1 1.89543 1.89543 1 3 1H6V2Z"/></svg></button>
|
|
1151
1193
|
<button class="segbtn" data-jump="1" aria-label="Top-right corner" title="Jump to the top-right corner"><svg viewBox="0 0 16 16" aria-hidden="true"><path fill="var(--mute)" d="M2 13C2 13.5523 2.44772 14 3 14H6V15H3L2.7959 14.9893C1.85435 14.8938 1.1062 14.1457 1.01074 13.2041L1 13V10H2V13ZM15 13C15 14.0357 14.2128 14.887 13.2041 14.9893L13 15H10V14H13C13.5523 14 14 13.5523 14 13V10H15V13ZM6 2H3C2.44772 2 2 2.44772 2 3V6H1V3C1 1.89543 1.89543 1 3 1H6V2ZM13 1C14.1046 1 15 1.89543 15 3V6H14V3C14 2.44772 13.5523 2 13 2H10V1H13Z"/><path fill="var(--acc-ink)" d="M10 2H13C13.5523 2 14 2.44772 14 3V6H15V3C15 1.89543 14.1046 1 13 1H10V2Z"/></svg></button>
|
|
1152
1194
|
<button class="segbtn" data-jump="3" aria-label="Bottom-left corner" title="Jump to the bottom-left corner"><svg viewBox="0 0 16 16" aria-hidden="true"><path fill="var(--mute)" d="M2 13C2 13.5523 2.44772 14 3 14H6V15H3L2.7959 14.9893C1.85435 14.8938 1.1062 14.1457 1.01074 13.2041L1 13V10H2V13ZM15 13C15 14.0357 14.2128 14.887 13.2041 14.9893L13 15H10V14H13C13.5523 14 14 13.5523 14 13V10H15V13ZM6 2H3C2.44772 2 2 2.44772 2 3V6H1V3C1 1.89543 1.89543 1 3 1H6V2ZM13 1C14.1046 1 15 1.89543 15 3V6H14V3C14 2.44772 13.5523 2 13 2H10V1H13Z"/><path fill="var(--acc-ink)" d="M6 14H3C2.44772 14 2 13.5523 2 13V10H1V13C1 14.1046 1.89543 15 3 15H6V14Z"/></svg></button>
|
|
@@ -1162,9 +1204,9 @@
|
|
|
1162
1204
|
as ONE horizontal Button Group of four segments, centred 20px off the
|
|
1163
1205
|
bottom. Corner selection lives on the left rail — it is navigation,
|
|
1164
1206
|
not an action on the fit. The frame's last two segments are
|
|
1165
|
-
placeholders; Rotate and Reset fill them
|
|
1207
|
+
placeholders; Rotate and Reset fill them. -->
|
|
1166
1208
|
<div class="brail" id="cvbar">
|
|
1167
|
-
<div class="bgroup row" role="group" aria-label="Fit actions">
|
|
1209
|
+
<div class="bgroup row" role="group" aria-label="Fit actions"><i class="ring" aria-hidden="true"></i>
|
|
1168
1210
|
<button class="segbtn txt" id="redetect">Re-detect</button>
|
|
1169
1211
|
<button class="segbtn txt" id="pointat" title="Click once inside the screen and the detector will use that point. The detectors usually do find the screen — they just cannot tell which region IS one, and that is the part you can answer instantly.">Point at screen</button>
|
|
1170
1212
|
<button class="segbtn txt" id="rotatequad" title="Turn the screenshot inside the same four edges. The edges stay where they are; only which one is the top changes. A portrait screenshot fits a phone only two ways, so on a phone this is a half turn; a quarter turn where the screen is near-square or the screenshot fits the other way.">Rotate</button>
|
|
@@ -1176,12 +1218,12 @@
|
|
|
1176
1218
|
<section class="pane" id="outPane">
|
|
1177
1219
|
<!-- No header (Figma 7:38): the well fills the pane so it lines up with
|
|
1178
1220
|
the fit well beside it, and the render state floats as a pill. -->
|
|
1179
|
-
<span class="status" id="outSt">Press Preview</span>
|
|
1180
1221
|
<div id="outWrap"><span class="empty" id="outEmpty">The composite appears here and updates as you drag.</span><img id="outImg" alt="" hidden><video id="outVid" hidden loop muted playsinline controls></video><svg id="dofGizmo" hidden aria-label="Depth of field: drag the solid line to place focus, its ends to turn it, the dashed line for strength"></svg><!--
|
|
1181
1222
|
The live layer: the PHOTOGRAPH with the source clip warped onto the
|
|
1182
1223
|
fitted quad by the browser. Not the composite — the composite already
|
|
1183
1224
|
has a frame burned into it, so playing over that would show two screens.
|
|
1184
1225
|
--><div id="liveWrap" hidden><img id="liveBg" alt=""><video id="liveVid" loop muted playsinline></video></div></div>
|
|
1226
|
+
<span class="status" id="outSt">Press Preview</span>
|
|
1185
1227
|
<!-- Video controls. Hidden entirely for a still source: a scrubber over a
|
|
1186
1228
|
screenshot is a control that can only confuse. The fit itself is
|
|
1187
1229
|
unchanged — you match the edges on one frame and every frame gets the
|