{"id":"darkroom","title":"Darkroom","category":"Media","prompt":"A contact sheet that grades itself. Three photographs — a black-and-white pier, a misted ridge, a pastel sunset sea — arrive on white mats that slide up one after another, and each is shown TWICE: the original beside its graded twin, labelled BEFORE and AFTER. A sepia wash, a cool lift and a golden-hour plate land on the AFTER frames in turn, each named beneath its pair. Then the review pass draws over the top — a gold ring fading in on the ridge's focal point with a pointer tag, a rule-of-thirds grid flashing across the third frame, a red PROOF stamp landing on the pier. End card: images in · edits on · video out.","designedBy":{"provider":"anthropic","model":"claude-fable-5","inference":{"inputTokens":0,"outputTokens":0,"costUsd":0,"inPerMTokUsd":10,"outPerMTokUsd":50,"interactive":true}},"render":{"jobId":"rnd_1788538265931_dywymk","credits":10,"priceUsd":0.15,"format":"mp4","renderMs":47535,"frameCount":300,"bytes":439044,"seed":1777706395,"engineVersion":"v0.5.6","width":1440,"height":1080,"fps":30,"durationSeconds":10},"howToReproduce":{"renderApi":"POST /v1/renders with `renderRequest` below as the body (Authorization: Bearer <your pp_sa_… key>).","editor":"/gallery/darkroom/editor opens this piece live in the studio.","codeConsole":"/gallery/darkroom.js is this piece as PinePaper code — paste it into the editor's AI code console."},"renderRequest":{"scene":{"canvas":{"width":800,"height":600},"items":[],"relations":[],"generators":[],"media":[],"skeletons":[],"background":"#0b0b10","engineOps":[{"tool":"execute_custom_code","args":{"description":"Darkroom: three photographs, before/after blend-mode grades, curtain reveals, drawn annotation, triptych assembly","code":"var CX = [170, 400, 630], PW = 220, PH = 330, PY = 290;\nvar RAW = ['/scene-assets/panel-dark-forest.jpg', '/scene-assets/panel-old-harbor.jpg', '/scene-assets/panel-red-city.jpg'];\nvar SRCS = RAW.map(function (p) { return (window.__ppAssets && window.__ppAssets[p]) || p; });\n// The AFTER treatment is a TRANSLUCENT GRADE PLATE, not the GPU filter API.\n// applyImageFilter runs through an offscreen canvas that the server engine\n// cannot allocate — it took the whole render down with 'out of memory' on\n// the first captured frame, every time. Blend modes are synchronous, are\n// what the storm-watch grade already uses, and say what they are.\nvar GRADES = [\n  { label: 'GRADE: sepia wash', color: '#b57a33', op: 0.42 },\n  { label: 'GRADE: cool lift', color: '#2f7fd8', op: 0.30 },\n  { label: 'GRADE: golden hour', color: '#f0871f', op: 0.38 }\n];\nvar CAPS = ['01 — the pier', '02 — the ridge', '03 — the burn'];\n// WHAT MOVES AND WHAT DOES NOT, in this renderer:\n//   raw paper.Raster photographs hold still AND draw above every app item,\n//     so nothing composites over them; keyframes on one never advance and\n//     applyAnimatedMask clips it away for good. All three were tried and\n//     all three shipped blank mats. PinePaper.create('image', ...) has\n//     none of those problems — it is an app item like any other.\n//   everything the app creates (rectangles, text) animates normally.\n// So the edit is shown SPATIALLY — before beside after, inside one mat —\n// and every reveal is a mat-coloured curtain fading off the top.\nvar HW = PW / 2 - 6;\nfor (var p = 0; p < 3; p++) (function (p) {\n  var t0 = 0.5 + p * 0.7;\n  var mat = PinePaper.create('rectangle', { x: CX[p], y: PY, width: PW + 18, height: PH + 18, fillColor: '#f4f1ea', opacity: 0 });\n  PinePaper.modifyItem(mat, { animationType: 'keyframe', duration: 10, keyframes: [\n    { time: 0, properties: { opacity: 0, y: PY + 30 } },\n    { time: t0, properties: { opacity: 0, y: PY + 30 } },\n    { time: t0 + 0.5, properties: { opacity: 1, y: PY }, easing: 'easeOut' },\n    { time: 10, properties: { opacity: 1, y: PY } }\n  ] });\n  // BEFORE (left) and AFTER (right) — the same photograph, one of them\n  // under a grade plate, side by side so the edit reads at a glance.\n  var bx = CX[p] - PW / 4 - 3, ax = CX[p] + PW / 4 + 3;\n  var PHW = Math.round(HW), PHH = Math.round(HW * 1.5);\n  // These are the ENGINE's own image items, not raw paper.Raster objects.\n  // A raw Raster draws above every app-created item regardless of the\n  // order it was made in, so nothing can ever be composited over one —\n  // the grade plate was invisible in every frame until this changed. An\n  // image item lives in the app's tree, where z-order behaves.\n  PinePaper.create('image', { src: SRCS[p], x: bx, y: PY - 10, width: PHW, height: PHH });\n  PinePaper.create('image', { src: SRCS[p], x: ax, y: PY - 10, width: PHW, height: PHH });\n  // The grade: a translucent plate over the AFTER frame only, fading in\n  // as its pass lands. This visible difference is what the piece is for.\n  var grade = PinePaper.create('rectangle', { x: ax, y: PY - 10, width: PHW, height: PHH, fillColor: GRADES[p].color, opacity: 0 });\n  PinePaper.modifyItem(grade, { animationType: 'keyframe', duration: 10, keyframes: [\n    { time: 0, properties: { opacity: 0 } },\n    { time: 3.0 + p * 0.7, properties: { opacity: 0 } },\n    { time: 3.7 + p * 0.7, properties: { opacity: GRADES[p].op }, easing: 'easeInOut' },\n    { time: 10, properties: { opacity: GRADES[p].op } }\n  ] });\n  var lblB = PinePaper.create('text', { x: bx, y: PY + PH * 0.34, content: 'BEFORE', fontSize: 16, fillColor: '#6b7280', opacity: 0 });\n  var lblA = PinePaper.create('text', { x: ax, y: PY + PH * 0.34, content: 'AFTER', fontSize: 16, fontWeight: 'bold', fillColor: '#15803d', opacity: 0 });\n  [lblB, lblA].forEach(function (id, k) {\n    PinePaper.modifyItem(id, { animationType: 'keyframe', duration: 10, keyframes: [\n      { time: 0, properties: { opacity: 0 } },\n      { time: t0 + 1.5 + k * 0.25, properties: { opacity: 0 } },\n      { time: t0 + 1.9 + k * 0.25, properties: { opacity: 0.95 } },\n      { time: 10, properties: { opacity: 0.95 } }\n    ] });\n  });\n  var badge = PinePaper.create('text', { x: CX[p], y: PY + PH / 2 + 34, content: GRADES[p].label, fontSize: 16, fillColor: '#a3e635', opacity: 0 });\n  PinePaper.modifyItem(badge, { animationType: 'keyframe', duration: 10, keyframes: [\n    { time: 0, properties: { opacity: 0 } },\n    { time: 3.0 + p * 0.7, properties: { opacity: 0 } },\n    { time: 3.4 + p * 0.7, properties: { opacity: 0.95 } },\n    { time: 10, properties: { opacity: 0.95 } }\n  ] });\n  var cap = PinePaper.create('text', { x: CX[p], y: PY + PH / 2 + 58, content: CAPS[p], fontSize: 16, fillColor: '#9fb0c4', opacity: 0 });\n  PinePaper.modifyItem(cap, { animationType: 'keyframe', duration: 10, keyframes: [\n    { time: 0, properties: { opacity: 0 } },\n    { time: 6.2 + p * 0.25, properties: { opacity: 0 } },\n    { time: 6.7 + p * 0.25, properties: { opacity: 0.9 } },\n    { time: 10, properties: { opacity: 0.9 } }\n  ] });\n})(p);\n// Annotation pass — authored AFTER the photographs so it sits above them.\nvar ring = PinePaper.create('circle', { x: 458, y: 252, radius: 30, fillColor: null, strokeColor: '#fbbf24', strokeWidth: 2.5, opacity: 0 });\n// Opacity, not trimEnd: the trim-path draw-on never advanced here, so the\n// ring simply never appeared. A fade lands.\nPinePaper.modifyItem(ring, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0 } },\n  { time: 4.2, properties: { opacity: 0 } },\n  { time: 4.9, properties: { opacity: 0.95 }, easing: 'easeInOut' },\n  { time: 10, properties: { opacity: 0.95 } }\n] });\nvar tagLine = PinePaper.create('line', { from: [480, 234], to: [548, 196], strokeColor: '#fbbf24', strokeWidth: 1.5, opacity: 0 });\nPinePaper.modifyItem(tagLine, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0 } }, { time: 5.0, properties: { opacity: 0 } },\n  { time: 5.3, properties: { opacity: 0.95 } }, { time: 10, properties: { opacity: 0.95 } }\n] });\nvar tag = PinePaper.create('text', { x: 596, y: 190, content: 'focal point', fontSize: 16, fillColor: '#fbbf24', opacity: 0 });\nPinePaper.modifyItem(tag, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0 } }, { time: 5.3, properties: { opacity: 0 } },\n  { time: 5.6, properties: { opacity: 0.95 } }, { time: 10, properties: { opacity: 0.95 } }\n] });\n// Rule-of-thirds grid over the THIRD PAIR'S AFTER FRAME only. It used to\n// span the whole panel width, so it drew across both photographs and out\n// over the bare mat — a guide that marks nothing in particular.\nvar AX3 = CX[2] + PW / 4 + 3, PHW = Math.round(HW), PHH = Math.round(HW * 1.5);\nfor (var g = 0; g < 4; g++) {\n  var vert = g < 2;\n  var gx = vert ? (AX3 - PHW / 2 + (g + 1) * PHW / 3) : AX3;\n  var gy = vert ? PY - 10 : (PY - 10 - PHH / 2 + (g - 1) * PHH / 3);\n  var gl = PinePaper.create('line', {\n    from: vert ? [gx, PY - 10 - PHH / 2] : [AX3 - PHW / 2, gy],\n    to: vert ? [gx, PY - 10 + PHH / 2] : [AX3 + PHW / 2, gy],\n    strokeColor: '#7dd3fc', strokeWidth: 1.2, opacity: 0 });\n  PinePaper.modifyItem(gl, { animationType: 'keyframe', duration: 10, keyframes: [\n    { time: 0, properties: { opacity: 0 } }, { time: 4.0 + g * 0.12, properties: { opacity: 0 } },\n    { time: 4.3 + g * 0.12, properties: { opacity: 0.9 } }, { time: 5.6, properties: { opacity: 0.9 } },\n    { time: 6.1, properties: { opacity: 0.25 } }, { time: 10, properties: { opacity: 0.25 } }\n  ] });\n}\n// PROOF stamp landing on panel 1's corner.\nvar stamp = PinePaper.create('text', { x: 214, y: 168, content: 'PROOF', fontSize: 20, fontWeight: 'bold', fillColor: '#ef4444', opacity: 0, rotation: -18 });\nPinePaper.modifyItem(stamp, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0, scale: 1.8 } },\n  { time: 5.8, properties: { opacity: 0, scale: 1.8 } },\n  { time: 6.05, properties: { opacity: 0.9, scale: 1 }, easing: 'easeIn' },\n  { time: 10, properties: { opacity: 0.9, scale: 1 } }\n] });\n// Session chrome: title + end card.\nvar title = PinePaper.create('text', { x: 108, y: 52, content: '', fontSize: 24, fontWeight: 'bold', fillColor: '#e2e8f0' });\nPinePaper.modifyItem(title, { animationType: 'keyframe', duration: 10, keyframes: [{ time: 0, properties: { content: '' } }, { time: 0.30, properties: { content: '' } }, { time: 0.390, properties: { content: 'D' } }, { time: 0.480, properties: { content: 'DA' } }, { time: 0.570, properties: { content: 'DAR' } }, { time: 0.660, properties: { content: 'DARK' } }, { time: 0.750, properties: { content: 'DARKR' } }, { time: 0.840, properties: { content: 'DARKRO' } }, { time: 0.930, properties: { content: 'DARKROO' } }, { time: 1.020, properties: { content: 'DARKROOM' } }, { time: 10.00, properties: { content: 'DARKROOM' } }] });\nvar sub = PinePaper.create('text', { x: 268, y: 80, content: 'three photographs · before and after · one render', fontSize: 16, fillColor: '#b6c2d4', opacity: 0 });\nPinePaper.modifyItem(sub, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0 } }, { time: 1.6, properties: { opacity: 0 } },\n  { time: 2.2, properties: { opacity: 0.85 } }, { time: 10, properties: { opacity: 0.85 } }\n] });\nvar end = PinePaper.create('text', { x: 400, y: 560, content: 'images in · edits on · video out', fontSize: 18, fillColor: '#dbe4f0', opacity: 0 });\nPinePaper.modifyItem(end, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0 } }, { time: 8.2, properties: { opacity: 0 } },\n  { time: 8.9, properties: { opacity: 0.95 } }, { time: 10, properties: { opacity: 0.95 } }\n] });"}}]},"exportProfile":{"format":"mp4","resolution":"1080p","fps":30,"durationSeconds":6},"source":{"prompt":"A contact sheet that grades itself. Three photographs — a black-and-white pier, a misted ridge, a pastel sunset sea — arrive on white mats that slide up one after another, and each is shown TWICE: the original beside its graded twin, labelled BEFORE and AFTER. A sepia wash, a cool lift and a golden-hour plate land on the AFTER frames in turn, each named beneath its pair. Then the review pass draws over the top — a gold ring fading in on the ridge's focal point with a pointer tag, a rule-of-thirds grid flashing across the third frame, a red PROOF stamp landing on the pier. End card: images in · edits on · video out.","provider":"anthropic","model":"claude-fable-5","app":"gallery-reproduction"}},"ops":[{"tool":"pinepaper_set_background_color","args":{"color":"#0b0b10"}},{"tool":"pinepaper_execute_custom_code","args":{"description":"Darkroom: three photographs, before/after blend-mode grades, curtain reveals, drawn annotation, triptych assembly","code":"var CX = [170, 400, 630], PW = 220, PH = 330, PY = 290;\nvar RAW = ['/scene-assets/panel-dark-forest.jpg', '/scene-assets/panel-old-harbor.jpg', '/scene-assets/panel-red-city.jpg'];\nvar SRCS = RAW.map(function (p) { return (window.__ppAssets && window.__ppAssets[p]) || p; });\n// The AFTER treatment is a TRANSLUCENT GRADE PLATE, not the GPU filter API.\n// applyImageFilter runs through an offscreen canvas that the server engine\n// cannot allocate — it took the whole render down with 'out of memory' on\n// the first captured frame, every time. Blend modes are synchronous, are\n// what the storm-watch grade already uses, and say what they are.\nvar GRADES = [\n  { label: 'GRADE: sepia wash', color: '#b57a33', op: 0.42 },\n  { label: 'GRADE: cool lift', color: '#2f7fd8', op: 0.30 },\n  { label: 'GRADE: golden hour', color: '#f0871f', op: 0.38 }\n];\nvar CAPS = ['01 — the pier', '02 — the ridge', '03 — the burn'];\n// WHAT MOVES AND WHAT DOES NOT, in this renderer:\n//   raw paper.Raster photographs hold still AND draw above every app item,\n//     so nothing composites over them; keyframes on one never advance and\n//     applyAnimatedMask clips it away for good. All three were tried and\n//     all three shipped blank mats. PinePaper.create('image', ...) has\n//     none of those problems — it is an app item like any other.\n//   everything the app creates (rectangles, text) animates normally.\n// So the edit is shown SPATIALLY — before beside after, inside one mat —\n// and every reveal is a mat-coloured curtain fading off the top.\nvar HW = PW / 2 - 6;\nfor (var p = 0; p < 3; p++) (function (p) {\n  var t0 = 0.5 + p * 0.7;\n  var mat = PinePaper.create('rectangle', { x: CX[p], y: PY, width: PW + 18, height: PH + 18, fillColor: '#f4f1ea', opacity: 0 });\n  PinePaper.modifyItem(mat, { animationType: 'keyframe', duration: 10, keyframes: [\n    { time: 0, properties: { opacity: 0, y: PY + 30 } },\n    { time: t0, properties: { opacity: 0, y: PY + 30 } },\n    { time: t0 + 0.5, properties: { opacity: 1, y: PY }, easing: 'easeOut' },\n    { time: 10, properties: { opacity: 1, y: PY } }\n  ] });\n  // BEFORE (left) and AFTER (right) — the same photograph, one of them\n  // under a grade plate, side by side so the edit reads at a glance.\n  var bx = CX[p] - PW / 4 - 3, ax = CX[p] + PW / 4 + 3;\n  var PHW = Math.round(HW), PHH = Math.round(HW * 1.5);\n  // These are the ENGINE's own image items, not raw paper.Raster objects.\n  // A raw Raster draws above every app-created item regardless of the\n  // order it was made in, so nothing can ever be composited over one —\n  // the grade plate was invisible in every frame until this changed. An\n  // image item lives in the app's tree, where z-order behaves.\n  PinePaper.create('image', { src: SRCS[p], x: bx, y: PY - 10, width: PHW, height: PHH });\n  PinePaper.create('image', { src: SRCS[p], x: ax, y: PY - 10, width: PHW, height: PHH });\n  // The grade: a translucent plate over the AFTER frame only, fading in\n  // as its pass lands. This visible difference is what the piece is for.\n  var grade = PinePaper.create('rectangle', { x: ax, y: PY - 10, width: PHW, height: PHH, fillColor: GRADES[p].color, opacity: 0 });\n  PinePaper.modifyItem(grade, { animationType: 'keyframe', duration: 10, keyframes: [\n    { time: 0, properties: { opacity: 0 } },\n    { time: 3.0 + p * 0.7, properties: { opacity: 0 } },\n    { time: 3.7 + p * 0.7, properties: { opacity: GRADES[p].op }, easing: 'easeInOut' },\n    { time: 10, properties: { opacity: GRADES[p].op } }\n  ] });\n  var lblB = PinePaper.create('text', { x: bx, y: PY + PH * 0.34, content: 'BEFORE', fontSize: 16, fillColor: '#6b7280', opacity: 0 });\n  var lblA = PinePaper.create('text', { x: ax, y: PY + PH * 0.34, content: 'AFTER', fontSize: 16, fontWeight: 'bold', fillColor: '#15803d', opacity: 0 });\n  [lblB, lblA].forEach(function (id, k) {\n    PinePaper.modifyItem(id, { animationType: 'keyframe', duration: 10, keyframes: [\n      { time: 0, properties: { opacity: 0 } },\n      { time: t0 + 1.5 + k * 0.25, properties: { opacity: 0 } },\n      { time: t0 + 1.9 + k * 0.25, properties: { opacity: 0.95 } },\n      { time: 10, properties: { opacity: 0.95 } }\n    ] });\n  });\n  var badge = PinePaper.create('text', { x: CX[p], y: PY + PH / 2 + 34, content: GRADES[p].label, fontSize: 16, fillColor: '#a3e635', opacity: 0 });\n  PinePaper.modifyItem(badge, { animationType: 'keyframe', duration: 10, keyframes: [\n    { time: 0, properties: { opacity: 0 } },\n    { time: 3.0 + p * 0.7, properties: { opacity: 0 } },\n    { time: 3.4 + p * 0.7, properties: { opacity: 0.95 } },\n    { time: 10, properties: { opacity: 0.95 } }\n  ] });\n  var cap = PinePaper.create('text', { x: CX[p], y: PY + PH / 2 + 58, content: CAPS[p], fontSize: 16, fillColor: '#9fb0c4', opacity: 0 });\n  PinePaper.modifyItem(cap, { animationType: 'keyframe', duration: 10, keyframes: [\n    { time: 0, properties: { opacity: 0 } },\n    { time: 6.2 + p * 0.25, properties: { opacity: 0 } },\n    { time: 6.7 + p * 0.25, properties: { opacity: 0.9 } },\n    { time: 10, properties: { opacity: 0.9 } }\n  ] });\n})(p);\n// Annotation pass — authored AFTER the photographs so it sits above them.\nvar ring = PinePaper.create('circle', { x: 458, y: 252, radius: 30, fillColor: null, strokeColor: '#fbbf24', strokeWidth: 2.5, opacity: 0 });\n// Opacity, not trimEnd: the trim-path draw-on never advanced here, so the\n// ring simply never appeared. A fade lands.\nPinePaper.modifyItem(ring, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0 } },\n  { time: 4.2, properties: { opacity: 0 } },\n  { time: 4.9, properties: { opacity: 0.95 }, easing: 'easeInOut' },\n  { time: 10, properties: { opacity: 0.95 } }\n] });\nvar tagLine = PinePaper.create('line', { from: [480, 234], to: [548, 196], strokeColor: '#fbbf24', strokeWidth: 1.5, opacity: 0 });\nPinePaper.modifyItem(tagLine, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0 } }, { time: 5.0, properties: { opacity: 0 } },\n  { time: 5.3, properties: { opacity: 0.95 } }, { time: 10, properties: { opacity: 0.95 } }\n] });\nvar tag = PinePaper.create('text', { x: 596, y: 190, content: 'focal point', fontSize: 16, fillColor: '#fbbf24', opacity: 0 });\nPinePaper.modifyItem(tag, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0 } }, { time: 5.3, properties: { opacity: 0 } },\n  { time: 5.6, properties: { opacity: 0.95 } }, { time: 10, properties: { opacity: 0.95 } }\n] });\n// Rule-of-thirds grid over the THIRD PAIR'S AFTER FRAME only. It used to\n// span the whole panel width, so it drew across both photographs and out\n// over the bare mat — a guide that marks nothing in particular.\nvar AX3 = CX[2] + PW / 4 + 3, PHW = Math.round(HW), PHH = Math.round(HW * 1.5);\nfor (var g = 0; g < 4; g++) {\n  var vert = g < 2;\n  var gx = vert ? (AX3 - PHW / 2 + (g + 1) * PHW / 3) : AX3;\n  var gy = vert ? PY - 10 : (PY - 10 - PHH / 2 + (g - 1) * PHH / 3);\n  var gl = PinePaper.create('line', {\n    from: vert ? [gx, PY - 10 - PHH / 2] : [AX3 - PHW / 2, gy],\n    to: vert ? [gx, PY - 10 + PHH / 2] : [AX3 + PHW / 2, gy],\n    strokeColor: '#7dd3fc', strokeWidth: 1.2, opacity: 0 });\n  PinePaper.modifyItem(gl, { animationType: 'keyframe', duration: 10, keyframes: [\n    { time: 0, properties: { opacity: 0 } }, { time: 4.0 + g * 0.12, properties: { opacity: 0 } },\n    { time: 4.3 + g * 0.12, properties: { opacity: 0.9 } }, { time: 5.6, properties: { opacity: 0.9 } },\n    { time: 6.1, properties: { opacity: 0.25 } }, { time: 10, properties: { opacity: 0.25 } }\n  ] });\n}\n// PROOF stamp landing on panel 1's corner.\nvar stamp = PinePaper.create('text', { x: 214, y: 168, content: 'PROOF', fontSize: 20, fontWeight: 'bold', fillColor: '#ef4444', opacity: 0, rotation: -18 });\nPinePaper.modifyItem(stamp, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0, scale: 1.8 } },\n  { time: 5.8, properties: { opacity: 0, scale: 1.8 } },\n  { time: 6.05, properties: { opacity: 0.9, scale: 1 }, easing: 'easeIn' },\n  { time: 10, properties: { opacity: 0.9, scale: 1 } }\n] });\n// Session chrome: title + end card.\nvar title = PinePaper.create('text', { x: 108, y: 52, content: '', fontSize: 24, fontWeight: 'bold', fillColor: '#e2e8f0' });\nPinePaper.modifyItem(title, { animationType: 'keyframe', duration: 10, keyframes: [{ time: 0, properties: { content: '' } }, { time: 0.30, properties: { content: '' } }, { time: 0.390, properties: { content: 'D' } }, { time: 0.480, properties: { content: 'DA' } }, { time: 0.570, properties: { content: 'DAR' } }, { time: 0.660, properties: { content: 'DARK' } }, { time: 0.750, properties: { content: 'DARKR' } }, { time: 0.840, properties: { content: 'DARKRO' } }, { time: 0.930, properties: { content: 'DARKROO' } }, { time: 1.020, properties: { content: 'DARKROOM' } }, { time: 10.00, properties: { content: 'DARKROOM' } }] });\nvar sub = PinePaper.create('text', { x: 268, y: 80, content: 'three photographs · before and after · one render', fontSize: 16, fillColor: '#b6c2d4', opacity: 0 });\nPinePaper.modifyItem(sub, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0 } }, { time: 1.6, properties: { opacity: 0 } },\n  { time: 2.2, properties: { opacity: 0.85 } }, { time: 10, properties: { opacity: 0.85 } }\n] });\nvar end = PinePaper.create('text', { x: 400, y: 560, content: 'images in · edits on · video out', fontSize: 18, fillColor: '#dbe4f0', opacity: 0 });\nPinePaper.modifyItem(end, { animationType: 'keyframe', duration: 10, keyframes: [\n  { time: 0, properties: { opacity: 0 } }, { time: 8.2, properties: { opacity: 0 } },\n  { time: 8.9, properties: { opacity: 0.95 } }, { time: 10, properties: { opacity: 0.95 } }\n] });"}}],"license":"CC0-1.0"}