paperlab 0.4.0 → 0.5.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/index.cjs CHANGED
@@ -36,17 +36,21 @@ __export(index_exports, {
36
36
  LightRig: () => LightRig,
37
37
  PARITY_EPSILON: () => PARITY_EPSILON,
38
38
  Paper: () => Paper,
39
+ PaperBackdrop: () => PaperBackdrop,
39
40
  PaperField: () => PaperField,
40
41
  PaperFieldMesh: () => PaperFieldMesh,
41
42
  PaperLighting: () => PaperLighting,
42
43
  PaperMesh: () => PaperMesh,
43
44
  SAG_TOL: () => SAG_TOL,
45
+ backdropSchema: () => backdropSchema,
44
46
  behaviorConfigSchema: () => behaviorConfigSchema,
45
47
  buildAgentPayload: () => buildAgentPayload,
46
48
  buildFieldAgentPayload: () => buildFieldAgentPayload,
47
49
  buildFieldComponentSource: () => buildFieldComponentSource,
48
50
  buildJsxSnippet: () => buildJsxSnippet,
49
51
  clothConfigSchema: () => clothConfigSchema,
52
+ contentNames: () => contentNames,
53
+ contentSchemaFor: () => contentSchemaFor,
50
54
  coreStateNames: () => coreStateNames,
51
55
  describeConfig: () => describeConfig,
52
56
  describeFieldConfig: () => describeFieldConfig,
@@ -83,6 +87,7 @@ __export(index_exports, {
83
87
  resolveLighting: () => resolveLighting,
84
88
  resolveStateConfig: () => resolveStateConfig,
85
89
  runParityHarness: () => runParityHarness,
90
+ sceneSchema: () => sceneSchema,
86
91
  segmentsForArc: () => segmentsForArc,
87
92
  segmentsForSine: () => segmentsForSine,
88
93
  serializePreset: () => serializePreset,
@@ -96,13 +101,14 @@ __export(index_exports, {
96
101
  unregisterPreset: () => unregisterPreset,
97
102
  usePaperStates: () => usePaperStates,
98
103
  usePrefersReducedMotion: () => usePrefersReducedMotion,
104
+ washSchema: () => washSchema,
99
105
  wrapLines: () => wrapLines
100
106
  });
101
107
  module.exports = __toCommonJS(index_exports);
102
108
 
103
109
  // src/Paper.tsx
104
- var import_fiber3 = require("@react-three/fiber");
105
- var import_react9 = require("react");
110
+ var import_fiber4 = require("@react-three/fiber");
111
+ var import_react10 = require("react");
106
112
 
107
113
  // src/PaperMesh.tsx
108
114
  var THREE7 = __toESM(require("three"), 1);
@@ -1023,6 +1029,31 @@ var stockNames = [
1023
1029
  "sticker"
1024
1030
  ];
1025
1031
  var stockSchema = import_zod14.z.enum(stockNames);
1032
+ var washSchema = import_zod14.z.object({
1033
+ /** The first pigment. */
1034
+ color: import_zod14.z.string().default("#4a5b8c").describe("color"),
1035
+ /** The second. Blooms alternate, and overlaps multiply into a third. */
1036
+ secondary: import_zod14.z.string().default("#b06a6a").describe("color"),
1037
+ /** How many pools of colour. */
1038
+ blooms: import_zod14.z.number().int().min(1).max(24).default(7),
1039
+ /** How far a pool runs before it dries — its size against the sheet. */
1040
+ spread: import_zod14.z.number().min(0.1).max(1).default(0.7),
1041
+ /** Softness of the wet edge. 0 is a hard cut, 1 is a pool still moving. */
1042
+ bleed: import_zod14.z.number().min(0).max(1).default(0.5),
1043
+ /** How much pigment is in the water. */
1044
+ intensity: import_zod14.z.number().min(0).max(1).default(0.55),
1045
+ /**
1046
+ * Edge darkening — the ring of pigment left where a pool dried.
1047
+ *
1048
+ * The signature of the medium, and the one thing a plain gradient cannot
1049
+ * fake. Without it a wash reads as an airbrush.
1050
+ */
1051
+ edge: import_zod14.z.number().min(0).max(1).default(0.6),
1052
+ /** Pigment settling into the tooth of the paper. */
1053
+ granulation: import_zod14.z.number().min(0).max(1).default(0.35),
1054
+ /** Fixed so a preset paints the same wash every time. */
1055
+ seed: import_zod14.z.number().int().min(0).max(99).default(0)
1056
+ });
1026
1057
  var blankContentBase = import_zod14.z.object({
1027
1058
  type: import_zod14.z.literal("blank")
1028
1059
  });
@@ -1047,7 +1078,7 @@ var textContentBase = import_zod14.z.object({
1047
1078
  /** px at texture resolution (long edge = 1024 logical px before DPR). */
1048
1079
  size: import_zod14.z.number().min(8).max(256).default(44),
1049
1080
  weight: import_zod14.z.number().min(100).max(900).default(400),
1050
- color: import_zod14.z.string().default("#2b2620"),
1081
+ color: import_zod14.z.string().default("#2b2620").describe("color"),
1051
1082
  align: import_zod14.z.enum(["left", "center", "right"]).default("left"),
1052
1083
  /** Fraction of the short edge. */
1053
1084
  padding: import_zod14.z.number().min(0).max(0.4).default(0.09),
@@ -1097,7 +1128,7 @@ var cardContentBase = import_zod14.z.object({
1097
1128
  * was cropped rather than as a card that was set.
1098
1129
  */
1099
1130
  size: import_zod14.z.number().min(8).max(256).default(58),
1100
- color: import_zod14.z.string().default("#2b2620"),
1131
+ color: import_zod14.z.string().default("#2b2620").describe("color"),
1101
1132
  align: import_zod14.z.enum(["left", "center"]).default("left"),
1102
1133
  padding: import_zod14.z.number().min(0).max(0.4).default(0.1)
1103
1134
  });
@@ -1116,14 +1147,15 @@ var receiptContentBase = import_zod14.z.object({
1116
1147
  timestamp: import_zod14.z.string().optional(),
1117
1148
  footer: import_zod14.z.string().default("KEEP FOR YOUR RECORDS")
1118
1149
  });
1150
+ var withWash = { wash: washSchema.optional() };
1119
1151
  var backContentSchema = import_zod14.z.discriminatedUnion("type", [
1120
- blankContentBase,
1121
- imageContentBase,
1122
- textContentBase,
1123
- cardContentBase,
1124
- receiptContentBase
1152
+ blankContentBase.extend(withWash),
1153
+ imageContentBase.extend(withWash),
1154
+ textContentBase.extend(withWash),
1155
+ cardContentBase.extend(withWash),
1156
+ receiptContentBase.extend(withWash)
1125
1157
  ]);
1126
- var withBack = { back: backContentSchema.optional() };
1158
+ var withBack = { back: backContentSchema.optional(), ...withWash };
1127
1159
  var blankContentSchema = blankContentBase.extend(withBack);
1128
1160
  var imageContentSchema = imageContentBase.extend(withBack);
1129
1161
  var textContentSchema = textContentBase.extend(withBack);
@@ -1136,6 +1168,14 @@ var contentSchema = import_zod14.z.discriminatedUnion("type", [
1136
1168
  cardContentSchema,
1137
1169
  receiptContentSchema
1138
1170
  ]);
1171
+ var contentNames = contentSchema.options.map(
1172
+ (option) => option.shape.type.value
1173
+ );
1174
+ function contentSchemaFor(type) {
1175
+ const option = contentSchema.options.find((candidate) => candidate.shape.type.value === type);
1176
+ if (!option) throw new Error(`Unknown content type: ${type}`);
1177
+ return option;
1178
+ }
1139
1179
  var paperEdges = ["top", "right", "bottom", "left"];
1140
1180
  var surfaceSchema = import_zod14.z.object({
1141
1181
  /** Paper fiber noise, 0..1. */
@@ -1223,8 +1263,70 @@ var lightingNames = [
1223
1263
  "lightbox"
1224
1264
  ];
1225
1265
  var filmNames = ["agx", "neutral", "filmic"];
1266
+ var lightSchema = import_zod14.z.object({
1267
+ /** Tone-mapping exposure — the stop the whole picture is printed at. */
1268
+ exposure: import_zod14.z.number().min(0.1).max(4).optional(),
1269
+ /**
1270
+ * The tone curve — the film, where `exposure` is the stop.
1271
+ *
1272
+ * `filmic` is ACES, which is what every preset used to be pinned to and is
1273
+ * kept so a scene tuned against it can say so. On near-white paper it is
1274
+ * the wrong film: it desaturates and drags bright neutrals toward
1275
+ * yellow-green, which is the sepia cast a lit sheet used to pick up.
1276
+ */
1277
+ film: import_zod14.z.enum(filmNames).optional(),
1278
+ /** Key light strength. */
1279
+ key: import_zod14.z.number().min(0).max(12).optional(),
1280
+ /** Key light colour. */
1281
+ color: import_zod14.z.string().optional().describe("color"),
1282
+ /**
1283
+ * Where the key stands, degrees around the vertical. 0° is straight in
1284
+ * front of the paper (+Z, beside the camera), 90° is off to the right,
1285
+ * and ±180° is directly behind it — which is where `nave` puts it, and
1286
+ * why that preset is carried by light coming THROUGH the paper.
1287
+ */
1288
+ direction: import_zod14.z.number().min(-180).max(180).optional(),
1289
+ /** How high the key stands, degrees above the horizon. */
1290
+ height: import_zod14.z.number().min(-30).max(89).optional(),
1291
+ /** Flat fill from every direction at once. Cheap, and it kills form — reach for `studio` first. */
1292
+ ambient: import_zod14.z.number().min(0).max(2).optional(),
1293
+ /** The room's own light: an environment map built from `sky`. Directional fill, and the only thing paper's sheen has to reflect. */
1294
+ studio: import_zod14.z.number().min(0).max(3).optional(),
1295
+ /** Distance haze, as a multiple of the preset's. 0 clears the air entirely; 2 halves the distance you can see. */
1296
+ haze: import_zod14.z.number().min(0).max(3).optional()
1297
+ });
1298
+ var backdropSchema = import_zod14.z.object({
1299
+ /** Behind everything, and behind the picture where it does not reach. */
1300
+ color: import_zod14.z.string().default("#171717").describe("color"),
1301
+ /** A URL, or an uploaded picture. Empty is the colour on its own. */
1302
+ image: import_zod14.z.string().default(""),
1303
+ fit: import_zod14.z.enum(["cover", "contain"]).default("cover"),
1304
+ /**
1305
+ * Toward the colour, so the paper stays the subject.
1306
+ *
1307
+ * A backdrop at full strength competes with the sheet in front of it —
1308
+ * which is what a real photographer solves by putting the background out
1309
+ * of the light, and what this solves by mixing it back toward the ground
1310
+ * it sits on.
1311
+ */
1312
+ fade: import_zod14.z.number().min(0).max(1).default(0.25),
1313
+ /** Out of focus, for the same reason. */
1314
+ blur: import_zod14.z.number().min(0).max(1).default(0.2)
1315
+ });
1226
1316
  var sceneSchema = import_zod14.z.object({
1227
- lighting: import_zod14.z.enum(lightingNames).default("studio")
1317
+ lighting: import_zod14.z.enum(lightingNames).default("studio"),
1318
+ /** What is behind the sheet. Unset leaves the canvas alone. */
1319
+ backdrop: backdropSchema.optional(),
1320
+ /**
1321
+ * Overrides on the named preset — the same authorable half stage mode has
1322
+ * always had, and which a lone sheet had no way to reach.
1323
+ *
1324
+ * A preset was the starting point everywhere EXCEPT here: a stage could be
1325
+ * "nave, but the sun is lower", and a `<Paper>` could only be one of seven
1326
+ * rigs exactly as shipped. `<PaperLighting>` has taken these overrides all
1327
+ * along; nothing was passing them.
1328
+ */
1329
+ light: lightSchema.default({})
1228
1330
  });
1229
1331
  var coreStateNames = ["rest", "hover", "pressed", "picked", "placed"];
1230
1332
  var isStateName = (s) => coreStateNames.includes(s) || s.startsWith("custom:");
@@ -1357,11 +1459,11 @@ function computeSheetNormals(geometry) {
1357
1459
  for (let i = 0, l = nrm.length; i < l; i += 3) {
1358
1460
  const x = nrm[i];
1359
1461
  const y = nrm[i + 1];
1360
- const z26 = nrm[i + 2];
1361
- const len = Math.sqrt(x * x + y * y + z26 * z26) || 1;
1462
+ const z25 = nrm[i + 2];
1463
+ const len = Math.sqrt(x * x + y * y + z25 * z25) || 1;
1362
1464
  nrm[i] = x / len;
1363
1465
  nrm[i + 1] = y / len;
1364
- nrm[i + 2] = z26 / len;
1466
+ nrm[i + 2] = z25 / len;
1365
1467
  }
1366
1468
  normalAttr.needsUpdate = true;
1367
1469
  }
@@ -1545,6 +1647,33 @@ var builtins = {
1545
1647
  behavior: { type: "letter-fold", progress: 0.4, crease: 0.3 },
1546
1648
  surface: { creaseLines: { angle: 0, positions: [1 / 3, 2 / 3], strength: 0.5 } }
1547
1649
  },
1650
+ /**
1651
+ * The wash, shown rather than described.
1652
+ *
1653
+ * A `wash` is a field on every content type, which means it is reachable
1654
+ * from any preset and discoverable from none — a toggle three folders down
1655
+ * is not an argument for the feature. This is the argument: type set over
1656
+ * paint, on cotton, deckled, with the paper still showing through both.
1657
+ */
1658
+ "washed-letter": {
1659
+ meta: { name: "Washed letter", tags: ["wash", "text"] },
1660
+ // Printer stock, not vellum: vellum is translucent and takes the room's
1661
+ // grey through the back of the sheet, which turns a wash the colour of
1662
+ // dishwater. Pigment needs something white behind it.
1663
+ sheet: { width: 1.05, height: 1.45 },
1664
+ stock: "printer",
1665
+ content: {
1666
+ type: "text",
1667
+ text: "Painted first,\nwritten after.",
1668
+ font: 'Georgia, "Times New Roman", serif',
1669
+ size: 52,
1670
+ valign: "center",
1671
+ align: "center",
1672
+ wash: { color: "#5b6f9a", secondary: "#c08a86", blooms: 6, spread: 0.85, intensity: 0.5, seed: 3 }
1673
+ },
1674
+ behavior: { type: "peel", progress: 0.12, corner: "bottom-right", radius: 0.3 },
1675
+ surface: { grain: 0.35, deckle: { edges: ["bottom", "right"], roughness: 0.45 } }
1676
+ },
1548
1677
  "vintage-note": {
1549
1678
  meta: { name: "Vintage note", tags: ["aging", "text"] },
1550
1679
  sheet: { width: 1.1, height: 1.4 },
@@ -1985,6 +2114,109 @@ function paintCard(ctx, w, h, content, stock, dpr) {
1985
2114
  }
1986
2115
  }
1987
2116
 
2117
+ // src/content/wash.ts
2118
+ function mulberry32(seed) {
2119
+ let a = seed * 1831565813 + 2654435769;
2120
+ return () => {
2121
+ a |= 0;
2122
+ a = a + 1831565813 | 0;
2123
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
2124
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
2125
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
2126
+ };
2127
+ }
2128
+ function rgba(hex, alpha) {
2129
+ const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
2130
+ const n = m ? Number.parseInt(m[1], 16) : 8421504;
2131
+ return `rgba(${n >> 16 & 255}, ${n >> 8 & 255}, ${n & 255}, ${Math.max(0, Math.min(1, alpha))})`;
2132
+ }
2133
+ function bloomShape(rng) {
2134
+ return {
2135
+ phase: [rng() * Math.PI * 2, rng() * Math.PI * 2, rng() * Math.PI * 2],
2136
+ amp: [0.18 + rng() * 0.1, 0.09 + rng() * 0.06, 0.05 + rng() * 0.04]
2137
+ };
2138
+ }
2139
+ function bloomPath(ctx, cx, cy, radius, shape, from = 0, to = 1) {
2140
+ const { phase, amp } = shape;
2141
+ const STEPS = Math.max(6, Math.round(72 * (to - from)));
2142
+ ctx.beginPath();
2143
+ for (let i = 0; i <= STEPS; i++) {
2144
+ const t = (from + (to - from) * i / STEPS) * Math.PI * 2;
2145
+ const wobble = amp[0] * Math.sin(2 * t + phase[0]) + amp[1] * Math.sin(3 * t + phase[1]) + amp[2] * Math.sin(5 * t + phase[2]);
2146
+ const r = radius * (1 + wobble);
2147
+ const x = cx + Math.cos(t) * r;
2148
+ const y = cy + Math.sin(t) * r;
2149
+ if (i === 0) ctx.moveTo(x, y);
2150
+ else ctx.lineTo(x, y);
2151
+ }
2152
+ if (from === 0 && to === 1) ctx.closePath();
2153
+ }
2154
+ function canBlur(ctx) {
2155
+ if (!("filter" in ctx)) return false;
2156
+ const before = ctx.filter;
2157
+ try {
2158
+ ctx.filter = "blur(2px)";
2159
+ const worked = ctx.filter !== "none" && ctx.filter !== before;
2160
+ ctx.filter = before;
2161
+ return worked;
2162
+ } catch {
2163
+ return false;
2164
+ }
2165
+ }
2166
+ function paintWash(ctx, w, h, wash) {
2167
+ const rng = mulberry32(wash.seed + 1);
2168
+ const short = Math.min(w, h);
2169
+ const blurs = canBlur(ctx);
2170
+ ctx.save();
2171
+ ctx.globalCompositeOperation = "multiply";
2172
+ for (let i = 0; i < wash.blooms; i++) {
2173
+ const pigment = i % 2 === 0 ? wash.color : wash.secondary;
2174
+ const cx = (rng() * 1.5 - 0.25) * w;
2175
+ const cy = (rng() * 1.5 - 0.25) * h;
2176
+ const radius = short * wash.spread * (0.28 + rng() * 0.34);
2177
+ const alpha = wash.intensity * (0.35 + rng() * 0.4);
2178
+ const shape = bloomShape(rng);
2179
+ ctx.save();
2180
+ if (blurs) ctx.filter = `blur(${(2 + wash.bleed * 26) * (0.6 + rng() * 0.8)}px)`;
2181
+ const gx = cx + (rng() - 0.5) * radius * 0.5;
2182
+ const gy = cy + (rng() - 0.5) * radius * 0.5;
2183
+ const grad = ctx.createRadialGradient(gx, gy, radius * 0.05, gx, gy, radius * 1.15);
2184
+ grad.addColorStop(0, rgba(pigment, alpha * 0.55));
2185
+ grad.addColorStop(0.62, rgba(pigment, alpha));
2186
+ grad.addColorStop(1, rgba(pigment, alpha * 0.75));
2187
+ ctx.fillStyle = grad;
2188
+ bloomPath(ctx, cx, cy, radius, shape);
2189
+ ctx.fill();
2190
+ if (wash.edge > 0) {
2191
+ if (blurs) ctx.filter = `blur(${(1.5 + wash.bleed * 9) * (0.6 + rng() * 0.6)}px)`;
2192
+ ctx.lineWidth = Math.max(1, radius * (0.03 + wash.edge * 0.05));
2193
+ const arcs = 5;
2194
+ const overlap = 0.4 / arcs;
2195
+ for (let a = 0; a < arcs; a++) {
2196
+ ctx.strokeStyle = rgba(pigment, alpha * wash.edge * (0.15 + rng() * 1.05));
2197
+ bloomPath(ctx, cx, cy, radius, shape, a / arcs, (a + 1) / arcs + overlap);
2198
+ ctx.stroke();
2199
+ }
2200
+ }
2201
+ ctx.restore();
2202
+ if (wash.granulation > 0) {
2203
+ ctx.save();
2204
+ bloomPath(ctx, cx, cy, radius, shape);
2205
+ ctx.clip();
2206
+ const grains = Math.round(wash.granulation * 900);
2207
+ ctx.fillStyle = rgba(pigment, wash.granulation * alpha * 0.5);
2208
+ for (let g = 0; g < grains; g++) {
2209
+ const a = rng() * Math.PI * 2;
2210
+ const d = Math.sqrt(rng()) * radius;
2211
+ const size = 1 + rng() * (short * 4e-3);
2212
+ ctx.fillRect(cx + Math.cos(a) * d, cy + Math.sin(a) * d, size, size);
2213
+ }
2214
+ ctx.restore();
2215
+ }
2216
+ }
2217
+ ctx.restore();
2218
+ }
2219
+
1988
2220
  // src/content/texture.ts
1989
2221
  var LONG_EDGE = 1024;
1990
2222
  var DPR = 2;
@@ -2035,6 +2267,7 @@ function renderContentToCanvas(content, sheet2, stock, image) {
2035
2267
  canvas.height = h;
2036
2268
  const ctx = canvas.getContext("2d");
2037
2269
  paintBackground(ctx, w, h, stock);
2270
+ if (content.wash) paintWash(ctx, w, h, content.wash);
2038
2271
  if (content.type === "image" && image && content.src) paintImage(ctx, w, h, image, content.fit);
2039
2272
  if (content.type === "text") paintText(ctx, w, h, content, stock);
2040
2273
  if (content.type === "receipt") paintReceipt(ctx, w, h, content, stock);
@@ -2907,13 +3140,13 @@ var ClothSim = class {
2907
3140
  this.stillFrames = 0;
2908
3141
  }
2909
3142
  /** Nearest particle to a local-space point — the grab interface. */
2910
- grabNearest(x, y, z26) {
3143
+ grabNearest(x, y, z25) {
2911
3144
  let best = -1;
2912
3145
  let bestDist = Infinity;
2913
3146
  for (let i = 0; i < this.count; i++) {
2914
3147
  const dx = this.positions[i * 3] - x;
2915
3148
  const dy = this.positions[i * 3 + 1] - y;
2916
- const dz = this.positions[i * 3 + 2] - z26;
3149
+ const dz = this.positions[i * 3 + 2] - z25;
2917
3150
  const d = dx * dx + dy * dy + dz * dz;
2918
3151
  if (d < bestDist) {
2919
3152
  bestDist = d;
@@ -2924,15 +3157,15 @@ var ClothSim = class {
2924
3157
  this.wake();
2925
3158
  return best;
2926
3159
  }
2927
- moveGrab(x, y, z26) {
3160
+ moveGrab(x, y, z25) {
2928
3161
  if (this.grabbedIndex < 0) return;
2929
3162
  const i3 = this.grabbedIndex * 3;
2930
3163
  this.positions[i3] = x;
2931
3164
  this.positions[i3 + 1] = y;
2932
- this.positions[i3 + 2] = z26;
3165
+ this.positions[i3 + 2] = z25;
2933
3166
  this.prev[i3] = x;
2934
3167
  this.prev[i3 + 1] = y;
2935
- this.prev[i3 + 2] = z26;
3168
+ this.prev[i3 + 2] = z25;
2936
3169
  this.wake();
2937
3170
  }
2938
3171
  release() {
@@ -2968,19 +3201,19 @@ var ClothSim = class {
2968
3201
  }
2969
3202
  const x = p[i3];
2970
3203
  const y = p[i3 + 1];
2971
- const z26 = p[i3 + 2];
3204
+ const z25 = p[i3 + 2];
2972
3205
  const gust2 = wind * (0.55 + 0.45 * Math.sin(this.time * 1.7 + x * 2.1 + y * 1.3)) * 0.9;
2973
3206
  const ax = gust2 * 0.25;
2974
3207
  const az = gust2;
2975
3208
  const vx = (x - this.prev[i3]) * damping;
2976
3209
  const vy = (y - this.prev[i3 + 1]) * damping;
2977
- const vz = (z26 - this.prev[i3 + 2]) * damping;
3210
+ const vz = (z25 - this.prev[i3 + 2]) * damping;
2978
3211
  this.prev[i3] = x;
2979
3212
  this.prev[i3 + 1] = y;
2980
- this.prev[i3 + 2] = z26;
3213
+ this.prev[i3 + 2] = z25;
2981
3214
  p[i3] = x + vx + ax * dt2;
2982
3215
  p[i3 + 1] = y + vy - gravity * 3.2 * dt2;
2983
- p[i3 + 2] = z26 + vz + az * dt2;
3216
+ p[i3 + 2] = z25 + vz + az * dt2;
2984
3217
  maxTravel = Math.max(maxTravel, vx * vx + vy * vy + vz * vz);
2985
3218
  }
2986
3219
  for (let iter = 0; iter < SOLVER_ITERATIONS; iter++) {
@@ -3037,7 +3270,6 @@ var THREE5 = __toESM(require("three"), 1);
3037
3270
  var THREE4 = __toESM(require("three"), 1);
3038
3271
 
3039
3272
  // src/scene/lighting.ts
3040
- var import_zod22 = require("zod");
3041
3273
  var lightingPresets = {
3042
3274
  studio: {
3043
3275
  id: "studio",
@@ -3198,46 +3430,14 @@ var lightingPresets = {
3198
3430
  function getLightingPreset(name) {
3199
3431
  return lightingPresets[name];
3200
3432
  }
3201
- var lightSchema = import_zod22.z.object({
3202
- /** Tone-mapping exposure — the stop the whole picture is printed at. */
3203
- exposure: import_zod22.z.number().min(0.1).max(4).optional(),
3204
- /**
3205
- * The tone curve — the film, where `exposure` is the stop.
3206
- *
3207
- * `filmic` is ACES, which is what every preset used to be pinned to and is
3208
- * kept so a scene tuned against it can say so. On near-white paper it is
3209
- * the wrong film: it desaturates and drags bright neutrals toward
3210
- * yellow-green, which is the sepia cast a lit sheet used to pick up.
3211
- */
3212
- film: import_zod22.z.enum(filmNames).optional(),
3213
- /** Key light strength. */
3214
- key: import_zod22.z.number().min(0).max(12).optional(),
3215
- /** Key light colour. */
3216
- color: import_zod22.z.string().optional(),
3217
- /**
3218
- * Where the key stands, degrees around the vertical. 0° is straight in
3219
- * front of the paper (+Z, beside the camera), 90° is off to the right,
3220
- * and ±180° is directly behind it — which is where `nave` puts it, and
3221
- * why that preset is carried by light coming THROUGH the paper.
3222
- */
3223
- direction: import_zod22.z.number().min(-180).max(180).optional(),
3224
- /** How high the key stands, degrees above the horizon. */
3225
- height: import_zod22.z.number().min(-30).max(89).optional(),
3226
- /** Flat fill from every direction at once. Cheap, and it kills form — reach for `studio` first. */
3227
- ambient: import_zod22.z.number().min(0).max(2).optional(),
3228
- /** The room's own light: an environment map built from `sky`. Directional fill, and the only thing paper's sheen has to reflect. */
3229
- studio: import_zod22.z.number().min(0).max(3).optional(),
3230
- /** Distance haze, as a multiple of the preset's. 0 clears the air entirely; 2 halves the distance you can see. */
3231
- haze: import_zod22.z.number().min(0).max(3).optional()
3232
- });
3233
3433
  var DEG6 = 180 / Math.PI;
3234
3434
  function lightAngles(position) {
3235
- const [x, y, z26] = position;
3236
- const distance = Math.hypot(x, y, z26);
3435
+ const [x, y, z25] = position;
3436
+ const distance = Math.hypot(x, y, z25);
3237
3437
  if (distance < 1e-9) return { azimuth: 0, elevation: 90, distance: 0 };
3238
- const ground = Math.hypot(x, z26);
3438
+ const ground = Math.hypot(x, z25);
3239
3439
  return {
3240
- azimuth: ground < 1e-9 ? 0 : Math.atan2(x, z26) * DEG6,
3440
+ azimuth: ground < 1e-9 ? 0 : Math.atan2(x, z25) * DEG6,
3241
3441
  elevation: Math.atan2(y, ground) * DEG6,
3242
3442
  distance
3243
3443
  };
@@ -3335,8 +3535,8 @@ vec3 plTransmission(vec3 inkFilter) {
3335
3535
  );
3336
3536
  function translucencyValues(translucency, lighting) {
3337
3537
  const preset = typeof lighting === "string" ? getLightingPreset(lighting) : lighting;
3338
- const [x, y, z26] = preset.key.position;
3339
- const direction = new THREE4.Vector3(x, y, z26);
3538
+ const [x, y, z25] = preset.key.position;
3539
+ const direction = new THREE4.Vector3(x, y, z25);
3340
3540
  if (direction.lengthSq() < 1e-12) direction.set(0, 1, 0);
3341
3541
  direction.normalize();
3342
3542
  const color = new THREE4.Color(preset.key.color).multiplyScalar(preset.key.intensity * TRANSMISSION_GAIN);
@@ -4498,6 +4698,7 @@ var PaperMesh = (0, import_react7.forwardRef)(function PaperMesh2(props, ref) {
4498
4698
  props.interactive && !isCloth && behavior?.handles?.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
4499
4699
  "mesh",
4500
4700
  {
4701
+ userData: { paperlabChrome: true },
4501
4702
  ref: (m) => {
4502
4703
  handleRefs.current[i] = m;
4503
4704
  },
@@ -4641,7 +4842,7 @@ var toneMappings = {
4641
4842
  neutral: THREE9.NeutralToneMapping,
4642
4843
  filmic: THREE9.ACESFilmicToneMapping
4643
4844
  };
4644
- function mulberry32(seed) {
4845
+ function mulberry322(seed) {
4645
4846
  let a = seed;
4646
4847
  return () => {
4647
4848
  a |= 0;
@@ -4675,7 +4876,7 @@ function makeGoboTexture(kind) {
4675
4876
  }
4676
4877
  ctx.restore();
4677
4878
  } else {
4678
- const rand = mulberry32(7);
4879
+ const rand = mulberry322(7);
4679
4880
  for (const [count, radius, alpha] of [
4680
4881
  [26, 70, 0.75],
4681
4882
  [40, 38, 0.6],
@@ -4823,17 +5024,85 @@ function PaperLighting({
4823
5024
  ] });
4824
5025
  }
4825
5026
 
5027
+ // src/scene/backdrop.tsx
5028
+ var THREE10 = __toESM(require("three"), 1);
5029
+ var import_react9 = require("react");
5030
+ var import_fiber3 = require("@react-three/fiber");
5031
+ function PaperBackdrop({ backdrop }) {
5032
+ const scene = (0, import_fiber3.useThree)((s) => s.scene);
5033
+ const size = (0, import_fiber3.useThree)((s) => s.size);
5034
+ const [image, setImage] = (0, import_react9.useState)(null);
5035
+ const src = backdrop?.image ?? "";
5036
+ (0, import_react9.useEffect)(() => {
5037
+ if (!src) {
5038
+ setImage(null);
5039
+ return;
5040
+ }
5041
+ let live = true;
5042
+ const img = new Image();
5043
+ img.crossOrigin = "anonymous";
5044
+ img.onload = () => live && setImage(img);
5045
+ img.onerror = () => live && setImage(null);
5046
+ img.src = src;
5047
+ return () => {
5048
+ live = false;
5049
+ };
5050
+ }, [src]);
5051
+ const key = JSON.stringify({ backdrop: backdrop ?? null, w: size.width, h: size.height, loaded: !!image });
5052
+ (0, import_react9.useEffect)(() => {
5053
+ if (!backdrop) return;
5054
+ const previous = scene.background;
5055
+ const texture = paintBackdrop(backdrop, size.width, size.height, image);
5056
+ scene.background = texture;
5057
+ return () => {
5058
+ scene.background = previous;
5059
+ texture.dispose();
5060
+ };
5061
+ }, [key]);
5062
+ return null;
5063
+ }
5064
+ function paintBackdrop(backdrop, width, height, image) {
5065
+ const w = Math.max(2, Math.round(width / 2));
5066
+ const h = Math.max(2, Math.round(height / 2));
5067
+ const canvas = document.createElement("canvas");
5068
+ canvas.width = w;
5069
+ canvas.height = h;
5070
+ const ctx = canvas.getContext("2d");
5071
+ ctx.fillStyle = backdrop.color;
5072
+ ctx.fillRect(0, 0, w, h);
5073
+ if (image && image.width > 0 && image.height > 0) {
5074
+ const scale = backdrop.fit === "cover" ? Math.max(w / image.width, h / image.height) : Math.min(w / image.width, h / image.height);
5075
+ const dw = image.width * scale;
5076
+ const dh = image.height * scale;
5077
+ ctx.save();
5078
+ if (backdrop.blur > 0) ctx.filter = `blur(${backdrop.blur * Math.min(w, h) * 0.06}px)`;
5079
+ ctx.drawImage(image, (w - dw) / 2, (h - dh) / 2, dw, dh);
5080
+ ctx.restore();
5081
+ if (backdrop.fade > 0) {
5082
+ ctx.globalAlpha = backdrop.fade;
5083
+ ctx.fillStyle = backdrop.color;
5084
+ ctx.fillRect(0, 0, w, h);
5085
+ ctx.globalAlpha = 1;
5086
+ }
5087
+ }
5088
+ const texture = new THREE10.CanvasTexture(canvas);
5089
+ texture.colorSpace = THREE10.SRGBColorSpace;
5090
+ return texture;
5091
+ }
5092
+
4826
5093
  // src/Paper.tsx
4827
5094
  var import_jsx_runtime6 = require("react/jsx-runtime");
4828
- var Paper = (0, import_react9.forwardRef)(function Paper2({ children, className, style, ...meshProps }, ref) {
5095
+ var Paper = (0, import_react10.forwardRef)(function Paper2({ children, className, style, ...meshProps }, ref) {
4829
5096
  const config = useResolvedConfig(meshProps);
4830
- const webgl = (0, import_react9.useMemo)(() => typeof window === "undefined" ? true : supportsWebGL(), []);
5097
+ const webgl = (0, import_react10.useMemo)(() => typeof window === "undefined" ? true : supportsWebGL(), []);
4831
5098
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className, style: { width: "100%", height: "100%", ...style }, children: [
4832
- webgl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_fiber3.Canvas, { shadows: true, camera: { position: [0, 0.35, 2.4], fov: 40 }, dpr: [1, 2], children: [
5099
+ webgl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_fiber4.Canvas, { shadows: true, camera: { position: [0, 0.35, 2.4], fov: 40 }, dpr: [1, 2], children: [
5100
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PaperBackdrop, { backdrop: config.scene.backdrop }),
4833
5101
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4834
5102
  PaperLighting,
4835
5103
  {
4836
5104
  preset: config.scene.lighting,
5105
+ light: config.scene.light,
4837
5106
  floor: -1.05,
4838
5107
  scale: 8,
4839
5108
  reducedMotion: meshProps.reducedMotion
@@ -4847,14 +5116,14 @@ var Paper = (0, import_react9.forwardRef)(function Paper2({ children, className,
4847
5116
  });
4848
5117
 
4849
5118
  // src/PaperField.tsx
4850
- var THREE15 = __toESM(require("three"), 1);
5119
+ var THREE16 = __toESM(require("three"), 1);
4851
5120
  var import_gsap5 = require("gsap");
4852
- var import_fiber6 = require("@react-three/fiber");
4853
- var import_react16 = require("react");
5121
+ var import_fiber7 = require("@react-three/fiber");
5122
+ var import_react17 = require("react");
4854
5123
 
4855
5124
  // src/field/dropZones.tsx
4856
- var THREE10 = __toESM(require("three"), 1);
4857
- var import_react10 = require("react");
5125
+ var THREE11 = __toESM(require("three"), 1);
5126
+ var import_react11 = require("react");
4858
5127
  var import_jsx_runtime7 = require("react/jsx-runtime");
4859
5128
  var DropZoneRegistry = class {
4860
5129
  zones = /* @__PURE__ */ new Map();
@@ -4893,7 +5162,7 @@ var DropZoneRegistry = class {
4893
5162
  for (const fn of this.listeners) fn();
4894
5163
  }
4895
5164
  };
4896
- var DropZoneContext = (0, import_react10.createContext)(null);
5165
+ var DropZoneContext = (0, import_react11.createContext)(null);
4897
5166
  var globCache = /* @__PURE__ */ new Map();
4898
5167
  var globRegExp = (glob) => {
4899
5168
  let re = globCache.get(glob);
@@ -4909,13 +5178,13 @@ function zoneAccepts(zone, name) {
4909
5178
  }
4910
5179
  var zoneContains = (zone, x, y) => Math.abs(x - zone.bounds.position[0]) <= zone.bounds.size[0] / 2 && Math.abs(y - zone.bounds.position[1]) <= zone.bounds.size[1] / 2;
4911
5180
  function DropZone(props) {
4912
- const registry4 = (0, import_react10.useContext)(DropZoneContext);
5181
+ const registry4 = (0, import_react11.useContext)(DropZoneContext);
4913
5182
  const { id, accept, bounds, highlight = "glow", onPlace } = props;
4914
- const place = (0, import_react10.useRef)(onPlace);
4915
- (0, import_react10.useEffect)(() => {
5183
+ const place = (0, import_react11.useRef)(onPlace);
5184
+ (0, import_react11.useEffect)(() => {
4916
5185
  place.current = onPlace;
4917
5186
  }, [onPlace]);
4918
- (0, import_react10.useEffect)(() => {
5187
+ (0, import_react11.useEffect)(() => {
4919
5188
  if (!registry4) return;
4920
5189
  return registry4.register({
4921
5190
  id,
@@ -4929,17 +5198,17 @@ function DropZone(props) {
4929
5198
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DropZoneVisual, { registry: registry4, config: { id, accept, bounds, highlight } });
4930
5199
  }
4931
5200
  function DropZoneVisual({ registry: registry4, config }) {
4932
- (0, import_react10.useSyncExternalStore)(registry4.subscribe, registry4.getVersion, registry4.getVersion);
5201
+ (0, import_react11.useSyncExternalStore)(registry4.subscribe, registry4.getVersion, registry4.getVersion);
4933
5202
  const hovered = registry4.hovered === config.id;
4934
5203
  const style = config.highlight ?? "glow";
4935
5204
  const [w, h] = config.bounds.size;
4936
- const edges = (0, import_react10.useMemo)(() => {
4937
- const plane = new THREE10.PlaneGeometry(w, h);
4938
- const geo = new THREE10.EdgesGeometry(plane);
5205
+ const edges = (0, import_react11.useMemo)(() => {
5206
+ const plane = new THREE11.PlaneGeometry(w, h);
5207
+ const geo = new THREE11.EdgesGeometry(plane);
4939
5208
  plane.dispose();
4940
5209
  return geo;
4941
5210
  }, [w, h]);
4942
- (0, import_react10.useEffect)(() => () => edges.dispose(), [edges]);
5211
+ (0, import_react11.useEffect)(() => () => edges.dispose(), [edges]);
4943
5212
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("group", { position: config.bounds.position, children: [
4944
5213
  style !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("mesh", { children: [
4945
5214
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("planeGeometry", { args: [w, h] }),
@@ -4958,18 +5227,18 @@ function DropZoneVisual({ registry: registry4, config }) {
4958
5227
  }
4959
5228
 
4960
5229
  // src/field/sheetGrid.ts
4961
- var import_zod23 = require("zod");
4962
- var sheetLayoutSchema = import_zod23.z.object({
4963
- rows: import_zod23.z.number().int().min(1).max(12).default(2),
4964
- columns: import_zod23.z.number().int().min(1).max(12).default(5),
5230
+ var import_zod22 = require("zod");
5231
+ var sheetLayoutSchema = import_zod22.z.object({
5232
+ rows: import_zod22.z.number().int().min(1).max(12).default(2),
5233
+ columns: import_zod22.z.number().int().min(1).max(12).default(5),
4965
5234
  /** World-units gap between slots. Stamps are printed in register — no jitter. */
4966
- gutter: import_zod23.z.number().min(0).max(1).default(0.08),
5235
+ gutter: import_zod22.z.number().min(0).max(1).default(0.08),
4967
5236
  /** Slot footprint in world units (the paper preset should match). */
4968
- cellWidth: import_zod23.z.number().min(0.1).max(4).default(0.72),
4969
- cellHeight: import_zod23.z.number().min(0.1).max(4).default(0.86),
5237
+ cellWidth: import_zod22.z.number().min(0.1).max(4).default(0.72),
5238
+ cellHeight: import_zod22.z.number().min(0.1).max(4).default(0.86),
4970
5239
  /** Render the shared backing sheet behind the grid. */
4971
- backing: import_zod23.z.boolean().default(true),
4972
- backingMargin: import_zod23.z.number().min(0).max(1).default(0.12)
5240
+ backing: import_zod22.z.boolean().default(true),
5241
+ backingMargin: import_zod22.z.number().min(0).max(1).default(0.12)
4973
5242
  });
4974
5243
  var SHEET_LIFT = 0.012;
4975
5244
  function withSheetCellFromPaper(parsed, rawOptions, paperDims) {
@@ -5061,24 +5330,24 @@ function resolveFieldSlotConfig(slot, fallback, index, layoutId, layoutOptions)
5061
5330
  }
5062
5331
 
5063
5332
  // src/field/fieldGroup.tsx
5064
- var THREE12 = __toESM(require("three"), 1);
5333
+ var THREE13 = __toESM(require("three"), 1);
5065
5334
  var import_gsap3 = require("gsap");
5066
- var import_fiber4 = require("@react-three/fiber");
5067
- var import_react12 = require("react");
5335
+ var import_fiber5 = require("@react-three/fiber");
5336
+ var import_react13 = require("react");
5068
5337
  var import_three_custom_shader_material2 = __toESM(require("three-custom-shader-material"), 1);
5069
5338
 
5070
5339
  // src/content/atlas.ts
5071
- var THREE11 = __toESM(require("three"), 1);
5072
- var import_react11 = require("react");
5340
+ var THREE12 = __toESM(require("three"), 1);
5341
+ var import_react12 = require("react");
5073
5342
  var MAX_ATLAS = 4096;
5074
5343
  function atlasGrid(count, aspect = 1) {
5075
5344
  const cols = Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count * Math.max(aspect, 0.01)))));
5076
5345
  return { cols, rows: Math.max(1, Math.ceil(count / cols)) };
5077
5346
  }
5078
5347
  function useContentAtlas(contents, sheet2, stock) {
5079
- const [atlas, setAtlas] = (0, import_react11.useState)(null);
5348
+ const [atlas, setAtlas] = (0, import_react12.useState)(null);
5080
5349
  const stableContents = useStable(contents);
5081
- (0, import_react11.useEffect)(() => {
5350
+ (0, import_react12.useEffect)(() => {
5082
5351
  let disposed = false;
5083
5352
  const aspect = sheet2.height / sheet2.width;
5084
5353
  const { cols, rows } = atlasGrid(contents.length, aspect);
@@ -5094,8 +5363,8 @@ function useContentAtlas(contents, sheet2, stock) {
5094
5363
  const ctx = canvas.getContext("2d");
5095
5364
  ctx.fillStyle = stock.color;
5096
5365
  ctx.fillRect(0, 0, canvas.width, canvas.height);
5097
- const texture = new THREE11.CanvasTexture(canvas);
5098
- texture.colorSpace = THREE11.SRGBColorSpace;
5366
+ const texture = new THREE12.CanvasTexture(canvas);
5367
+ texture.colorSpace = THREE12.SRGBColorSpace;
5099
5368
  texture.anisotropy = 4;
5100
5369
  setAtlas({ texture, cols, rows });
5101
5370
  const drawTile = (index, tile) => {
@@ -5254,21 +5523,21 @@ function cap(s) {
5254
5523
  }
5255
5524
 
5256
5525
  // src/field/layouts/index.ts
5257
- var import_zod25 = require("zod");
5526
+ var import_zod24 = require("zod");
5258
5527
 
5259
5528
  // src/stage/path.ts
5260
- var import_zod24 = require("zod");
5261
- var walkPathSchema = import_zod24.z.object({
5529
+ var import_zod23 = require("zod");
5530
+ var walkPathSchema = import_zod23.z.object({
5262
5531
  /**
5263
5532
  * Control points on the ground plane, [x, z]. The default walks away from
5264
5533
  * the camera down -Z — the shot every reference image is composed on.
5265
5534
  */
5266
- points: import_zod24.z.array(import_zod24.z.tuple([import_zod24.z.number(), import_zod24.z.number()])).min(2).default([
5535
+ points: import_zod23.z.array(import_zod23.z.tuple([import_zod23.z.number(), import_zod23.z.number()])).min(2).default([
5267
5536
  [0, 9],
5268
5537
  [0, -9]
5269
5538
  ]),
5270
5539
  /** Join the last point back to the first: an endless walk, and the only form `phase` can slide. */
5271
- closed: import_zod24.z.boolean().default(false)
5540
+ closed: import_zod23.z.boolean().default(false)
5272
5541
  });
5273
5542
  var SAMPLES_PER_SEGMENT = 24;
5274
5543
  var EPSILON = 1e-6;
@@ -5394,9 +5663,9 @@ function jitter2(seed, i) {
5394
5663
  function ramp(i, n) {
5395
5664
  return n > 1 ? i / (n - 1) : 1;
5396
5665
  }
5397
- var ringSchema = import_zod25.z.object({
5398
- radius: import_zod25.z.number().min(0.5).max(12).default(2.6),
5399
- tiltDeg: import_zod25.z.number().min(-45).max(45).default(8)
5666
+ var ringSchema = import_zod24.z.object({
5667
+ radius: import_zod24.z.number().min(0.5).max(12).default(2.6),
5668
+ tiltDeg: import_zod24.z.number().min(-45).max(45).default(8)
5400
5669
  });
5401
5670
  var ring = {
5402
5671
  id: "ring",
@@ -5414,15 +5683,15 @@ var ring = {
5414
5683
  };
5415
5684
  }
5416
5685
  };
5417
- var fanSchema = import_zod25.z.object({
5686
+ var fanSchema = import_zod24.z.object({
5418
5687
  /** Total angular sweep from the first sheet to the last, degrees. */
5419
- sweep: import_zod25.z.number().min(0).max(180).default(72),
5688
+ sweep: import_zod24.z.number().min(0).max(180).default(72),
5420
5689
  /** Where the shared pin sits, in half-sheet-heights below center. 1 = the bottom edge. */
5421
- hinge: import_zod25.z.number().min(0).max(4).default(1.15),
5690
+ hinge: import_zod24.z.number().min(0).max(4).default(1.15),
5422
5691
  /** Thickness step so the sheets stack in order instead of z-fighting. */
5423
- lift: import_zod25.z.number().min(2e-3).max(0.08).default(0.012),
5692
+ lift: import_zod24.z.number().min(2e-3).max(0.08).default(0.012),
5424
5693
  /** How much flatter the middle of the fan sits than its outer sheets. */
5425
- bow: import_zod25.z.number().min(0).max(1).default(0.7)
5694
+ bow: import_zod24.z.number().min(0).max(1).default(0.7)
5426
5695
  });
5427
5696
  var fan = {
5428
5697
  id: "fan",
@@ -5442,16 +5711,16 @@ var fan = {
5442
5711
  };
5443
5712
  }
5444
5713
  };
5445
- var spreadSchema = import_zod25.z.object({
5714
+ var spreadSchema = import_zod24.z.object({
5446
5715
  /** How far each sheet slides past the one below it. */
5447
- slip: import_zod25.z.number().min(0.02).max(2).default(0.3),
5716
+ slip: import_zod24.z.number().min(0.02).max(2).default(0.3),
5448
5717
  /** Direction of the slide, degrees. 0 slides right, 90 slides up. */
5449
- angle: import_zod25.z.number().min(-180).max(180).default(28),
5450
- lift: import_zod25.z.number().min(2e-3).max(0.08).default(0.012),
5718
+ angle: import_zod24.z.number().min(-180).max(180).default(28),
5719
+ lift: import_zod24.z.number().min(2e-3).max(0.08).default(0.012),
5451
5720
  /** How much more the sheets at the far end of the slide bow. */
5452
- bow: import_zod25.z.number().min(0).max(1).default(0.6),
5721
+ bow: import_zod24.z.number().min(0).max(1).default(0.6),
5453
5722
  /** Nothing hand-slid is perfectly square — a touch of per-sheet rotation. */
5454
- drift: import_zod25.z.number().min(0).max(1).default(0.15)
5723
+ drift: import_zod24.z.number().min(0).max(1).default(0.15)
5455
5724
  });
5456
5725
  var spread = {
5457
5726
  id: "spread",
@@ -5469,15 +5738,15 @@ var spread = {
5469
5738
  };
5470
5739
  }
5471
5740
  };
5472
- var pileSchema = import_zod25.z.object({
5741
+ var pileSchema = import_zod24.z.object({
5473
5742
  /** How far sheets wander from the center of the heap. */
5474
- scatter: import_zod25.z.number().min(0).max(2).default(0.22),
5743
+ scatter: import_zod24.z.number().min(0).max(2).default(0.22),
5475
5744
  /** Widest angle a sheet sits off square, degrees. */
5476
- turn: import_zod25.z.number().min(0).max(180).default(24),
5477
- lift: import_zod25.z.number().min(2e-3).max(0.08).default(0.011),
5745
+ turn: import_zod24.z.number().min(0).max(180).default(24),
5746
+ lift: import_zod24.z.number().min(2e-3).max(0.08).default(0.011),
5478
5747
  /** How flat the sheets underneath are pressed by the ones on top. */
5479
- press: import_zod25.z.number().min(0).max(1).default(0.85),
5480
- seed: import_zod25.z.number().int().min(0).max(9999).default(3)
5748
+ press: import_zod24.z.number().min(0).max(1).default(0.85),
5749
+ seed: import_zod24.z.number().int().min(0).max(9999).default(3)
5481
5750
  });
5482
5751
  var pile = {
5483
5752
  id: "pile",
@@ -5493,12 +5762,12 @@ var pile = {
5493
5762
  };
5494
5763
  }
5495
5764
  };
5496
- var wallSchema = import_zod25.z.object({
5497
- gapX: import_zod25.z.number().min(0.05).max(1).default(0.22),
5498
- gapY: import_zod25.z.number().min(0.05).max(1).default(0.3),
5499
- jitterAmt: import_zod25.z.number().min(0).max(1).default(0.25),
5765
+ var wallSchema = import_zod24.z.object({
5766
+ gapX: import_zod24.z.number().min(0.05).max(1).default(0.22),
5767
+ gapY: import_zod24.z.number().min(0.05).max(1).default(0.3),
5768
+ jitterAmt: import_zod24.z.number().min(0).max(1).default(0.25),
5500
5769
  /** Spread of sag across the wall — no two pinned sheets hang alike. */
5501
- sag: import_zod25.z.number().min(0).max(1).default(0.45)
5770
+ sag: import_zod24.z.number().min(0).max(1).default(0.45)
5502
5771
  });
5503
5772
  var wall = {
5504
5773
  id: "wall",
@@ -5524,15 +5793,15 @@ var wall = {
5524
5793
  };
5525
5794
  }
5526
5795
  };
5527
- var spillSchema = import_zod25.z.object({
5528
- spreadX: import_zod25.z.number().min(0.5).max(8).default(2.4),
5529
- spreadY: import_zod25.z.number().min(0.5).max(8).default(1.5),
5530
- depth: import_zod25.z.number().min(0).max(6).default(1.6),
5796
+ var spillSchema = import_zod24.z.object({
5797
+ spreadX: import_zod24.z.number().min(0.5).max(8).default(2.4),
5798
+ spreadY: import_zod24.z.number().min(0.5).max(8).default(1.5),
5799
+ depth: import_zod24.z.number().min(0).max(6).default(1.6),
5531
5800
  /** How far sheets pitch and roll out of the picture plane. */
5532
- tumble: import_zod25.z.number().min(0).max(1).default(0.5),
5801
+ tumble: import_zod24.z.number().min(0).max(1).default(0.5),
5533
5802
  /** Spread of bend across the sheets — a spill does not fold them alike. */
5534
- vary: import_zod25.z.number().min(0).max(1).default(0.6),
5535
- seed: import_zod25.z.number().int().min(0).max(9999).default(7)
5803
+ vary: import_zod24.z.number().min(0).max(1).default(0.6),
5804
+ seed: import_zod24.z.number().int().min(0).max(9999).default(7)
5536
5805
  });
5537
5806
  var spill = {
5538
5807
  id: "spill",
@@ -5557,13 +5826,13 @@ var spill = {
5557
5826
  };
5558
5827
  }
5559
5828
  };
5560
- var sweepSchema = import_zod25.z.object({
5561
- columns: import_zod25.z.number().int().min(1).max(24).default(5),
5829
+ var sweepSchema = import_zod24.z.object({
5830
+ columns: import_zod24.z.number().int().min(1).max(24).default(5),
5562
5831
  /** Breathing room around each specimen. */
5563
- gap: import_zod25.z.number().min(0).max(2).default(0.22),
5832
+ gap: import_zod24.z.number().min(0).max(2).default(0.22),
5564
5833
  /** Deformation at the first specimen and at the last. */
5565
- from: import_zod25.z.number().min(0).max(1).default(0),
5566
- to: import_zod25.z.number().min(0).max(1).default(1)
5834
+ from: import_zod24.z.number().min(0).max(1).default(0),
5835
+ to: import_zod24.z.number().min(0).max(1).default(1)
5567
5836
  });
5568
5837
  var sweep = {
5569
5838
  id: "sweep",
@@ -5587,15 +5856,15 @@ var sweep = {
5587
5856
  };
5588
5857
  }
5589
5858
  };
5590
- var bookSchema = import_zod25.z.object({
5859
+ var bookSchema = import_zod24.z.object({
5591
5860
  /** How far the outermost page lifts off the block, degrees. */
5592
- spread: import_zod25.z.number().min(0).max(150).default(55),
5861
+ spread: import_zod24.z.number().min(0).max(150).default(55),
5593
5862
  /** Fraction of the pages bound to the left. 0 = a one-sided sample book. */
5594
- split: import_zod25.z.number().min(0).max(1).default(0.5),
5863
+ split: import_zod24.z.number().min(0).max(1).default(0.5),
5595
5864
  /** Page thickness — the gap between pages of one block. */
5596
- lift: import_zod25.z.number().min(1e-3).max(0.05).default(8e-3),
5865
+ lift: import_zod24.z.number().min(1e-3).max(0.05).default(8e-3),
5597
5866
  /** How much more a lifted page arcs than one lying flat in the block. */
5598
- gutter: import_zod25.z.number().min(0).max(1).default(0.6)
5867
+ gutter: import_zod24.z.number().min(0).max(1).default(0.6)
5599
5868
  });
5600
5869
  var book = {
5601
5870
  id: "book",
@@ -5622,11 +5891,11 @@ var book = {
5622
5891
  };
5623
5892
  }
5624
5893
  };
5625
- var accordionSchema = import_zod25.z.object({
5894
+ var accordionSchema = import_zod24.z.object({
5626
5895
  /** How far each panel tilts off the strip's line, degrees. 0 = flat, 90 = shut. */
5627
- angle: import_zod25.z.number().min(0).max(89).default(55),
5896
+ angle: import_zod24.z.number().min(0).max(89).default(55),
5628
5897
  /** A concertina holds its creases — how much bow the panels keep. */
5629
- slack: import_zod25.z.number().min(0).max(1).default(0.15)
5898
+ slack: import_zod24.z.number().min(0).max(1).default(0.15)
5630
5899
  });
5631
5900
  var accordion = {
5632
5901
  id: "accordion",
@@ -5645,16 +5914,16 @@ var accordion = {
5645
5914
  };
5646
5915
  }
5647
5916
  };
5648
- var rackSchema = import_zod25.z.object({
5917
+ var rackSchema = import_zod24.z.object({
5649
5918
  /** Gap along the row, as a fraction of the paper's width. Under 1 they overlap. */
5650
- spacing: import_zod25.z.number().min(0.05).max(2).default(0.82),
5919
+ spacing: import_zod24.z.number().min(0.05).max(2).default(0.82),
5651
5920
  /** How far a sheet leans back off vertical, degrees. */
5652
- lean: import_zod25.z.number().min(0).max(70).default(16),
5921
+ lean: import_zod24.z.number().min(0).max(70).default(16),
5653
5922
  /** How much that lean differs sheet to sheet — nothing propped is uniform. */
5654
- vary: import_zod25.z.number().min(0).max(1).default(0.55),
5923
+ vary: import_zod24.z.number().min(0).max(1).default(0.55),
5655
5924
  /** Small rotations off square. */
5656
- sway: import_zod25.z.number().min(0).max(1).default(0.35),
5657
- seed: import_zod25.z.number().int().min(0).max(9999).default(5)
5925
+ sway: import_zod24.z.number().min(0).max(1).default(0.35),
5926
+ seed: import_zod24.z.number().int().min(0).max(9999).default(5)
5658
5927
  });
5659
5928
  var rack = {
5660
5929
  id: "rack",
@@ -5678,19 +5947,19 @@ var rack = {
5678
5947
  };
5679
5948
  }
5680
5949
  };
5681
- var colonnadeSchema = import_zod25.z.object({
5950
+ var colonnadeSchema = import_zod24.z.object({
5682
5951
  /** The walk the colonnade is built along — see `stage/path`. */
5683
5952
  path: walkPathSchema.default({}),
5684
5953
  /** Half-width of the clear aisle: how far each banner stands off the walk line. */
5685
- aisle: import_zod25.z.number().min(0.2).max(20).default(2.4),
5954
+ aisle: import_zod24.z.number().min(0.2).max(20).default(2.4),
5686
5955
  /** How much that gap opens and closes along the walk. Nothing hung by hand is a corridor. */
5687
- breathe: import_zod25.z.number().min(0).max(1).default(0.3),
5956
+ breathe: import_zod24.z.number().min(0).max(1).default(0.3),
5688
5957
  /** Widest angle a banner turns off square to the aisle, degrees. */
5689
- twist: import_zod25.z.number().min(0).max(90).default(22),
5958
+ twist: import_zod24.z.number().min(0).max(90).default(22),
5690
5959
  /** Fraction of the walk left clear at each end, so the figure has somewhere to enter from. */
5691
- margin: import_zod25.z.number().min(0).max(0.45).default(0.05),
5960
+ margin: import_zod24.z.number().min(0).max(0.45).default(0.05),
5692
5961
  /** Spread of banner heights, 0..1. */
5693
- rise: import_zod25.z.number().min(0).max(1).default(0.28),
5962
+ rise: import_zod24.z.number().min(0).max(1).default(0.28),
5694
5963
  /**
5695
5964
  * How far the banners lift off the floor, as a fraction of their height.
5696
5965
  *
@@ -5704,10 +5973,10 @@ var colonnadeSchema = import_zod25.z.object({
5704
5973
  * The bound used to be 0, so the one thing this option documented itself
5705
5974
  * as doing was the one thing it could not do.
5706
5975
  */
5707
- hover: import_zod25.z.number().min(-0.5).max(1).default(0),
5976
+ hover: import_zod24.z.number().min(-0.5).max(1).default(0),
5708
5977
  /** Spread of deformation — no two lengths of hung paper drape alike. */
5709
- drape: import_zod25.z.number().min(0).max(1).default(0.5),
5710
- seed: import_zod25.z.number().int().min(0).max(9999).default(2)
5978
+ drape: import_zod24.z.number().min(0).max(1).default(0.5),
5979
+ seed: import_zod24.z.number().int().min(0).max(9999).default(2)
5711
5980
  });
5712
5981
  function colonnadeStop(i, n, margin) {
5713
5982
  const side = i % 2 === 0 ? 1 : -1;
@@ -5792,7 +6061,7 @@ function fieldShapeStack(config, progress) {
5792
6061
 
5793
6062
  // src/field/fieldGroup.tsx
5794
6063
  var import_jsx_runtime8 = require("react/jsx-runtime");
5795
- var scratchObj = new THREE12.Object3D();
6064
+ var scratchObj = new THREE13.Object3D();
5796
6065
  var scratchAero = { position: [0, 0, 0], rotation: [0, 0, 0] };
5797
6066
  var FIELD_SEGMENT_CAP = 48;
5798
6067
  var FIELD_AUTO_CEILING = 128;
@@ -5811,11 +6080,11 @@ function FieldGroup({
5811
6080
  const stock = getStock(config.stock);
5812
6081
  const rig = useLightRig(config.scene.lighting);
5813
6082
  const behavior = config.behavior && !config.deformers ? getBehavior(config.behavior.type) : null;
5814
- const progressRef = (0, import_react12.useRef)(
6083
+ const progressRef = (0, import_react13.useRef)(
5815
6084
  behavior ? config.behavior[behavior.progressParam] : 0
5816
6085
  );
5817
6086
  const buildStackAt = (progress) => fieldShapeStack(config, progress);
5818
- const initialStack = (0, import_react12.useMemo)(
6087
+ const initialStack = (0, import_react13.useMemo)(
5819
6088
  () => buildStackAt(progressRef.current),
5820
6089
  [
5821
6090
  JSON.stringify(config.behavior ?? null),
@@ -5824,7 +6093,7 @@ function FieldGroup({
5824
6093
  ]
5825
6094
  );
5826
6095
  const structureKey = initialStack.map((i) => i.type).join("|");
5827
- const geometry = (0, import_react12.useMemo)(() => {
6096
+ const geometry = (0, import_react13.useMemo)(() => {
5828
6097
  const floor = stackMinSegments(initialStack, config.sheet);
5829
6098
  const want = [0, 0];
5830
6099
  for (const p of PROGRESS_SAMPLES2) {
@@ -5853,28 +6122,28 @@ function FieldGroup({
5853
6122
  atlasIdx[i] = i;
5854
6123
  phase[i] = indices[i] * 0.618034 % 1 * 4;
5855
6124
  }
5856
- geo.setAttribute("aAtlas", new THREE12.InstancedBufferAttribute(atlasIdx, 1));
5857
- geo.setAttribute("aPhase", new THREE12.InstancedBufferAttribute(phase, 1));
5858
- geo.setAttribute("aBias", new THREE12.InstancedBufferAttribute(bias, 1));
6125
+ geo.setAttribute("aAtlas", new THREE13.InstancedBufferAttribute(atlasIdx, 1));
6126
+ geo.setAttribute("aPhase", new THREE13.InstancedBufferAttribute(phase, 1));
6127
+ geo.setAttribute("aBias", new THREE13.InstancedBufferAttribute(bias, 1));
5859
6128
  return geo;
5860
6129
  }, [JSON.stringify(config.sheet), structureKey, count, autoCeiling]);
5861
- (0, import_react12.useEffect)(() => () => geometry.dispose(), [geometry]);
6130
+ (0, import_react13.useEffect)(() => () => geometry.dispose(), [geometry]);
5862
6131
  const atlas = useContentAtlas(contents, config.sheet, stock);
5863
- const shader = (0, import_react12.useMemo)(() => {
6132
+ const shader = (0, import_react13.useMemo)(() => {
5864
6133
  const composed = buildDisplacementGLSL(initialStack, config.sheet);
5865
6134
  const uniforms = {};
5866
6135
  for (const [name, value] of Object.entries(composed.uniforms)) {
5867
6136
  uniforms[name] = {
5868
- value: Array.isArray(value) && value.length === 2 ? new THREE12.Vector2(...value) : value
6137
+ value: Array.isArray(value) && value.length === 2 ? new THREE13.Vector2(...value) : value
5869
6138
  };
5870
6139
  }
5871
6140
  uniforms.uPlTime = { value: 0 };
5872
6141
  uniforms.uAtlas = { value: null };
5873
- uniforms.uAtlasGrid = { value: new THREE12.Vector2(1, 1) };
6142
+ uniforms.uAtlasGrid = { value: new THREE13.Vector2(1, 1) };
5874
6143
  uniforms.uBackDarken = {
5875
6144
  value: 1 - Math.min(0.45, 0.12 + config.sheet.thickness * 0.9) * stock.opacity
5876
6145
  };
5877
- uniforms.uStockColor = { value: new THREE12.Color(stock.color) };
6146
+ uniforms.uStockColor = { value: new THREE13.Color(stock.color) };
5878
6147
  uniforms.uShowThrough = { value: config.surface.showThrough ?? stock.showThrough };
5879
6148
  Object.assign(uniforms, translucencyUniforms(config.surface.translucency ?? stock.translucency, rig));
5880
6149
  return {
@@ -5889,19 +6158,19 @@ function FieldGroup({
5889
6158
  config.surface.showThrough,
5890
6159
  config.surface.translucency
5891
6160
  ]);
5892
- (0, import_react12.useEffect)(() => {
6161
+ (0, import_react13.useEffect)(() => {
5893
6162
  const values = translucencyValues(config.surface.translucency ?? stock.translucency, rig);
5894
6163
  shader.uniforms.uTranslucency.value = values.translucency;
5895
6164
  shader.uniforms.uBackLightDir.value.copy(values.direction);
5896
6165
  shader.uniforms.uBackLightColor.value.copy(values.color);
5897
6166
  shader.uniforms.uAmbientTransmission.value = values.ambient;
5898
6167
  }, [shader, rig, config.surface.translucency, stock.translucency]);
5899
- (0, import_react12.useEffect)(() => {
6168
+ (0, import_react13.useEffect)(() => {
5900
6169
  if (!atlas) return;
5901
6170
  shader.uniforms.uAtlas.value = atlas.texture;
5902
6171
  shader.uniforms.uAtlasGrid.value.set(atlas.cols, atlas.rows);
5903
6172
  }, [atlas, shader]);
5904
- (0, import_react12.useEffect)(() => {
6173
+ (0, import_react13.useEffect)(() => {
5905
6174
  if (!behavior || shared.reduced) return;
5906
6175
  const state = { p: progressRef.current };
5907
6176
  const tween = import_gsap3.gsap.to(state, {
@@ -5918,8 +6187,8 @@ function FieldGroup({
5918
6187
  tween.kill();
5919
6188
  };
5920
6189
  }, [structureKey, shared.reduced]);
5921
- const meshRef = (0, import_react12.useRef)(null);
5922
- (0, import_fiber4.useFrame)(({ clock }) => {
6190
+ const meshRef = (0, import_react13.useRef)(null);
6191
+ (0, import_fiber5.useFrame)(({ clock }) => {
5923
6192
  const mesh = meshRef.current;
5924
6193
  if (!mesh) return;
5925
6194
  const elapsed = clock.elapsedTime - Math.max(shared.mountTimeRef.current, 0);
@@ -5929,7 +6198,7 @@ function FieldGroup({
5929
6198
  for (const [name, value] of Object.entries(values)) {
5930
6199
  const uniform = shader.uniforms[name];
5931
6200
  if (!uniform) continue;
5932
- if (uniform.value instanceof THREE12.Vector2 && Array.isArray(value)) {
6201
+ if (uniform.value instanceof THREE13.Vector2 && Array.isArray(value)) {
5933
6202
  uniform.value.set(value[0], value[1]);
5934
6203
  } else {
5935
6204
  uniform.value = value;
@@ -6013,13 +6282,13 @@ function FieldGroup({
6013
6282
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
6014
6283
  import_three_custom_shader_material2.default,
6015
6284
  {
6016
- baseMaterial: THREE12.MeshStandardMaterial,
6285
+ baseMaterial: THREE13.MeshStandardMaterial,
6017
6286
  vertexShader: shader.vertexShader,
6018
6287
  fragmentShader: shader.fragmentShader,
6019
6288
  uniforms: shader.uniforms,
6020
6289
  roughness: stock.roughness,
6021
6290
  metalness: 0,
6022
- side: THREE12.DoubleSide
6291
+ side: THREE13.DoubleSide
6023
6292
  },
6024
6293
  `${structureKey}:${count}`
6025
6294
  )
@@ -6065,8 +6334,8 @@ var easeOut = (t) => 1 - (1 - t) ** 3;
6065
6334
  var easeInOut = (t) => t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
6066
6335
 
6067
6336
  // src/field/backingSheet.tsx
6068
- var THREE13 = __toESM(require("three"), 1);
6069
- var import_react13 = require("react");
6337
+ var THREE14 = __toESM(require("three"), 1);
6338
+ var import_react14 = require("react");
6070
6339
 
6071
6340
  // src/content/backing.ts
6072
6341
  function silhouetteRects(o, count) {
@@ -6139,7 +6408,7 @@ function BackingSheet({
6139
6408
  removed
6140
6409
  }) {
6141
6410
  const { width, height } = sheetBackingSize(options);
6142
- const canvas = (0, import_react13.useMemo)(() => {
6411
+ const canvas = (0, import_react14.useMemo)(() => {
6143
6412
  if (typeof document === "undefined") return null;
6144
6413
  const c = document.createElement("canvas");
6145
6414
  const scale = Math.min(1024, Math.round(360 * Math.max(width, height))) / Math.max(width, height);
@@ -6147,14 +6416,14 @@ function BackingSheet({
6147
6416
  c.height = Math.max(2, Math.round(height * scale));
6148
6417
  return c;
6149
6418
  }, [width, height]);
6150
- const texture = (0, import_react13.useMemo)(() => canvas ? new THREE13.CanvasTexture(canvas) : null, [canvas]);
6419
+ const texture = (0, import_react14.useMemo)(() => canvas ? new THREE14.CanvasTexture(canvas) : null, [canvas]);
6151
6420
  const removedKey = [...removed].sort((a, b) => a - b).join(",");
6152
- (0, import_react13.useEffect)(() => {
6421
+ (0, import_react14.useEffect)(() => {
6153
6422
  if (!canvas || !texture) return;
6154
6423
  drawBacking(canvas, { options, count, tint: BACKING_TINT, removed });
6155
6424
  texture.needsUpdate = true;
6156
6425
  }, [canvas, texture, JSON.stringify(options), count, removedKey]);
6157
- (0, import_react13.useEffect)(() => () => texture?.dispose(), [texture]);
6426
+ (0, import_react14.useEffect)(() => () => texture?.dispose(), [texture]);
6158
6427
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("mesh", { receiveShadow: true, children: [
6159
6428
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("planeGeometry", { args: [width, height] }),
6160
6429
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("meshStandardMaterial", { map: texture, color: "#ffffff", roughness: 0.92, metalness: 0 })
@@ -6162,10 +6431,10 @@ function BackingSheet({
6162
6431
  }
6163
6432
 
6164
6433
  // src/field/interactiveField.tsx
6165
- var THREE14 = __toESM(require("three"), 1);
6434
+ var THREE15 = __toESM(require("three"), 1);
6166
6435
  var import_gsap4 = require("gsap");
6167
- var import_fiber5 = require("@react-three/fiber");
6168
- var import_react14 = require("react");
6436
+ var import_fiber6 = require("@react-three/fiber");
6437
+ var import_react15 = require("react");
6169
6438
  var import_jsx_runtime10 = require("react/jsx-runtime");
6170
6439
  var PICK_BEHAVIORS = /* @__PURE__ */ new Set(["peel", "carry"]);
6171
6440
  function InteractiveField(props) {
@@ -6173,15 +6442,15 @@ function InteractiveField(props) {
6173
6442
  const total = papers.length;
6174
6443
  const layout = getLayout(layoutId);
6175
6444
  const reduced = usePrefersReducedMotion(props.reducedMotion);
6176
- const contextRegistry = (0, import_react14.useContext)(DropZoneContext);
6177
- const registry4 = (0, import_react14.useMemo)(() => contextRegistry ?? new DropZoneRegistry(), [contextRegistry]);
6178
- const camera = (0, import_fiber5.useThree)((s) => s.camera);
6179
- const gl = (0, import_fiber5.useThree)((s) => s.gl);
6180
- const controls = (0, import_fiber5.useThree)((s) => s.controls);
6181
- const [removed, setRemoved] = (0, import_react14.useState)(EMPTY_SET);
6182
- const [slotPatches, setSlotPatches] = (0, import_react14.useState)({});
6183
- const [slotStates, setSlotStates] = (0, import_react14.useState)({});
6184
- const slotConfigs = (0, import_react14.useMemo)(
6445
+ const contextRegistry = (0, import_react15.useContext)(DropZoneContext);
6446
+ const registry4 = (0, import_react15.useMemo)(() => contextRegistry ?? new DropZoneRegistry(), [contextRegistry]);
6447
+ const camera = (0, import_fiber6.useThree)((s) => s.camera);
6448
+ const gl = (0, import_fiber6.useThree)((s) => s.gl);
6449
+ const controls = (0, import_fiber6.useThree)((s) => s.controls);
6450
+ const [removed, setRemoved] = (0, import_react15.useState)(EMPTY_SET);
6451
+ const [slotPatches, setSlotPatches] = (0, import_react15.useState)({});
6452
+ const [slotStates, setSlotStates] = (0, import_react15.useState)({});
6453
+ const slotConfigs = (0, import_react15.useMemo)(
6185
6454
  () => papers.map((slot, i) => {
6186
6455
  const config = resolveFieldSlotConfig(slot, fallback, i, layoutId, layoutOptions);
6187
6456
  const patch = slotPatches[i];
@@ -6195,14 +6464,14 @@ function InteractiveField(props) {
6195
6464
  slotPatches
6196
6465
  ]
6197
6466
  );
6198
- const poses = (0, import_react14.useMemo)(
6467
+ const poses = (0, import_react15.useMemo)(
6199
6468
  () => slotConfigs.map((_, i) => layout.pose(i, total, layoutOptions, 0, props.sheet)),
6200
6469
  [layoutId, JSON.stringify(layoutOptions), total, props.sheet.width, props.sheet.height]
6201
6470
  );
6202
- const groupRefs = (0, import_react14.useRef)([]);
6203
- const handleRefs = (0, import_react14.useRef)([]);
6204
- const pressRef = (0, import_react14.useRef)(null);
6205
- const carriedRef = (0, import_react14.useRef)(null);
6471
+ const groupRefs = (0, import_react15.useRef)([]);
6472
+ const handleRefs = (0, import_react15.useRef)([]);
6473
+ const pressRef = (0, import_react15.useRef)(null);
6474
+ const carriedRef = (0, import_react15.useRef)(null);
6206
6475
  const zonesLive = () => [...props.zones ?? [], ...registry4.list()];
6207
6476
  const slotName = (i) => {
6208
6477
  const preset = papers[i]?.preset ?? fallback;
@@ -6215,10 +6484,10 @@ function InteractiveField(props) {
6215
6484
  }
6216
6485
  props.onSlotStateChange?.(i, state);
6217
6486
  };
6218
- const raycaster = (0, import_react14.useMemo)(() => new THREE14.Raycaster(), []);
6219
- const planeScratch = (0, import_react14.useMemo)(() => new THREE14.Plane(new THREE14.Vector3(0, 0, 1), 0), []);
6220
- const pointScratch = (0, import_react14.useMemo)(() => new THREE14.Vector3(), []);
6221
- const ndcScratch = (0, import_react14.useMemo)(() => new THREE14.Vector2(), []);
6487
+ const raycaster = (0, import_react15.useMemo)(() => new THREE15.Raycaster(), []);
6488
+ const planeScratch = (0, import_react15.useMemo)(() => new THREE15.Plane(new THREE15.Vector3(0, 0, 1), 0), []);
6489
+ const pointScratch = (0, import_react15.useMemo)(() => new THREE15.Vector3(), []);
6490
+ const ndcScratch = (0, import_react15.useMemo)(() => new THREE15.Vector2(), []);
6222
6491
  const planePoint = (clientX, clientY, planeZ) => {
6223
6492
  const rect = gl.domElement.getBoundingClientRect();
6224
6493
  ndcScratch.set(
@@ -6348,7 +6617,7 @@ function InteractiveField(props) {
6348
6617
  import_gsap4.gsap.to(group.rotation, { x: home.rotation[0], y: home.rotation[1], z: home.rotation[2], duration });
6349
6618
  import_gsap4.gsap.to(group.scale, { x: home.scale, y: home.scale, z: home.scale, duration: duration * 0.5 });
6350
6619
  };
6351
- const onPointerMoveRef = (0, import_react14.useRef)(() => {
6620
+ const onPointerMoveRef = (0, import_react15.useRef)(() => {
6352
6621
  });
6353
6622
  onPointerMoveRef.current = (e) => {
6354
6623
  const press = pressRef.current;
@@ -6372,7 +6641,7 @@ function InteractiveField(props) {
6372
6641
  pick(slot, hit[0], hit[1], pointerId);
6373
6642
  }
6374
6643
  };
6375
- const onPointerUpRef = (0, import_react14.useRef)(() => {
6644
+ const onPointerUpRef = (0, import_react15.useRef)(() => {
6376
6645
  });
6377
6646
  onPointerUpRef.current = (e) => {
6378
6647
  pressRef.current = null;
@@ -6381,12 +6650,12 @@ function InteractiveField(props) {
6381
6650
  if (carried.pointerId !== null && e.pointerId !== carried.pointerId) return;
6382
6651
  const name = slotName(carried.slot);
6383
6652
  const zone = zonesLive().find(
6384
- (z26) => zoneContains(z26, carried.x.value, carried.y.value) && zoneAccepts(z26, name)
6653
+ (z25) => zoneContains(z25, carried.x.value, carried.y.value) && zoneAccepts(z25, name)
6385
6654
  );
6386
6655
  if (zone) settleInto(carried, zone);
6387
6656
  else returnHome(carried);
6388
6657
  };
6389
- (0, import_react14.useEffect)(() => {
6658
+ (0, import_react15.useEffect)(() => {
6390
6659
  const move = (e) => onPointerMoveRef.current(e);
6391
6660
  const up = (e) => onPointerUpRef.current(e);
6392
6661
  window.addEventListener("pointermove", move);
@@ -6396,7 +6665,7 @@ function InteractiveField(props) {
6396
6665
  window.removeEventListener("pointerup", up);
6397
6666
  };
6398
6667
  }, []);
6399
- (0, import_fiber5.useFrame)((_, delta) => {
6668
+ (0, import_fiber6.useFrame)((_, delta) => {
6400
6669
  const carried = carriedRef.current;
6401
6670
  if (!carried || carried.settling) return;
6402
6671
  const group = groupRefs.current[carried.slot];
@@ -6422,19 +6691,19 @@ function InteractiveField(props) {
6422
6691
  group.position.z = carried.homePose.position[2] + 0.15;
6423
6692
  handle.set("drive", carryDrive(speed));
6424
6693
  const lag = 0.25;
6425
- group.rotation.y += (THREE14.MathUtils.clamp(-vx * lag, -0.6, 0.6) - group.rotation.y) * 0.12;
6426
- group.rotation.x += (THREE14.MathUtils.clamp(vy * lag * 0.7, -0.5, 0.5) - group.rotation.x) * 0.12;
6694
+ group.rotation.y += (THREE15.MathUtils.clamp(-vx * lag, -0.6, 0.6) - group.rotation.y) * 0.12;
6695
+ group.rotation.x += (THREE15.MathUtils.clamp(vy * lag * 0.7, -0.5, 0.5) - group.rotation.x) * 0.12;
6427
6696
  }
6428
6697
  const name = slotName(carried.slot);
6429
6698
  const zone = zonesLive().find(
6430
- (z26) => zoneContains(z26, group.position.x, group.position.y) && zoneAccepts(z26, name)
6699
+ (z25) => zoneContains(z25, group.position.x, group.position.y) && zoneAccepts(z25, name)
6431
6700
  );
6432
6701
  registry4.setHovered(zone?.id ?? null);
6433
6702
  const targetScale = (zone ? 1.03 : 1) * carried.homePose.scale;
6434
6703
  group.scale.x += (targetScale - group.scale.x) * 0.15;
6435
6704
  group.scale.y = group.scale.z = group.scale.x;
6436
6705
  });
6437
- const a11yImplRef = (0, import_react14.useRef)(null);
6706
+ const a11yImplRef = (0, import_react15.useRef)(null);
6438
6707
  a11yImplRef.current = {
6439
6708
  pick: (slot) => {
6440
6709
  const home = poses[slot];
@@ -6443,7 +6712,7 @@ function InteractiveField(props) {
6443
6712
  },
6444
6713
  placeAtZone: (slot, zoneId) => {
6445
6714
  const carried = carriedRef.current;
6446
- const zone = zonesLive().find((z26) => z26.id === zoneId);
6715
+ const zone = zonesLive().find((z25) => z25.id === zoneId);
6447
6716
  const group = groupRefs.current[slot];
6448
6717
  if (!carried || carried.slot !== slot || !zone || !group) return;
6449
6718
  group.position.x = zone.bounds.position[0];
@@ -6456,10 +6725,10 @@ function InteractiveField(props) {
6456
6725
  const carried = carriedRef.current;
6457
6726
  if (carried && carried.slot === slot && !carried.settling) returnHome(carried);
6458
6727
  },
6459
- zoneIds: () => zonesLive().map((z26) => z26.id),
6728
+ zoneIds: () => zonesLive().map((z25) => z25.id),
6460
6729
  slotState: (slot) => slotStates[slot] ?? "rest"
6461
6730
  };
6462
- (0, import_react14.useEffect)(() => {
6731
+ (0, import_react15.useEffect)(() => {
6463
6732
  const ref = props.a11yRef;
6464
6733
  if (!ref) return;
6465
6734
  ref.current = {
@@ -6512,7 +6781,7 @@ function InteractiveField(props) {
6512
6781
  }
6513
6782
 
6514
6783
  // src/field/keyboardMirror.tsx
6515
- var import_react15 = require("react");
6784
+ var import_react16 = require("react");
6516
6785
  var import_jsx_runtime11 = require("react/jsx-runtime");
6517
6786
  function fieldKeyboardStep(carry2, slot, key, controller) {
6518
6787
  if (!carry2) {
@@ -6558,7 +6827,7 @@ function FieldKeyboardMirror({
6558
6827
  papers,
6559
6828
  controller
6560
6829
  }) {
6561
- const [carrying, setCarrying] = (0, import_react15.useState)(null);
6830
+ const [carrying, setCarrying] = (0, import_react16.useState)(null);
6562
6831
  const paperLabel = (slot, i) => {
6563
6832
  try {
6564
6833
  const config = resolveConfig({ preset: slot.preset });
@@ -6661,16 +6930,16 @@ function fitCamera(layout, n, options, sheet2, fovDeg, aspect, margin = 1.06) {
6661
6930
 
6662
6931
  // src/PaperField.tsx
6663
6932
  var import_jsx_runtime12 = require("react/jsx-runtime");
6664
- var PaperFieldMesh = (0, import_react16.forwardRef)(
6933
+ var PaperFieldMesh = (0, import_react17.forwardRef)(
6665
6934
  function PaperFieldMesh2(props, ref) {
6666
6935
  const reduced = usePrefersReducedMotion(props.reducedMotion);
6667
- const papers = (0, import_react16.useMemo)(
6936
+ const papers = (0, import_react17.useMemo)(
6668
6937
  () => effectiveFieldPapers(props.papers, props.images),
6669
6938
  [props.papers, props.images]
6670
6939
  );
6671
6940
  const stablePapers = useStable(papers);
6672
6941
  const stablePreset = useStable(props.preset ?? null);
6673
- const groups = (0, import_react16.useMemo)(
6942
+ const groups = (0, import_react17.useMemo)(
6674
6943
  () => groupFieldPapers(stablePapers, stablePreset ?? void 0),
6675
6944
  [stablePapers, stablePreset]
6676
6945
  );
@@ -6679,22 +6948,22 @@ var PaperFieldMesh = (0, import_react16.forwardRef)(
6679
6948
  const layout = getLayout(layoutId);
6680
6949
  const firstSheet = groups[0]?.config.sheet;
6681
6950
  const stableLayoutOptions = useStable(props.layoutOptions ?? {});
6682
- const layoutOptions = (0, import_react16.useMemo)(
6951
+ const layoutOptions = (0, import_react17.useMemo)(
6683
6952
  // Sheet grids size their cells from the papers themselves — gutter is
6684
6953
  // then literally the spacing between stamps (explicit cell dims win).
6685
6954
  () => resolveLayoutOptions(layoutId, layout, stableLayoutOptions, firstSheet),
6686
6955
  [layoutId, stableLayoutOptions, firstSheet?.width, firstSheet?.height]
6687
6956
  );
6688
- const phaseRef = (0, import_react16.useRef)(0);
6689
- const dragVelRef = (0, import_react16.useRef)(0);
6690
- const mountTimeRef = (0, import_react16.useRef)(-1);
6691
- const morphRef = (0, import_react16.useRef)({ from: null, t: 1 });
6692
- const prevLayout = (0, import_react16.useRef)({ id: layoutId, options: layoutOptions });
6693
- const gl = (0, import_fiber6.useThree)((s) => s.gl);
6957
+ const phaseRef = (0, import_react17.useRef)(0);
6958
+ const dragVelRef = (0, import_react17.useRef)(0);
6959
+ const mountTimeRef = (0, import_react17.useRef)(-1);
6960
+ const morphRef = (0, import_react17.useRef)({ from: null, t: 1 });
6961
+ const prevLayout = (0, import_react17.useRef)({ id: layoutId, options: layoutOptions });
6962
+ const gl = (0, import_fiber7.useThree)((s) => s.gl);
6694
6963
  const driver = reduced ? "none" : props.motion?.driver ?? "autoplay";
6695
6964
  const speed = props.motion?.speed ?? 0.5;
6696
6965
  const entranceType = reduced ? "none" : props.entrance?.type ?? "rise";
6697
- (0, import_react16.useEffect)(() => {
6966
+ (0, import_react17.useEffect)(() => {
6698
6967
  const prev = prevLayout.current;
6699
6968
  if (prev.id !== layoutId || JSON.stringify(prev.options) !== JSON.stringify(layoutOptions)) {
6700
6969
  morphRef.current = { from: prev, t: 0 };
@@ -6702,7 +6971,7 @@ var PaperFieldMesh = (0, import_react16.forwardRef)(
6702
6971
  prevLayout.current = { id: layoutId, options: layoutOptions };
6703
6972
  }
6704
6973
  }, [layoutId, layoutOptions]);
6705
- (0, import_react16.useEffect)(() => {
6974
+ (0, import_react17.useEffect)(() => {
6706
6975
  if (driver !== "drag") return;
6707
6976
  const el = gl.domElement;
6708
6977
  let lastX = null;
@@ -6728,7 +6997,7 @@ var PaperFieldMesh = (0, import_react16.forwardRef)(
6728
6997
  window.removeEventListener("pointerup", up);
6729
6998
  };
6730
6999
  }, [driver, gl]);
6731
- (0, import_fiber6.useFrame)(({ clock }, delta) => {
7000
+ (0, import_fiber7.useFrame)(({ clock }, delta) => {
6732
7001
  if (mountTimeRef.current < 0) mountTimeRef.current = clock.elapsedTime;
6733
7002
  if (driver === "autoplay") phaseRef.current += delta * speed * 0.02;
6734
7003
  if (driver === "drag") {
@@ -6786,14 +7055,14 @@ var PaperFieldMesh = (0, import_react16.forwardRef)(
6786
7055
  }
6787
7056
  );
6788
7057
  function FitCamera(meshProps) {
6789
- const camera = (0, import_fiber6.useThree)((s) => s.camera);
6790
- const width = (0, import_fiber6.useThree)((s) => s.size.width);
6791
- const height = (0, import_fiber6.useThree)((s) => s.size.height);
7058
+ const camera = (0, import_fiber7.useThree)((s) => s.camera);
7059
+ const width = (0, import_fiber7.useThree)((s) => s.size.width);
7060
+ const height = (0, import_fiber7.useThree)((s) => s.size.height);
6792
7061
  const papersProp = useStable(meshProps.papers ?? null);
6793
7062
  const imagesProp = useStable(meshProps.images ?? null);
6794
7063
  const presetProp = useStable(meshProps.preset ?? null);
6795
7064
  const optionsProp = useStable(meshProps.layoutOptions ?? {});
6796
- const field = (0, import_react16.useMemo)(() => {
7065
+ const field = (0, import_react17.useMemo)(() => {
6797
7066
  const papers = effectiveFieldPapers(papersProp ?? void 0, imagesProp ?? void 0);
6798
7067
  const layoutId = meshProps.layout ?? "ring";
6799
7068
  const layout = getLayout(layoutId);
@@ -6801,8 +7070,8 @@ function FitCamera(meshProps) {
6801
7070
  const options = resolveLayoutOptions(layoutId, layout, optionsProp, sheet2);
6802
7071
  return { layout, n: papers.length, options, sheet: sheet2 ?? DEFAULT_SHEET };
6803
7072
  }, [papersProp, imagesProp, presetProp, meshProps.layout, optionsProp]);
6804
- (0, import_react16.useEffect)(() => {
6805
- if (!(camera instanceof THREE15.PerspectiveCamera)) return;
7073
+ (0, import_react17.useEffect)(() => {
7074
+ if (!(camera instanceof THREE16.PerspectiveCamera)) return;
6806
7075
  const { position, target } = fitCamera(
6807
7076
  field.layout,
6808
7077
  field.n,
@@ -6817,33 +7086,25 @@ function FitCamera(meshProps) {
6817
7086
  }, [camera, width, height, field]);
6818
7087
  return null;
6819
7088
  }
6820
- var PaperField = (0, import_react16.forwardRef)(function PaperField2({ children, className, style, ...meshProps }, ref) {
6821
- const registry4 = (0, import_react16.useMemo)(() => new DropZoneRegistry(), []);
6822
- const a11yRef = (0, import_react16.useRef)(null);
6823
- const papers = (0, import_react16.useMemo)(
7089
+ var PaperField = (0, import_react17.forwardRef)(function PaperField2({ children, className, style, scene, ...meshProps }, ref) {
7090
+ const rig = sceneSchema.parse(scene ?? {});
7091
+ const registry4 = (0, import_react17.useMemo)(() => new DropZoneRegistry(), []);
7092
+ const a11yRef = (0, import_react17.useRef)(null);
7093
+ const papers = (0, import_react17.useMemo)(
6824
7094
  () => effectiveFieldPapers(meshProps.papers, meshProps.images),
6825
7095
  [meshProps.papers, meshProps.images]
6826
7096
  );
6827
7097
  const stablePapers = useStable(papers);
6828
7098
  const stablePreset = useStable(meshProps.preset ?? null);
6829
- const interactive = (0, import_react16.useMemo)(
7099
+ const interactive = (0, import_react17.useMemo)(
6830
7100
  () => fieldIsInteractive(stablePapers, stablePreset ?? void 0, meshProps.interactive),
6831
7101
  [stablePapers, stablePreset, meshProps.interactive]
6832
7102
  );
6833
7103
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className, style: { width: "100%", height: "100%", ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(DropZoneContext.Provider, { value: registry4, children: [
6834
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_fiber6.Canvas, { shadows: true, camera: { position: [0, 0.6, 5.2], fov: 45 }, dpr: [1, 2], children: [
7104
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_fiber7.Canvas, { shadows: true, camera: { position: [0, 0.6, 5.2], fov: 45 }, dpr: [1, 2], children: [
6835
7105
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FitCamera, { ...meshProps }),
6836
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ambientLight", { intensity: 0.7 }),
6837
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
6838
- "directionalLight",
6839
- {
6840
- position: [3, 5, 4],
6841
- intensity: 1.4,
6842
- castShadow: true,
6843
- "shadow-mapSize": [1024, 1024],
6844
- "shadow-normalBias": 0.05
6845
- }
6846
- ),
7106
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PaperBackdrop, { backdrop: rig.backdrop }),
7107
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PaperLighting, { preset: rig.lighting, light: rig.light, floor: -2.4, scale: 14 }),
6847
7108
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PaperFieldMesh, { ref, a11yControllerRef: a11yRef, ...meshProps }),
6848
7109
  children
6849
7110
  ] }),
@@ -6895,7 +7156,8 @@ function diffConfig(config) {
6895
7156
  } else if (config.physics !== "none") {
6896
7157
  out.physics = config.physics;
6897
7158
  }
6898
- if (config.scene.lighting !== "studio") out.scene = { lighting: config.scene.lighting };
7159
+ const scene = diffAgainst(config.scene, sceneSchema.parse({}));
7160
+ if (Object.keys(scene).length > 0) out.scene = scene;
6899
7161
  if (config.onTwos) out.onTwos = true;
6900
7162
  if (config.states) out.states = config.states;
6901
7163
  const meta = diffAgainst(config.meta, paperConfigSchema.parse({}).meta);
@@ -6906,14 +7168,35 @@ function jsxValue(value) {
6906
7168
  if (typeof value === "string") return `"${value}"`;
6907
7169
  return `{${JSON.stringify(value)}}`;
6908
7170
  }
7171
+ function withoutUploads(value) {
7172
+ let replaced = 0;
7173
+ const walk = (node) => {
7174
+ if (typeof node === "string") {
7175
+ if (!node.startsWith("data:")) return node;
7176
+ replaced++;
7177
+ const extension = node.startsWith("data:image/png") ? "png" : "jpg";
7178
+ return `/paperlab-image-${replaced}.${extension}`;
7179
+ }
7180
+ if (Array.isArray(node)) return node.map(walk);
7181
+ if (node && typeof node === "object") {
7182
+ return Object.fromEntries(Object.entries(node).map(([k, v]) => [k, walk(v)]));
7183
+ }
7184
+ return node;
7185
+ };
7186
+ return { value: walk(value), replaced };
7187
+ }
7188
+ var UPLOAD_NOTE = "// Uploaded pictures cannot travel in a snippet \u2014 the paths below are\n// stand-ins, in order. Point them at your own files.";
6909
7189
  function buildJsxSnippet(config) {
6910
- const diff = diffConfig(config);
6911
- delete diff.meta;
7190
+ const diffed = diffConfig(config);
7191
+ delete diffed.meta;
7192
+ const { value: diff, replaced } = withoutUploads(diffed);
6912
7193
  const props = Object.entries(diff).map(([key, value]) => ` ${key}=${jsxValue(value)}`);
6913
7194
  if (props.length === 0) return "<Paper />";
6914
- return `<Paper
7195
+ const snippet = `<Paper
6915
7196
  ${props.join("\n")}
6916
7197
  />`;
7198
+ return replaced > 0 ? `${UPLOAD_NOTE}
7199
+ ${snippet}` : snippet;
6917
7200
  }
6918
7201
 
6919
7202
  // src/config/agent-payload.ts
@@ -6963,7 +7246,10 @@ function componentName(config) {
6963
7246
  }
6964
7247
  function buildAgentPayload(config) {
6965
7248
  const name = componentName(config);
6966
- const preset = JSON.stringify(diffConfig(config), null, 2);
7249
+ const { value: stripped, replaced } = withoutUploads(diffConfig(config));
7250
+ const preset = JSON.stringify(stripped, null, 2);
7251
+ const uploadNote = replaced > 0 ? `${UPLOAD_NOTE}
7252
+ ` : "";
6967
7253
  return `Integrate a Paperlab paper component into this project. (paperlab agent-payload v${AGENT_PAYLOAD_VERSION})
6968
7254
 
6969
7255
  1. Install the dependencies:
@@ -6976,7 +7262,7 @@ function buildAgentPayload(config) {
6976
7262
  \`\`\`tsx
6977
7263
  import { Paper, type PaperConfigInput } from 'paperlab'
6978
7264
 
6979
- const preset = ${preset.replace(/\n/g, "\n")} satisfies PaperConfigInput
7265
+ ${uploadNote}const preset = ${preset.replace(/\n/g, "\n")} satisfies PaperConfigInput
6980
7266
 
6981
7267
  export function ${name}() {
6982
7268
  return <Paper preset={preset} />
@@ -7019,7 +7305,9 @@ function distinctFieldPresets(input) {
7019
7305
  function nonDefault(value, defaults) {
7020
7306
  const out = {};
7021
7307
  for (const [k, v] of Object.entries(value ?? {})) {
7022
- if (v !== void 0 && v !== defaults[k]) out[k] = v;
7308
+ if (v === void 0) continue;
7309
+ const same = v === defaults[k] || JSON.stringify(v) === JSON.stringify(defaults[k]);
7310
+ if (!same) out[k] = v;
7023
7311
  }
7024
7312
  return out;
7025
7313
  }
@@ -7032,6 +7320,11 @@ function diffFieldProps(input) {
7032
7320
  if (Object.keys(motion).length > 0) out.motion = motion;
7033
7321
  const entrance = nonDefault(input.entrance, ENTRANCE_DEFAULTS);
7034
7322
  if (Object.keys(entrance).length > 0) out.entrance = entrance;
7323
+ const scene = nonDefault(
7324
+ sceneSchema.parse(input.scene ?? {}),
7325
+ sceneSchema.parse({})
7326
+ );
7327
+ if (Object.keys(scene).length > 0) out.scene = scene;
7035
7328
  return out;
7036
7329
  }
7037
7330
  var LAYOUT_PHRASES = {
@@ -7065,7 +7358,7 @@ function describeFieldConfig(input) {
7065
7358
  parts.push("hovering peels/reacts per paper (interaction states)");
7066
7359
  if (zones.length > 0) {
7067
7360
  parts.push(
7068
- `dragging one past its tear threshold detaches it to carry to the ${zones.map((z26) => z26.id).join(" or ")} zone (release elsewhere flutters it back)`
7361
+ `dragging one past its tear threshold detaches it to carry to the ${zones.map((z25) => z25.id).join(" or ")} zone (release elsewhere flutters it back)`
7069
7362
  );
7070
7363
  }
7071
7364
  } else {
@@ -7174,7 +7467,7 @@ required; the component needs no props.`;
7174
7467
  }
7175
7468
 
7176
7469
  // src/field/parity.ts
7177
- var THREE16 = __toESM(require("three"), 1);
7470
+ var THREE17 = __toESM(require("three"), 1);
7178
7471
  var GRID = 48;
7179
7472
  var PARITY_EPSILON = 5e-4;
7180
7473
  var parityCases = [
@@ -7450,7 +7743,7 @@ function runParityHarness(canvas) {
7450
7743
  if (!gl.getExtension("EXT_color_buffer_float")) {
7451
7744
  throw new Error("[paperlab parity] EXT_color_buffer_float unavailable");
7452
7745
  }
7453
- const point = new THREE16.Vector3();
7746
+ const point = new THREE17.Vector3();
7454
7747
  const parity = parityCases.map((c) => {
7455
7748
  const gpu = runCaseOnGPU(gl, c);
7456
7749
  let maxError = 0;
@@ -7481,17 +7774,21 @@ function runParityHarness(canvas) {
7481
7774
  LightRig,
7482
7775
  PARITY_EPSILON,
7483
7776
  Paper,
7777
+ PaperBackdrop,
7484
7778
  PaperField,
7485
7779
  PaperFieldMesh,
7486
7780
  PaperLighting,
7487
7781
  PaperMesh,
7488
7782
  SAG_TOL,
7783
+ backdropSchema,
7489
7784
  behaviorConfigSchema,
7490
7785
  buildAgentPayload,
7491
7786
  buildFieldAgentPayload,
7492
7787
  buildFieldComponentSource,
7493
7788
  buildJsxSnippet,
7494
7789
  clothConfigSchema,
7790
+ contentNames,
7791
+ contentSchemaFor,
7495
7792
  coreStateNames,
7496
7793
  describeConfig,
7497
7794
  describeFieldConfig,
@@ -7528,6 +7825,7 @@ function runParityHarness(canvas) {
7528
7825
  resolveLighting,
7529
7826
  resolveStateConfig,
7530
7827
  runParityHarness,
7828
+ sceneSchema,
7531
7829
  segmentsForArc,
7532
7830
  segmentsForSine,
7533
7831
  serializePreset,
@@ -7541,6 +7839,7 @@ function runParityHarness(canvas) {
7541
7839
  unregisterPreset,
7542
7840
  usePaperStates,
7543
7841
  usePrefersReducedMotion,
7842
+ washSchema,
7544
7843
  wrapLines
7545
7844
  });
7546
7845
  //# sourceMappingURL=index.cjs.map