Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Embed Photopea in web apps using photopea.js. Covers embedding, file I/O, scripting, exporting, layers, text, filters, and the full Photoshop-compatible API.
.claude/skills/sickn33-photopea-embedded-editor/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 297% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 479% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 420% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 631% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 550% | 0% |
Read the detailed guide before executing this skill. It retains the complete procedure and reference material. Treat its safety, prerequisites, and validation requirements as mandatory. For focused work, load the relevant sections; for end-to-end work, read the guide completely.
Use this skill for every task that involves:
Do NOT use raw postMessage wiring — always use photopea.js as the wrapper.
jsapp.preferences.rulerUnits = Units.PIXELS; var doc = app.activeDocument; function processLayers(parent) { for (var i = 0; i < parent.layers.length; i++) { var l = parent.layers[i]; if (l.typename === "LayerSet") processLayers(l); else if (l.kind === LayerKind.TEXT) { l.name = l.textItem.contents.substring(0, 30); } } } processLayers(doc); app.echoToOE("done");
jsapp.preferences.rulerUnits = Units.PIXELS; var doc = app.activeDocument; for (var i = 0; i < doc.layers.length; i++) { // Hide all layers for (var j = 0; j < doc.layers.length; j++) doc.layers[j].visible = false; // Show only this layer doc.layers[i].visible = true; // Export var opts = new ExportOptionsSaveForWeb(); opts.format = SaveDocumentType.PNG; opts.PNG8 = false; opts.quality = 100; doc.exportDocument( new File("/" + doc.layers[i].name + ".png"), ExportType.SAVEFORWEB, opts ); } // Restore visibility for (var i = 0; i < doc.layers.length; i++) doc.layers[i].visible = true;
jsvar searchText = "2024"; var replaceText = "2025"; function findReplaceText(parent) { for (var i = 0; i < parent.layers.length; i++) { var l = parent.layers[i]; if (l.typename === "LayerSet") findReplaceText(l); else if (l.kind === LayerKind.TEXT) { var t = l.textItem; if (t.contents.indexOf(searchText) !== -1) { t.contents = t.contents.split(searchText).join(replaceText); } } } } findReplaceText(app.activeDocument); app.echoToOE("Find & Replace complete");
jsapp.preferences.rulerUnits = Units.PIXELS; var doc = app.activeDocument; var layer = doc.activeLayer; var cols = 4, rows = 3; var padX = 20, padY = 20; var w = layer.bounds[2] - layer.bounds[0]; var h = layer.bounds[3] - layer.bounds[1]; for (var r = 0; r < rows; r++) { for (var c = 0; c < cols; c++) { if (r === 0 && c === 0) continue; // skip original var copy = layer.duplicate(); var targetX = layer.bounds[0] + c * (w + padX); var targetY = layer.bounds[1] + r * (h + padY); copy.translate(targetX - copy.bounds[0], targetY - copy.bounds[1]); copy.opacity = 100 - (r * cols + c) * 5; } }
jsapp.preferences.rulerUnits = Units.PIXELS; var doc = app.activeDocument; // Open watermark as smart object layer app.open("https://example.com/watermark.png", null, true); var wm = doc.activeLayer; // Resize to 20% of document width var wmW = wm.bounds[2] - wm.bounds[0]; var targetW = doc.width * 0.2; var scalePct = (targetW / wmW) * 100; wm.resize(scalePct, scalePct, AnchorPosition.TOPLEFT); // Move to bottom-right with 20px margin var wmNewW = wm.bounds[2] - wm.bounds[0]; var wmNewH = wm.bounds[3] - wm.bounds[1]; wm.translate( doc.width - wmNewW - 20 - wm.bounds[0], doc.height - wmNewH - 20 - wm.bounds[1] ); wm.opacity = 60; app.echoToOE("watermark applied");
jsfunction getLayerInfo(parent, depth) { depth = depth || 0; var result = []; for (var i = 0; i < parent.layers.length; i++) { var l = parent.layers[i]; var info = { name: l.name, type: l.typename, visible: l.visible, opacity: l.opacity, depth: depth }; if (l.typename === "ArtLayer") { info.kind = l.kind.toString(); info.bounds = [l.bounds[0], l.bounds[1], l.bounds[2], l.bounds[3]]; if (l.kind === LayerKind.TEXT) { info.text = l.textItem.contents; info.font = l.textItem.font; info.size = l.textItem.size; } } else if (l.typename === "LayerSet") { info.children = getLayerInfo(l, depth + 1); } result.push(info); } return result; } app.echoToOE(JSON.stringify(getLayerInfo(app.activeDocument)));
runScript executes scripts inside the embedded Photopea document context. Only run scripts you understand and only with user-approved files.JSON.stringify before embedding them in a runScript string. Never concatenate user-provided URLs, layer names, or text directly into Photopea script source.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-14 | pass→pass | 11,196 | 9,222 | -18% | 1 | 1 | 0% | 2,159 | 17,484 | +710% | 0 | 0 | — |
case-01 | fail→pass | 44,924 | 16,012 | -64% | 1 | 1 | 0% | 4,920 | 19,541 | +297% | 0 | 0 | — |
case-02 | fail→pass | 15,064 | 13,618 | -10% | 1 | 1 | 0% | 3,262 | 18,896 | +479% | 0 | 0 | — |
case-03 | fail→pass | 15,272 | 11,979 | -22% | 1 | 1 | 0% | 3,561 | 18,531 | +420% | 0 | 0 | — |
case-04 | fail→pass | 12,143 | 7,581 | -38% | 1 | 1 | 0% | 2,350 | 17,178 | +631% | 0 | 0 | — |
case-05 | fail→pass | 14,246 | 6,143 | -57% | 1 | 1 | 0% | 2,598 | 16,897 | +550% | 0 | 0 | — |
case-06 | fail→pass | 13,807 | 9,142 | -34% | 1 | 1 | 0% | 2,630 | 17,357 | +560% | 0 | 0 | — |
case-07 | fail→pass | 14,230 | 5,890 | -59% | 1 | 1 | 0% | 2,669 | 16,900 | +533% | 0 | 0 | — |
case-08 | fail→pass | 12,984 | 9,676 | -25% | 1 | 1 | 0% | 2,460 | 17,893 | +627% | 0 | 0 | — |
case-13 | fail→pass | 15,361 | 6,432 | -58% | 1 | 1 | 0% | 2,865 | 16,964 | +492% | 0 | 0 | — |
case-09 | fail→pass | 13,827 | 4,355 | -69% | 1 | 1 | 0% | 2,753 | 16,482 | +499% | 0 | 0 | — |
case-10 | fail→pass | 14,908 | 6,145 | -59% | 1 | 1 | 0% | 2,653 | 16,919 | +538% | 0 | 0 | — |
case-11 | pass→pass | 13,981 | 10,574 | -24% | 1 | 1 | 0% | 2,702 | 17,708 | +555% | 0 | 0 | — |
case-12 | pass→pass | 9,806 | 6,497 | -34% | 1 | 1 | 0% | 1,992 | 17,061 | +756% | 0 | 0 | — |
case-15 | pass→pass | 12,421 | 5,542 | -55% | 1 | 1 | 0% | 2,363 | 16,651 | +605% | 0 | 0 | — |
case-16 | fail→pass | 13,982 | 8,310 | -41% | 1 | 1 | 0% | 2,576 | 17,456 | +578% | 0 | 0 | — |
case-17 | pass→pass | 9,452 | 5,274 | -44% | 1 | 1 | 0% | 1,733 | 16,707 | +864% | 0 | 0 | — |
case-18 | fail→pass | 4,975 | 5,525 | +11% | 1 | 1 | 0% | 1,002 | 16,739 | +1571% | 0 | 0 | — |
case-19 | pass→pass | 7,301 | 5,648 | -23% | 1 | 1 | 0% | 1,534 | 16,803 | +995% | 0 | 0 | — |
case-20 | pass→pass | 12,631 | 7,483 | -41% | 1 | 1 | 0% | 2,735 | 17,446 | +538% | 0 | 0 | — |
case-21 | pass→pass | 12,216 | 9,713 | -20% | 1 | 1 | 0% | 2,746 | 17,759 | +547% | 0 | 0 | — |
case-22 | pass→pass | 8,789 | 4,175 | -52% | 1 | 1 | 0% | 1,673 | 16,588 | +892% | 0 | 0 | — |
case-23 | fail→pass | 8,852 | 8,483 | -4% | 1 | 1 | 0% | 1,709 | 17,528 | +926% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +61 percentage points is the difference between those two pass rates over the 23 comparable cases.
The publisher has shipped newer versions since this run, so these numbers describe v1, not the version currently listed.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.