← back to Dw Photo Capture

scripts/probooth-figma-build.js

180 lines

// use_figma build script — "DW Photo — Pro Booth (iPad/Phone)" (TK-12162).
// Run in ONE use_figma call after create_new_file. Builds 6 frames on one page:
// iPad landscape 1024×768 (Live / Result / Saved) + Phone 390×844 (Live / Result / Saved).
(async () => {
  await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
  await figma.loadFontAsync({ family: 'Inter', style: 'Semi Bold' });
  await figma.loadFontAsync({ family: 'Inter', style: 'Bold' });

  const C = {
    bg: { r: 0.067, g: 0.063, b: 0.031 },      // #111008
    panel: { r: 0.106, g: 0.098, b: 0.071 },   // #1b1912
    line: { r: 0.173, g: 0.161, b: 0.125 },    // #2c2920
    ink: { r: 0.949, g: 0.929, b: 0.882 },     // #f2ede1
    dim: { r: 0.604, g: 0.569, b: 0.490 },     // #9a917d
    gold: { r: 0.784, g: 0.635, b: 0.290 },    // #c8a24a
    gold2: { r: 0.886, g: 0.741, b: 0.400 },   // #e2bd66
    black: { r: 0, g: 0, b: 0 },
    darkBtn: { r: 0.090, g: 0.075, b: 0.031 }, // #171308
    stage: { r: 0.16, g: 0.15, b: 0.13 },
  };
  const fill = c => [{ type: 'SOLID', color: c }];
  function txt(parent, s, size, color, style, ls) {
    const t = figma.createText();
    t.fontName = { family: 'Inter', style: style || 'Regular' };
    t.characters = s; t.fontSize = size; t.fills = fill(color);
    if (ls) t.letterSpacing = { value: ls, unit: 'PERCENT' };
    parent.appendChild(t); return t;
  }
  function rect(parent, w, h, c, r) {
    const n = figma.createRectangle(); n.resize(w, h); n.fills = fill(c);
    if (r) n.cornerRadius = r; parent.appendChild(n); return n;
  }
  function frameNode(name, w, h, x, y, c) {
    const f = figma.createFrame(); f.name = name; f.resize(w, h); f.x = x; f.y = y;
    f.fills = fill(c || C.bg); figma.currentPage.appendChild(f); return f;
  }
  // slider group: label row (label + value) over a track with a gold round thumb
  function slider(parent, x, y, w, label, val, pct) {
    const g = figma.createFrame(); g.name = 'Slider · ' + label;
    g.resize(w, 56); g.x = x; g.y = y; g.fills = [];
    parent.appendChild(g);
    const l = txt(g, label.toUpperCase(), 11, C.dim, 'Semi Bold', 18); l.x = 0; l.y = 0;
    const v = txt(g, String(val), 14, C.gold2, 'Regular'); v.x = w - 34; v.y = -2; v.textAlignHorizontal = 'RIGHT';
    const track = rect(g, w, 4, C.line, 2); track.x = 0; track.y = 34;
    const notch = rect(g, 2, 8, C.gold, 1); notch.x = w / 2 - 1; notch.y = 32;
    const th = figma.createEllipse(); th.resize(30, 30);
    th.fills = fill(C.gold); th.strokes = fill(C.darkBtn); th.strokeWeight = 2;
    th.x = Math.round((w - 30) * pct); th.y = 21; th.effects = [{ type: 'DROP_SHADOW', color: { r: 0, g: 0, b: 0, a: 0.55 }, offset: { x: 0, y: 2 }, radius: 8, visible: true, blendMode: 'NORMAL' }];
    g.appendChild(th);
    return g;
  }
  function shutter(parent, cx, cy, d) {
    d = d || 78;
    const o = figma.createEllipse(); o.resize(d, d); o.x = cx - d / 2; o.y = cy - d / 2;
    o.fills = []; o.strokes = fill(C.ink); o.strokeWeight = 4; parent.appendChild(o);
    const i = figma.createEllipse(); i.resize(d - 16, d - 16); i.x = cx - (d - 16) / 2; i.y = cy - (d - 16) / 2;
    i.fills = fill(C.ink); parent.appendChild(i);
  }
  function chip(parent, x, y, label, goldBorder) {
    const g = figma.createFrame(); g.name = 'Chip'; g.layoutMode = 'HORIZONTAL';
    g.paddingLeft = 14; g.paddingRight = 14; g.paddingTop = 7; g.paddingBottom = 7;
    g.primaryAxisSizingMode = 'AUTO'; g.counterAxisSizingMode = 'AUTO';
    g.cornerRadius = 999; g.fills = [{ type: 'SOLID', color: C.black, opacity: 0.35 }];
    g.strokes = fill(goldBorder ? C.gold : C.line); g.strokeWeight = 1;
    g.x = x; g.y = y; parent.appendChild(g);
    txt(g, label, 12.5, C.dim, 'Regular');
    return g;
  }
  function brand(parent, x, y) {
    const t = txt(parent, 'DW  PRO BOOTH', 12, C.gold, 'Semi Bold', 34); t.x = x; t.y = y; return t;
  }
  function field(parent, x, y, w, label, value, placeholder) {
    const l = txt(parent, label.toUpperCase(), 10.5, C.dim, 'Semi Bold', 20); l.x = x; l.y = y;
    const box = rect(parent, w, 46, C.panel, 10); box.x = x; box.y = y + 18;
    box.strokes = fill(C.line); box.strokeWeight = 1;
    const v = txt(parent, value || placeholder || '', 16, value ? C.ink : C.dim, 'Regular');
    v.x = x + 12; v.y = y + 31;
  }
  function button(parent, x, y, w, label, gold) {
    const b = rect(parent, w, 54, gold ? C.gold : C.bg, 12); b.x = x; b.y = y;
    if (!gold) { b.strokes = fill(C.line); b.strokeWeight = 1; b.fills = []; }
    const t = txt(parent, label, 16, gold ? C.darkBtn : C.dim, 'Semi Bold');
    t.resize(w, 20); t.textAlignHorizontal = 'CENTER'; t.x = x; t.y = y + 17;
  }

  figma.currentPage.name = 'Pro Booth · iPad + Phone';

  /* ═══ iPad · LIVE (1024×768) ═══ */
  let f = frameNode('iPad · 1 · Live', 1024, 768, 0, 0, C.black);
  // stage
  const stage = rect(f, 788, 768, C.stage); stage.x = 0; stage.y = 0;
  const sampleHint = txt(f, 'live viewfinder — WYSIWYG preview (same bake as capture)', 12, C.dim, 'Regular');
  sampleHint.resize(788, 16); sampleHint.textAlignHorizontal = 'CENTER'; sampleHint.x = 0; sampleHint.y = 730;
  brand(f, 16, 14); chip(f, 806 - 196, 10, '↺ Reset'); chip(f, 806 - 106, 10, '✕');
  // right rail
  const rail = rect(f, 236, 768, C.panel); rail.x = 788; rail.y = 0;
  const railLine = rect(f, 1, 768, C.line); railLine.x = 788; railLine.y = 0;
  slider(f, 808, 170, 196, 'Exposure', '+12', 0.56);
  slider(f, 808, 258, 196, 'Warmth', '-8', 0.46);
  slider(f, 808, 346, 196, 'Saturation', '+6', 0.53);
  shutter(f, 906, 520);
  const sess = txt(f, '3 ✓', 12, C.dim, 'Regular'); sess.x = 960; sess.y = 514;

  /* ═══ iPad · RESULT ═══ */
  f = frameNode('iPad · 2 · Result', 1024, 768, 1104, 0);
  brand(f, 16, 14);
  const bkchip = chip(f, 850, 10, 'baked · full res'); bkchip.strokes = [];
  const ph = rect(f, 520, 620, C.stage, 12); ph.x = 22; ph.y = 78; ph.strokes = fill(C.line); ph.strokeWeight = 1;
  const phLbl = txt(f, 'captured JPEG — already baked, no re-processing', 12, C.dim, 'Regular');
  phLbl.resize(520, 16); phLbl.textAlignHorizontal = 'CENTER'; phLbl.x = 22; phLbl.y = 380;
  const fx = 586, fw = 196;
  const st = txt(f, '✓ Label read — check the fields, then Save.', 12.5, C.gold2, 'Regular'); st.x = fx; st.y = 150;
  field(f, fx, 186, fw, 'Vendor', 'Schumacher');
  field(f, fx + fw + 14, 186, fw, 'Mfr # / SKU', '5015301');
  field(f, fx, 268, fw, 'Pattern name', 'Torbay Handprint');
  field(f, fx + fw + 14, 268, fw, 'Color', 'Aloe');
  button(f, fx, 372, fw, '↩ Retake', false);
  button(f, fx + fw + 14, 372, fw, 'Save draft', true);
  const tm = txt(f, 'bake 60ms @ 2000×1125 · upload started +61ms after shutter · extract 808ms', 11, { r: 0.435, g: 0.408, b: 0.310 }, 'Regular');
  tm.resize(fw * 2 + 14, 14); tm.textAlignHorizontal = 'CENTER'; tm.x = fx; tm.y = 446;

  /* ═══ iPad · SAVED ═══ */
  f = frameNode('iPad · 3 · Saved', 1024, 768, 2208, 0);
  const ck = txt(f, '✓', 52, C.ink, 'Regular'); ck.resize(1024, 60); ck.textAlignHorizontal = 'CENTER'; ck.x = 0; ck.y = 250;
  const sku = txt(f, 'DWSH-99001', 26, C.gold2, 'Bold', 4); sku.resize(1024, 34); sku.textAlignHorizontal = 'CENTER'; sku.x = 0; sku.y = 330;
  const sub = txt(f, 'Saved as a Shopify draft — never auto-published.\nAdjustments and camera stay warm for the next sample.', 13.5, C.dim, 'Regular');
  sub.resize(1024, 40); sub.textAlignHorizontal = 'CENTER'; sub.x = 0; sub.y = 380;
  button(f, 392, 452, 240, 'Next sample', true);
  const s2 = txt(f, '3 saved this session', 12, C.dim, 'Regular'); s2.resize(1024, 16); s2.textAlignHorizontal = 'CENTER'; s2.x = 0; s2.y = 522;

  /* ═══ Phone · LIVE (390×844) ═══ */
  f = frameNode('Phone · 1 · Live', 390, 844, 0, 880, C.black);
  const pstage = rect(f, 390, 470, C.stage); pstage.x = 0; pstage.y = 0;
  brand(f, 16, 14); chip(f, 250, 10, '↺'); chip(f, 316, 10, '✕');
  const ph2 = txt(f, 'Frame the sample · adjust · shoot', 12, C.dim, 'Regular');
  ph2.resize(390, 16); ph2.textAlignHorizontal = 'CENTER'; ph2.x = 0; ph2.y = 440;
  const ctl = rect(f, 390, 374, { r: 0.082, g: 0.075, b: 0.047 }); ctl.x = 0; ctl.y = 470;
  // phone sliders: label 96 + track + value, horizontal rows
  function phoneSlider(y, label, val, pct) {
    const l = txt(f, label.toUpperCase(), 11, C.dim, 'Semi Bold', 18); l.x = 16; l.y = y + 6;
    const v = txt(f, String(val), 14, C.gold2, 'Regular'); v.x = 344; v.y = y + 4;
    const track = rect(f, 210, 4, C.line, 2); track.x = 118; track.y = y + 12;
    const notch = rect(f, 2, 8, C.gold, 1); notch.x = 222; notch.y = y + 10;
    const th = figma.createEllipse(); th.resize(30, 30); th.fills = fill(C.gold);
    th.strokes = fill(C.darkBtn); th.strokeWeight = 2;
    th.x = 118 + Math.round(180 * pct); th.y = y - 1; f.appendChild(th);
  }
  phoneSlider(500, 'Exposure', '+12', 0.56);
  phoneSlider(558, 'Warmth', '-8', 0.46);
  phoneSlider(616, 'Saturation', '+6', 0.53);
  shutter(f, 195, 730);
  const psess = txt(f, '3 ✓', 12, C.dim, 'Regular'); psess.x = 285, psess.y = 724;

  /* ═══ Phone · RESULT ═══ */
  f = frameNode('Phone · 2 · Result', 390, 844, 470, 880);
  brand(f, 16, 14);
  const pph = rect(f, 358, 300, C.stage, 12); pph.x = 16; pph.y = 60; pph.strokes = fill(C.line); pph.strokeWeight = 1;
  const pst = txt(f, '✓ Label read — check the fields, then Save.', 12.5, C.gold2, 'Regular'); pst.x = 16; pst.y = 376;
  const pfw = 172;
  field(f, 16, 402, pfw, 'Vendor', 'Schumacher');
  field(f, 202, 402, pfw, 'Mfr # / SKU', '5015301');
  field(f, 16, 484, pfw, 'Pattern name', 'Torbay Handprint');
  field(f, 202, 484, pfw, 'Color', 'Aloe');
  button(f, 16, 588, pfw, '↩ Retake', false);
  button(f, 202, 588, pfw, 'Save draft', true);
  const ptm = txt(f, 'bake 60ms · upload +61ms after shutter · extract 808ms', 11, { r: 0.435, g: 0.408, b: 0.310 }, 'Regular');
  ptm.resize(358, 14); ptm.textAlignHorizontal = 'CENTER'; ptm.x = 16; ptm.y = 660;

  /* ═══ Phone · SAVED ═══ */
  f = frameNode('Phone · 3 · Saved', 390, 844, 940, 880);
  const pck = txt(f, '✓', 52, C.ink, 'Regular'); pck.resize(390, 60); pck.textAlignHorizontal = 'CENTER'; pck.x = 0; pck.y = 280;
  const psku = txt(f, 'DWSH-99001', 26, C.gold2, 'Bold', 4); psku.resize(390, 34); psku.textAlignHorizontal = 'CENTER'; psku.x = 0; psku.y = 360;
  const psub = txt(f, 'Saved as a Shopify draft — never auto-published.\nAdjustments and camera stay warm.', 13.5, C.dim, 'Regular');
  psub.resize(390, 40); psub.textAlignHorizontal = 'CENTER'; psub.x = 0; psub.y = 410;
  button(f, 75, 486, 240, 'Next sample', true);

  figma.viewport.scrollAndZoomIntoView(figma.currentPage.children);
  return 'built ' + figma.currentPage.children.length + ' frames';
})()