@popcraft/cli 0.47.0 → 0.48.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.
Files changed (2) hide show
  1. package/dist/popcraft.mjs +194 -42
  2. package/package.json +2 -2
package/dist/popcraft.mjs CHANGED
@@ -1196,7 +1196,7 @@ function operations(spec = openapi) {
1196
1196
  }
1197
1197
  async function call(target2, method, path, opts = {}) {
1198
1198
  const qs = new URLSearchParams(opts.query ?? {}).toString();
1199
- const headers = { "user-agent": `popcraft-cli/${"0.47.0"}`, ...opts.headers };
1199
+ const headers = { "user-agent": `popcraft-cli/${"0.48.0"}`, ...opts.headers };
1200
1200
  if (target2.token) headers.authorization = `Bearer ${target2.token}`;
1201
1201
  if (opts.contentType) headers["content-type"] = opts.contentType;
1202
1202
  try {
@@ -33233,6 +33233,7 @@ var init_en2 = __esm({
33233
33233
  "designPanel.instanceSwap": "Instance swap",
33234
33234
  "designPanel.italic": "Italic",
33235
33235
  "designPanel.join": "Join",
33236
+ "designPanel.keepProportions": "Keep proportions (height follows width)",
33236
33237
  "designPanel.language": "Language",
33237
33238
  "designPanel.lastFrame": "Last frame",
33238
33239
  "designPanel.layer": "Layer",
@@ -97957,6 +97958,11 @@ function applyAutoLayout(doc2) {
97957
97958
  } else {
97958
97959
  let height = node2.height;
97959
97960
  let width = node2.width;
97961
+ const ar = node2.aspectRatio;
97962
+ if (ar && ar > 0 && node2.layoutSizingVertical !== "FILL" && Math.abs(width / ar - height) > 0.01) {
97963
+ height = Math.round(width / ar * 1e3) / 1e3;
97964
+ cloneNode(nodeId2).height = height;
97965
+ }
97960
97966
  if (node2.type === "INSTANCE") {
97961
97967
  const h = instanceContentHeight(newNodes, nodeId2);
97962
97968
  if (h !== null && Math.abs(h - height) > 0.5) {
@@ -98085,6 +98091,8 @@ function applyAutoLayout(doc2) {
98085
98091
  const r = Math.floor(index2 / actualCols);
98086
98092
  if (child.layoutSizingHorizontal === "FILL") child.width = clampedWidth(colW[c2], child);
98087
98093
  if (child.layoutSizingVertical === "FILL") child.height = clampedHeight(rowH[r], child);
98094
+ keepAspect(child);
98095
+ fitWipe(child, newNodes, cloneNode);
98088
98096
  const cellX = colX[c2];
98089
98097
  const cellY = rowY[r];
98090
98098
  child.x = cellX;
@@ -98123,6 +98131,8 @@ function applyAutoLayout(doc2) {
98123
98131
  if (child.layoutSizingVertical === "FILL") child.height = clampedHeight(fillPrimarySize, child);
98124
98132
  if (child.layoutSizingHorizontal === "FILL") child.width = clampedWidth(Math.max(0, frame3.width - paddingX), child);
98125
98133
  }
98134
+ keepAspect(child);
98135
+ fitWipe(child, newNodes, cloneNode);
98126
98136
  });
98127
98137
  if (wraps(frame3)) {
98128
98138
  const lines2 = wrapLines(frame3, childIds, (id3) => ({ intrinsicWidth: newNodes[id3].width, intrinsicHeight: newNodes[id3].height }));
@@ -98191,7 +98201,12 @@ function applyAutoLayout(doc2) {
98191
98201
  });
98192
98202
  }
98193
98203
  }
98194
- autoLayoutRoots.forEach((root2) => {
98204
+ let aspect = false;
98205
+ for (const id3 in newNodes) if (newNodes[id3].aspectRatio) {
98206
+ aspect = true;
98207
+ break;
98208
+ }
98209
+ for (let pass = 0; pass < (aspect ? 2 : 1); pass++) autoLayoutRoots.forEach((root2) => {
98195
98210
  measureNode(root2.id);
98196
98211
  layoutNode(root2.id);
98197
98212
  });
@@ -98204,6 +98219,21 @@ function applyAutoLayout(doc2) {
98204
98219
  }
98205
98220
  return { ...doc2, nodes: newNodes };
98206
98221
  }
98222
+ function keepAspect(n) {
98223
+ if (n.aspectRatio > 0 && n.layoutSizingVertical !== "FILL") n.height = Math.round(n.width / n.aspectRatio * 1e3) / 1e3;
98224
+ }
98225
+ function fitWipe(group2, nodes, clone2) {
98226
+ if (group2.type !== "GROUP" || !group2.isMaskGroup || !group2.childIds.some((id3) => nodes[id3]?.isMask && nodes[id3]?.name === "Wipe mask")) return;
98227
+ const w = group2.layoutSizingHorizontal === "FILL", h = group2.layoutSizingVertical === "FILL" || group2.aspectRatio > 0;
98228
+ if (!w && !h) return;
98229
+ for (const id3 of group2.childIds) {
98230
+ const c2 = nodes[id3];
98231
+ if (!c2 || (!w || c2.width === group2.width) && (!h || c2.height === group2.height)) continue;
98232
+ const n = clone2(id3);
98233
+ if (w) n.width = group2.width;
98234
+ if (h) n.height = group2.height;
98235
+ }
98236
+ }
98207
98237
  function wraps(frame3) {
98208
98238
  if (frame3.layoutWrap !== "WRAP" || frame3.layoutMode === "GRID") return false;
98209
98239
  return frame3.layoutMode === "HORIZONTAL" ? frame3.layoutSizingHorizontal !== "HUG" : frame3.layoutSizingVertical !== "HUG";
@@ -246764,6 +246794,45 @@ function lineWidth(line, family, weight, fontSize, italic, shaping) {
246764
246794
  }
246765
246795
  return units * fontSize;
246766
246796
  }
246797
+ function unitAdvances(line, family, weight, fontSize, italic, shaping) {
246798
+ const out = new Array(line.length).fill(0);
246799
+ if (!line) return out;
246800
+ const share = (start2, len3, em) => {
246801
+ const n = Math.max(1, len3);
246802
+ for (let k2 = 0; k2 < n && start2 + k2 < line.length; k2++) out[start2 + k2] += em * fontSize / n;
246803
+ };
246804
+ const place4 = (glyphs, upem, offset, notdef) => {
246805
+ for (const g of glyphs) {
246806
+ if (notdef && g.gid === 0) {
246807
+ if (g.clusterLength === 0) continue;
246808
+ const text = line.slice(offset + g.clusterStart, offset + g.clusterStart + g.clusterLength);
246809
+ share(offset + g.clusterStart, g.clusterLength, fallbackEm(text, family, weight, italic));
246810
+ continue;
246811
+ }
246812
+ share(offset + g.clusterStart, g.clusterLength, g.xAdvance / upem);
246813
+ }
246814
+ };
246815
+ const shaped = shapeRun(line, family, italic, weight, shaping?.features, shaping?.variations);
246816
+ if (shaped) {
246817
+ place4(shaped.glyphs, shaped.unitsPerEm, 0, true);
246818
+ return out;
246819
+ }
246820
+ const fb = shapeFallback(line, family, italic, weight, shaping?.features);
246821
+ if (fb === "loading") {
246822
+ let i2 = 0;
246823
+ for (const ch of line) {
246824
+ share(i2, ch.length, fallbackEm(ch, family, weight, italic));
246825
+ i2 += ch.length;
246826
+ }
246827
+ return out;
246828
+ }
246829
+ for (const item of fb.items) place4(item.shaped.glyphs, item.shaped.unitsPerEm, item.start, false);
246830
+ for (const i2 of fb.missing) {
246831
+ const c2 = line.charCodeAt(i2);
246832
+ if (!(c2 >= 56320 && c2 <= 57343 && i2 > 0)) out[i2] += 0.5 * fontSize;
246833
+ }
246834
+ return out;
246835
+ }
246767
246836
  function fallbackEm(text, family, weight, italic, strict = false) {
246768
246837
  let em = 0;
246769
246838
  for (const ch of text) {
@@ -246795,24 +246864,35 @@ function casedText(text, textCase) {
246795
246864
  function measureAutoWidth(t3) {
246796
246865
  return measureTextWidth(casedText(t3.characters ?? "", t3.textCase), t3.fontFamily, t3.fontWeight, t3.fontSize, t3.fontStyle, t3.letterSpacing ?? 0, { features: t3.fontFeatures, variations: t3.fontVariations });
246797
246866
  }
246798
- function estimateTextHeight(characters, width, family, weight, size6, lineHeight, wraps2 = true, letterSpacing = 0) {
246799
- const lh2 = lineHeight === "AUTO" ? size6 * 1.25 : lineHeight;
246800
- const measure4 = (t3, fallbackChars) => measureTextWidth(t3, family, weight, size6, void 0, letterSpacing) || (size6 * 0.55 + letterSpacing) * fallbackChars;
246867
+ function estimateTextHeight(characters, width, family, weight, size6, lineHeight, wraps2 = true, letterSpacing = 0, setting = {}) {
246868
+ const lh2 = lineHeight === "AUTO" ? size6 * 1.2 : lineHeight;
246869
+ const italic = setting.fontStyle === "italic";
246801
246870
  let lines2 = 0;
246802
246871
  for (const para of characters.split("\n")) {
246803
- let cur = 0, n = 1;
246804
- if (wraps2) for (const word of para.split(" ")) {
246805
- const w = measure4(word, word.length);
246806
- const space = cur > 0 ? measure4(" ", 1) : 0;
246807
- if (cur > 0 && cur + space + w > width) {
246808
- n++;
246809
- cur = w;
246810
- } else cur += space + w;
246872
+ lines2++;
246873
+ if (!wraps2 || !para) continue;
246874
+ const adv = unitAdvances(para, family, weight, size6, italic, setting.shaping);
246875
+ let start2 = 0, w = 0, lastSpace = -1;
246876
+ for (let i2 = 0; i2 < para.length; i2++) {
246877
+ const a = adv[i2] + letterSpacing;
246878
+ const space = para[i2] === " ";
246879
+ if (!space && w + a > width && lastSpace !== -1 && start2 < lastSpace) {
246880
+ lines2++;
246881
+ start2 = lastSpace + 1;
246882
+ w = 0;
246883
+ for (let j = start2; j <= i2; j++) w += adv[j] + letterSpacing;
246884
+ lastSpace = -1;
246885
+ } else {
246886
+ if (space) lastSpace = i2;
246887
+ w += a;
246888
+ }
246811
246889
  }
246812
- lines2 += n;
246813
246890
  }
246814
246891
  return Math.max(lh2, lines2 * lh2);
246815
246892
  }
246893
+ function textSetting(t3) {
246894
+ return { fontStyle: t3.fontStyle, shaping: { features: t3.fontFeatures, variations: t3.fontVariations } };
246895
+ }
246816
246896
  var init_measure = __esm({
246817
246897
  "lib/engine/text/measure.ts"() {
246818
246898
  "use strict";
@@ -246833,7 +246913,7 @@ function autoTextBounds(node2, styles) {
246833
246913
  const chars = casedText(resolved.characters || "", resolved.textCase);
246834
246914
  const family = resolved.fontFamily || "Inter", weight = resolved.fontWeight || 400, size6 = resolved.fontSize || 16;
246835
246915
  const width = hugs2 ? Math.max(measureAutoWidth({ ...resolved, fontFamily: family, fontWeight: weight, fontSize: size6 }) || size6, 10) : node2.width;
246836
- const estimate2 = estimateTextHeight(chars, width, family, weight, size6, resolved.lineHeight ?? "AUTO", !hugs2, resolved.letterSpacing || 0);
246916
+ const estimate2 = estimateTextHeight(chars, width, family, weight, size6, resolved.lineHeight ?? "AUTO", !hugs2, resolved.letterSpacing || 0, textSetting(resolved));
246837
246917
  return { width, height: hugs2 ? estimate2 : Math.max(node2.height, estimate2) };
246838
246918
  }
246839
246919
  var init_text_bounds = __esm({
@@ -252457,7 +252537,6 @@ function resizeWindow(doc2, target2, rect) {
252457
252537
  }
252458
252538
  var init_crop = __esm({
252459
252539
  "lib/editor/crop.ts"() {
252460
- "use strict";
252461
252540
  init_dist_node();
252462
252541
  init_scene_graph();
252463
252542
  init_photo_frames();
@@ -256954,7 +257033,6 @@ function followPeers(peers) {
256954
257033
  var PALETTE, usePresence, awareness, CURSOR_SEND_MS, VIEWPORT_SEND_MS, cursorTimer, pendingCursor, sentCursor, EMOTE_MS, lastFollowCamera;
256955
257034
  var init_presence = __esm({
256956
257035
  "lib/collab/presence.ts"() {
256957
- "use strict";
256958
257036
  init_esm();
256959
257037
  init_editor_store();
256960
257038
  init_status_announcements();
@@ -259138,7 +259216,11 @@ function ensureWipeMask(nodes, id3, hostFrameId) {
259138
259216
  childIds: [maskId, id3],
259139
259217
  blendMode: "NORMAL",
259140
259218
  isMaskGroup: true,
259141
- ...n.layoutPositioning ? { layoutPositioning: n.layoutPositioning } : {}
259219
+ ...n.layoutPositioning ? { layoutPositioning: n.layoutPositioning } : {},
259220
+ // A picture that keeps its proportions (a page's content picture) is wiped in its place in the layout: the group
259221
+ // fills and keeps proportions as the picture did, and the layout sizes what it holds to it (lib/document/auto-layout.ts
259222
+ // `fitWipe`), so a phone scales it rather than keeping the desktop's width.
259223
+ ...n.aspectRatio ? Object.fromEntries(["layoutSizingHorizontal", "layoutSizingVertical", "aspectRatio"].filter((k2) => n[k2] !== void 0).map((k2) => [k2, n[k2]])) : {}
259142
259224
  };
259143
259225
  const moved3 = { ...n, parentId: gid, x: 0, y: 0, motion: shiftPositionKeys(n.motion, -n.x, -n.y) };
259144
259226
  if (!moved3.motion) delete moved3.motion;
@@ -261041,11 +261123,32 @@ function breakpointDocument(doc2, id3) {
261041
261123
  const width = isScreen(doc2, n) && over2?.width === void 0 ? { width: bp.previewWidth } : null;
261042
261124
  if (over2 || width) nodes[nid] = { ...n, ...over2, ...width };
261043
261125
  }
261044
- const view2 = applyAutoLayout({ ...doc2, nodes });
261126
+ const view2 = rewrapped(doc2, applyAutoLayout({ ...doc2, nodes }));
261045
261127
  const forDoc = viewMemo.get(doc2) ?? viewMemo.set(doc2, /* @__PURE__ */ new Map()).get(doc2);
261046
261128
  forDoc.set(id3, view2);
261047
261129
  return view2;
261048
261130
  }
261131
+ function rewrapped(doc2, view2) {
261132
+ for (let pass = 0; pass < 3; pass++) {
261133
+ let nodes = null;
261134
+ for (const id3 in view2.nodes) {
261135
+ const n = view2.nodes[id3], was = doc2.nodes[id3];
261136
+ if (n.type !== "TEXT" || !n.visible || !was) continue;
261137
+ const t3 = n;
261138
+ if (t3.textAutoResize === "NONE") continue;
261139
+ if (Math.abs(t3.width - was.width) < 0.5 && t3.textStyleId === was.textStyleId && t3.fontSize === was.fontSize && t3.textAutoResize === was.textAutoResize) continue;
261140
+ const m = measureTextLayout(resolveTextNodeStyles(t3, doc2.styles ?? {}));
261141
+ if (!m) continue;
261142
+ const width = t3.textAutoResize === "WIDTH_AND_HEIGHT" ? m.width : t3.width;
261143
+ if (Math.abs(m.height - t3.height) < 0.5 && Math.abs(width - t3.width) < 0.5) continue;
261144
+ nodes ??= { ...view2.nodes };
261145
+ nodes[id3] = { ...t3, width, height: m.height };
261146
+ }
261147
+ if (!nodes) break;
261148
+ view2 = applyAutoLayout({ ...view2, nodes });
261149
+ }
261150
+ return view2;
261151
+ }
261049
261152
  function withOverrides(node2, id3, overrides2) {
261050
261153
  const next2 = { ...node2.responsive };
261051
261154
  if (Object.keys(overrides2).length) next2[id3] = overrides2;
@@ -261081,8 +261184,9 @@ function laidOut(nodes, n, key5) {
261081
261184
  const inFlow3 = !!parent && parent.layoutMode !== void 0 && parent.layoutMode !== "NONE" && s.layoutPositioning !== "ABSOLUTE";
261082
261185
  const lays = own2.layoutMode !== void 0 && own2.layoutMode !== "NONE";
261083
261186
  if (key5 === "x" || key5 === "y") return inFlow3;
261084
- if (key5 === "width") return s.layoutSizingHorizontal === "FILL" || s.layoutSizingHorizontal === "HUG" && (lays || n.type === "TEXT");
261085
- if (key5 === "height") return s.layoutSizingVertical === "FILL" || s.layoutSizingVertical === "HUG" && (lays || n.type === "TEXT") || n.type === "TEXT" && n.textAutoResize !== "NONE";
261187
+ const fills = (sizing3) => sizing3 === "FILL" && s.layoutPositioning !== "ABSOLUTE";
261188
+ if (key5 === "width") return fills(s.layoutSizingHorizontal) || s.layoutSizingHorizontal === "HUG" && (lays || n.type === "TEXT");
261189
+ if (key5 === "height") return fills(s.layoutSizingVertical) || s.layoutSizingVertical === "HUG" && (lays || n.type === "TEXT") || n.type === "TEXT" && n.textAutoResize !== "NONE";
261086
261190
  return false;
261087
261191
  }
261088
261192
  function breakpointResult(rest, baked, edited, id3) {
@@ -261123,7 +261227,9 @@ function foldVariant(doc2, masterId, variantId, id3) {
261123
261227
  const restyled = variant.textStyleId !== master.textStyleId;
261124
261228
  for (const k2 of RESPONSIVE_PROPS) {
261125
261229
  if (isRoot && (k2 === "x" || k2 === "y" || k2 === "width")) continue;
261126
- const value3 = variant[k2];
261230
+ const set4 = variant[k2];
261231
+ const inherited = k2 in UNSET && (master[k2] !== void 0 || overridesAt(master, doc2, id3)?.[k2] !== void 0);
261232
+ const value3 = set4 ?? (inherited ? UNSET[k2] : void 0);
261127
261233
  if (value3 === void 0 || laidOut(doc2.nodes, variant, k2)) continue;
261128
261234
  if ((k2 === "fontSize" || k2 === "lineHeight") && restyled) continue;
261129
261235
  props[k2] = value3;
@@ -261149,11 +261255,13 @@ function setBreakpoints(doc2, list2) {
261149
261255
  }
261150
261256
  return nodes ? { ...next2, nodes } : next2;
261151
261257
  }
261152
- var DEFAULT_BREAKPOINTS, RESPONSIVE_PROPS, RESPONSIVE, isBaseBreakpoint, same2, ownOverrides, isScreen, viewMemo;
261258
+ var DEFAULT_BREAKPOINTS, RESPONSIVE_PROPS, RESPONSIVE, isBaseBreakpoint, UNSET, same2, ownOverrides, isScreen, viewMemo;
261153
261259
  var init_breakpoints = __esm({
261154
261260
  "lib/document/breakpoints.ts"() {
261155
261261
  "use strict";
261156
261262
  init_auto_layout();
261263
+ init_instance_layout();
261264
+ init_text_styles();
261157
261265
  DEFAULT_BREAKPOINTS = [
261158
261266
  { id: "desktop", name: "Desktop", maxWidth: null, previewWidth: 1280 },
261159
261267
  { id: "tablet", name: "Tablet", maxWidth: 1024, previewWidth: 834 },
@@ -261183,10 +261291,24 @@ var init_breakpoints = __esm({
261183
261291
  "fontSize",
261184
261292
  "lineHeight",
261185
261293
  "textAlign",
261186
- "textStyleId"
261294
+ "textStyleId",
261295
+ "scaleX",
261296
+ "scaleY",
261297
+ "anchorX",
261298
+ "anchorY",
261299
+ "rotation",
261300
+ "cornerRadius",
261301
+ "textAutoResize",
261302
+ "paths",
261303
+ "translateX",
261304
+ "translateY",
261305
+ "z",
261306
+ "rotateX",
261307
+ "rotateY"
261187
261308
  ];
261188
261309
  RESPONSIVE = new Set(RESPONSIVE_PROPS);
261189
261310
  isBaseBreakpoint = (doc2, id3) => !id3 || breakpointsOf(doc2)[0].id === id3 || !breakpointsOf(doc2).some((b) => b.id === id3);
261311
+ UNSET = { scaleX: 1, scaleY: 1, anchorX: 0.5, anchorY: 0.5, rotation: 0, translateX: 0, translateY: 0, z: 0, rotateX: 0, rotateY: 0 };
261190
261312
  same2 = (a, b) => a === b || typeof a === "object" && typeof b === "object" && JSON.stringify(a) === JSON.stringify(b);
261191
261313
  ownOverrides = (node2, id3) => Object.keys(node2?.responsive?.[id3] ?? {});
261192
261314
  isScreen = (doc2, n) => n.type === "FRAME" && doc2.nodes[n.parentId ?? ""]?.type === "PAGE";
@@ -262989,7 +263111,6 @@ function planEntryAction({ home }) {
262989
263111
  var STORAGE_KEY2, useHyperyTeam, TEAM_PROMPT_ENABLED, TEAM_PROMPTED_KEY, PROMPTED_KEY, useTeamPrompt, started;
262990
263112
  var init_team = __esm({
262991
263113
  "lib/client/cloud/team.ts"() {
262992
- "use strict";
262993
263114
  init_esm();
262994
263115
  init_api4();
262995
263116
  init_session();
@@ -306672,7 +306793,6 @@ function decodeSource(key5, label3) {
306672
306793
  var DECODE_CACHE_BYTES, cache7, pending4, held, sizeOf, lengths, decodedLengths, DECODE_TIMEOUT_MS, decodedAudio, decodedSource, pcmOf;
306673
306794
  var init_audio_decode = __esm({
306674
306795
  "lib/motion/audio-decode.ts"() {
306675
- "use strict";
306676
306796
  init_media_store();
306677
306797
  init_audio_plan();
306678
306798
  init_time_stretch();
@@ -310059,7 +310179,6 @@ function importSVGFile(file2, document2, parentId) {
310059
310179
  var ENTITIES, decode3, I, mul2, apply2, scaleOf, NUM2, nums, near, NAMED, INHERITED, PRESENTATION, len2, CAP, JOIN, SKIPPED, refId;
310060
310180
  var init_svg_import = __esm({
310061
310181
  "lib/io/svg-import.ts"() {
310062
- "use strict";
310063
310182
  init_dist_node();
310064
310183
  init_types();
310065
310184
  ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\xA0" };
@@ -311452,6 +311571,7 @@ function startCloudSync() {
311452
311571
  var status, readOnlyDoc, activeLink, statusListeners, notice, urlArmed, opening, syncStatus, canEdit, uploaded, MEDIA_UPLOAD_PARALLEL, pushTimer, pushing, dirty, pendingMedia, collab, collabSession, collabAnonymous, uploadedThumbs, thumbCheckedAt, THUMB_EVERY_MS, THUMB_MIN_GAP_MS, putThumbnail, openingFiles, mediaHealed, started2;
311453
311572
  var init_sync2 = __esm({
311454
311573
  "lib/client/cloud/sync.ts"() {
311574
+ "use strict";
311455
311575
  init_paint_repair();
311456
311576
  init_migrate();
311457
311577
  init_view();
@@ -311721,7 +311841,7 @@ var init_text_measurer = __esm({
311721
311841
  const width = node2.textAutoResize === "WIDTH_AND_HEIGHT" ? Math.max(1, Math.ceil(measureAutoWidth(node2))) : node2.width;
311722
311842
  return {
311723
311843
  width,
311724
- height: Math.round(estimateTextHeight(characters, width, node2.fontFamily, node2.fontWeight, node2.fontSize, node2.lineHeight ?? "AUTO", node2.textAutoResize !== "WIDTH_AND_HEIGHT", node2.letterSpacing ?? 0))
311844
+ height: Math.round(estimateTextHeight(characters, width, node2.fontFamily, node2.fontWeight, node2.fontSize, node2.lineHeight ?? "AUTO", node2.textAutoResize !== "WIDTH_AND_HEIGHT", node2.letterSpacing ?? 0, textSetting(node2)))
311725
311845
  };
311726
311846
  };
311727
311847
  }
@@ -313504,7 +313624,7 @@ function layoutTextUncached(node2, atlas, cameraZoom = 1) {
313504
313624
  }
313505
313625
  }
313506
313626
  if (hyphenated) {
313507
- } else if (currentIndent + lineWidth2 + advance > maxWidth2 && lastSpaceIndex !== -1 && lineStartIndex < lastSpaceIndex) {
313627
+ } else if (!isSpace2 && currentIndent + lineWidth2 + advance > maxWidth2 && lastSpaceIndex !== -1 && lineStartIndex < lastSpaceIndex) {
313508
313628
  let wrappedLineHeight = 0;
313509
313629
  for (let j = lineStartIndex; j < lastSpaceIndex; j++) {
313510
313630
  const lh3 = resolvedStyles[j].lineHeight === "AUTO" ? resolvedStyles[j].fontSize * 1.2 : resolvedStyles[j].lineHeight;
@@ -317278,6 +317398,7 @@ async function selectionReferenceImages(doc2, selection, opts = {}) {
317278
317398
  var FIT_QUALITIES, FIT_SCALES, blobToDataUrl;
317279
317399
  var init_reference_images = __esm({
317280
317400
  "lib/ai/reference-images.ts"() {
317401
+ "use strict";
317281
317402
  init_export();
317282
317403
  init_selection_bounds();
317283
317404
  init_renderer_registry();
@@ -318120,7 +318241,6 @@ function agentToolList() {
318120
318241
  var AGENT_CAPABILITIES, legacyCommandId, legacyTools, blobToDataUrl3, provided, legacyTool, legacyToolList;
318121
318242
  var init_agent = __esm({
318122
318243
  "lib/commands/agent.ts"() {
318123
- "use strict";
318124
318244
  init_types5();
318125
318245
  init_registry();
318126
318246
  init_errors();
@@ -323380,6 +323500,11 @@ function flowChild(node2, parent) {
323380
323500
  if (cross2 === "HUG") style[crossProp] = null;
323381
323501
  if ((parent.counterAxisAlignItems ?? "MIN") === "MIN") style["align-self"] = "flex-start";
323382
323502
  }
323503
+ const ar = node2.aspectRatio;
323504
+ if (ar && ar > 0 && v !== "FILL") {
323505
+ style.height = null;
323506
+ style["aspect-ratio"] = String(Math.round(ar * 1e4) / 1e4);
323507
+ }
323383
323508
  return { style };
323384
323509
  }
323385
323510
  function constrained(kind, pos, size6, parentSize, start2, end, sizeProp) {
@@ -323987,6 +324112,7 @@ var tree_exports = {};
323987
324112
  __export(tree_exports, {
323988
324113
  DEFAULT_NODE_CAP: () => DEFAULT_NODE_CAP,
323989
324114
  EXPORT_NODE_CAP: () => EXPORT_NODE_CAP,
324115
+ breakpointPictures: () => breakpointPictures,
323990
324116
  buildPlainTree: () => buildPlainTree,
323991
324117
  buildWebTree: () => buildWebTree,
323992
324118
  flatten: () => flatten2,
@@ -324332,7 +324458,7 @@ function buildPlainTree(doc2, nodeId2, opts = {}, start2, hooks = {}) {
324332
324458
  }
324333
324459
  } else for (const child of childrenOf(node2, nodesNow)) {
324334
324460
  const built = build3(child, node2, kids3D, kidFold);
324335
- if (built) web.children.push(built);
324461
+ if (built) web.children.push(...opts.target === "site" ? breakpointPictures(doc2, child, built) : [built]);
324336
324462
  }
324337
324463
  }
324338
324464
  } finally {
@@ -324348,6 +324474,23 @@ function buildPlainTree(doc2, nodeId2, opts = {}, start2, hooks = {}) {
324348
324474
  if (!root2) throw new Error("Node cap of 0");
324349
324475
  return { root: root2, truncated, ...folds ? { rules: foldRules(folds) } : {}, ...tokens?.vars.length ? { tokens } : {} };
324350
324476
  }
324477
+ function breakpointPictures(doc2, raw, built) {
324478
+ const r = raw.responsive;
324479
+ if (!built.asset || built.island || !r) return [built];
324480
+ const order2 = breakpointsOf(doc2).map((b) => b.id);
324481
+ const redrawn = order2.filter((b) => r[b]?.paths);
324482
+ if (!redrawn.length) return [built];
324483
+ const paths = JSON.stringify(raw.paths);
324484
+ const shown2 = [...redrawn].reverse().find((b) => JSON.stringify(r[b].paths) === paths) ?? null;
324485
+ const hide2 = (n) => ({ ...n, style: { ...n.style, display: "none" } });
324486
+ return [
324487
+ shown2 ? hide2(built) : built,
324488
+ ...redrawn.map((b) => {
324489
+ const alt = { ...built, id: `${built.id}@${b}`, children: [], asset: { kind: built.asset.kind, reason: built.asset.reason, of: built.asset.of ?? built.id, breakpoint: b } };
324490
+ return b === shown2 ? alt : hide2(alt);
324491
+ })
324492
+ ];
324493
+ }
324351
324494
  function childLookup(doc2, id3) {
324352
324495
  const at4 = id3.lastIndexOf("::");
324353
324496
  const host = at4 > 0 ? id3.slice(0, at4) : "";
@@ -324364,6 +324507,7 @@ var init_tree = __esm({
324364
324507
  "lib/io/web/tree.ts"() {
324365
324508
  "use strict";
324366
324509
  init_pixel_art();
324510
+ init_breakpoints();
324367
324511
  init_instance_layout();
324368
324512
  init_text_styles();
324369
324513
  init_variables();
@@ -327984,7 +328128,7 @@ var init_package = __esm({
327984
328128
  "packages/runtime/package.json"() {
327985
328129
  package_default = {
327986
328130
  name: "@popcraft/runtime",
327987
- version: "0.47.0",
328131
+ version: "0.48.0",
327988
328132
  description: "What code exported from PopCraft shares: the meaning of expressions (operators and functions) and, as it grows, the parts of a design's behaviour that are common code rather than generated React.",
327989
328133
  license: "MIT",
327990
328134
  homepage: "https://popcraft.app/docs/exporting/nextjs",
@@ -353226,7 +353370,7 @@ async function resolveWebAssets(tree, resolvers) {
353226
353370
  const render2 = node2.asset.kind === "png" ? resolvers.renderPNG : resolvers.renderSVG;
353227
353371
  if (!render2) return;
353228
353372
  try {
353229
- const out = await render2(node2.asset.of ?? node2.id, node2.asset.self ? { self: true } : void 0);
353373
+ const out = await render2(node2.asset.of ?? node2.id, node2.asset.self || node2.asset.breakpoint ? { ...node2.asset.self ? { self: true } : {}, ...node2.asset.breakpoint ? { breakpoint: node2.asset.breakpoint } : {} } : void 0);
353230
353374
  if (!out) return;
353231
353375
  if (node2.asset.kind === "png") node2.asset.pngDataUrl = out;
353232
353376
  else node2.asset.svg = out;
@@ -353490,9 +353634,10 @@ __export(resolvers_exports, {
353490
353634
  });
353491
353635
  function rendererAssetResolvers(source3, scale = 2) {
353492
353636
  const styles = source3.styles;
353493
- const shownDoc = (id3) => {
353494
- const n = source3.nodes[id3];
353495
- return n && n.visible === false ? { ...source3, nodes: { ...source3.nodes, [id3]: { ...n, visible: true } } } : source3;
353637
+ const shownDoc = (id3, breakpoint2) => {
353638
+ const at4 = breakpoint2 ? breakpointDocument(source3, breakpoint2) : source3;
353639
+ const n = at4.nodes[id3];
353640
+ return n && n.visible === false ? { ...at4, nodes: { ...at4.nodes, [id3]: { ...n, visible: true } } } : at4;
353496
353641
  };
353497
353642
  const alone = (doc2, id3) => {
353498
353643
  const n = doc2.nodes[id3];
@@ -353501,10 +353646,10 @@ function rendererAssetResolvers(source3, scale = 2) {
353501
353646
  return { doc: { ...doc2, nodes: { ...doc2.nodes, [id3]: node2 } }, node: node2 };
353502
353647
  };
353503
353648
  return {
353504
- renderSVG: async (id3) => {
353649
+ renderSVG: async (id3, opts) => {
353505
353650
  const renderer = getActiveRenderer();
353506
353651
  if (!renderer) return null;
353507
- const doc2 = shownDoc(id3);
353652
+ const doc2 = shownDoc(id3, opts?.breakpoint);
353508
353653
  const node2 = nodeFor(doc2, id3);
353509
353654
  const svg = node2 ? await exportNodeToSVG(renderer, node2, doc2, styles) : null;
353510
353655
  if (svg) return svg;
@@ -353514,7 +353659,7 @@ function rendererAssetResolvers(source3, scale = 2) {
353514
353659
  renderPNG: async (id3, opts) => {
353515
353660
  const renderer = getActiveRenderer();
353516
353661
  if (!renderer) return null;
353517
- const doc2 = shownDoc(id3);
353662
+ const doc2 = shownDoc(id3, opts?.breakpoint);
353518
353663
  if (opts?.self) {
353519
353664
  const a = alone(doc2, id3);
353520
353665
  const blob = a ? await exportNodeToBlob(renderer, a.node, { format: "PNG", scale, suffix: "" }, a.doc, styles) : null;
@@ -353533,6 +353678,7 @@ var init_resolvers = __esm({
353533
353678
  "lib/io/web/resolvers.ts"() {
353534
353679
  "use strict";
353535
353680
  init_scene_graph();
353681
+ init_breakpoints();
353536
353682
  init_renderer_registry();
353537
353683
  init_export();
353538
353684
  init_svg_export();
@@ -361206,7 +361352,6 @@ function publishSite(key5, files, opts = {}) {
361206
361352
  var siteStatus, setSiteVersion, workspaceSites, deleteSite, renameSite;
361207
361353
  var init_sites = __esm({
361208
361354
  "lib/client/cloud/sites.ts"() {
361209
- "use strict";
361210
361355
  init_files();
361211
361356
  init_upload2();
361212
361357
  init_api4();
@@ -404051,7 +404196,14 @@ Set the timeline first, then add moves. Times are ms on the frame's timeline.
404051
404196
  - On the phone \`webResponsive\` folds the nav's links and the layer named "Brand name" away, leaving the logo tile and
404052
404197
  the button; to keep the name, call the brand text something else. Keep body text 14 px or more on the phone,
404053
404198
  recompose the hero rather than cropping it, and keep every size under 8,192 px tall. Leave room in text boxes: a line that only just fits wraps differently
404054
- in a browser.`;
404199
+ in a browser.
404200
+ - A content picture on a page is \`k.contentImage(parent, url, width, { ratio, name, radius })\`: whole, at its own
404201
+ proportions (\`ratio\`, width over height, for a picture of your own), and its height follows the width its column
404202
+ gives it at every breakpoint and in the browser. Never crop a content picture to a fixed-height band: on a phone it
404203
+ loses its sides. Only decoration behind words may cover-crop (\`k.image\`), marked \`semantics: { hidden: true }\`.
404204
+ - What a breakpoint carries from a size: layout, size, visibility, type, and a drawing's scale, turn, offset and
404205
+ redrawn paths. Scale scenery down for the phone (\`scaleX\`/\`scaleY\` with \`anchorX: 0, anchorY: 0\`) rather than
404206
+ cutting it with a smaller frame. Ambient motion is not carried: it plays the desktop's keys at every width.`;
404055
404207
  EDITING = `## Editing designs that already exist
404056
404208
 
404057
404209
  Use \`run_script\` (from a terminal \`popcraft run <file> run_script --code @fix.js\`) to change many layers in one call.
@@ -413557,7 +413709,7 @@ init_resources();
413557
413709
  init_desktop();
413558
413710
  init_hypery();
413559
413711
  import { createInterface } from "node:readline";
413560
- var VERSION2 = "0.47.0";
413712
+ var VERSION2 = "0.48.0";
413561
413713
  var HELP = `popcraft ${VERSION2} \u2014 the PopCraft command line
413562
413714
 
413563
413715
  Files (headless, on .popcraft files):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popcraft/cli",
3
- "version": "0.47.0",
3
+ "version": "0.48.0",
4
4
  "description": "The PopCraft command line: edit .popcraft files headlessly, call the REST API, publish to the marketplace and manage the desktop app's local plugins, themes, fonts, skills and agents.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://popcraft.app/docs/api/cli",
@@ -34,7 +34,7 @@
34
34
  "esbuild": "0.28.2"
35
35
  },
36
36
  "dependencies": {
37
- "@popcraft/kit": "0.47.0"
37
+ "@popcraft/kit": "0.48.0"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "node build.mjs",