diff --git a/TODO.md b/TODO.md
index f25fd36..fcf4f56 100644
--- a/TODO.md
+++ b/TODO.md
@@ -7,10 +7,6 @@ Running list of deferred work. Detailed specs for the larger items live in
- **Slide deck PDF export.** Add multi-slide PDF export for slide
presentations, producing one page per slide.
-- **Color picker fixes (vector tools).** The in-app HSV picker now works in
- the pixel editor; verify the OS-native `` flow in the
- vector tool panels (Pen / Shape / Text fill + stroke). If the same Brave
- R=NaN bug bites there, port the in-app HSV picker over.
- **Copy / paste transform consistency.** Audit Copy, Paste, Copy All, and
cross-target paste so copied objects keep consistent position and scale
relative to the other copied objects, without unexpected misalignment or
@@ -81,6 +77,13 @@ Each has a full spec in `docs/`.
## Shipped (recent)
+- **Pixel-editor colour picker** — ported the OS native ``
+ over from the vector tools (same swatch styling; opens the OS picker with
+ its built-in eyedropper on click). Removed the Screen-Capture-API screen-
+ eyedropper workaround (~170 lines of dead code). Palette swatches and
+ the in-canvas eyedropper tool now sync the picker too, so grabbing a
+ palette colour as the starting point for a tweaked variant just works.
+
- **Pixel Art** — `docs/PIXEL_ART_FEATURE.md`. Phases A–D all shipped:
embedded `pixelart` layer + standalone `casewrap-pixelart` design,
PNG / sprite-sheet / GIF export, drawing tools (line / rect / ellipse,
diff --git a/src/index.html b/src/index.html
index 5c8745b..f3be47f 100644
--- a/src/index.html
+++ b/src/index.html
@@ -7873,16 +7873,42 @@
pushHistory(`paste ${items.length} layer${items.length === 1 ? '' : 's'}`);
const target = currentPasteTarget();
const bounds = targetBounds(target);
+ // Compute the bounding box over all clipboard items in source coords.
+ // Each item missing w/h falls back to a 70%-of-target default — same
+ // as the previous behaviour, applied uniformly so it can't desync
+ // items within the same paste.
+ const defW = bounds.w * 0.7, defH = bounds.h * 0.7;
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
+ for (const it of items) {
+ const w = typeof it.w === 'number' && it.w > 0 ? it.w : defW;
+ const h = typeof it.h === 'number' && it.h > 0 ? it.h : defH;
+ const x = typeof it.x === 'number' ? it.x : 0;
+ const y = typeof it.y === 'number' ? it.y : 0;
+ if (x < minX) minX = x;
+ if (y < minY) minY = y;
+ if (x + w > maxX) maxX = x + w;
+ if (y + h > maxY) maxY = y + h;
+ }
+ const srcW = Math.max(0.01, maxX - minX);
+ const srcH = Math.max(0.01, maxY - minY);
+ // Single scale factor — only shrinks (never enlarges) so a paste into
+ // a larger target keeps the items at their original size. Both axes
+ // share the same scale so the group's aspect ratio is preserved.
+ const scale = Math.min(1, bounds.w / srcW, bounds.h / srcH);
+ // Centre the (possibly scaled) bbox within the target.
+ const tx = bounds.x + (bounds.w - srcW * scale) / 2 - minX * scale;
+ const ty = bounds.y + (bounds.h - srcH * scale) / 2 - minY * scale;
let newLast = null;
const newIds = [];
for (const item of items) {
- const w = Math.max(0.1, Math.min(item.w || bounds.w * 0.7, bounds.w));
- const h = Math.max(0.1, Math.min(item.h || bounds.h * 0.7, bounds.h));
- let x = typeof item.x === 'number' ? item.x : bounds.x + (bounds.w - w) / 2;
- let y = typeof item.y === 'number' ? item.y : bounds.y + (bounds.h - h) / 2;
- // Clamp inside the target bounds so cross-sub-mode pastes don't land off-canvas.
- if (x < bounds.x || x + w > bounds.x + bounds.w) x = bounds.x + (bounds.w - w) / 2;
- if (y < bounds.y || y + h > bounds.y + bounds.h) y = bounds.y + (bounds.h - h) / 2;
+ const w0 = typeof item.w === 'number' && item.w > 0 ? item.w : defW;
+ const h0 = typeof item.h === 'number' && item.h > 0 ? item.h : defH;
+ const x0 = typeof item.x === 'number' ? item.x : 0;
+ const y0 = typeof item.y === 'number' ? item.y : 0;
+ const w = Math.max(0.01, w0 * scale);
+ const h = Math.max(0.01, h0 * scale);
+ const x = x0 * scale + tx;
+ const y = y0 * scale + ty;
const layer = { ...item, id: makeLayerId(), target, x, y, w, h, z: nextLayerZ() };
state.layers.push(layer);
newLast = layer;