resize
Point this skill at the current design and one or more target formats, and it produces or repairs the size/format editions by re-composing each for its new aspect. It operates on the design in the current session, and each produced format is saved as its own .imgly file. Single home for the resize workflow, the re-composition technique, and the reformat no-goes.
The one mechanic to understand before anything else. A session holds one design, so N formats are produced in sequence, not in parallel, and the saved source file is what you return to between them (handbook §1):
export({ format: 'imgly' }) → SOURCE_URI (save the source FIRST) per format: import({ source: { uri: SOURCE_URI } }) → a copy of the source is the session edit(…) re-compose for the new canvas preview() → check it export({ format: 'imgly' }) → this format's own file
Save the source before you touch it. Re-importing is what makes every format a re-composition of the source rather than of the previous format — and it is why "never resize a resize" is a mechanical rule here, not just advice.
Builds on two things it does not restate: handbook §6.3 Hierarchy + §6.4 Composition & grid (margins as % of the shortest dimension, the spacing ladder, balanced whitespace / no dead void, nothing off-canvas) and §6.1.2 type-scale bands; and the handbook's engine mechanics (the text.font declaration, auto-reshape, loadResources). For the same-aspect, translate-the-strings case, use the localize skill instead.
Scoring: reformatting is judged with the judge skill — every applicable axis ≥ 8. A dead void, a stretched element, a broken margin, or anything off-canvas fails composition.
Reference files (load on demand)
This SKILL.md holds the always-needed spine: inputs, the localize-vs-resize distinction, core
principle, workflow, the re-composition routine, the no-goes, and the resize composition
checklist (end of this file). Per-format data lives in the formats skill — exact W×H,
aspect, and platform safe zones, one file per format: ../formats/SKILL.md for the index,
../formats/<id>.md for the format(s) you're building.
Building several editions of one design, or re-applying a fix to editions you already built: the
master → editions workflow, editions.md.
For an animated or video design, read Video and animated designs below as well.
Localize vs resize — snap vs re-compose
Format is the outer distinction: a different format is a different layout; a different language is the same layout with different strings.
- localize — same canvas: change words + the script's font, then snap every block back to the reference geometry. Any geometry deviation is drift.
- resize — new canvas: the geometry must change. You re-derive the layout from the design system on the new canvas. Here "drift" is not "moved from the reference" — it's re-composition that loses the design's identity or leaves the canvas unbalanced.
Never confuse the two: don't snap a resize (that reproduces the source's proportions on the wrong canvas → a dead void), and don't re-compose a localization (that's how localized sets drift).
Inputs — what to resize, and into what
Two inputs, resolved before any work by the intake contract — derive, ask once, echo, invent
nothing: ../handbook/intake.md. R = required, no default; A =
ask if underived; D = defaultable.
| Parameter | Kind | Chips / values | Default |
|---|---|---|---|
| target formats | R | Instagram post · Instagram story · LinkedIn · Widescreen · A4 print · Custom W×H · Other | none |
| source design | derived | — | the design in the current session |
1. The source — the design in the current session. Save it first (export({ format: 'imgly' })) and hold onto the uri: every target format is re-composed from that file — never resize a resize (don't chain; re-import the source between formats).
If the user asks to resize a design saved earlier, its uri IS the source uri: open it to preview and capture it, skip the first save, then import a copy of it per format.
If the request is a brief (no design exists yet), build the native base first at the brief's canvas per handbook §6, preview and sanity-check it, then save it and treat that file as the source.
2. The formats — named formats (ig-post, ig-square, ig-story, widescreen, and the
rest of the formats skill's index) or a custom W×H. Resolve each named format's exact size,
aspect, and safe zones from the formats skill (../formats/<id>.md).
If the format set is not stated, ask — Which sizes do you need? — with the chips above; a format set has no sensible default, so never pick one for the user. Then echo the resolved set ("1 design → 3 formats") before building.
Core principle — re-compose for the aspect, preserve the system
A new format is a new layout. Re-lay it out for the new canvas while holding the design system constant:
- Invariant (carry to every format): palette + the single accent; type-scale bands (headline / body / caption as % of page height, §6.1.2); margin ratio (§6.4: 8–12% of the shortest side); spacing scale (§6.4 ladder); hierarchy order (§6.3); the group hierarchy (carry the source's grouping — re-group each cluster after re-composition, don't ship a flattened tree); the region of interest of every image/video fill (the subject stays framed — see the ROI section below); each decoration's role (a ghost quote stays a ghost quote); the element inventory (every content cluster on the source has a counterpart on the new canvas — see below); and the appearance of every block (fill/gradient, effect/blur stack, opacity, corner radius, stroke, shadow, and font weight — carried in by re-importing the source, not re-created by eye).
- Element inventory — nothing silently dropped. A reformat re-composes; a tight format tempts you to drop elements, and the result reads "way too simplistic / missing elements from the original." Before finishing, count: every text cluster, image, chip, icon, and decoration on the source must have a counterpart on the new canvas. An element may be omitted only deliberately (it genuinely can't earn its place on a smaller canvas) — never by oversight, and the omission is a judgment you can name. Re-composing from an impression of the source instead of its actual block list is how elements vanish.
- Variable (re-derive per canvas): absolute positions, absolute sizes, wrap width, line count, and how the content group is distributed vertically.
Never stretch or squish the whole design to force-fit (that distorts type and shapes — resize by re-flow, not by scaling the canvas contents). Never leave a lopsided void (§6.4).
Workflow
- Save the source and keep its
uri—export({ format: 'imgly' }). Everything below re-imports it, once per format. - Read the source's design system (palette, accent, type bands, margin %, spacing rhythm, hierarchy) and capture its layout as fractions of the source canvas (positions/sizes as % of source W/H — see routine). Do this once, on the source; the JSON it returns is what you carry from format to format.
- Re-import the source for this target format (
import({ source: { uri: SOURCE_URI } })); capture the region of interest of every image/video fill first (ROI section below); set the page to the new W×H and resize the full-bleed background to cover it; then restore each fill's ROI so the same region of the image stays framed — never let aCoverfill silently re-center the subject on the new aspect. - Re-derive from the new canvas: margins = 8–12% of the new shortest dimension; type sizes from the new page height within their bands (§6.1.2); gaps from the spacing scale (§6.4).
- Proportional re-flow to fill, then balance. Place blocks by their captured fractions × the
new canvas (this fills a taller/wider canvas instead of letterboxing it), then center or
distribute the content group within the platform safe band — no top-anchored void. (Safe-zone
bounds and per-format guidance: the
formatsskill,<id>.md.) - Scale decoration proportionally to the canvas (a ghost glyph sized as % of page height grows on a taller format, shrinks on a shorter one) — keep its role and framing.
- Re-wrap & re-fit: text re-wraps at the new column width; check overflow / clipping / off-canvas; adjust size within its band only.
- Let the size/font write carry the rich text. A
text.fontSize/text.font/text.lineHeightwrite reshapes on its own and preserves every per-character style range — color, weight, style, case, decoration (handbook §6.1.3). A resize keeps the same string, so there is nothing to capture and replay. What still flattens the accent is a whole-block styling write:getProps(id, ['text.color']).text.colorreads run 0 only, and writing it back astext: { color }collapses the whole block to run 0's formatting. Don't re-apply whole-block styling in a resize; if you must, pass the runs fromgetProps(id, ['text.ranges'])back astext: { ranges: [...] }in the samesetProps— every run, not just the accent. loadResources→previewthis format (dead void / crop / tofu only show in the render), then save it:export({ format: 'imgly' }). That file is the format's edition — until you save, it exists nowhere the user can reach, and the nextimportwill replace it. Collect the returneduris and report them all at the end. Give each build'seditanotenaming the format (e.g. "ig-story 1080×1920"). Then go back to step 2 for the next format. For a video design, preview each format at severaltimes — see Video and animated designs below.
Proportional re-composition routine
The resize analog of localize's snap-to-reference — but a first pass, not the finish. It fills
the new canvas proportionally (killing the letterbox void); you then clamp margins/bands/safe-zones
and judge the balance in preview. Resize is judgment-driven; there is no single deterministic
answer.
Capture the source as fractions (run on the source page):
jsconst [page] = await engine.design.findByType('page'); const pg = await engine.design.getProps(page, ['width', 'height']); const PW = pg.width, PH = pg.height; const out = []; async function walk(id) { const p = await engine.design.getProps(id, [ 'type', 'width', 'height', 'position' ]); const r = { type: p.type, // the SHORT name: 'text', 'graphic', 'page' fx: p.position.x / PW, fy: p.position.y / PH, fw: p.width / PW, fh: p.height / PH }; if (p.type === 'text') { const t = (await engine.design.getProps(id, ['text'])).text; r.s = t.string; // the size read carries its unit ('32px', always pixels) — parse for math r.ffs = parseFloat(t.fontSize) / PH; // fraction of page height r.lh = t.lineHeight; r.ls = t.letterSpacing; r.align = t.horizontalAlignment; } if (id !== page) out.push(r); for (const c of await engine.design.getChildren(id)) await walk(c); } await walk(page); return { type: 'text', text: JSON.stringify({ PW, PH, blocks: out }, null, 1) };
Apply to the re-imported source at the new size (first pass, then clamp):
jsconst NW = 1080, NH = 1920; // target format const SAFE_TOP = 250, SAFE_BOT = 320; // ig-story/reel UI chrome (authoritative per-format values: formats skill, <id>.md); 0 for post/square/widescreen const [page] = await engine.design.findByType('page'); await engine.design.setProps(page, { width: NW, height: NH }); // full-bleed background → cover the new page (don't reposition it proportionally) let bg; for (const id of await engine.design.getChildren(page)) { const p = await engine.design.getProps(id, ['type', 'width']); if (p.type === 'graphic' && p.width >= NW * 0.9) { bg = id; break; } } if (bg !== undefined) { const roi = await captureFill(bg); // BEFORE the aspect change — ROI section await engine.design.setProps(bg, { position: { x: 0, y: 0 }, width: NW, height: NH }); await reapplyFill(bg, roi); // keep the same image region framed on the new aspect } // ...and captureFill/reapplyFill around EVERY other image/video-fill block you resize. // content blocks → position/size by captured fractions (fill), then you clamp + balance below // for each captured fraction f matched to its block id: // await engine.design.setProps(id, { position: { x: f.fx*NW, y: f.fy*NH } }); // fills the taller canvas // if (f.fw) await engine.design.setProps(id, { width: f.fw*NW }); // a logo: set height by the same factor // if (f.ffs) await engine.design.setProps(id, { text: { fontSize: clampToBand(f.ffs*NH) + 'pt' } }); // // the unit is required; the write auto-reshapes, ranges preserved // // THEN, not optional: // • margins: ensure left/right within 8–12% of min(NW,NH); re-center centered blocks on NW. // • safe band: shift the whole content group so nothing vital sits above SAFE_TOP or below NH-SAFE_BOT. // • balance: if proportional spread looks airy/crowded, distribute the group evenly or center it — // preview and adjust. Scale decoration with the canvas; keep type within §6.1.2 bands. return { type: 'text', text: 'first pass placed — now clamp margins/safe-zone/balance and preview' };
Platform safe zones and per-format notes are in the formats skill — read <id>.md for each
target format.
Preserve the region of interest (image / video / pattern fills)
Re-flowing positions is only half the job. A block backed by an image or video fill shows a
window onto its source, and that window is recomputed when the block's frame aspect changes:
a Cover fill re-centers, a Crop fill exposes gaps. Left alone, the resize crops the subject
out of frame — the face, product, logo, or focal point that made the asset worth using. That is a
composition and craft failure, and it only shows in preview.
The ROI is defined by (all on the block, via the api skill): contentFill.mode
(Crop | Cover | Contain); for the auto modes, contentFill.horizontalAlignment +
contentFill.verticalAlignment (Left|Center|Right, Top|Center|Bottom) — this is the focal side
a Cover fill keeps; and for Crop, crop.scaleRatio + crop.translationX/Y (translation in
% of the crop frame) + crop.rotation, which pin an explicit window.
Capture before the resize, re-apply after — around every fill-bearing block, not just the background:
jsasync function captureFill(id) { // image/video/pixel fills only const caps = await engine.design.getCapabilities(id); if (!caps.includes('contentFillMode')) return null; const cf = (await engine.design.getProps(id, ['contentFill'])).contentFill; const cap = { mode: cf.mode, hAlign: cf.horizontalAlignment, vAlign: cf.verticalAlignment }; if (cap.mode === 'Crop') { const c = (await engine.design.getProps(id, ['crop'])).crop; cap.crop = { sr: c.scaleRatio, tx: c.translationX, ty: c.translationY, rot: c.rotation }; } return cap; } async function reapplyFill(id, cap) { if (!cap) return; await engine.design.setProps(id, { contentFill: { mode: cap.mode, horizontalAlignment: cap.hAlign, // ROI focus for Cover/Contain verticalAlignment: cap.vAlign } }); if (cap.mode === 'Crop' && cap.crop) { const c = cap.crop; await engine.design.setProps(id, { crop: { rotation: c.rot, translationX: c.tx, translationY: c.ty } }); await engine.design.adjustCropToFillFrame(id, c.sr); // refill gaps, keep scale ≥ source } }
Order matters: captureFill before you write the block's width/height, reapplyFill
after. setProps(id, { crop: null }) forces the mode back to Cover, so never call it in a resize
path. Default to keeping the ROI centered; when the brief or the asset's metadata names a focal point, bias
alignment (Cover) or translation (Crop) toward it. preview each format and confirm the subject
is still framed — the render is the only place ROI drift shows.
Crop invariants — the three properties every photo-in-a-frame must hold (checked in preview;
they fail independently of ROI focus, and a shape frame — circle/rounded card — makes them easy to
miss). A logo or other mark is the exception: it is shown whole, never cropped — keep its frame
at the logo's own aspect (scale width and height by the same factor) or its Contain mode, and
leave the space around it empty (handbook design rules, "A logo is shown whole").
- Full coverage, zero gap. The fill covers its frame completely — no sliver of empty frame at
any edge (the "gap at the top of the circle" defect). If the aspect change exposed a gap,
adjustCropToFillFrame(Crop) or the Cover mode refills it; never leave the frame partly unfilled. - Uniform scale — no stretch. The image scales the same on both axes; a hand-set
non-uniform crop distorts the subject. Scale is one ratio, not two. (
Containnever stretches: it scales uniformly and leaves the rest of the frame empty — which is why it suits a logo and breaks invariant 1 for a photo.) - Subject inside the window. After 1–2, the focal point is still within the visible crop, not pushed out by the refill. This is the ROI check above — do it last, after coverage is guaranteed.
Re-group after re-composition. Resize moves and resizes loose blocks; before you finish, restore the source's group hierarchy on the new canvas (handbook §4 "Group blocks & hierarchy") so each format ships the same grouped, nested structure — not a flattened tree.
Video and animated designs
Applies when the page has a playback.duration, blocks carry playback.timeOffset /
playback.duration, animations, an audio block, or a captionTrack. A resize re-composes the
frame; the timeline is invariant.
- Fork, move, resize — never recreate a timed block. A page resize keeps every block's
timeOffset,duration, animations, audio and video trims; a re-created block loses all of them. Walk the whole tree, not just the page's children: timed blocks can sit undertracks and captions under acaptionTrack(the capturewalkabove already recurses). - Re-aim every entrance for the new layout.
slide/pandirectionis the direction of travel in radians, y pointing down:0enters from the left moving right,π/2from the top moving down,πfrom the right,3π/2from the bottom moving up. A slide that entered from the photo's side on 16:9 crosses the photo once 9:16 stacks text under it — aim it along the new reading axis. Patch in place with the same type, which keeps the animation's duration:setProps(id, { animations: { in: { type: 'slide', slide: { direction: (3 * Math.PI) / 2 } } } })(a different type replaces the animation and resets its duration);duration: sin the same spec sets it. - Scaling entrances overdraw.
zoom/growscale around the block's centre, so a full-height photo spills over its neighbours during the entrance — check the first frames after every re-layout. - Vertical safe zones hold for the whole timeline. Story / reel / TikTok / Shorts UI covers the
top and bottom bands (and the right edge on TikTok / Shorts) —
../formats/ig-story.md,../formats/tiktok.md,../formats/yt-shorts.md. Captions and CTAs belong inside the safe band, and so does every block's position mid-entrance. - Captions carry their own frame, per caption. Writing the first caption's position or size
does not reach the others — set
position,width,height,caption/fontSizeon every caption, withcaption/automaticFontSizeEnabledoff (otherwise the text grows past its frame and over the layout). Keep the frame's bottom at or aboveH − bottom safe band. - A fill's region of interest moves over time. Capture / re-apply the ROI as above, then check
it at the start, middle and end of every video clip and of every
ken_burns/panmove — a crop that frames the subject at 0 s can cut it at 3 s. - Preview at several
times, per edition. One frame of a video proves nothing about the rest:preview({ time })at the first frame, mid-entrance of each timed block, each hold, each caption change and the last second. Thenexport({ format: 'mp4', blockId: page })for the real motion.
No-goes / red flags
- Dead void / letterboxing — the headline failure: content clustered on one side of a taller/wider canvas, an empty band elsewhere. Fill or balance the group; respect safe zones.
- Design drift — re-composition that loses the design's identity: margins wander, the type scale or hierarchy shifts, the accent is misused, or decoration loses its role. Re-compose within the system, don't redesign.
- Stretch / squish — scaling the canvas contents non-proportionally to force-fit. Never distort type or shapes; re-flow instead.
- Broken margin rhythm — margins not re-derived from the new shortest dimension, or asymmetric.
- Off-canvas / clipping / overflow — elements pushed past the new edges; decoration over-cropped; text overflowing the new column.
- Decoration mis-scale — a ghost glyph or accent kept at the source's absolute size instead of scaling with the canvas.
- ROI drift — a resized image/video fill re-centered and cropped the subject out of frame (face, product, logo, focal point). Capture and re-apply the fill's region of interest.
- Missing elements — the reformat re-composed from an impression and silently dropped source content ("too simplistic / elements missing from the original"). Inventory every source cluster against the new canvas; omit only deliberately, never by oversight.
- Appearance drift — a block's fill/gradient/effect/corner-radius/overlay differs from the source, or an overlay was added/removed the source didn't have (a cover that "became darker"). Appearance is inherited by re-importing the saved source; don't re-create or restyle it by eye.
- Crop broken — a fill leaves a gap inside its frame, or was stretched non-uniformly. See the three crop invariants (coverage, uniform scale, subject-in-window).
- Flattened hierarchy — the resize dropped the source's group blocks and shipped a flat list of siblings. Re-group each cluster (nested where nested) on the new canvas.
- Ignoring platform safe zones — vital content under story/reel UI chrome.
- Timeline drift (video) — a timed block re-created instead of moved, an entrance still aimed across the old layout, or a caption left in the platform's UI band.
- tofu / overflow at new wrap widths — a new measure can push a glyph the font lacks, or wrap to
a line count that overflows; only
previewcatches it.
Before reporting done
Run the resize composition checklist below against each format's render, then close with a judge run per format (the judge skill; composition and hierarchy are the axes reformatting most often breaks). Score honestly — a residual void is a composition fail, say so.
Resize composition checklist
Any "no" on an applicable line drops the axis below 8.
Composition (§6.4)
- Margins 8–12% of the new shortest dimension; on the shared grid / one alignment
- Whitespace balanced — no lopsided dead void / letterbox; content occupies or balances the canvas
- Consistent spacing scale (§6.4 ladder); nothing off-canvas, clipped, or overlapping
- Primary content within the format's safe band (per its
formatsfile; story/reel: clear of top 269px / bottom 672px)
Hierarchy (§6.3) & identity
- One dominant element preserved; reading order intact; ≈3 levels
- Palette + single accent, type-scale bands (§6.1.2), and each decoration's role carried over
- Decoration scaled proportionally to the canvas (not the source's absolute size)
- Every image/video fill keeps its region of interest — subject/focal point still framed, nothing important cropped out or newly empty (checked in
preview) - Source's group hierarchy restored on the new canvas (grouped clusters, nested where nested — not a flat list)
- Element inventory complete — every source content cluster (text, image, chip, icon, decoration) has a counterpart on the new canvas; any omission is deliberate and nameable, not an oversight
- Appearance parity — every block's fill/gradient, effect/blur, opacity, corner radius, stroke, shadow, and font weight match the source; no overlay added or removed the source didn't have (checked against the source render)
Craft / baseline
- No stretch/squish; type re-fit within its band, not distorted
- Crop invariants hold on every fill-in-frame — full coverage (no gap), uniform scale (no stretch), subject inside the window (checked in
preview) - Zero tofu / overflow at the new wrap width, checked in
preview - Native export dimensions (the page's own pixel size)
Video / animated (when the design has a timeline)
- Every block's
timeOffset/duration, animations and audio carried over — moved, not re-created - Every entrance re-aimed for the new layout (
direction= direction of travel); no block crosses the photo or leaves the safe band mid-entrance - Every caption framed and sized on the new canvas, inside the safe band, auto font size off
- Checked at several
times (entrances, holds, caption changes, last second), not one frame
Cross-references
formatsskill — per-format W×H, aspect, and platform safe zones (one file per format id).- handbook §6.3 Hierarchy, §6.4 Composition & grid, §6.1.2 type-scale bands — the general composition criteria this skill operationalizes; §6.1.2's canvas table for type sizing by % of page height; and the handbook's engine mechanics (the
text.fontdeclaration, auto-reshape,loadResources, dirty-on-throw). judge— the acceptance gate every produced format must pass.localize— the sibling for translating a design at the same size (snap, don't re-compose).


