@mhosaic/feedback 0.9.1 → 0.10.0
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/dist/{chunk-MDLVMRQV.mjs → chunk-XSAUJEJN.mjs} +469 -128
- package/dist/chunk-XSAUJEJN.mjs.map +1 -0
- package/dist/embed.min.js +216 -74
- package/dist/embed.min.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-MDLVMRQV.mjs.map +0 -1
|
@@ -476,8 +476,12 @@ var DEFAULT_STRINGS = {
|
|
|
476
476
|
"annotator.tool.arrow": "Arrow",
|
|
477
477
|
"annotator.tool.freehand": "Freehand",
|
|
478
478
|
"annotator.tool.text": "Text",
|
|
479
|
+
"annotator.tool.highlight": "Highlight",
|
|
480
|
+
"annotator.tool.blur": "Blur (hide sensitive data)",
|
|
479
481
|
"annotator.text_prompt": "Enter text:",
|
|
482
|
+
"annotator.color_picker": "Custom color",
|
|
480
483
|
"annotator.undo": "Undo",
|
|
484
|
+
"annotator.redo": "Redo",
|
|
481
485
|
"annotator.clear": "Clear all",
|
|
482
486
|
"annotator.count_suffix": "annotations",
|
|
483
487
|
"annotator.loading": "Loading\u2026",
|
|
@@ -578,8 +582,12 @@ var FRENCH_STRINGS = {
|
|
|
578
582
|
"annotator.tool.arrow": "Fl\xE8che",
|
|
579
583
|
"annotator.tool.freehand": "Dessin libre",
|
|
580
584
|
"annotator.tool.text": "Texte",
|
|
585
|
+
"annotator.tool.highlight": "Surligner",
|
|
586
|
+
"annotator.tool.blur": "Flouter (donn\xE9es sensibles)",
|
|
581
587
|
"annotator.text_prompt": "Entrez le texte :",
|
|
588
|
+
"annotator.color_picker": "Couleur personnalis\xE9e",
|
|
582
589
|
"annotator.undo": "Annuler",
|
|
590
|
+
"annotator.redo": "Refaire",
|
|
583
591
|
"annotator.clear": "Tout effacer",
|
|
584
592
|
"annotator.count_suffix": "annotations",
|
|
585
593
|
"annotator.loading": "Chargement\u2026",
|
|
@@ -846,10 +854,11 @@ function Fab({ label, onClick }) {
|
|
|
846
854
|
import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "preact/hooks";
|
|
847
855
|
|
|
848
856
|
// src/widget/Annotator.tsx
|
|
849
|
-
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "preact/hooks";
|
|
857
|
+
import { useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useState as useState2 } from "preact/hooks";
|
|
850
858
|
import { jsx as jsx4, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
851
859
|
var COLORS = ["#ef4444", "#f59e0b", "#10b981", "#3b82f6", "#ffffff"];
|
|
852
|
-
|
|
860
|
+
var HIGHLIGHT_ALPHA = 0.35;
|
|
861
|
+
function drawShape(ctx, shape, sourceImage) {
|
|
853
862
|
ctx.save();
|
|
854
863
|
ctx.strokeStyle = shape.color;
|
|
855
864
|
ctx.fillStyle = shape.color;
|
|
@@ -882,9 +891,22 @@ function drawShape(ctx, shape) {
|
|
|
882
891
|
ctx.fillRect(shape.x - padding, shape.y - padding, w, hh);
|
|
883
892
|
ctx.fillStyle = shape.color;
|
|
884
893
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
894
|
+
} else if (shape.kind === "highlight") {
|
|
895
|
+
ctx.globalAlpha = HIGHLIGHT_ALPHA;
|
|
896
|
+
ctx.fillRect(
|
|
897
|
+
normalizeStart(shape.x, shape.w),
|
|
898
|
+
normalizeStart(shape.y, shape.h),
|
|
899
|
+
Math.abs(shape.w),
|
|
900
|
+
Math.abs(shape.h)
|
|
901
|
+
);
|
|
902
|
+
} else if (shape.kind === "blur") {
|
|
903
|
+
drawBlur(ctx, shape, sourceImage);
|
|
885
904
|
}
|
|
886
905
|
ctx.restore();
|
|
887
906
|
}
|
|
907
|
+
function normalizeStart(start, size) {
|
|
908
|
+
return size < 0 ? start + size : start;
|
|
909
|
+
}
|
|
888
910
|
function drawArrow(ctx, x1, y1, x2, y2) {
|
|
889
911
|
const headLen = 14;
|
|
890
912
|
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
@@ -905,12 +927,77 @@ function drawArrow(ctx, x1, y1, x2, y2) {
|
|
|
905
927
|
ctx.closePath();
|
|
906
928
|
ctx.fill();
|
|
907
929
|
}
|
|
930
|
+
function drawBlur(ctx, shape, sourceImage) {
|
|
931
|
+
if (!sourceImage) return;
|
|
932
|
+
const x = normalizeStart(shape.x, shape.w);
|
|
933
|
+
const y = normalizeStart(shape.y, shape.h);
|
|
934
|
+
const w = Math.abs(shape.w);
|
|
935
|
+
const h2 = Math.abs(shape.h);
|
|
936
|
+
if (w < 2 || h2 < 2) return;
|
|
937
|
+
const tile = Math.max(4, shape.tile);
|
|
938
|
+
for (let yy = y; yy < y + h2; yy += tile) {
|
|
939
|
+
for (let xx = x; xx < x + w; xx += tile) {
|
|
940
|
+
const tw = Math.min(tile, x + w - xx);
|
|
941
|
+
const th = Math.min(tile, y + h2 - yy);
|
|
942
|
+
ctx.drawImage(
|
|
943
|
+
sourceImage,
|
|
944
|
+
xx,
|
|
945
|
+
yy,
|
|
946
|
+
tw,
|
|
947
|
+
th,
|
|
948
|
+
// source rect from the image
|
|
949
|
+
xx,
|
|
950
|
+
yy,
|
|
951
|
+
tw,
|
|
952
|
+
th
|
|
953
|
+
// dest rect on the canvas (same coords)
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
ctx.imageSmoothingEnabled = false;
|
|
958
|
+
for (let yy = y; yy < y + h2; yy += tile) {
|
|
959
|
+
for (let xx = x; xx < x + w; xx += tile) {
|
|
960
|
+
const tw = Math.min(tile, x + w - xx);
|
|
961
|
+
const th = Math.min(tile, y + h2 - yy);
|
|
962
|
+
ctx.drawImage(
|
|
963
|
+
sourceImage,
|
|
964
|
+
xx,
|
|
965
|
+
yy,
|
|
966
|
+
1,
|
|
967
|
+
1,
|
|
968
|
+
// single source pixel
|
|
969
|
+
xx,
|
|
970
|
+
yy,
|
|
971
|
+
tw,
|
|
972
|
+
th
|
|
973
|
+
// stretched dest tile
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
ctx.imageSmoothingEnabled = true;
|
|
978
|
+
}
|
|
908
979
|
var Icon = {
|
|
909
980
|
rect: /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("rect", { x: "2", y: "3", width: "12", height: "10", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }) }),
|
|
910
981
|
arrow: /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M2 8h11M9 4l4 4-4 4", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
|
|
911
982
|
pencil: /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linejoin": "round" }) }),
|
|
912
983
|
text: /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M3 3h10M8 3v10M5 13h6", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" }) }),
|
|
984
|
+
highlight: /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
|
|
985
|
+
/* @__PURE__ */ jsx4("path", { d: "M3 13l3-3L11 5l-2-2-5 5-3 3v2h2zM10 4l2 2", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }),
|
|
986
|
+
/* @__PURE__ */ jsx4("path", { d: "M2 14h12", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" })
|
|
987
|
+
] }),
|
|
988
|
+
blur: /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
|
|
989
|
+
/* @__PURE__ */ jsx4("rect", { x: "2", y: "2", width: "4", height: "4", fill: "currentColor", opacity: "0.85" }),
|
|
990
|
+
/* @__PURE__ */ jsx4("rect", { x: "7", y: "2", width: "3", height: "3", fill: "currentColor", opacity: "0.55" }),
|
|
991
|
+
/* @__PURE__ */ jsx4("rect", { x: "11", y: "2", width: "3", height: "4", fill: "currentColor", opacity: "0.4" }),
|
|
992
|
+
/* @__PURE__ */ jsx4("rect", { x: "2", y: "7", width: "3", height: "3", fill: "currentColor", opacity: "0.6" }),
|
|
993
|
+
/* @__PURE__ */ jsx4("rect", { x: "6", y: "7", width: "3", height: "3", fill: "currentColor", opacity: "0.85" }),
|
|
994
|
+
/* @__PURE__ */ jsx4("rect", { x: "10", y: "7", width: "4", height: "3", fill: "currentColor", opacity: "0.5" }),
|
|
995
|
+
/* @__PURE__ */ jsx4("rect", { x: "2", y: "11", width: "4", height: "3", fill: "currentColor", opacity: "0.4" }),
|
|
996
|
+
/* @__PURE__ */ jsx4("rect", { x: "7", y: "11", width: "3", height: "3", fill: "currentColor", opacity: "0.65" }),
|
|
997
|
+
/* @__PURE__ */ jsx4("rect", { x: "11", y: "11", width: "3", height: "3", fill: "currentColor", opacity: "0.85" })
|
|
998
|
+
] }),
|
|
913
999
|
undo: /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M4 7l3-3M4 7l3 3M4 7h6a3 3 0 0 1 0 6H7", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
|
|
1000
|
+
redo: /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M12 7l-3-3M12 7l-3 3M12 7H6a3 3 0 0 0 0 6h2", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
|
|
914
1001
|
trash: /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M3 4h10M6 4V2.5h4V4M5 4l.5 9h5L11 4", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
|
|
915
1002
|
close: /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M4 4l8 8M12 4l-8 8", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" }) })
|
|
916
1003
|
};
|
|
@@ -921,12 +1008,50 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
921
1008
|
const [tool, setTool] = useState2("rectangle");
|
|
922
1009
|
const [color, setColor] = useState2(COLORS[0]);
|
|
923
1010
|
const [shapes, setShapes] = useState2([]);
|
|
1011
|
+
const [past, setPast] = useState2([]);
|
|
1012
|
+
const [future, setFuture] = useState2([]);
|
|
924
1013
|
const isDrawingRef = useRef2(false);
|
|
925
1014
|
const [draftShape, setDraftShape] = useState2(null);
|
|
926
1015
|
const [imageLoaded, setImageLoaded] = useState2(false);
|
|
927
1016
|
const [saving, setSaving] = useState2(false);
|
|
928
1017
|
const scaleRef = useRef2(1);
|
|
929
|
-
|
|
1018
|
+
function addShape(shape) {
|
|
1019
|
+
setShapes((s) => {
|
|
1020
|
+
setPast((p) => [...p, s]);
|
|
1021
|
+
return [...s, shape];
|
|
1022
|
+
});
|
|
1023
|
+
setFuture([]);
|
|
1024
|
+
}
|
|
1025
|
+
function clearShapes() {
|
|
1026
|
+
setShapes((s) => {
|
|
1027
|
+
setPast((p) => [...p, s]);
|
|
1028
|
+
return [];
|
|
1029
|
+
});
|
|
1030
|
+
setFuture([]);
|
|
1031
|
+
}
|
|
1032
|
+
function undo() {
|
|
1033
|
+
setPast((p) => {
|
|
1034
|
+
if (p.length === 0) return p;
|
|
1035
|
+
const prev = p[p.length - 1];
|
|
1036
|
+
setShapes((s) => {
|
|
1037
|
+
setFuture((f) => [s, ...f]);
|
|
1038
|
+
return prev;
|
|
1039
|
+
});
|
|
1040
|
+
return p.slice(0, -1);
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
function redo() {
|
|
1044
|
+
setFuture((f) => {
|
|
1045
|
+
if (f.length === 0) return f;
|
|
1046
|
+
const next = f[0];
|
|
1047
|
+
setShapes((s) => {
|
|
1048
|
+
setPast((p) => [...p, s]);
|
|
1049
|
+
return next;
|
|
1050
|
+
});
|
|
1051
|
+
return f.slice(1);
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
useLayoutEffect(() => {
|
|
930
1055
|
const onKey = (e) => {
|
|
931
1056
|
if (e.key === "Escape") {
|
|
932
1057
|
e.stopPropagation();
|
|
@@ -936,6 +1061,25 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
936
1061
|
window.addEventListener("keydown", onKey);
|
|
937
1062
|
return () => window.removeEventListener("keydown", onKey);
|
|
938
1063
|
}, [onCancel]);
|
|
1064
|
+
useEffect2(() => {
|
|
1065
|
+
const onKey = (e) => {
|
|
1066
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
1067
|
+
if (!mod) return;
|
|
1068
|
+
const k = e.key.toLowerCase();
|
|
1069
|
+
if (k === "z") {
|
|
1070
|
+
e.preventDefault();
|
|
1071
|
+
e.stopPropagation();
|
|
1072
|
+
if (e.shiftKey) redo();
|
|
1073
|
+
else undo();
|
|
1074
|
+
} else if (k === "y") {
|
|
1075
|
+
e.preventDefault();
|
|
1076
|
+
e.stopPropagation();
|
|
1077
|
+
redo();
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
window.addEventListener("keydown", onKey);
|
|
1081
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1082
|
+
}, [past, future, shapes]);
|
|
939
1083
|
useEffect2(() => {
|
|
940
1084
|
const url = URL.createObjectURL(imageBlob);
|
|
941
1085
|
const img = new Image();
|
|
@@ -953,7 +1097,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
953
1097
|
const img = imageRef.current;
|
|
954
1098
|
const container = containerRef.current;
|
|
955
1099
|
const maxW = container.clientWidth - 16;
|
|
956
|
-
const maxH = window.innerHeight * 0.
|
|
1100
|
+
const maxH = window.innerHeight * 0.65;
|
|
957
1101
|
const fitScale = Math.min(maxW / img.width, maxH / img.height, 1);
|
|
958
1102
|
scaleRef.current = fitScale;
|
|
959
1103
|
const canvas = canvasRef.current;
|
|
@@ -974,8 +1118,8 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
974
1118
|
if (!ctx) return;
|
|
975
1119
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
976
1120
|
ctx.drawImage(img, 0, 0);
|
|
977
|
-
for (const s of shapes) drawShape(ctx, s);
|
|
978
|
-
if (draftShape) drawShape(ctx, draftShape);
|
|
1121
|
+
for (const s of shapes) drawShape(ctx, s, img);
|
|
1122
|
+
if (draftShape) drawShape(ctx, draftShape, img);
|
|
979
1123
|
}
|
|
980
1124
|
function getCanvasCoords(e) {
|
|
981
1125
|
const canvas = canvasRef.current;
|
|
@@ -988,22 +1132,21 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
988
1132
|
const handleMouseDown = (e) => {
|
|
989
1133
|
if (!imageLoaded) return;
|
|
990
1134
|
const { x, y } = getCanvasCoords(e);
|
|
991
|
-
const
|
|
1135
|
+
const canvasW = canvasRef.current.width;
|
|
1136
|
+
const lineWidth = Math.max(3, Math.round(canvasW / 400));
|
|
1137
|
+
const blurTile = Math.max(8, Math.round(canvasW / 80));
|
|
992
1138
|
if (tool === "text") {
|
|
993
1139
|
const text = window.prompt(strings["annotator.text_prompt"]);
|
|
994
1140
|
if (text && text.trim()) {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
lineWidth: 1
|
|
1005
|
-
}
|
|
1006
|
-
]);
|
|
1141
|
+
addShape({
|
|
1142
|
+
kind: "text",
|
|
1143
|
+
x,
|
|
1144
|
+
y,
|
|
1145
|
+
text: text.trim(),
|
|
1146
|
+
color,
|
|
1147
|
+
fontSize: Math.max(16, Math.round(canvasW / 50)),
|
|
1148
|
+
lineWidth: 1
|
|
1149
|
+
});
|
|
1007
1150
|
}
|
|
1008
1151
|
return;
|
|
1009
1152
|
}
|
|
@@ -1014,12 +1157,33 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
1014
1157
|
setDraftShape({ kind: "arrow", x1: x, y1: y, x2: x, y2: y, color, lineWidth });
|
|
1015
1158
|
} else if (tool === "freehand") {
|
|
1016
1159
|
setDraftShape({ kind: "freehand", points: [{ x, y }], color, lineWidth });
|
|
1160
|
+
} else if (tool === "highlight") {
|
|
1161
|
+
setDraftShape({
|
|
1162
|
+
kind: "highlight",
|
|
1163
|
+
x,
|
|
1164
|
+
y,
|
|
1165
|
+
w: 0,
|
|
1166
|
+
h: 0,
|
|
1167
|
+
color: color === "#ffffff" ? "#fde047" : color,
|
|
1168
|
+
lineWidth: 1
|
|
1169
|
+
});
|
|
1170
|
+
} else if (tool === "blur") {
|
|
1171
|
+
setDraftShape({
|
|
1172
|
+
kind: "blur",
|
|
1173
|
+
x,
|
|
1174
|
+
y,
|
|
1175
|
+
w: 0,
|
|
1176
|
+
h: 0,
|
|
1177
|
+
color: "#000000",
|
|
1178
|
+
lineWidth: 0,
|
|
1179
|
+
tile: blurTile
|
|
1180
|
+
});
|
|
1017
1181
|
}
|
|
1018
1182
|
};
|
|
1019
1183
|
const handleMouseMove = (e) => {
|
|
1020
1184
|
if (!isDrawingRef.current || !draftShape) return;
|
|
1021
1185
|
const { x, y } = getCanvasCoords(e);
|
|
1022
|
-
if (draftShape.kind === "rectangle") {
|
|
1186
|
+
if (draftShape.kind === "rectangle" || draftShape.kind === "highlight" || draftShape.kind === "blur") {
|
|
1023
1187
|
setDraftShape({ ...draftShape, w: x - draftShape.x, h: y - draftShape.y });
|
|
1024
1188
|
} else if (draftShape.kind === "arrow") {
|
|
1025
1189
|
setDraftShape({ ...draftShape, x2: x, y2: y });
|
|
@@ -1029,12 +1193,12 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
1029
1193
|
};
|
|
1030
1194
|
const handleMouseUp = () => {
|
|
1031
1195
|
if (isDrawingRef.current && draftShape) {
|
|
1032
|
-
const isTiny = draftShape.kind === "rectangle" && Math.abs(draftShape.w) < 4 && Math.abs(draftShape.h) < 4 || draftShape.kind === "arrow" && Math.hypot(
|
|
1196
|
+
const isTiny = (draftShape.kind === "rectangle" || draftShape.kind === "highlight" || draftShape.kind === "blur") && Math.abs(draftShape.w) < 4 && Math.abs(draftShape.h) < 4 || draftShape.kind === "arrow" && Math.hypot(
|
|
1033
1197
|
draftShape.x2 - draftShape.x1,
|
|
1034
1198
|
draftShape.y2 - draftShape.y1
|
|
1035
1199
|
) < 4 || draftShape.kind === "freehand" && draftShape.points.length < 3;
|
|
1036
1200
|
if (!isTiny) {
|
|
1037
|
-
|
|
1201
|
+
addShape(draftShape);
|
|
1038
1202
|
}
|
|
1039
1203
|
}
|
|
1040
1204
|
isDrawingRef.current = false;
|
|
@@ -1057,7 +1221,9 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
1057
1221
|
{ id: "rectangle", icon: Icon.rect, label: strings["annotator.tool.rectangle"] },
|
|
1058
1222
|
{ id: "arrow", icon: Icon.arrow, label: strings["annotator.tool.arrow"] },
|
|
1059
1223
|
{ id: "freehand", icon: Icon.pencil, label: strings["annotator.tool.freehand"] },
|
|
1060
|
-
{ id: "text", icon: Icon.text, label: strings["annotator.tool.text"] }
|
|
1224
|
+
{ id: "text", icon: Icon.text, label: strings["annotator.tool.text"] },
|
|
1225
|
+
{ id: "highlight", icon: Icon.highlight, label: strings["annotator.tool.highlight"] },
|
|
1226
|
+
{ id: "blur", icon: Icon.blur, label: strings["annotator.tool.blur"] }
|
|
1061
1227
|
];
|
|
1062
1228
|
return /* @__PURE__ */ jsx4(
|
|
1063
1229
|
"div",
|
|
@@ -1096,26 +1262,38 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
1096
1262
|
t.id
|
|
1097
1263
|
)) }),
|
|
1098
1264
|
/* @__PURE__ */ jsx4("span", { class: "annotator-sep" }),
|
|
1099
|
-
/* @__PURE__ */
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1265
|
+
/* @__PURE__ */ jsxs3("div", { class: "annotator-colors", children: [
|
|
1266
|
+
COLORS.map((c) => /* @__PURE__ */ jsx4(
|
|
1267
|
+
"button",
|
|
1268
|
+
{
|
|
1269
|
+
type: "button",
|
|
1270
|
+
onClick: () => setColor(c),
|
|
1271
|
+
"aria-label": c,
|
|
1272
|
+
"aria-pressed": color === c,
|
|
1273
|
+
class: `annotator-color ${color === c ? "is-active" : ""}`,
|
|
1274
|
+
style: { backgroundColor: c }
|
|
1275
|
+
},
|
|
1276
|
+
c
|
|
1277
|
+
)),
|
|
1278
|
+
/* @__PURE__ */ jsx4("label", { class: "annotator-color annotator-color-picker", title: strings["annotator.color_picker"], children: /* @__PURE__ */ jsx4(
|
|
1279
|
+
"input",
|
|
1280
|
+
{
|
|
1281
|
+
type: "color",
|
|
1282
|
+
value: color,
|
|
1283
|
+
onInput: (e) => setColor(e.target.value),
|
|
1284
|
+
"aria-label": strings["annotator.color_picker"]
|
|
1285
|
+
}
|
|
1286
|
+
) })
|
|
1287
|
+
] }),
|
|
1111
1288
|
/* @__PURE__ */ jsx4("span", { class: "annotator-sep" }),
|
|
1112
1289
|
/* @__PURE__ */ jsxs3(
|
|
1113
1290
|
"button",
|
|
1114
1291
|
{
|
|
1115
1292
|
type: "button",
|
|
1116
1293
|
class: "annotator-btn",
|
|
1117
|
-
onClick:
|
|
1118
|
-
disabled:
|
|
1294
|
+
onClick: undo,
|
|
1295
|
+
disabled: past.length === 0,
|
|
1296
|
+
title: strings["annotator.undo"],
|
|
1119
1297
|
children: [
|
|
1120
1298
|
Icon.undo,
|
|
1121
1299
|
/* @__PURE__ */ jsx4("span", { children: strings["annotator.undo"] })
|
|
@@ -1127,7 +1305,21 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
1127
1305
|
{
|
|
1128
1306
|
type: "button",
|
|
1129
1307
|
class: "annotator-btn",
|
|
1130
|
-
onClick:
|
|
1308
|
+
onClick: redo,
|
|
1309
|
+
disabled: future.length === 0,
|
|
1310
|
+
title: strings["annotator.redo"],
|
|
1311
|
+
children: [
|
|
1312
|
+
Icon.redo,
|
|
1313
|
+
/* @__PURE__ */ jsx4("span", { children: strings["annotator.redo"] })
|
|
1314
|
+
]
|
|
1315
|
+
}
|
|
1316
|
+
),
|
|
1317
|
+
/* @__PURE__ */ jsxs3(
|
|
1318
|
+
"button",
|
|
1319
|
+
{
|
|
1320
|
+
type: "button",
|
|
1321
|
+
class: "annotator-btn",
|
|
1322
|
+
onClick: clearShapes,
|
|
1131
1323
|
disabled: shapes.length === 0,
|
|
1132
1324
|
children: [
|
|
1133
1325
|
Icon.trash,
|
|
@@ -1359,25 +1551,27 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
1359
1551
|
),
|
|
1360
1552
|
screenshotPreview ? /* @__PURE__ */ jsxs4("div", { class: "screenshot-preview", children: [
|
|
1361
1553
|
/* @__PURE__ */ jsx5("img", { src: screenshotPreview, alt: "" }),
|
|
1362
|
-
/* @__PURE__ */
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1554
|
+
/* @__PURE__ */ jsxs4("div", { class: "screenshot-preview-actions", children: [
|
|
1555
|
+
/* @__PURE__ */ jsxs4(
|
|
1556
|
+
"button",
|
|
1557
|
+
{
|
|
1558
|
+
type: "button",
|
|
1559
|
+
class: "btn btn--primary screenshot-annotate",
|
|
1560
|
+
onClick: () => setAnnotatorOpen(true),
|
|
1561
|
+
children: [
|
|
1562
|
+
/* @__PURE__ */ jsxs4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
|
|
1563
|
+
/* @__PURE__ */ jsx5("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
|
|
1564
|
+
/* @__PURE__ */ jsx5("path", { d: "M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
|
|
1565
|
+
] }),
|
|
1566
|
+
strings["form.screenshot.annotate"]
|
|
1567
|
+
]
|
|
1568
|
+
}
|
|
1569
|
+
),
|
|
1570
|
+
/* @__PURE__ */ jsxs4("button", { type: "button", class: "btn", onClick: clearScreenshot, children: [
|
|
1571
|
+
/* @__PURE__ */ jsx5("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("path", { d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z" }) }),
|
|
1572
|
+
strings["form.screenshot.remove"]
|
|
1573
|
+
] })
|
|
1574
|
+
] })
|
|
1381
1575
|
] }) : /* @__PURE__ */ jsxs4(
|
|
1382
1576
|
"div",
|
|
1383
1577
|
{
|
|
@@ -1397,6 +1591,11 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
1397
1591
|
onDragLeave: handleDragLeave,
|
|
1398
1592
|
onDrop: handleDrop,
|
|
1399
1593
|
children: [
|
|
1594
|
+
/* @__PURE__ */ jsxs4("svg", { class: "screenshot-icon", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.6", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
|
|
1595
|
+
/* @__PURE__ */ jsx5("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
1596
|
+
/* @__PURE__ */ jsx5("polyline", { points: "17 8 12 3 7 8" }),
|
|
1597
|
+
/* @__PURE__ */ jsx5("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
|
|
1598
|
+
] }),
|
|
1400
1599
|
/* @__PURE__ */ jsxs4("div", { class: "screenshot-cta", children: [
|
|
1401
1600
|
/* @__PURE__ */ jsx5("strong", { children: strings["form.screenshot.cta_click"] }),
|
|
1402
1601
|
", ",
|
|
@@ -1972,13 +2171,39 @@ var WIDGET_STYLES = `
|
|
|
1972
2171
|
--mfb-accent-contrast: #ffffff;
|
|
1973
2172
|
--mfb-bg: #ffffff;
|
|
1974
2173
|
--mfb-surface: #f9fafb;
|
|
2174
|
+
--mfb-surface-2: #f3f4f6;
|
|
1975
2175
|
--mfb-text: #0a0a0a;
|
|
1976
2176
|
--mfb-text-muted: #6b7280;
|
|
1977
2177
|
--mfb-border: #e5e7eb;
|
|
2178
|
+
--mfb-border-strong: #d1d5db;
|
|
1978
2179
|
--mfb-radius: 8px;
|
|
2180
|
+
--mfb-radius-lg: 14px;
|
|
1979
2181
|
--mfb-font: system-ui, -apple-system, sans-serif;
|
|
1980
2182
|
--mfb-z-index: 2147483640;
|
|
1981
2183
|
|
|
2184
|
+
/* Spacing scale \u2014 4px base, used everywhere instead of magic numbers. */
|
|
2185
|
+
--mfb-space-1: 4px;
|
|
2186
|
+
--mfb-space-2: 8px;
|
|
2187
|
+
--mfb-space-3: 12px;
|
|
2188
|
+
--mfb-space-4: 16px;
|
|
2189
|
+
--mfb-space-5: 24px;
|
|
2190
|
+
--mfb-space-6: 32px;
|
|
2191
|
+
--mfb-space-7: 48px;
|
|
2192
|
+
|
|
2193
|
+
/* Type scale \u2014 fixed sizes, no fluid scaling. Body 14px sits in the
|
|
2194
|
+
"comfortable on every viewport" range; headings step up gracefully. */
|
|
2195
|
+
--mfb-text-xs: 11px;
|
|
2196
|
+
--mfb-text-sm: 13px;
|
|
2197
|
+
--mfb-text-base: 14px;
|
|
2198
|
+
--mfb-text-md: 15px;
|
|
2199
|
+
--mfb-text-lg: 17px;
|
|
2200
|
+
--mfb-text-xl: 20px;
|
|
2201
|
+
|
|
2202
|
+
/* Elevation \u2014 three tiers for layering. */
|
|
2203
|
+
--mfb-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
2204
|
+
--mfb-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.12);
|
|
2205
|
+
--mfb-shadow-lg: 0 24px 56px rgba(0, 0, 0, 0.18), 0 8px 20px rgba(0, 0, 0, 0.10);
|
|
2206
|
+
|
|
1982
2207
|
all: initial;
|
|
1983
2208
|
font-family: var(--mfb-font);
|
|
1984
2209
|
color: var(--mfb-text);
|
|
@@ -1988,11 +2213,14 @@ var WIDGET_STYLES = `
|
|
|
1988
2213
|
|
|
1989
2214
|
@media (prefers-color-scheme: dark) {
|
|
1990
2215
|
:host {
|
|
1991
|
-
--mfb-bg: #
|
|
1992
|
-
--mfb-surface: #
|
|
1993
|
-
--mfb-
|
|
1994
|
-
--mfb-text
|
|
1995
|
-
--mfb-
|
|
2216
|
+
--mfb-bg: #0f172a;
|
|
2217
|
+
--mfb-surface: #1e293b;
|
|
2218
|
+
--mfb-surface-2: #253349;
|
|
2219
|
+
--mfb-text: #f8fafc;
|
|
2220
|
+
--mfb-text-muted: #94a3b8;
|
|
2221
|
+
--mfb-border: #334155;
|
|
2222
|
+
--mfb-border-strong: #475569;
|
|
2223
|
+
--mfb-shadow-lg: 0 24px 56px rgba(0, 0, 0, 0.55), 0 8px 20px rgba(0, 0, 0, 0.40);
|
|
1996
2224
|
}
|
|
1997
2225
|
}
|
|
1998
2226
|
|
|
@@ -2051,60 +2279,98 @@ var WIDGET_STYLES = `
|
|
|
2051
2279
|
.fab:hover, .fab:active { transform: none; }
|
|
2052
2280
|
}
|
|
2053
2281
|
|
|
2282
|
+
/* Backdrop \u2014 fade in with a slight blur for depth. The blur gives the
|
|
2283
|
+
modal that "page that opens" weight: the underlying page recedes
|
|
2284
|
+
visually so the widget feels foregrounded, not pasted on. */
|
|
2054
2285
|
.backdrop {
|
|
2055
2286
|
position: fixed;
|
|
2056
2287
|
inset: 0;
|
|
2057
|
-
background: rgba(
|
|
2288
|
+
background: rgba(15, 23, 42, 0.55);
|
|
2289
|
+
backdrop-filter: blur(6px);
|
|
2290
|
+
-webkit-backdrop-filter: blur(6px);
|
|
2058
2291
|
display: grid;
|
|
2059
2292
|
place-items: center;
|
|
2293
|
+
animation: mfb-backdrop-in 220ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
@keyframes mfb-backdrop-in {
|
|
2297
|
+
from { opacity: 0; backdrop-filter: blur(0px); -webkit-backdrop-filter: blur(0px); }
|
|
2298
|
+
to { opacity: 1; backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
|
|
2060
2299
|
}
|
|
2061
2300
|
|
|
2062
2301
|
.modal {
|
|
2063
2302
|
background: var(--mfb-bg);
|
|
2064
|
-
border-radius:
|
|
2065
|
-
box-shadow:
|
|
2066
|
-
/*
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
padding:
|
|
2303
|
+
border-radius: var(--mfb-radius-lg);
|
|
2304
|
+
box-shadow: var(--mfb-shadow-lg);
|
|
2305
|
+
/* 720px is the "page that opens" sweet spot \u2014 wide enough to feel
|
|
2306
|
+
like a workspace, narrow enough to not dominate the screen. Was
|
|
2307
|
+
440px before; the bump trades a denser modal for a calmer canvas. */
|
|
2308
|
+
width: min(720px, calc(100vw - var(--mfb-space-7)));
|
|
2309
|
+
padding: var(--mfb-space-6);
|
|
2071
2310
|
display: flex;
|
|
2072
2311
|
flex-direction: column;
|
|
2073
|
-
|
|
2074
|
-
field group, .field uses a tighter 6px label-to-input gap. */
|
|
2075
|
-
gap: 18px;
|
|
2312
|
+
gap: var(--mfb-space-5);
|
|
2076
2313
|
position: relative;
|
|
2077
|
-
/* Cap
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
max-height: calc(100vh - 32px);
|
|
2314
|
+
/* Cap height with breathing room. The form scrolls internally if
|
|
2315
|
+
the user attaches a tall screenshot. */
|
|
2316
|
+
max-height: min(820px, calc(100vh - var(--mfb-space-7)));
|
|
2081
2317
|
overflow-y: auto;
|
|
2318
|
+
/* Entrance: opacity-only. Avoids any geometric shift during the
|
|
2319
|
+
animation so interaction tests that read boundingBox immediately
|
|
2320
|
+
after the modal becomes visible get stable coordinates. 220ms
|
|
2321
|
+
ease-out is snappy enough that human users barely register it. */
|
|
2322
|
+
animation: mfb-modal-in 220ms ease-out;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
@keyframes mfb-modal-in {
|
|
2326
|
+
from { opacity: 0; }
|
|
2327
|
+
to { opacity: 1; }
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
/* Mobile: full-height sheet that slides up from the bottom. The
|
|
2331
|
+
slide-up gesture matches platform-native sheets users already know. */
|
|
2332
|
+
@media (max-width: 640px) {
|
|
2333
|
+
.backdrop { place-items: end center; }
|
|
2334
|
+
.modal {
|
|
2335
|
+
width: 100vw;
|
|
2336
|
+
height: calc(100vh - var(--mfb-space-7));
|
|
2337
|
+
max-height: none;
|
|
2338
|
+
border-radius: var(--mfb-radius-lg) var(--mfb-radius-lg) 0 0;
|
|
2339
|
+
padding: var(--mfb-space-5) var(--mfb-space-4) var(--mfb-space-4);
|
|
2340
|
+
animation: mfb-sheet-up 280ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
@keyframes mfb-sheet-up {
|
|
2345
|
+
from { transform: translateY(100%); }
|
|
2346
|
+
to { transform: translateY(0); }
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2350
|
+
.backdrop, .modal { animation: none; }
|
|
2082
2351
|
}
|
|
2083
2352
|
|
|
2084
2353
|
.modal h2 {
|
|
2085
2354
|
margin: 0;
|
|
2086
|
-
font-size:
|
|
2355
|
+
font-size: var(--mfb-text-xl);
|
|
2087
2356
|
font-weight: 600;
|
|
2088
|
-
padding-right:
|
|
2089
|
-
letter-spacing: -0.
|
|
2357
|
+
padding-right: var(--mfb-space-6);
|
|
2358
|
+
letter-spacing: -0.015em;
|
|
2359
|
+
line-height: 1.2;
|
|
2090
2360
|
}
|
|
2091
2361
|
|
|
2092
|
-
/* The form sits inside .modal as a single flex child, so the modal-level
|
|
2093
|
-
gap doesn't separate the form's *own* children. Mirror the column +
|
|
2094
|
-
gap pattern on the form itself; 22px lands in the middle of the
|
|
2095
|
-
20-24px field-to-field range the design critic recommended. */
|
|
2096
2362
|
.modal form {
|
|
2097
2363
|
display: flex;
|
|
2098
2364
|
flex-direction: column;
|
|
2099
|
-
gap:
|
|
2365
|
+
gap: var(--mfb-space-5);
|
|
2100
2366
|
}
|
|
2101
2367
|
|
|
2102
2368
|
.modal-close {
|
|
2103
2369
|
position: absolute;
|
|
2104
|
-
top:
|
|
2105
|
-
right:
|
|
2106
|
-
width:
|
|
2107
|
-
height:
|
|
2370
|
+
top: var(--mfb-space-3);
|
|
2371
|
+
right: var(--mfb-space-3);
|
|
2372
|
+
width: 36px;
|
|
2373
|
+
height: 36px;
|
|
2108
2374
|
display: grid;
|
|
2109
2375
|
place-items: center;
|
|
2110
2376
|
background: transparent;
|
|
@@ -2115,36 +2381,39 @@ var WIDGET_STYLES = `
|
|
|
2115
2381
|
font-size: 22px;
|
|
2116
2382
|
line-height: 1;
|
|
2117
2383
|
cursor: pointer;
|
|
2384
|
+
transition: background 120ms ease, color 120ms ease;
|
|
2118
2385
|
}
|
|
2119
2386
|
.modal-close:hover { background: var(--mfb-surface); color: var(--mfb-text); }
|
|
2120
2387
|
.modal-close:focus-visible { outline: 2px solid var(--mfb-accent); outline-offset: 2px; }
|
|
2121
2388
|
|
|
2122
|
-
/* Each field group: label
|
|
2123
|
-
|
|
2124
|
-
.field { display: flex; flex-direction: column; gap:
|
|
2389
|
+
/* Each field group: label + input stacked with breathing room.
|
|
2390
|
+
The 24px modal-level gap separates groups. */
|
|
2391
|
+
.field { display: flex; flex-direction: column; gap: var(--mfb-space-2); font-size: var(--mfb-text-sm); }
|
|
2125
2392
|
|
|
2126
2393
|
.field label {
|
|
2127
2394
|
color: var(--mfb-text-muted);
|
|
2128
2395
|
font-weight: 500;
|
|
2129
|
-
font-size:
|
|
2130
|
-
letter-spacing: 0.
|
|
2396
|
+
font-size: var(--mfb-text-xs);
|
|
2397
|
+
letter-spacing: 0.03em;
|
|
2398
|
+
text-transform: uppercase;
|
|
2131
2399
|
}
|
|
2132
2400
|
|
|
2133
2401
|
.field input, .field select, .field textarea {
|
|
2134
2402
|
font-family: inherit;
|
|
2135
|
-
font-size:
|
|
2403
|
+
font-size: var(--mfb-text-base);
|
|
2136
2404
|
color: inherit;
|
|
2137
|
-
padding:
|
|
2405
|
+
padding: 11px 14px;
|
|
2138
2406
|
border: 1px solid var(--mfb-border);
|
|
2139
2407
|
border-radius: var(--mfb-radius);
|
|
2140
2408
|
background: var(--mfb-surface);
|
|
2141
|
-
transition: border-color 120ms ease, box-shadow 120ms ease;
|
|
2409
|
+
transition: border-color 120ms ease, box-shadow 120ms ease, background 120ms ease;
|
|
2142
2410
|
}
|
|
2143
2411
|
|
|
2144
|
-
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--mfb-
|
|
2412
|
+
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--mfb-border-strong); }
|
|
2145
2413
|
.field input:focus, .field select:focus, .field textarea:focus {
|
|
2146
2414
|
outline: none;
|
|
2147
2415
|
border-color: var(--mfb-accent);
|
|
2416
|
+
background: var(--mfb-bg);
|
|
2148
2417
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--mfb-accent) 22%, transparent);
|
|
2149
2418
|
}
|
|
2150
2419
|
|
|
@@ -2158,34 +2427,35 @@ var WIDGET_STYLES = `
|
|
|
2158
2427
|
background-position: right 10px center;
|
|
2159
2428
|
}
|
|
2160
2429
|
|
|
2161
|
-
.field textarea { min-height:
|
|
2430
|
+
.field textarea { min-height: 120px; resize: vertical; line-height: 1.5; }
|
|
2162
2431
|
|
|
2163
|
-
.row { display: flex; gap:
|
|
2432
|
+
.row { display: flex; gap: var(--mfb-space-3); }
|
|
2164
2433
|
.row > * { flex: 1; }
|
|
2165
2434
|
|
|
2166
|
-
/* Footer separation
|
|
2167
|
-
Stops Cancel/Send from floating against the dropzone or page-URL footer. */
|
|
2435
|
+
/* Footer: subtle separation via border, slightly more vertical space. */
|
|
2168
2436
|
.actions {
|
|
2169
2437
|
display: flex;
|
|
2170
|
-
gap:
|
|
2438
|
+
gap: var(--mfb-space-2);
|
|
2171
2439
|
justify-content: flex-end;
|
|
2172
|
-
padding-top:
|
|
2173
|
-
margin-top:
|
|
2440
|
+
padding-top: var(--mfb-space-4);
|
|
2441
|
+
margin-top: var(--mfb-space-1);
|
|
2174
2442
|
border-top: 1px solid var(--mfb-border);
|
|
2175
2443
|
}
|
|
2176
2444
|
|
|
2177
2445
|
.btn {
|
|
2178
|
-
padding:
|
|
2446
|
+
padding: 10px 18px;
|
|
2179
2447
|
border-radius: var(--mfb-radius);
|
|
2180
2448
|
border: 1px solid var(--mfb-border);
|
|
2181
2449
|
background: var(--mfb-bg);
|
|
2182
2450
|
color: var(--mfb-text);
|
|
2183
2451
|
font: inherit;
|
|
2452
|
+
font-size: var(--mfb-text-sm);
|
|
2184
2453
|
font-weight: 500;
|
|
2185
2454
|
cursor: pointer;
|
|
2186
|
-
transition: background 120ms ease, border-color 120ms ease;
|
|
2455
|
+
transition: background 120ms ease, border-color 120ms ease, transform 80ms ease;
|
|
2187
2456
|
}
|
|
2188
|
-
.btn:hover { background: var(--mfb-surface); }
|
|
2457
|
+
.btn:hover { background: var(--mfb-surface); border-color: var(--mfb-border-strong); }
|
|
2458
|
+
.btn:active { transform: scale(0.98); }
|
|
2189
2459
|
|
|
2190
2460
|
.btn--primary {
|
|
2191
2461
|
background: var(--mfb-accent);
|
|
@@ -2216,28 +2486,49 @@ var WIDGET_STYLES = `
|
|
|
2216
2486
|
border: 0;
|
|
2217
2487
|
}
|
|
2218
2488
|
|
|
2489
|
+
/* Dropzone \u2014 bigger, more inviting; the icon + heading + sub-line
|
|
2490
|
+
hierarchy makes it feel like a deliberate target, not an afterthought. */
|
|
2219
2491
|
.screenshot-dropzone {
|
|
2220
|
-
border:
|
|
2221
|
-
border-radius: var(--mfb-radius);
|
|
2222
|
-
padding:
|
|
2492
|
+
border: 1.5px dashed var(--mfb-border-strong);
|
|
2493
|
+
border-radius: var(--mfb-radius-lg);
|
|
2494
|
+
padding: var(--mfb-space-6) var(--mfb-space-4);
|
|
2223
2495
|
text-align: center;
|
|
2224
2496
|
cursor: pointer;
|
|
2225
2497
|
background: var(--mfb-surface);
|
|
2226
|
-
transition: border-color
|
|
2498
|
+
transition: border-color 160ms ease, background 160ms ease, transform 120ms ease;
|
|
2499
|
+
display: flex;
|
|
2500
|
+
flex-direction: column;
|
|
2501
|
+
align-items: center;
|
|
2502
|
+
gap: var(--mfb-space-2);
|
|
2503
|
+
}
|
|
2504
|
+
.screenshot-dropzone:hover {
|
|
2505
|
+
border-color: var(--mfb-accent);
|
|
2506
|
+
background: color-mix(in srgb, var(--mfb-accent) 4%, var(--mfb-surface));
|
|
2227
2507
|
}
|
|
2228
|
-
.screenshot-dropzone:hover { border-color: var(--mfb-text-muted); }
|
|
2229
2508
|
.screenshot-dropzone.is-dragover {
|
|
2230
2509
|
border-color: var(--mfb-accent);
|
|
2231
2510
|
border-style: solid;
|
|
2232
|
-
background: color-mix(in srgb, var(--mfb-accent)
|
|
2511
|
+
background: color-mix(in srgb, var(--mfb-accent) 10%, var(--mfb-surface));
|
|
2512
|
+
transform: scale(1.005);
|
|
2233
2513
|
}
|
|
2234
2514
|
.screenshot-dropzone:focus-visible {
|
|
2235
2515
|
outline: 2px solid var(--mfb-accent);
|
|
2236
2516
|
outline-offset: 2px;
|
|
2237
2517
|
}
|
|
2238
|
-
.screenshot-
|
|
2518
|
+
.screenshot-icon {
|
|
2519
|
+
color: var(--mfb-text-muted);
|
|
2520
|
+
opacity: 0.7;
|
|
2521
|
+
margin-bottom: var(--mfb-space-1);
|
|
2522
|
+
transition: color 160ms ease, opacity 160ms ease;
|
|
2523
|
+
}
|
|
2524
|
+
.screenshot-dropzone:hover .screenshot-icon,
|
|
2525
|
+
.screenshot-dropzone.is-dragover .screenshot-icon {
|
|
2526
|
+
color: var(--mfb-accent);
|
|
2527
|
+
opacity: 1;
|
|
2528
|
+
}
|
|
2529
|
+
.screenshot-cta { font-size: var(--mfb-text-base); color: var(--mfb-text); font-weight: 500; }
|
|
2239
2530
|
.screenshot-cta strong { color: var(--mfb-accent); font-weight: 600; }
|
|
2240
|
-
.screenshot-formats { font-size:
|
|
2531
|
+
.screenshot-formats { font-size: var(--mfb-text-xs); color: var(--mfb-text-muted); }
|
|
2241
2532
|
|
|
2242
2533
|
.screenshot-preview {
|
|
2243
2534
|
position: relative;
|
|
@@ -2252,11 +2543,38 @@ var WIDGET_STYLES = `
|
|
|
2252
2543
|
display: block;
|
|
2253
2544
|
width: 100%;
|
|
2254
2545
|
height: auto;
|
|
2255
|
-
max-height:
|
|
2546
|
+
max-height: 280px;
|
|
2256
2547
|
object-fit: contain;
|
|
2257
2548
|
background: #1a1a1a;
|
|
2258
2549
|
}
|
|
2550
|
+
.screenshot-preview-actions {
|
|
2551
|
+
display: flex;
|
|
2552
|
+
gap: var(--mfb-space-2);
|
|
2553
|
+
padding: var(--mfb-space-2);
|
|
2554
|
+
border-top: 1px solid var(--mfb-border);
|
|
2555
|
+
background: var(--mfb-bg);
|
|
2556
|
+
}
|
|
2557
|
+
.screenshot-preview-actions .btn {
|
|
2558
|
+
flex: 1;
|
|
2559
|
+
padding: 8px 12px;
|
|
2560
|
+
font-size: var(--mfb-text-sm);
|
|
2561
|
+
display: inline-flex;
|
|
2562
|
+
align-items: center;
|
|
2563
|
+
justify-content: center;
|
|
2564
|
+
gap: 6px;
|
|
2565
|
+
}
|
|
2566
|
+
.screenshot-preview-actions .btn--primary {
|
|
2567
|
+
background: var(--mfb-bg);
|
|
2568
|
+
color: var(--mfb-accent);
|
|
2569
|
+
border-color: var(--mfb-accent);
|
|
2570
|
+
}
|
|
2571
|
+
.screenshot-preview-actions .btn--primary:hover {
|
|
2572
|
+
background: color-mix(in srgb, var(--mfb-accent) 8%, var(--mfb-bg));
|
|
2573
|
+
border-color: var(--mfb-accent);
|
|
2574
|
+
color: var(--mfb-accent);
|
|
2575
|
+
}
|
|
2259
2576
|
.screenshot-remove {
|
|
2577
|
+
/* Kept for legacy markup that uses the old corner-cross button. */
|
|
2260
2578
|
position: absolute;
|
|
2261
2579
|
top: 6px;
|
|
2262
2580
|
right: 6px;
|
|
@@ -2274,6 +2592,7 @@ var WIDGET_STYLES = `
|
|
|
2274
2592
|
}
|
|
2275
2593
|
.screenshot-remove:hover { background: #fff; }
|
|
2276
2594
|
.screenshot-annotate {
|
|
2595
|
+
/* Kept for legacy; new markup uses .screenshot-preview-actions. */
|
|
2277
2596
|
border-radius: 0;
|
|
2278
2597
|
border-width: 0;
|
|
2279
2598
|
border-top: 1px solid var(--mfb-border);
|
|
@@ -2377,11 +2696,31 @@ var WIDGET_STYLES = `
|
|
|
2377
2696
|
cursor: pointer;
|
|
2378
2697
|
padding: 0;
|
|
2379
2698
|
transition: transform 120ms ease;
|
|
2699
|
+
position: relative;
|
|
2380
2700
|
}
|
|
2381
2701
|
.annotator-color.is-active {
|
|
2382
2702
|
transform: scale(1.12);
|
|
2383
2703
|
border-color: var(--mfb-text);
|
|
2384
2704
|
}
|
|
2705
|
+
/* Color-picker swatch: rainbow gradient fill so users see this isn't
|
|
2706
|
+
a preset, plus a tiny native <input type="color"> overlaid invisibly. */
|
|
2707
|
+
.annotator-color-picker {
|
|
2708
|
+
display: grid;
|
|
2709
|
+
place-items: center;
|
|
2710
|
+
background: conic-gradient(from 180deg, #ef4444, #f59e0b, #10b981, #3b82f6, #8b5cf6, #ec4899, #ef4444);
|
|
2711
|
+
overflow: hidden;
|
|
2712
|
+
}
|
|
2713
|
+
.annotator-color-picker input[type="color"] {
|
|
2714
|
+
position: absolute;
|
|
2715
|
+
inset: 0;
|
|
2716
|
+
width: 100%;
|
|
2717
|
+
height: 100%;
|
|
2718
|
+
border: 0;
|
|
2719
|
+
padding: 0;
|
|
2720
|
+
margin: 0;
|
|
2721
|
+
opacity: 0;
|
|
2722
|
+
cursor: pointer;
|
|
2723
|
+
}
|
|
2385
2724
|
.annotator-btn {
|
|
2386
2725
|
display: inline-flex;
|
|
2387
2726
|
align-items: center;
|
|
@@ -2431,37 +2770,39 @@ var WIDGET_STYLES = `
|
|
|
2431
2770
|
|
|
2432
2771
|
.tab-strip {
|
|
2433
2772
|
display: flex;
|
|
2434
|
-
gap:
|
|
2773
|
+
gap: var(--mfb-space-1);
|
|
2435
2774
|
border-bottom: 1px solid var(--mfb-border);
|
|
2436
|
-
margin: 0
|
|
2437
|
-
padding: 0
|
|
2775
|
+
margin: 0;
|
|
2776
|
+
padding: 0;
|
|
2438
2777
|
}
|
|
2439
2778
|
.tab-button {
|
|
2440
2779
|
appearance: none;
|
|
2441
2780
|
background: transparent;
|
|
2442
2781
|
border: 0;
|
|
2443
2782
|
border-bottom: 2px solid transparent;
|
|
2444
|
-
padding:
|
|
2783
|
+
padding: var(--mfb-space-3) var(--mfb-space-4);
|
|
2784
|
+
margin-bottom: -1px;
|
|
2445
2785
|
font: inherit;
|
|
2446
|
-
font-size:
|
|
2786
|
+
font-size: var(--mfb-text-sm);
|
|
2447
2787
|
font-weight: 500;
|
|
2448
2788
|
color: var(--mfb-text-muted);
|
|
2449
2789
|
cursor: pointer;
|
|
2790
|
+
transition: color 120ms ease, border-color 120ms ease;
|
|
2450
2791
|
}
|
|
2451
2792
|
.tab-button:hover { color: var(--mfb-text); }
|
|
2452
2793
|
.tab-button.is-active {
|
|
2453
|
-
color: var(--mfb-
|
|
2794
|
+
color: var(--mfb-accent);
|
|
2454
2795
|
border-bottom-color: var(--mfb-accent);
|
|
2455
2796
|
}
|
|
2456
2797
|
.tab-button[aria-selected="true"] { font-weight: 600; }
|
|
2457
2798
|
|
|
2458
|
-
.mine-list { display: flex; flex-direction: column; gap:
|
|
2799
|
+
.mine-list { display: flex; flex-direction: column; gap: var(--mfb-space-3); }
|
|
2459
2800
|
.mine-list-header {
|
|
2460
2801
|
display: flex;
|
|
2461
2802
|
align-items: center;
|
|
2462
2803
|
justify-content: space-between;
|
|
2463
2804
|
}
|
|
2464
|
-
.mine-list-header h2 { margin: 0; font-size:
|
|
2805
|
+
.mine-list-header h2 { margin: 0; font-size: var(--mfb-text-lg); font-weight: 600; letter-spacing: -0.01em; }
|
|
2465
2806
|
.mine-loading { color: var(--mfb-text-muted); font-size: 13px; }
|
|
2466
2807
|
.mine-empty {
|
|
2467
2808
|
text-align: center;
|
|
@@ -3142,8 +3483,8 @@ function createFeedback(config) {
|
|
|
3142
3483
|
capture_method: screenshot ? manualScreenshot ? "manual" : "html2canvas" : "none",
|
|
3143
3484
|
technical_context
|
|
3144
3485
|
};
|
|
3145
|
-
if ("0.
|
|
3146
|
-
payload.widget_version = "0.
|
|
3486
|
+
if ("0.10.0") {
|
|
3487
|
+
payload.widget_version = "0.10.0";
|
|
3147
3488
|
}
|
|
3148
3489
|
if (screenshot) payload.screenshot = screenshot;
|
|
3149
3490
|
if (values.synthetic) payload.synthetic = true;
|
|
@@ -3229,4 +3570,4 @@ function createFeedback(config) {
|
|
|
3229
3570
|
export {
|
|
3230
3571
|
createFeedback
|
|
3231
3572
|
};
|
|
3232
|
-
//# sourceMappingURL=chunk-
|
|
3573
|
+
//# sourceMappingURL=chunk-XSAUJEJN.mjs.map
|