{"id":"sneaker-drop-ar","title":"Sneaker Drop — Arabic","category":"Template in your language","prompt":"The Sneaker Drop template with its words written in Arabic (not translated word for word): وصل الجديد · لايلاك رانر · 349 ر.س · للأعضاء أولاً · نعومة كالسحاب · نسيج معاد تدويره · تسوّق الآن","designedBy":{"provider":"pinepaper","model":"template","inference":{"measured":false,"estimated":true,"inputTokens":0,"outputTokens":0,"costUsd":0.4341,"basis":"estimated: the Sneaker Drop template's own design cost (its overlay, estimated as one chat turn); writing the words in is free"}},"render":{"credits":4,"priceUsd":0.06,"format":"mp4","bytes":2395187,"width":720,"height":1280,"fps":30,"durationSeconds":8},"howToReproduce":{"renderApi":"POST /v1/renders with `renderRequest` below as the body (Authorization: Bearer <your pp_sa_… key>).","chat":"Paste the prompt into /chat: an illustrated film brief is answered with a program like this one, and plays at once.","note":"This is a draw contract — a Canvas 2D program. It has no editor ops; it opens in the Cloud Studio as a film (/studio?gallery-film=sneaker-drop-ar), and it exports as MP4 or GIF, not as a still."},"template":{"id":"sneaker-drop","url":"/templates?t=sneaker-drop"},"renderRequest":{"drawContract":"// PinePaper film template \"sneaker-drop\" — an Omni Flash + Lyria + PinePaper ad, made a template.\n// The footage is the clip under the canvas (the render's baseVideo); with no clip, the plate part draws the photo.\nconst meta = { duration: 8, fps: 30, size: [1080, 1920], seed: 7, poster: 4.55 };\nconst template = { fields: [\n  { kind: \"clips\", label: \"Your clip\", min: 0, max: 1, minSeconds: 1.5, maxSeconds: 60, fill: true },\n  { entity: \"plate\", param: \"url\", label: \"Or a photo\", kind: \"image\" },\n  { entity: \"hook\", param: \"text\", label: \"Hook\", kind: \"text\" },\n  { entity: \"message\", param: \"text\", label: \"Product name\", kind: \"text\" },\n  { entity: \"price_burst\", param: \"text\", label: \"Price\", kind: \"text\" },\n  { entity: \"price_burst\", param: \"sub\", label: \"Offer\", kind: \"text\" },\n  { entity: \"benefit_1\", param: \"text\", label: \"Feature 1\", kind: \"text\" },\n  { entity: \"benefit_2\", param: \"text\", label: \"Feature 2\", kind: \"text\" },\n  { entity: \"cta\", param: \"text\", label: \"Button\", kind: \"text\" },\n  { entity: \"logo_bug\", param: \"name\", label: \"Brand name\", kind: \"text\" },\n  { entity: \"brand\", param: \"primary\", label: \"Brand colour\", kind: \"color\" },\n  { entity: \"brand\", param: \"accent\", label: \"Accent colour\", kind: \"color\" },\n] };\nconst AD = { W: 1080, H: 1920, f: { display: \"Anton\", dw: \"400\", body: \"Poppins\", bw: \"700\" }, c: { primary: \"#a78bfa\", primaryInk: \"#111114\", accent: \"#14b8a6\", accentInk: \"#111114\", ground: \"#0b0b12\", groundInk: \"#ffffff\", groundSoft: \"#bbbbbd\", hookInk: \"#ffffff\", edge: \"#0b0b0f\", scrim: \"rgba(8,9,12,0.66)\", chipBg: \"#ffffff\", chipInk: \"#14161c\", capBg: \"#ffffff\", capInk: \"#111114\", cardBg: \"#ffffff\", cardInk: \"#14161c\", cardSoft: \"#4a5060\", track: \"#e3e6ed\", accentOnCard: \"#14b8a6\", star: \"#14b8a6\", shadow: \"rgba(0,0,0,0.28)\", dim: \"rgba(12,12,14,0.5)\", divider: \"#ffffff\", bugPlate: \"rgba(255,255,255,0.94)\", logoPlate: \"#ffffff\" } };\nfunction clamp01(x) { return x < 0 ? 0 : x > 1 ? 1 : x; }\nfunction easeOutCubic(x) { x = clamp01(x); return 1 - Math.pow(1 - x, 3); }\nfunction easeInCubic(x) { x = clamp01(x); return x * x * x; }\nfunction easeOutBack(x) { x = clamp01(x); var c1 = 1.70158, c3 = c1 + 1; return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2); }\nfunction inOut(u, len, a, b) { var i = clamp01(u / a); var o = b > 0 ? clamp01((len - u) / b) : 1; return { i: i, o: o, a: Math.min(easeOutCubic(i), easeOutCubic(o)) }; }\nfunction fontOf(fam, weight, px) { return weight + \" \" + Math.round(px) + 'px \"' + fam + '\", sans-serif'; }\n\nfunction rr(ctx, x, y, w, h, r) {\n  r = Math.max(0, Math.min(r, w / 2, h / 2));\n  ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.quadraticCurveTo(x + w, y, x + w, y + r);\n  ctx.lineTo(x + w, y + h - r); ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); ctx.lineTo(x + r, y + h);\n  ctx.quadraticCurveTo(x, y + h, x, y + h - r); ctx.lineTo(x, y + r); ctx.quadraticCurveTo(x, y, x + r, y); ctx.closePath();\n}\n\nfunction wrapLines(ctx, text, maxW) {\n  var words = String(text).split(/\\s+/).filter(function (w) { return w.length > 0; });\n  var lines = [], cur = \"\";\n  for (var i = 0; i < words.length; i++) {\n    var w = words[i];\n    if (ctx.measureText(w).width > maxW) {\n      if (cur) { lines.push(cur); cur = \"\"; }\n      var piece = \"\";\n      for (var k = 0; k < w.length; k++) { var nx = piece + w[k]; if (ctx.measureText(nx).width > maxW && piece) { lines.push(piece); piece = w[k]; } else piece = nx; }\n      cur = piece; continue;\n    }\n    var next = cur ? cur + \" \" + w : w;\n    if (ctx.measureText(next).width <= maxW || !cur) cur = next; else { lines.push(cur); cur = w; }\n  }\n  if (cur) lines.push(cur);\n  return lines;\n}\n\nvar FIT = new Map();\n/** The largest size, from maxPx down to minPx, at which the text wraps into maxLines lines inside maxW \\u00D7 maxH. */\nfunction fit(ctx, text, fam, weight, maxW, maxH, maxPx, minPx, maxLines, lh) {\n  var key = [text, fam, weight, Math.round(maxW), Math.round(maxH), Math.round(maxPx), Math.round(minPx), maxLines, lh].join(\"|\");\n  var hit = FIT.get(key); if (hit) return hit;\n  var px = maxPx, lines = [], ok = false;\n  for (var n = 0; n < 60; n++) {\n    ctx.font = fontOf(fam, weight, px);\n    lines = wrapLines(ctx, text, maxW);\n    if (lines.length <= maxLines && lines.length * px * lh <= maxH) { ok = true; break; }\n    if (px <= minPx) break;\n    px = Math.max(minPx, px * 0.94);\n  }\n  if (!ok) {\n    ctx.font = fontOf(fam, weight, px);\n    var rows = Math.max(1, Math.min(maxLines, Math.floor(maxH / (px * lh))));\n    if (lines.length > rows) {\n      lines = lines.slice(0, rows);\n      var last = lines[rows - 1];\n      while (last.length > 1 && ctx.measureText(last + \"\\u2026\").width > maxW) last = last.slice(0, -1);\n      lines[rows - 1] = last.replace(/\\s+$/, \"\") + \"\\u2026\";\n    }\n  }\n  var out = { px: px, lines: lines, cut: !ok };\n  FIT.set(key, out);\n  return out;\n}\n\n/** Outline then fill: the outline is the separation from any footage (contract-screen's rule: a stroke of the same text). */\nfunction outlined(ctx, s, x, y, ink, edge, lw) {\n  ctx.lineJoin = \"round\"; ctx.miterLimit = 2;\n  if (lw > 0) { ctx.strokeStyle = edge; ctx.lineWidth = lw; ctx.strokeText(s, x, y); }\n  ctx.fillStyle = ink; ctx.fillText(s, x, y);\n}\n\nfunction scrimBand(ctx, x, y, w, h, color, alpha) {\n  var g = ctx.createLinearGradient(0, y, 0, y + h);\n  g.addColorStop(0, \"rgba(0,0,0,0)\");\n  g.addColorStop(0.22, color); g.addColorStop(0.78, color);\n  g.addColorStop(1, \"rgba(0,0,0,0)\");\n  ctx.save(); ctx.globalAlpha *= alpha; ctx.fillStyle = g; ctx.fillRect(x, y, w, h); ctx.restore();\n}\n\nfunction coverImage(ctx, img, x, y, w, h, zoom, fx, fy) {\n  var iw = img.width || 1, ih = img.height || 1;\n  var s = Math.max(w / iw, h / ih) * zoom;\n  var dw = iw * s, dh = ih * s;\n  ctx.save(); ctx.beginPath(); ctx.rect(x, y, w, h); ctx.clip();\n  ctx.drawImage(img, x + (w - dw) * fx, y + (h - dh) * fy, dw, dh);\n  ctx.restore();\n}\n\nfunction containRect(img, x, y, w, h, align) {\n  var iw = img.width || 1, ih = img.height || 1;\n  var s = Math.min(w / iw, h / ih), dw = iw * s, dh = ih * s;\n  return { x: align === \"left\" ? x : x + (w - dw) / 2, y: y + (h - dh) / 2, w: dw, h: dh };\n}\nfunction containImage(ctx, img, x, y, w, h, align) {\n  var r = containRect(img, x, y, w, h, align);\n  ctx.drawImage(img, r.x, r.y, r.w, r.h);\n  return r;\n}\n\nfunction arrow(ctx, x, y, s, color) {\n  ctx.save(); ctx.strokeStyle = color; ctx.lineWidth = Math.max(3, s * 0.16); ctx.lineCap = \"round\"; ctx.lineJoin = \"round\";\n  ctx.beginPath(); ctx.moveTo(x - s * 0.45, y); ctx.lineTo(x + s * 0.4, y); ctx.moveTo(x + s * 0.05, y - s * 0.38); ctx.lineTo(x + s * 0.42, y); ctx.lineTo(x + s * 0.05, y + s * 0.38); ctx.stroke(); ctx.restore();\n}\n\nfunction check(ctx, cx, cy, r, bg, ink) {\n  ctx.fillStyle = bg; ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.fill();\n  ctx.strokeStyle = ink; ctx.lineWidth = Math.max(2.5, r * 0.22); ctx.lineCap = \"round\"; ctx.lineJoin = \"round\";\n  ctx.beginPath(); ctx.moveTo(cx - r * 0.45, cy + r * 0.02); ctx.lineTo(cx - r * 0.1, cy + r * 0.36); ctx.lineTo(cx + r * 0.48, cy - r * 0.34); ctx.stroke();\n}\n\nfunction starShape(ctx, cx, cy, ro, ri, n, rot) {\n  ctx.beginPath();\n  for (var i = 0; i < n * 2; i++) { var r = i % 2 ? ri : ro, a = rot + (i * Math.PI) / n - Math.PI / 2; var px = cx + Math.cos(a) * r, py = cy + Math.sin(a) * r; if (i) ctx.lineTo(px, py); else ctx.moveTo(px, py); }\n  ctx.closePath();\n}\n\n// \\u2500\\u2500 the parts \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\n\nvar R = {};\n\n/** The plate itself when it is a still: a slow push-in so a photo reads as footage. A video plate is under the canvas. */\nR.plate = function (ctx, u, len, p) {\n  if (typeof meta !== \"undefined\" && meta && meta.clips && meta.clips.length) return;\n  var img = image(p.url);\n  var z = 1.04 + 0.08 * clamp01(u / Math.max(0.1, len));\n  coverImage(ctx, img, 0, 0, AD.W, AD.H, z, p.fx, p.fy);\n};\n\n/** A soft dark band where text sits, so footage under it never decides whether the words read. */\nR.scrim = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.35, 0.35);\n  scrimBand(ctx, 0, p.y, AD.W, p.h, AD.c.scrim, e.a * p.alpha);\n};\n\n/** THE HOOK: big kinetic type, every word on screen by 1.0 s, one word in the accent with a marker behind it. */\nR.hook = function (ctx, u, len, p) {\n  var text = p.upper ? String(p.text).toUpperCase() : String(p.text);\n  var f = fit(ctx, text, AD.f.display, AD.f.dw, p.w, p.h, p.size, p.min, p.lines, 1.04);\n  ctx.font = fontOf(AD.f.display, AD.f.dw, f.px);\n  ctx.textBaseline = \"middle\"; ctx.textAlign = \"left\";\n  var lh = f.px * 1.04, total = f.lines.length * lh;\n  var y0 = p.y + (p.valign === \"top\" ? 0 : (p.h - total) / 2) + lh / 2;\n  var all = [];\n  for (var li = 0; li < f.lines.length; li++) {\n    var words = f.lines[li].split(\" \"), lw = ctx.measureText(f.lines[li]).width, sp = ctx.measureText(\" \").width;\n    var x = p.align === \"center\" ? p.x + (p.w - lw) / 2 : p.x;\n    for (var wi = 0; wi < words.length; wi++) { var ww = ctx.measureText(words[wi]).width; all.push({ s: words[wi], x: x, y: y0 + li * lh, w: ww }); x += ww + sp; }\n  }\n  var n = all.length, stagger = Math.min(0.11, 0.85 / Math.max(1, n));\n  var out = len > 0 ? clamp01((len - u) / 0.3) : 1;\n  var lift = (1 - easeOutCubic(out)) * f.px * 0.5;\n  var em = String(p.em || \"\").toUpperCase(), bare = function (x) { return String(x).toUpperCase().replace(/[^\\p{L}\\p{N}]/gu, \"\"); };\n  if (!all.some(function (w) { return bare(w.s) === bare(em); })) em = all.length ? all[all.length - 1].s.toUpperCase() : \"\";\n  for (var k = 0; k < n; k++) {\n    var w = all[k], local = (u - k * stagger) / 0.26;\n    if (local <= 0) continue;\n    var cxw = w.x + w.w / 2, s = Math.min(1.2 - 0.2 * easeOutBack(local), 2 * (cxw - p.sx0) / w.w, 2 * (p.sx1 - cxw) / w.w), a = clamp01(local * 1.6) * easeOutCubic(out);\n    ctx.save(); ctx.globalAlpha *= a;\n    ctx.translate(w.x + w.w / 2, w.y - lift); ctx.scale(s, s);\n    var hit = em && w.s.toUpperCase().replace(/[^\\p{L}\\p{N}]/gu, \"\") === em.replace(/[^\\p{L}\\p{N}]/gu, \"\");\n    if (hit) { ctx.fillStyle = AD.c.accent; rr(ctx, -w.w / 2 - f.px * 0.12, -f.px * 0.52, w.w + f.px * 0.24, f.px * 1.0, f.px * 0.14); ctx.fill(); }\n    outlined(ctx, w.s, -w.w / 2, 0, hit ? AD.c.accentInk : AD.c.hookInk, AD.c.edge, hit ? 0 : Math.max(4, f.px * 0.1));\n    ctx.restore();\n  }\n};\n\n/**\n * THE MESSAGE: the headline at display size, held through the middle of the ad. style: \"outline\" (white, outlined, over\n * a scrim), \"ink\" (on a band of the brand colour the layout lays under it), \"box\" (each line on a white caption box).\n */\nR.message = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.4, 0.3);\n  var body = p.face === \"body\", fam = body ? AD.f.body : AD.f.display, wt = body ? AD.f.bw : AD.f.dw, lh = body ? 1.14 : 1.04;\n  var text = p.upper ? String(p.text).toUpperCase() : String(p.text);\n  var boxPad = p.style === \"box\" ? p.padx : 0;\n  var f = fit(ctx, text, fam, wt, p.w - boxPad * 2 - p.size * 0.6, p.h - p.pady * 2, p.size, p.min, p.lines, lh);\n  ctx.font = fontOf(fam, wt, f.px);\n  ctx.textBaseline = \"middle\"; ctx.textAlign = \"left\";\n  var L = f.px * lh, total = f.lines.length * L;\n  var y0 = p.y + (p.h - total) / 2 + L / 2;\n  ctx.save(); ctx.globalAlpha *= e.a; ctx.translate(0, (1 - easeOutCubic(e.i)) * f.px * 0.3);\n  for (var i = 0; i < f.lines.length; i++) {\n    var s = f.lines[i], lw = ctx.measureText(s).width, y = y0 + i * L;\n    var x = p.align === \"center\" ? p.x + (p.w - lw) / 2 : p.x + boxPad;\n    if (p.style === \"box\") {\n      ctx.fillStyle = AD.c.capBg; rr(ctx, x - boxPad, y - L / 2 - f.px * 0.06, lw + boxPad * 2, L + f.px * 0.12, f.px * 0.24); ctx.fill();\n      ctx.fillStyle = AD.c.capInk; ctx.fillText(s, x, y + f.px * 0.03);\n    } else if (p.style === \"outline\") outlined(ctx, s, x, y, AD.c.hookInk, AD.c.edge, Math.max(4, f.px * 0.09));\n    else { ctx.fillStyle = p.ink || AD.c.primaryInk; ctx.fillText(s, x, y); }\n  }\n  ctx.restore();\n};\n\n/** A GROUND: a full-bleed band of colour under the message or the offer \\u2014 across the top, across the middle, or a floor. */\nR.band = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.35, 0.3), k = easeOutCubic(e.i), t = Math.max(4, AD.H * 0.006);\n  ctx.save(); ctx.globalAlpha *= easeOutCubic(e.o);\n  ctx.fillStyle = p.color || AD.c.primary;\n  if (p.edge === \"top\") { ctx.fillRect(0, 0, AD.W, p.size * k); ctx.fillStyle = p.line || AD.c.accent; ctx.fillRect(0, p.size * k - t, AD.W, t); }\n  else if (p.edge === \"bottom\") { ctx.fillRect(0, AD.H - p.size * k, AD.W, p.size * k + 1); ctx.fillStyle = p.line || AD.c.accent; ctx.fillRect(0, AD.H - p.size * k, AD.W, t); }\n  else { var hh = p.size * k; ctx.fillRect(0, p.at - hh / 2, AD.W, hh); ctx.fillStyle = p.line || AD.c.accent; ctx.fillRect(0, p.at - hh / 2, AD.W, t); ctx.fillRect(0, p.at + hh / 2 - t, AD.W, t); }\n  ctx.restore();\n};\n\n/** A line of supporting copy on a solid plate in the brand colour (the headline under a hook, an offer line). */\nR.line = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.4, 0.3);\n  var f = fit(ctx, String(p.text), AD.f.body, AD.f.bw, p.w - p.pad * 2, p.h - p.pad * 2, p.size, p.min, p.lines, 1.18);\n  ctx.font = fontOf(AD.f.body, AD.f.bw, f.px);\n  var tw = 0; for (var i = 0; i < f.lines.length; i++) tw = Math.max(tw, ctx.measureText(f.lines[i]).width);\n  var bw = tw + p.pad * 2, bh = f.lines.length * f.px * 1.18 + p.pad * 1.6;\n  var bx = p.align === \"center\" ? p.x + (p.w - bw) / 2 : p.x, by = p.y;\n  ctx.save(); ctx.globalAlpha *= e.a; ctx.translate(0, (1 - easeOutCubic(e.i)) * 30);\n  ctx.fillStyle = p.bg; rr(ctx, bx, by, bw, bh, Math.min(p.radius, bh / 2)); ctx.fill();\n  ctx.fillStyle = p.ink; ctx.textBaseline = \"middle\"; ctx.textAlign = \"left\";\n  for (var j = 0; j < f.lines.length; j++) ctx.fillText(f.lines[j], bx + p.pad, by + p.pad * 0.8 + f.px * 1.18 * (j + 0.5));\n  ctx.restore();\n};\n\n/** A benefit chip: a check in the accent, the words on a light pill; slides in from its side. */\nR.chip = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.35, 0.25);\n  var r = p.h / 2, icon = p.h * 0.34;\n  var f = fit(ctx, String(p.text), AD.f.body, AD.f.bw, p.w - p.h - r, p.h * 0.9, p.size, p.min, 1, 1.1);\n  ctx.font = fontOf(AD.f.body, AD.f.bw, f.px);\n  var tw = ctx.measureText(f.lines[0] || \"\").width, bw = Math.min(p.w, tw + p.h + r * 0.9);\n  var bx = p.align === \"right\" ? p.x + p.w - bw : p.align === \"center\" ? p.x + (p.w - bw) / 2 : p.x;\n  var dx = (1 - easeOutCubic(e.i)) * (p.align === \"right\" ? 120 : -120);\n  ctx.save(); ctx.globalAlpha *= e.a; ctx.translate(dx, 0);\n  ctx.fillStyle = AD.c.chipBg; rr(ctx, bx, p.y, bw, p.h, r); ctx.fill();\n  check(ctx, bx + r, p.y + r, icon, AD.c.accent, AD.c.accentInk);\n  ctx.fillStyle = AD.c.chipInk; ctx.textBaseline = \"middle\"; ctx.textAlign = \"left\";\n  ctx.fillText(f.lines[0] || \"\", bx + p.h * 0.92, p.y + p.h / 2 + f.px * 0.04);\n  ctx.restore();\n};\n\n/** THE PRICE / OFFER BURST: a turning star in the accent, popped in, the price at display size, the offer under it. */\nR.burst = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.45, 0.25), s = easeOutBack(clamp01(u / 0.45)) * easeOutCubic(e.o);\n  if (s <= 0.001) return;\n  ctx.save(); ctx.translate(p.x, p.y); ctx.scale(s, s); ctx.rotate(-0.12 + 0.02 * Math.sin(u * 2.4));\n  ctx.save(); ctx.rotate(u * 0.25); ctx.fillStyle = AD.c.accent; starShape(ctx, 0, 0, p.r, p.r * 0.84, 18, 0); ctx.fill();\n  ctx.strokeStyle = AD.c.accentInk; ctx.globalAlpha *= 0.35; ctx.lineWidth = Math.max(2, p.r * 0.02); starShape(ctx, 0, 0, p.r * 0.9, p.r * 0.76, 18, 0); ctx.stroke(); ctx.restore();\n  ctx.fillStyle = AD.c.accentInk; ctx.textAlign = \"center\"; ctx.textBaseline = \"middle\";\n  var inner = p.r * 1.3, top = p.sub ? -p.r * 0.2 : 0;\n  var f = fit(ctx, String(p.text), AD.f.display, AD.f.dw, inner, p.r * (p.sub ? 0.62 : 0.9), p.r * 0.62, p.r * 0.22, 2, 1.0);\n  ctx.font = fontOf(AD.f.display, AD.f.dw, f.px);\n  for (var i = 0; i < f.lines.length; i++) ctx.fillText(f.lines[i], 0, top + (i - (f.lines.length - 1) / 2) * f.px);\n  if (p.sub) {\n    var g = fit(ctx, String(p.sub), AD.f.body, AD.f.bw, inner * 0.92, p.r * 0.36, p.r * 0.2, p.r * 0.1, 2, 1.1);\n    ctx.font = fontOf(AD.f.body, AD.f.bw, g.px);\n    var y0 = top + (f.lines.length / 2) * f.px + g.px * 0.75;\n    for (var j = 0; j < g.lines.length; j++) ctx.fillText(g.lines[j], 0, y0 + j * g.px * 1.1);\n  }\n  ctx.restore();\n};\n\n/** BURNED-IN CAPTION: one phrase at a time in a white box, the word being said lit in the brand colour. */\nR.caption = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.12, 0.12);\n  var f = fit(ctx, String(p.text), AD.f.body, AD.f.bw, p.w - p.pad * 2, p.h, p.size, p.min, p.lines, 1.22);\n  ctx.font = fontOf(AD.f.body, AD.f.bw, f.px);\n  var words = [], lh = f.px * 1.22, sp = ctx.measureText(\" \").width;\n  for (var li = 0; li < f.lines.length; li++) {\n    var lw = ctx.measureText(f.lines[li]).width, x = p.x + (p.w - lw) / 2, parts = f.lines[li].split(\" \");\n    for (var wi = 0; wi < parts.length; wi++) { var ww = ctx.measureText(parts[wi]).width; words.push({ s: parts[wi], x: x, y: p.y + li * lh, w: ww }); x += ww + sp; }\n  }\n  var lit = Math.min(words.length - 1, Math.floor(clamp01(u / Math.max(0.2, len * 0.92)) * words.length));\n  var pop = easeOutBack(clamp01(u / 0.18));\n  ctx.save(); ctx.globalAlpha *= e.a;\n  ctx.translate(p.x + p.w / 2, p.y + (f.lines.length * lh) / 2); ctx.scale(0.9 + 0.1 * pop, 0.9 + 0.1 * pop); ctx.translate(-(p.x + p.w / 2), -(p.y + (f.lines.length * lh) / 2));\n  ctx.textBaseline = \"middle\"; ctx.textAlign = \"left\";\n  for (var l2 = 0; l2 < f.lines.length; l2++) {\n    var w2 = ctx.measureText(f.lines[l2]).width;\n    ctx.fillStyle = AD.c.capBg; rr(ctx, p.x + (p.w - w2) / 2 - p.pad, p.y + l2 * lh - f.px * 0.08, w2 + p.pad * 2, lh + f.px * 0.1, f.px * 0.28); ctx.fill();\n  }\n  for (var k = 0; k < words.length; k++) {\n    var w = words[k];\n    if (k === lit) { ctx.fillStyle = AD.c.primary; rr(ctx, w.x - f.px * 0.1, w.y + f.px * 0.02, w.w + f.px * 0.2, lh - f.px * 0.04, f.px * 0.2); ctx.fill(); ctx.fillStyle = AD.c.primaryInk; }\n    else ctx.fillStyle = AD.c.capInk;\n    ctx.fillText(w.s, w.x, w.y + lh / 2);\n  }\n  ctx.restore();\n};\n\n/** THE CTA BUTTON: pops in, then breathes; a light sweep crosses it once a second. */\nR.cta = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.4, 0.2), pop = easeOutBack(clamp01(u / 0.42));\n  if (pop <= 0.001) return;\n  var f = fit(ctx, String(p.text), AD.f.body, AD.f.bw, p.w - p.h * 1.4, p.h * 0.62, p.h * 0.42, p.h * 0.22, 1, 1.1);\n  ctx.font = fontOf(AD.f.body, AD.f.bw, f.px);\n  var tw = ctx.measureText(f.lines[0] || \"\").width, bw = Math.min(p.w, tw + p.h * 1.5), bx = p.x + (p.w - bw) / 2;\n  var breathe = 1 + 0.035 * Math.sin(Math.max(0, u - 0.45) * Math.PI * 2 * 0.9);\n  ctx.save(); ctx.globalAlpha *= e.a;\n  ctx.translate(bx + bw / 2, p.y + p.h / 2); ctx.scale(pop * breathe, pop * breathe); ctx.translate(-(bx + bw / 2), -(p.y + p.h / 2));\n  ctx.fillStyle = AD.c.shadow; rr(ctx, bx, p.y + p.h * 0.1, bw, p.h, p.h / 2); ctx.fill();\n  ctx.fillStyle = AD.c.primary; rr(ctx, bx, p.y, bw, p.h, p.h / 2); ctx.fill();\n  var sweep = ((u * 0.9) % 1.6) / 1.6;\n  ctx.save(); rr(ctx, bx, p.y, bw, p.h, p.h / 2); ctx.clip();\n  var sx = bx - p.h + sweep * (bw + p.h * 2);\n  var g = ctx.createLinearGradient(sx - p.h * 0.6, 0, sx + p.h * 0.6, 0);\n  g.addColorStop(0, \"rgba(255,255,255,0)\"); g.addColorStop(0.5, \"rgba(255,255,255,0.28)\"); g.addColorStop(1, \"rgba(255,255,255,0)\");\n  ctx.fillStyle = g; ctx.fillRect(bx, p.y, bw, p.h); ctx.restore();\n  ctx.fillStyle = AD.c.primaryInk; ctx.textBaseline = \"middle\"; ctx.textAlign = \"left\";\n  var tx = bx + (bw - tw - p.h * 0.55) / 2;\n  ctx.fillText(f.lines[0] || \"\", tx, p.y + p.h / 2 + f.px * 0.04);\n  arrow(ctx, tx + tw + p.h * 0.36 + Math.sin(u * 5) * p.h * 0.04, p.y + p.h / 2, p.h * 0.34, AD.c.primaryInk);\n  ctx.restore();\n};\n\n/** THE END CARD: the brand ground wipes up; logo (or the name set in the display face), a line, the CTA. */\nR.endcard = function (ctx, u, len, p) {\n  var wipe = easeOutCubic(clamp01(u / 0.5));\n  ctx.save();\n  ctx.fillStyle = AD.c.ground; ctx.fillRect(0, AD.H * (1 - wipe), AD.W, AD.H * wipe + 1);\n  ctx.fillStyle = AD.c.primary; ctx.fillRect(0, AD.H * (1 - wipe) - AD.H * 0.012, AD.W, AD.H * 0.012);\n  var c = clamp01((u - 0.3) / 0.45), ce = easeOutCubic(c);\n  ctx.globalAlpha *= ce; ctx.translate(0, (1 - ce) * 40);\n  if (p.logo) {\n    var img = image(p.logo);\n    // the plate hugs the logo as drawn (contained in its box), not the box\n    var lr = containRect(img, p.lx, p.ly, p.lw, p.lh);\n    ctx.save();\n    if (AD.c.logoPlate !== \"none\") { ctx.fillStyle = AD.c.logoPlate; rr(ctx, lr.x - p.lpad, lr.y - p.lpad, lr.w + p.lpad * 2, lr.h + p.lpad * 2, p.lpad); ctx.fill(); }\n    ctx.drawImage(img, lr.x, lr.y, lr.w, lr.h); ctx.restore();\n  } else if (p.name) {\n    var fn = fit(ctx, String(p.name), AD.f.display, AD.f.dw, p.lw, p.lh, p.lh * 0.8, p.lh * 0.3, 2, 1.0);\n    ctx.font = fontOf(AD.f.display, AD.f.dw, fn.px); ctx.fillStyle = AD.c.groundInk; ctx.textAlign = \"center\"; ctx.textBaseline = \"middle\";\n    for (var i = 0; i < fn.lines.length; i++) ctx.fillText(fn.lines[i], p.lx + p.lw / 2, p.ly + p.lh / 2 + (i - (fn.lines.length - 1) / 2) * fn.px);\n  }\n  if (p.text) {\n    var f = fit(ctx, String(p.text), AD.f.body, AD.f.bw, p.tw, p.th, p.tsize, p.tsize * 0.5, 3, 1.18);\n    ctx.font = fontOf(AD.f.body, AD.f.bw, f.px); ctx.fillStyle = AD.c.groundInk; ctx.textAlign = \"center\"; ctx.textBaseline = \"middle\";\n    for (var j = 0; j < f.lines.length; j++) ctx.fillText(f.lines[j], p.tx + p.tw / 2, p.ty + p.th / 2 + (j - (f.lines.length - 1) / 2) * f.px * 1.18);\n  }\n  if (p.url) {\n    ctx.font = fontOf(AD.f.body, AD.f.bw, p.usize); ctx.fillStyle = AD.c.groundSoft; ctx.textAlign = \"center\"; ctx.textBaseline = \"middle\";\n    var uf = fit(ctx, String(p.url), AD.f.body, AD.f.bw, p.tw, p.usize * 1.3, p.usize, p.usize * 0.6, 1, 1.1);\n    ctx.font = fontOf(AD.f.body, AD.f.bw, uf.px); ctx.fillText(uf.lines[0] || \"\", p.tx + p.tw / 2, p.uy);\n  }\n  ctx.restore();\n};\n\n/** A logo bug in the corner while the ad runs, on a plate so any logo reads over any footage. */\nR.bug = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.5, 0.3);\n  ctx.save(); ctx.globalAlpha *= e.a * 0.96;\n  if (p.logo) {\n    var bimg = image(p.logo), pad = p.h * 0.16;\n    var br = containRect(bimg, p.x + pad, p.y + p.h * 0.14, p.w - pad * 2, p.h * 0.72, \"left\");\n    if (AD.c.bugPlate !== \"none\") { ctx.fillStyle = AD.c.bugPlate; rr(ctx, p.x, p.y, br.w + pad * 2, p.h, p.h * 0.24); ctx.fill(); }\n    ctx.drawImage(bimg, br.x, br.y, br.w, br.h);\n  } else if (p.name) {\n    var f = fit(ctx, String(p.name), AD.f.body, AD.f.bw, p.w - p.h * 0.5, p.h * 0.6, p.h * 0.46, p.h * 0.24, 1, 1.0);\n    ctx.font = fontOf(AD.f.body, AD.f.bw, f.px); var tw = ctx.measureText(f.lines[0] || \"\").width;\n    ctx.fillStyle = AD.c.chipBg; rr(ctx, p.x, p.y, tw + p.h * 0.5, p.h, p.h / 2); ctx.fill();\n    ctx.fillStyle = AD.c.chipInk; ctx.textAlign = \"left\"; ctx.textBaseline = \"middle\"; ctx.fillText(f.lines[0] || \"\", p.x + p.h * 0.25, p.y + p.h / 2 + f.px * 0.04);\n  }\n  ctx.restore();\n};\n\n/** A countdown that drains: a bar in the accent across the offer's window, with the deadline the brief gave (never a made-up clock). */\nR.timer = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.4, 0.3), left = 1 - clamp01(u / Math.max(0.1, len));\n  ctx.save(); ctx.globalAlpha *= e.a;\n  ctx.fillStyle = AD.c.cardBg; rr(ctx, p.x, p.y, p.w, p.h, p.h * 0.3); ctx.fill();\n  var pad = p.h * 0.18, bh = p.h * 0.22, by = p.y + p.h - pad - bh;\n  ctx.fillStyle = AD.c.track; rr(ctx, p.x + pad, by, p.w - pad * 2, bh, bh / 2); ctx.fill();\n  ctx.fillStyle = AD.c.accentOnCard; rr(ctx, p.x + pad, by, Math.max(bh, (p.w - pad * 2) * left), bh, bh / 2); ctx.fill();\n  var f = fit(ctx, String(p.text), AD.f.body, AD.f.bw, p.w - pad * 2, p.h - bh - pad * 2.4, p.h * 0.36, p.h * 0.18, 1, 1.1);\n  ctx.font = fontOf(AD.f.body, AD.f.bw, f.px); ctx.fillStyle = AD.c.cardInk; ctx.textAlign = \"left\"; ctx.textBaseline = \"middle\";\n  var blink = 0.75 + 0.25 * Math.cos(u * Math.PI * 2);\n  ctx.fillStyle = AD.c.accentOnCard; ctx.beginPath(); ctx.arc(p.x + pad + f.px * 0.32, p.y + pad + f.px * 0.55, f.px * 0.24 * blink, 0, Math.PI * 2); ctx.fill();\n  ctx.fillStyle = AD.c.cardInk; ctx.fillText(f.lines[0] || \"\", p.x + pad + f.px * 0.8, p.y + pad + f.px * 0.58);\n  ctx.restore();\n};\n\n/** A TESTIMONIAL: a card with the quote, the stars only when a rating was given, and who said it. */\nR.quote = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.45, 0.3);\n  var pad = p.pad, starH = p.rating > 0 ? p.size * 0.9 : 0, who = p.author ? p.size * 0.95 : 0;\n  var f = fit(ctx, \"\\u201C\" + String(p.text) + \"\\u201D\", AD.f.body, AD.f.bw, p.w - pad * 2, p.h - pad * 2 - starH * 1.4 - who * 1.6, p.size, p.min, p.lines, 1.25);\n  var bh = pad * 2 + starH * (starH ? 1.4 : 0) + f.lines.length * f.px * 1.25 + (who ? who * 1.6 : 0);\n  var by = p.y + (p.h - bh) / 2;\n  ctx.save(); ctx.globalAlpha *= e.a; ctx.translate(0, (1 - easeOutCubic(e.i)) * 50);\n  ctx.fillStyle = AD.c.shadow; rr(ctx, p.x, by + pad * 0.25, p.w, bh, pad * 0.8); ctx.fill();\n  ctx.fillStyle = AD.c.cardBg; rr(ctx, p.x, by, p.w, bh, pad * 0.8); ctx.fill();\n  ctx.fillStyle = AD.c.primary; ctx.fillRect(p.x + pad, by, p.w * 0.18, Math.max(4, pad * 0.18));\n  var y = by + pad;\n  if (starH) { for (var s = 0; s < 5; s++) { var lit = s < p.rating; ctx.fillStyle = lit ? AD.c.star : AD.c.track; starShape(ctx, p.x + pad + starH * 0.55 + s * starH * 1.15, y + starH * 0.5, starH * 0.5, starH * 0.21, 5, 0); ctx.fill(); } y += starH * 1.4; }\n  ctx.font = fontOf(AD.f.body, AD.f.bw, f.px); ctx.fillStyle = AD.c.cardInk; ctx.textAlign = \"left\"; ctx.textBaseline = \"middle\";\n  var shown = Math.ceil(clamp01((u - 0.3) / Math.max(0.6, f.lines.length * 0.35)) * f.lines.length);\n  for (var i = 0; i < f.lines.length; i++) { ctx.save(); ctx.globalAlpha *= i < shown ? 1 : 0.0; ctx.fillText(f.lines[i], p.x + pad, y + f.px * 1.25 * (i + 0.5)); ctx.restore(); }\n  y += f.lines.length * f.px * 1.25;\n  if (who) { ctx.font = fontOf(AD.f.body, AD.f.bw, who * 0.82); ctx.fillStyle = AD.c.cardSoft; ctx.fillText(\"\\u2014 \" + String(p.author), p.x + pad, y + who * 1.05); }\n  ctx.restore();\n};\n\n/** BEFORE / AFTER over one plate: the \"before\" side is dimmed and the divider sweeps; each side is labelled. */\nR.split = function (ctx, u, len, p) {\n  var e = inOut(u, len, 0.4, 0.3), sweep = easeOutCubic(clamp01(u / 1.1));\n  var horizontal = p.dir === \"h\";\n  var at = horizontal ? AD.W * (1 - 0.5 * sweep) : AD.H * (1 - 0.5 * sweep);\n  ctx.save(); ctx.globalAlpha *= e.a;\n  ctx.fillStyle = AD.c.dim;\n  if (horizontal) ctx.fillRect(0, 0, at, AD.H); else ctx.fillRect(0, 0, AD.W, at);\n  ctx.fillStyle = AD.c.divider;\n  if (horizontal) ctx.fillRect(at - 4, 0, 8, AD.H); else ctx.fillRect(0, at - 4, AD.W, 8);\n  ctx.beginPath(); if (horizontal) ctx.arc(at, AD.H / 2, 34, 0, Math.PI * 2); else ctx.arc(AD.W / 2, at, 34, 0, Math.PI * 2); ctx.fill();\n  ctx.restore();\n};\n/** A part by id — the end card reads the headline and the brand name from their own parts (one value, one field). */\nfunction part(id) { for (var i = 0; i < entities.length; i++) if (entities[i].id === id) return entities[i]; return null; }\n/** Dark type on a light colour, white on a dark one. */\nfunction inkOn(hex) { var m = /^#?([0-9a-f]{6})$/i.exec(String(hex || \"\")); if (!m) return \"#111114\"; var n = parseInt(m[1], 16), r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;\n  var L = function (v) { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); }; return 0.2126 * L(r) + 0.7152 * L(g) + 0.0722 * L(b) > 0.22 ? \"#111114\" : \"#ffffff\"; }\nconst entities = [\n  { id: \"brand\", kind: \"overlay\", from: 0, to: 8,\n    params: { primary: \"#a78bfa\", accent: \"#14b8a6\" },\n    draw(ctx, t, p, rng) { AD.c.primary = p.primary; AD.c.primaryInk = inkOn(p.primary); AD.c.accent = p.accent; AD.c.accentInk = inkOn(p.accent); AD.c.accentOnCard = p.accent; AD.c.star = p.accent; } },\n  { id: \"plate\", kind: \"overlay\", from: 0, to: 8,\n    params: { url: \"/scene-assets/2e5c6ad5f41f679c4908b09c272f7722\", fx: 0.5, fy: 0.5 },\n    draw(ctx, t, p, rng) { R.plate(ctx, t - this.from, this.to - this.from, p); } },\n  { id: \"logo_bug\", kind: \"overlay\", from: 0.2, to: 6.55,\n    params: { logo: \"\", name: \"KICKLAB\", x: 72, y: 250, w: 250, h: 78 },\n    draw(ctx, t, p, rng) { R.bug(ctx, t - this.from, this.to - this.from, p); } },\n  { id: \"hook_scrim\", kind: \"overlay\", from: 0, to: 2.3,\n    params: { y: 256, h: 595, alpha: 1 },\n    draw(ctx, t, p, rng) { R.scrim(ctx, t - this.from, this.to - this.from, p); } },\n  { id: \"hook\", kind: \"title\", from: 0, to: 2.3,\n    params: { text: \"Drop 04 is here\", em: \"here\", upper: true, x: 114, y: 360, w: 756, h: 387.2, size: 172.8, min: 86.4, lines: 4, align: \"center\", valign: \"top\", sx0: 72, sx1: 912 },\n    draw(ctx, t, p, rng) { R.hook(ctx, t - this.from, this.to - this.from, p); } },\n  { id: \"band\", kind: \"overlay\", from: 2.15, to: 6.2,\n    params: { edge: \"top\", size: 540, color: \"\", line: \"\" },\n    draw(ctx, t, p, rng) { R.band(ctx, t - this.from, this.to - this.from, p); } },\n  { id: \"message\", kind: \"title\", from: 2.2, to: 6.2,\n    params: { text: \"Lilac Runner\", upper: true, face: \"display\", style: \"ink\", x: 72, y: 360, w: 840, h: 153, size: 127.4, min: 101.5, lines: 3, align: \"left\", padx: 0, pady: 10, ink: \"\", bg: \"\" },\n    draw(ctx, t, p, rng) { R.message(ctx, t - this.from, this.to - this.from, p); } },\n  { id: \"benefit_1\", kind: \"caption\", from: 2.35, to: 6.2,\n    params: { text: \"Cloud foam\", x: 72, y: 1028.8, w: 722.4, h: 108, size: 45.4, min: 25.9, align: \"left\" },\n    draw(ctx, t, p, rng) { R.chip(ctx, t - this.from, this.to - this.from, p); } },\n  { id: \"benefit_2\", kind: \"caption\", from: 2.57, to: 6.2,\n    params: { text: \"Recycled knit\", x: 72, y: 1164.8, w: 722.4, h: 108, size: 45.4, min: 25.9, align: \"left\" },\n    draw(ctx, t, p, rng) { R.chip(ctx, t - this.from, this.to - this.from, p); } },\n  { id: \"price_burst\", kind: \"overlay\", from: 2.2, to: 6.2,\n    params: { text: \"$89\", sub: \"Members first\", x: 714.9, y: 770.9, r: 176 },\n    draw(ctx, t, p, rng) { R.burst(ctx, t - this.from, this.to - this.from, p); } },\n  { id: \"end_card\", kind: \"overlay\", from: 6.2, to: 8,\n    params: { logo: \"\", name: \"\", lx: 231.6, ly: 467.8, lw: 520.8, lh: 205.7, lpad: 27, text: \"\", tx: 72, ty: 727.5, tw: 840, th: 235, tsize: 65.3, url: \"\", usize: 40.5, uy: 1021.3 },\n    draw(ctx, t, p, rng) { var m = part(\"message\"), b = part(\"logo_bug\"); R.endcard(ctx, t - this.from, this.to - this.from, Object.assign({}, p, { text: p.text || (m ? m.params.text : \"\"), name: p.name || (b ? b.params.name : \"\") })); } },\n  { id: \"cta\", kind: \"overlay\", from: 4, to: 8,\n    params: { text: \"Shop the drop\", x: 139.2, y: 1302.8, w: 705.6, h: 131 },\n    draw(ctx, t, p, rng) { R.cta(ctx, t - this.from, this.to - this.from, p); } },\n];\nfunction draw(ctx, t, params, rng) {\n  for (const e of entities) {\n    if (t < e.from || t > e.to) continue;\n    ctx.save(); e.draw(ctx, t, e.params, rng.for(e.id)); ctx.restore();\n  }\n}\n","exportProfile":{"format":"mp4","resolution":"720p","fps":30,"durationSeconds":8},"source":{"prompt":"The Sneaker Drop template with its words written in Arabic (not translated word for word): وصل الجديد · لايلاك رانر · 349 ر.س · للأعضاء أولاً · نعومة كالسحاب · نسيج معاد تدويره · تسوّق الآن","provider":"pinepaper","model":"template","app":"gallery-reproduction"}},"license":"CC0-1.0"}