{ "modo": "holdout", "tag": "v2-bf16-holdout", "modelo": "qwen3.6-35b-a3b-mcp-v2-bf16", "total": 60, "prompts_en_archivo": 60, "errores_de_request": 0, "con_api_prohibida": 9, "tasa": 0.15, "umbral": 0.05, "aprueba": false, "patrones": { "flex.appendChild": 2, "import_image / importImage / createImage / filePath": 4, "fontSize/fontWeight/lineHeight/letterSpacing numerico": 3 }, "resultados": [ { "prompt": "Hazme una landing completa de una academia de tango, 1180 px de ancho, con encabezado, hero, tres beneficios, testimonios y pie. Ponele color, nada de cajas grises.", "id": "pd-001", "tool_calls": [ "penpot_api_info" ], "code_payloads": [], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Necesito la home de un estudio de doblaje en 1180 px: hero con foto de fondo, una franja de numeros y un bloque final de contacto. Paleta a tu criterio, que sea vibrante.", "id": "pd-002", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#2B1055', accent:'#E0218B', gold:'#FFD60A', white:'#FFFFFF' };\nconst T = { h1:'48', h2:'28', body:'16', small:'13' };\nconst S = { sm:12, md:24, lg:40 };\nconst mk = (chars, size, color, weight, align) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = 'Work Sans'; t.fontSize = size; t.fontWeight = weight || '400';\n t.lineHeight = '1.35'; t.letterSpacing = '0'; t.align = align || 'left';\n t.growType = 'auto-width'; t.fills = [{ fillColor: color, fillOpacity: 1 }];\n return t;\n};\nconst mkBtn = (chars, size, color, bg) => {\n const t = mk(chars, size, color, '700');\n const b = penpot.createRectangle();\n if (!b) throw new Error('createRectangle devolvio null');\n b.name = 'Boton ' + chars; b.fills = [{ fillColor: bg, fillOpacity: 1 }];\n b.borderRadius = 28; b.resize(220, 52); b.addFlexLayout();\n if (b.flex) { b.flex.dir = 'row'; b.flex.alignItems = 'center'; b.flex.justifyContent = 'center'; b.flex.rowGap = '8'; b.flex.horizontalPadding = '24'; b.flex.verticalPadding = '16'; }\n b.insertChild(b.children.length, t);\n return b;\n};\nconst mkNum = (n, l) => {\n const c = penpot.createBoard();\n if (!c) throw new Error('createBoard devolvio null');\n c.name = 'Num ' + n; c.fills = [{ fillColor: P.accent, fillOpacity: 1 }]; c.resize(220, 120); c.borderRadius = 16;\n c.insertChild(c.children.length, mk(n, '40', P.gold, '700', 'center'));\n c.insertChild(c.children.length, mk(l, '14', P.white, '400', 'center'));\n return c;\n};\nconst hero = penpot.createBoard();\nif (!hero) throw new Error('createBoard devolvio null');\nhero.name = 'Hero'; hero.fills = [{ fillColor: P.bg, fillOpacity: 1 }]; hero.resize(1180, 520);\nhero.insertChild(hero.children.length, mk('Voces que se recuerdan', '52', P.white, '700'));\nhero.insertChild(hero.children.length, mk('Estudio de doblaje profesional para cine, series y videojuegos.', '18', P.white, '400'));\nhero.insertChild(hero.children.length, mkBtn('Solicitar presupuesto', '16', P.bg, P.gold));\nif (hero.flex) { hero.flex.dir = 'column'; hero.flex.alignItems = 'start'; hero.flex.rowGap = '20'; hero.flex.horizontalPadding = '80'; hero.flex.verticalPadding = '60'; }\nconst stats = penpot.createBoard();\nif (!stats) throw new Error('createBoard devolvio null');\nstats.name = 'Estadisticas'; stats.fills = [{ fillColor: P.accent, fillOpacity: 1 }]; stats.resize(1180, 160);\nstats.insertChild(stats.children.length, mkNum('340', 'Proyectos entregados'));\nstats.insertChild(stats.children.length, mkNum('12', 'Anos de trayectoria'));\nstats.insertChild(stats.children.length, mkNum('85', 'Artistas en plantilla'));\nstats.insertChild(stats.children.length, mkNum('4', 'Idiomas'));\nif (stats.flex) { stats.flex.dir = 'row'; stats.flex.alignItems = 'center'; stats.flex.justifyContent = 'center'; stats.flex.columnGap = '40'; stats.flex.horizontalPadding = '80'; }\nconst cta = penpot.createBoard();\nif (!cta) throw new Error('createBoard devolvio null');\ncta.name = 'Contacto'; cta.fills = [{ fillColor: P.bg, fillOpacity: 1 }]; cta.resize(1180, 200);\ncta.insertChild(cta.children.length, mk('Hablemos de tu proyecto', '32', P.white, '700'));\ncta.insertChild(cta.children.length, mkBtn('Agendar reunion', '16', P.bg, P.gold));\nif (cta.flex) { cta.flex.dir = 'column'; cta.flex.alignItems = 'center'; cta.flex.rowGap = '20'; cta.flex.horizontalPadding = '80'; cta.flex.verticalPadding = '60'; }\nconst page = penpotUtils.getPageByName('Home');\nif (!page) throw new Error('pagina no encontrada');\npage.insertChild(page.root.children.length, hero);\npage.insertChild(page.root.children.length, stats);\npage.insertChild(page.root.children.length, cta);\nreturn { heroId: hero.id, statsId: stats.id, ctaId: cta.id, heroH: hero.height, statsH: stats.height, ctaH: cta.height, heroFlex: !!hero.flex, statsFlex: !!stats.flex, ctaFlex: !!cta.flex };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Armame algo lindo para un museo de maquinas de escribir, una pagina de presentacion. Vos elegis todo.", "id": "pd-003", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { fondo:'#FDF6EC', texto:'#1A1A2E', acento:'#C41E3A', secundario:'#E8B84B', blanco:'#FFFFFF', barra:'#2D2D44' };\nconst T = { display:'48', heading:'28', body:'16', caption:'13' };\nconst S = { xs:'8', sm:'16', md:'32', lg:'64' };\nconst C = { card:'Card', badge:'Badge', boton:'Boton' };\nconst mk = (chars, size, color, weight, lineH) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = 'Playfair Display';\n t.fontSize = size;\n t.fontWeight = weight || '400';\n t.lineHeight = lineH || '1.4';\n t.growType = 'auto-height';\n t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.strokes = [];\n t.shadows = [];\n return t;\n};\nconst mkCard = (name, anio, desc) => {\n const card = penpot.createRectangle();\n if (!card) throw new Error('createRectangle devolvio null');\n card.name = C.card;\n card.resize(480, 280);\n card.fills = [{ fillColor: P.blanco, fillOpacity: 1 }];\n card.strokes = [{ strokeColor: P.secundario, strokeWidth: 1, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\n card.borderRadius = 16;\n card.shadows = [{ style: 'drop-shadow', offsetX: 0, offsetY: 6, blur: 20, spread: 0, color: { color: '#1A1A2E', opacity: 0.12 } }];\n card.insertChild(card.children.length, mk(name, T.heading, P.texto, '700', '1.3'));\n card.insertChild(card.children.length, mk(anio, T.caption, P.acento, '700', '1.4'));\n card.insertChild(card.children.length, mk(desc, T.body, P.texto, '400', '1.6'));\n card.children.forEach(c => { c.alignSelf = 'start'; c.horizontalSizing = 'fit-content'; });\n return card;\n};\nconst mkBadge = (text) => {\n const badge = penpot.createRectangle();\n if (!badge) throw new Error('createRectangle devolvio null');\n badge.name = C.badge;\n badge.resize(160, 32);\n badge.fills = [{ fillColor: P.secundario, fillOpacity: 1 }];\n badge.borderRadius = 16;\n badge.insertChild(badge.children.length, mk(text, T.caption, P.texto, '700', '1.4'));\n return badge;\n};\nconst mkBoton = (text) => {\n const btn = penpot.createRectangle();\n if (!btn) throw new Error('createRectangle devolvio null');\n btn.name = C.boton;\n btn.resize(200, 48);\n btn.fills = [{ fillColor: P.acento, fillOpacity: 1 }];\n btn.borderRadius = 24;\n btn.insertChild(btn.children.length, mk(text, T.body, P.blanco, '700', '1.4'));\n return btn;\n};\nconst page = penpot.createPage('Museo de Maquinas de Escribir');\nif (!page) throw new Error('createPage devolvio null');\nconst root = page.root;\nroot.name = 'Museo - Home';\nroot.resize(1200, 1000);\nroot.fills = [{ fillColor: P.fondo, fillOpacity: 1 }];\nconst hero = penpot.createBoard('Hero');\nif (!hero) throw new Error('createBoard devolvio null');\nhero.name = 'Hero';\nhero.resize(1200, 520);\nhero.fills = [{ fillColor: P.barra, fillOpacity: 1 }];\nhero.borderRadius = 0;\nhero.insertChild(hero.children.length, mk('MUSEO DE MAQUINAS DE ESCRIBIR', '14', P.secundario, '700', '1.4'));\nhero.insertChild(hero.children.length, mk('Donde el clic de cada tecla revive una historia', '48', P.fondo, '700', '1.15'));\nhero.insertChild(hero.children.length, mk('Coleccion de 120 maquinas de 1874 a 1990. Visitas guiadas los sabados.', '16', P.fondo, '400', '1.6'));\nhero.insertChild(hero.children.length, mkBadge('Coleccion permanente'));\nhero.insertChild(hero.children.length, mkBoton('Reservar visita'));\nhero.flex = hero.addFlexLayout();\nif (hero.flex) {\n hero.flex.dir = 'column';\n hero.flex.rowGap = S.md;\n hero.flex.horizontalPadding = S.lg;\n hero.flex.verticalPadding = S.lg;\n hero.flex.alignItems = 'center';\n hero.flex.justifyContent = 'center';\n}\nconst col = penpot.createBoard('Coleccion');\nif (!col) throw new root ? null : null;\ncol.name = 'Coleccion';\ncol.resize(1200, 480);\ncol.fills = [{ fillColor: P.fondo, fillOpacity: 1 }];\ncol.grid = col.addGridLayout();\nif (col.grid) {\n col.grid.rows = '1fr 1fr';\n col.grid.columns = '1fr 1fr';\n col.grid.rowGap = S.md;\n col.grid.columnGap = S.md;\n col.grid.horizontalPadding = S.lg;\n col.grid.verticalPadding = S.lg;\n}\nconst maq = [\n ['Remington No. 2', '1874', 'La primera maquina de escribir comercialmente exitosa.'],\n ['Olivetti Lettera 32', '1950', 'Diseño italiano icnico, aluminio y teclas redondas.'],\n ['Underwood Standard', '1900', 'La maquina que escribio los tratados de paz.'],\n ['Smith Corona Sterling', '1968', 'Portatil ligera, la companera de los periodistas.']\n];\nmaq.forEach(m => col.appendChild(mkCard(m[0], m[1], m[2])));\nroot.insertChild(root.children.length, hero);\nroot.insertChild(root.children.length, col);\nconst heroS = penpotUtils.shapeStructure(hero, 2);\nconst colS = penpotUtils.shapeStructure(col, 2);\nreturn {\n pageId: page.id,\n heroLayout: hero.flex ? { dir: hero.flex.dir, rowGap: hero.flex.rowGap, horizontalPadding: hero.flex.horizontalPadding, verticalPadding: hero.flex.verticalPadding, alignItems: hero.flex.alignItems, justifyContent: hero.flex.justifyContent } : null,\n colLayout: col.grid ? { rows: col.grid.rows, columns: col.grid.columns, rowGap: col.grid.rowGap, columnGap: col.grid.columnGap, horizontalPadding: col.grid.horizontalPadding, verticalPadding: col.grid.verticalPadding } : null,\n heroChildren: heroS.children ? heroS.children.length : 0,\n colChildren: colS.children ? colS.children.length : 0,\n cardStrokes: penpotUtils.findShapes(s => s.type === 'rectangle', col).filter(s => s.strokes && s.strokes.length > 0).length,\n cardShadows: penpotUtils.findShapes(s => s.shadows && s.shadows.length > 0, col).length,\n heroFills: hero.fills ? hero.fills.length : 0,\n colFills: col.fills ? col.fills.length : 0,\n rootFills: root.fills ? root.fills.length : 0\n};" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Pagina de precios de un vivero de orquideas de 900 px: encabezado, tres planes con el del medio destacado en #312e81, preguntas frecuentes y pie.", "id": "pd-004", "tool_calls": [], "code_payloads": [], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Quiero la pantalla principal del panel interno de un vivero de orquideas, 1320 px, con barra lateral, seis tarjetas de indicadores y una tabla de movimientos.", "id": "pd-005", "tool_calls": [ "execute_code" ], "code_payloads": [ "const S = {\n verde: '#0B3D2E', hoja: '#1A7A5C', lima: '#34D399', crema: '#F4F1EB',\n blanco: '#FFFFFF', tinta: '#111827', gris: '#6B7280', borde: '#D1D5DB',\n rojo: '#DC2626', amarillo: '#F59E0B'\n};\nconst T = { sm: '13', md: '16', lg: '20', xl: '28' };\nconst P = { s: 8, m: 16, l: 24, xl: 32, xxl: 40, xxxl: 48 };\nconst addText = (chars, size, color, weight, parent) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = 'Inter'; t.fontSize = size; t.fontWeight = weight; t.lineHeight = '1.5';\n t.fills = [{ fillColor: color, fillOpacity: 1 }]; t.growType = 'auto-width';\n if (parent) parent.insertChild(parent.children.length, t);\n return t;\n};\nconst bd = penpot.createBoard();\nbd.name = 'Orquideas — Panel'; bd.fills = [{ fillColor: S.crema, fillOpacity: 1 }];\nbd.resize(1320, 800);\nconst mk = (name, w, h, fill) => {\n const s = penpot.createRectangle();\n s.name = name; s.fills = [{ fillColor: fill, fillOpacity: 1 }]; s.resize(w, h);\n return s;\n};\nconst card = (name, w, h, fill) => {\n const c = penpot.createBoard();\n c.name = name; c.fills = [{ fillColor: fill, fillOpacity: 1 }]; c.resize(w, h);\n c.borderRadius = 12;\n c.strokes = [{ strokeColor: S.borde, strokeWidth: 1, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\n c.shadows = [{ style: 'drop-shadow', offsetX: 0, offsetY: 2, blur: 8, spread: 0, color: { color: '#000000', opacity: 0.08' } }];\n return c;\n};\nconst ind = (label, valor, color, parent) => {\n const c = card(label, 320, 120, S.blanco);\n c.insertChild(0, addText(label, T.md, S.gris, '500', c));\n c.insertChild(1, addText(valor, T.xl, color, '700', c));\n if (parent) parent.insertChild(parent.children.length, c);\n return c;\n};\nconst fila = (cells, parent) => {\n const r = penpot.createBoard();\n r.name = 'Fila'; r.fills = [{ fillColor: S.blanco, fillOpacity: 1 }]; r.resize(1040, 48);\n r.strokes = [{ strokeColor: S.borde, strokeWidth: 1, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\n cells.forEach((c, i) => {\n c.x = i * 260; c.y = 0; c.width = 260; c.height = 48;\n if (c.type === 'text') { c.alignHorizontal = 'center'; }\n r.insertChild(r.children.length, c);\n });\n if (parent) parent.insertChild(parent.children.length, r);\n return r;\n};\nconst cab = (cells, parent) => {\n const r = fila(cells, parent);\n r.fills = [{ fillColor: S.verde, fillOpacity: 1 }];\n cells.forEach(c => { if (c.type === 'text') c.fills = [{ fillColor: S.blanco, fillOpacity: 1 }]; });\n return r;\n};\nconst sidebar = mk('Sidebar', 240, 800, S.verde);\nbd.insertChild(0, sidebar);\naddText('Orquideas', '28', S.lima, '700', sidebar);\naddText('Panel de control', '16', S.hoja, '500', sidebar);\nconst panel = penpot.createBoard();\npanel.name = 'Panel'; panel.fills = [{ fillColor: S.crema, fillOpacity: 1 }]; panel.resize(1080, 800);\nbd.insertChild(1, panel);\npanel.addFlexLayout();\nif (panel.flex) {\n panel.flex.dir = 'column'; panel.flex.horizontalPadding = P.xl; panel.flex.verticalPadding = P.xl;\n panel.flex.rowGap = P.xl; panel.flex.alignItems = 'start';\n}\nconst header = mk('Header', 1040, 48, S.crema);\npanel.flex.appendChild(header);\naddText('Resumen del vivero', '28', S.tinta, '700', header);\nconst grid = penpot.createBoard();\ngrid.name = 'Indicadores'; grid.fills = [{ fillColor: S.crema, fillOpacity: 1 }]; grid.resize(1040, 160);\npanel.flex.appendChild(grid);\ngrid.addFlexLayout();\nif (grid.flex) {\n grid.flex.dir = 'row'; grid.flex.wrap = 'wrap'; grid.flex.rowGap = P.m; grid.flex.columnGap = P.m;\n grid.flex.horizontalPadding = P.m;\n}\nconst indicadores = [\n ['Plantas vivas', '1.247', S.verde],\n ['En flor', '384', S.lima],\n ['En cuarentena', '23', S.amarillo],\n ['Trasplantes hoy', '12', S.verde],\n ['Riego pendiente', '7', S.amarillo],\n ['Plagas activas', '2', S.rojo]\n];\nindicadores.forEach(d => ind(d[0], d[1], d[2], grid));\nconst tabla = penpot.createBoard();\ntabla.name = 'Movimientos'; tabla.fills = [{ fillColor: S.crema, fillOpacity: 1 }]; tabla.resize(1040, 240);\npanel.flex.appendChild(tabla);\ntabla.addFlexLayout();\nif (tabla.flex) {\n tabla.flex.dir = 'column'; tabla.flex.rowGap = 0; tabla.flex.horizontalPadding = P.m;\n}\nconst cols = ['Especimen', 'Origen', 'Estado', 'Fecha'];\ncab(cols.map(c => addText(c, T.sm, S.blanco, '600', null)), tabla);\n[['Dendrobium phalaenopsis', 'Invernadero B', 'Establecida', '12/06'],\n ['Phalaenopsis amabilis', 'Externo', 'Cuarentena', '14/06'],\n ['Cattleya trianae', 'Invernadero A', 'En flor', '10/06'],\n ['Vanda coerulea', 'Invernadero C', 'Establecida', '08/06'],\n ['Paphiopedilum insigne', 'Invernadero A', 'En flor', '15/06']].forEach(d =>\n fila(d.map(c => addText(c, T.md, S.tinta, '400', null)), tabla));\nconst structure = penpotUtils.shapeStructure(bd, 3);\nconst flex = penpotUtils.findShapes(s => s.flex, bd);\nreturn { boardId: bd.id, sidebarWidth: sidebar.width, panelWidth: panel.width,\n indicators: penpotUtils.findShapes(s => s.name.startsWith('Indicador'), bd).length,\n tableRows: penpotUtils.findShapes(s => s.name === 'Fila', bd).length,\n flexBoards: flex.length, structure };" ], "veto_hits": [ { "patron": "flex.appendChild", "fragmento": "= mk('Header', 1040, 48, S.crema); panel.flex.appendChild(header); addText('Resumen del vivero', '" } ], "otros_patrones": [ { "patron": "asignacion a propiedad read-only", "fragmento": "c, i) => { c.x = i * 260; c.y = 0; c.width = 260; c.height = 48; if (c.type === " } ], "content": "" }, { "prompt": "Landing de un evento de un laboratorio de analisis de suelos: fecha grande, cuenta regresiva, grilla de oradores y formulario de inscripcion, 1240 px de ancho.", "id": "pd-006", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#0B1D3A', sec:'#1A3A6B', accent:'#00B4D8', white:'#FFFFFF', light:'#F0F4F8' };\nconst T = { xl:'72', lg:'48', md:'24', sm:'18', xs:'14' };\nconst W = { h1:'700', h2:'600', body:'400' };\nconst S = { sm:16, md:24, lg:40, xl:64 };\nconst mk = (chars, size, weight, color, family) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText returned null');\n t.fontFamily = family || 'Montserrat';\n t.fontSize = size;\n t.fontWeight = weight;\n t.lineHeight = '1.3';\n t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.growType = 'auto-width';\n return t;\n};\nconst mkSec = (name, bg, h) => {\n const b = penpot.createBoard();\n b.name = name;\n b.fills = [{ fillColor: bg, fillOpacity: 1 }];\n b.resize(1240, h);\n b.borderRadius = 0;\n return b;\n};\nconst add = (parent, shape) => { parent.insertChild(parent.children.length, shape); };\nconst root = penpot.root;\nconst pages = penpotUtils.getPages();\nconst page = pages.length ? penpotUtils.getPageById(pages[0].id) : null;\nif (!page) throw new Error('No page found');\nconst sections = [\n { name:'Hero', bg:P.bg, h:520 },\n { name:'Cuenta regresiva', bg:P.accent, h:200 },\n { name:'Oradores', bg:P.sec, h:560 },\n { name:'Inscripcion', bg:P.bg, h:440 }\n];\nconst boards = sections.map(s => {\n const b = mkSec(s.name, s.bg, s.h);\n add(root, b);\n return b;\n});\nconst hero = boards[0];\nadd(hero, mk('XV CONGRESO DE CIENCIAS DEL SUELO', T.xl, W.h1, P.accent));\nadd(hero, mk('Buenos Aires, 12-14 de noviembre de 2025', T.md, W.body, P.white));\nconst cd = boards[1];\nadd(cd, mk('FALTAN 47 DIAS', T.lg, W.h1, P.bg));\nconst oradores = boards[2];\nconst grid = oradores.addGridLayout();\nif (grid) {\n grid.rows = 'auto auto';\n grid.columns = '1fr 1fr';\n grid.rowGap = S.lg;\n grid.columnGap = S.lg;\n grid.horizontalPadding = S.xl;\n grid.verticalPadding = S.xl;\n}\nconst speakers = [\n { name:'Dra. Maria Fernandez', role:'Microbiologia del suelo' },\n { name:'Dr. Carlos Ruiz', role:'Quimica de nutrientes' },\n { name:'Ing. Laura Gomez', role:'Manejo de suelos degradados' },\n { name:'Dr. Pablo Torres', role:'Teledeteccion y suelos' }\n];\nconst speakerShapes = speakers.map(sp => {\n const card = penpot.createBoard();\n card.name = 'Orador - ' + sp.name;\n card.fills = [{ fillColor: P.light, fillOpacity: 1 }];\n card.resize(500, 180);\n card.borderRadius = 12;\n add(oradores, card);\n if (card.gridChild) {\n card.gridChild.row = 1;\n card.gridChild.column = (speakers.indexOf(sp) % 2) + 1;\n card.gridChild.rowSpan = 1;\n card.gridChild.columnSpan = 1;\n }\n add(card, mk(sp.name, T.md, W.h2, P.bg));\n add(card, mk(sp.role, T.sm, W.body, P.accent));\n return card;\n});\nconst insc = boards[3];\nadd(insc, mk('INSCRIPCION GRATUITA', T.lg, W.h1, P.accent));\nadd(insc, mk('Completa el formulario para reservar tu lugar', T.md, W.body, P.white));\nconst btn = penpot.createRectangle();\nbtn.name = 'Boton inscribir';\nbtn.fills = [{ fillColor: P.accent, fillOpacity: 1 }];\nbtn.resize(280, 56);\nbtn.borderRadius = 28;\nadd(insc, btn);\nadd(insc, mk('INSCRIBIRME AHORA', T.md, W.h2, P.bg));\nstorage.landing = { pageId: page.id, sections: sections.map(s => s.name), boardIds: boards.map(b => b.id), speakerCount: speakers.length };\nreturn { pageId: page.id, sections: sections.length, boardIds: boards.map(b => b.id), speakerCount: speakers.length, gridRows: grid ? grid.rows : null, gridColumns: grid ? grid.columns : null };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Un boton primario y uno secundario para un servicio de reparacion de relojes, ambos con estados normal y deshabilitado, usando #581c87 como acento.", "id": "pd-007", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { accent: '#581c87', bg: '#f5f0fa', white: '#ffffff', ink: '#1a1a2e', muted: '#6b5b7b', disabled: '#d4c5e2' };\nconst S = { xs: '12', sm: '14', md: '16', lg: '20' };\nconst T = { btn: '16', btnSm: '14' };\nconst mk = (chars, size, weight, color, family) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText returned null');\n t.fontFamily = family || 'Work Sans';\n t.fontSize = size;\n t.fontWeight = weight || '400';\n t.lineHeight = '1.3';\n t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.growType = 'auto-width';\n return t;\n};\nconst btn = (label, variant) => {\n const isDisabled = variant === 'disabled';\n const bg = isDisabled ? P.disabled : (variant === 'primary' ? P.accent : P.white);\n const fill = isDisabled ? P.muted : (variant === 'primary' ? P.white : P.accent);\n const stroke = isDisabled ? null : { color: P.accent, width: 2, style: 'solid' };\n const shape = penpot.createRectangle();\n if (!shape) throw new Error('createRectangle returned null');\n shape.name = label + ' ' + variant;\n shape.fills = [{ fillColor: bg, fillOpacity: 1 }];\n if (stroke) shape.strokes = [stroke];\n shape.borderRadius = 10;\n shape.resize(200, 52);\n shape.addFlexLayout();\n if (shape.flex) {\n shape.flex.dir = 'row';\n shape.flex.horizontalPadding = 24;\n shape.flex.verticalPadding = 14;\n shape.flex.alignItems = 'center';\n shape.flex.justifyContent = 'center';\n shape.flex.rowGap = 10;\n }\n const text = mk(label, T.btn, '600', fill);\n if (isDisabled) {\n text.fontWeight = '400';\n text.opacity = 0.7;\n }\n shape.insertChild(shape.children.length, text);\n return shape;\n};\nconst page = penpotUtils.getPageByName('Landing');\nif (!page) throw new Error('Page not found');\nconst root = page.root;\nconst panel = penpot.createRectangle();\npanel.name = 'Botones';\npanel.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\npanel.resize(440, 160);\npanel.borderRadius = 16;\nroot.insertChild(root.children.length, panel);\nif (panel.flex) {\n panel.flex.dir = 'row';\n panel.flex.horizontalPadding = 32;\n panel.flex.verticalPadding = 32;\n panel.flex.alignItems = 'center';\n panel.flex.justifyContent = 'center';\n panel.flex.rowGap = 20;\n}\nconst prim = btn('Agendar reparacion', 'primary');\nconst sec = btn('Ver precios', 'secondary');\nconst dis = btn('Agendar reparacion', 'disabled');\npanel.appendChild(prim);\npanel.appendChild(sec);\npanel.appendChild(dis);\nconst ids = [prim.id, sec.id, dis.id, panel.id];\nreturn { panelId: panel.id, buttonIds: ids, buttonNames: ids.map(id => penpotUtils.findShapeById(id)?.name), panelFlex: !!panel.flex };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Card de producto para un servicio de reparacion de relojes: foto arriba, titulo en Karla, precio tachado y precio final, y un boton. 320 de ancho.", "id": "pd-008", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#F7F5F0', ink:'#1A1A1A', muted:'#8A8A8A', gold:'#B8860B', border:'#E2DCC8', shadow:'#1A1A1A', btn:'#1A1A1A', btnText:'#F7F5F0' };\nconst T = { sm:'14', md:'16', lg:'20', xl:'28' };\nconst S = { xs:8, sm:16, md:24, lg:32 };\nconst mk = (chars, size, weight, color, lineH) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText returned null');\n t.fontFamily = 'Karla'; t.fontSize = size; t.fontWeight = weight; t.lineHeight = lineH; t.letterSpacing = '0'; t.growType = 'auto-width';\n t.fills = [{ fillOpacity: 1, fillColor: color, fillStyleMode: 'fill' }];\n return t;\n};\nconst board = penpot.createBoard();\nboard.name = 'Card Relojeria';\nboard.fills = [{ fillOpacity: 1, fillColor: P.bg, fillStyleMode: 'fill' }];\nboard.resize(320, 440);\nboard.borderRadius = 16;\nboard.strokes = [{ strokeOpacity: 1, strokeWidth: 1, strokeColor: P.border, strokeStyle: 'solid', strokeAlignment: 'inner' }];\nboard.shadows = [{ style: 'drop-shadow', offsetX: 0, offsetY: 4, blur: 16, spread: 0, color: { color: P.shadow, opacity: 0.06 } }];\nboard.addFlexLayout();\nif (board.flex) {\n board.flex.dir = 'column'; board.flex.rowGap = S.sm; board.flex.alignItems = 'center';\n board.flex.horizontalPadding = S.md; board.flex.verticalPadding = S.md;\n}\nconst img = penpot.createRectangle();\nimg.name = 'Foto engranajes';\nimg.fills = [{ fillOpacity: 1, fillColor: P.border, fillStyleMode: 'fill' }];\nimg.resize(272, 160);\nimg.borderRadius = 12;\nimg.strokes = [{ strokeOpacity: 1, strokeWidth: 1, strokeColor: P.bg, strokeStyle: 'solid', strokeAlignment: 'inner' }];\nconst svg = '';\nimg.fills = [{ fillOpacity: 1, fillColor: P.border, fillStyleMode: 'fill', fillImage: { url: 'data:image/svg+xml;base64,' + btoa(svg), width: 272, height: 160 } }];\nconst titulo = mk('Reparacion de relojes suizos', T.lg, '700', P.ink, '1.2');\nconst precioTachado = mk('Antes $120.000', T.md, '400', P.muted, '1.3');\nprecioTachado.texts.forEach(t => { if (t) t.textDecoration = 'line-through'; });\nconst precioFinal = mk('Ahora $89.000', T.xl, '700', P.gold, '1.2');\nconst boton = penpot.createRectangle();\nboton.name = 'Boton Agendar';\nboton.fills = [{ fillOpacity: 1, fillColor: P.btn, fillStyleMode: 'fill' }];\nboton.resize(272, 48);\nboton.borderRadius = 24;\nconst btnText = mk('Agendar reparacion', T.sm, '600', P.btnText, '1.4');\npenpotUtils.setParentXY(btnText, 136, 24);\n[img, titulo, precioTachado, precioFinal, boton].forEach(s => board.appendChild(s));\nif (boton.children) boton.children.forEach(c => penpotUtils.setParentXY(c, 136, 24));\nstorage.card = { boardId: board.id };\nreturn { boardId: board.id, shapes: [board, img, titulo, precioTachado, precioFinal, boton].length, flexDir: board.flex ? board.flex.dir : null };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Hazme una barra de navegacion de una academia de tango de 900x88 con logo, cinco enlaces y un boton de accion a la derecha.", "id": "pd-009", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg: '#0B1D3A', text: '#FFFFFF', accent: '#D4A843', link: '#D4A843' };\nconst S = { sm: '14', md: '16', lg: '22' };\nconst nav = penpot.createBoard();\nnav.name = 'Navbar Academia';\nnav.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nnav.resize(900, 88);\nnav.addFlexLayout();\nif (nav.flex) {\n nav.flex.dir = 'row';\n nav.flex.rowGap = 20;\n nav.flex.columnGap = 20;\n nav.flex.alignItems = 'center';\n nav.flex.horizontalPadding = 40;\n nav.flex.verticalPadding = 0;\n}\nconst mk = (chars, size, color, weight) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.name = chars;\n t.fontFamily = 'Montserrat';\n t.fontSize = size;\n t.fontWeight = weight || '400';\n t.lineHeight = '1.3';\n t.growType = 'auto-width';\n t.fills = [{ fillColor: color, fillOpacity: 1 }];\n return t;\n};\nconst logo = mk('TANGUERIA', S.lg, P.accent, '700');\nconst links = ['Clases', 'Parejas', 'Eventos', 'Galeria', 'Contacto'].map(n => mk(n, S.md, P.link));\nconst cta = mk('Reservar Clase', S.md, P.bg, '700');\nconst ctaBox = penpot.createRectangle();\nctaBox.name = 'Boton Reservar';\nctaBox.fills = [{ fillColor: P.accent, fillOpacity: 1 }];\nctaBox.resize(150, 40);\nctaBox.borderRadius = 20;\nctaBox.insertChild(0, cta);\nconst items = [logo, ...links, ctaBox];\nitems.forEach(s => nav.insertChild(nav.children.length, s));\nstorage.navbar = { boardId: nav.id, logoId: logo.id, ctaId: ctaBox.id };\nreturn { boardId: nav.id, flex: !!nav.flex, dir: nav.flex?.dir, padding: nav.flex ? { h: nav.flex.horizontalPadding, v: nav.flex.verticalPadding } : null, children: nav.children.length, ctaFill: ctaBox.fills[0]?.fillColor };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Un aviso de cookies para el sitio de un estudio de doblaje: texto corto, dos botones y fondo #3b0764.", "id": "pd-010", "tool_calls": [], "code_payloads": [], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Necesito una tarjeta de perfil de una escuela de circo con avatar circular, nombre, rol y tres datos de contacto.", "id": "pd-011", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { fondo:'#FFF8F0', acento:'#D91A64', texto:'#1A0A2E', sec:'#6B4C7A', verde:'#2DD4A8', estrella:'#FFD166' };\nconst T = { h1:'28', h2:'16', body:'14' };\nconst F = 'Montserrat';\nconst mk = (chars, size, color, weight) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = F; t.fontSize = size; t.fontWeight = weight || '400'; t.lineHeight = '1.3';\n t.fills = [{ fillColor: color, fillOpacity: 1 }]; t.growType = 'auto-width';\n return t;\n};\nconst board = penpot.createBoard();\nboard.name = 'Tarjeta Circo Estrella';\nboard.fills = [{ fillColor: P.fondo, fillOpacity: 1 }];\nboard.resize(400, 520);\nboard.addFlexLayout();\nif (board.flex) {\n board.flex.dir = 'column'; board.flex.rowGap = 24;\n board.flex.horizontalPadding = 32; board.flex.verticalPadding = 32;\n board.flex.alignItems = 'center';\n}\nconst avatar = penpot.createEllipse();\navatar.name = 'Avatar';\navatar.fills = [{ fillColor: P.acento, fillOpacity: 1 }];\navatar.strokes = [{ strokeColor: P.estrella, strokeWidth: 4, strokeStyle: 'solid', strokeOpacity: 1 }];\navatar.resize(120, 120);\nboard.insertChild(board.children.length, avatar);\nconst estrella = penpot.createText('★');\nif (!estrella) throw new Error('createText estrella devolvio null');\nestrella.name = 'Estrella';\nestrella.fontFamily = F; estrella.fontSize = '48'; estrella.fontWeight = '700'; estrella.lineHeight = '1';\nestrella.fills = [{ fillColor: P.estrella, fillOpacity: 1 }];\nestrella.growType = 'auto-width';\navatar.insertChild(avatar.children.length, estrella);\nconst nombre = mk('Circo Estrella', T.h1, P.texto, '700');\nnombre.name = 'Nombre';\nboard.insertChild(board.children.length, nombre);\nconst rol = mk('Escuela de circo', T.h2, P.sec);\nrol.name = 'Rol';\nboard.insertChild(board.children.length, rol);\nconst datos = [\n ['Tel.', '+34 91 555 0123'],\n ['Email', 'info@circoestrella.es'],\n ['Web', 'www.circoestrella.es']\n];\nconst cont = penpot.createBoard();\ncont.name = 'Contacto';\ncont.fills = [];\ncont.resize(336, 120);\ncont.addFlexLayout();\nif (cont.flex) {\n cont.flex.dir = 'column'; cont.flex.rowGap = 12;\n cont.flex.horizontalPadding = 0; cont.flex.verticalPadding = 0;\n cont.flex.alignItems = 'start';\n}\nboard.insertChild(board.children.length, cont);\ndatos.forEach(d => {\n const fila = penpot.createBoard();\n fila.name = 'Fila ' + d[0];\n fila.fills = [];\n fila.resize(336, 28);\n fila.addFlexLayout();\n if (fila.flex) {\n fila.flex.dir = 'row'; fila.flex.rowGap = 12;\n fila.flex.horizontalPadding = 0; fila.flex.verticalPadding = 0;\n fila.flex.alignItems = 'start';\n }\n cont.insertChild(cont.children.length, fila);\n const dot = penpot.createRectangle();\n dot.name = 'Bullet ' + d[0];\n dot.fills = [{ fillColor: P.verde, fillOpacity: 1 }];\n dot.resize(8, 8);\n dot.borderRadius = 4;\n fila.insertChild(fila.children.length, dot);\n const txt = mk(d[0] + ' ' + d[1], T.body, P.texto);\n txt.name = 'Texto ' + d[0];\n fila.insertChild(fila.children.length, txt);\n});\nconst css = penpot.generateStyle(board, { type: 'css', withChildren: true });\nstorage.tarjeta = { boardId: board.id, P: P, T: T, F: F };\nreturn { boardId: board.id, shapes: board.children.length, flexDir: board.flex ? board.flex.dir : null,\n avatarDots: avatar.width === 120 && avatar.height === 120,\n estrellas: penpotUtils.findShapes(s => s.type === 'text', avatar).length,\n lineasContacto: cont.children.length,\n datos: cont.children.length === 3 ? cont.children.map(f => {\n const textos = penpotUtils.findShapes(s => s.type === 'text', f);\n return textos.length === 1 ? textos[0].characters : null;\n }) : null,\n palette: Object.keys(P).length,\n fontSizes: [...new Set(penpotUtils.findShapes(s => s.type === 'text', board).map(s => s.fontSize))].length,\n css: css };" ], "veto_hits": [], "otros_patrones": [ { "patron": "generateStyle con withChildren", "fragmento": "pot.generateStyle(board, { type: 'css', withChildren: true }); storage.tarjeta = { boardId: b" } ], "content": "" }, { "prompt": "Necesito una tabla de 1320 px para un vivero de orquideas: encabezado oscuro, cinco filas con fondo alternado y una columna de estado con etiquetas de color.", "id": "pd-012", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#1B5E20', ink:'#FFFFFF', row:'#FFFFFF', alt:'#E8F5E9', col:'#C8E6C9', ok:'#2E7D32', pend:'#F57F17', agot:'#C62828' };\nconst T = { h:'20', b:'15', s:'13' };\nconst S = { sm:8, md:16, lg:24 };\nconst mk = (chars, size, weight, color, family) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = family || 'Inter'; t.fontSize = size; t.fontWeight = weight || '400';\n t.lineHeight = '1.3'; t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.growType = 'auto-width'; return t;\n};\nconst cols = [\n { n:'Especie', w:320 }, { n:'Origen', w:220 }, { n:'Luz', w:140 },\n { n:'Riego', w:140 }, { n:'Estado', w:180 }\n];\nconst rows = [\n ['Phalaenopsis amabilis','Indonesia','Indirecta','Semanal','Disponible'],\n ['Dendrobium nobile','Vietnam','Directa','Bisemanal','Disponible'],\n ['Cattleya labiata','Brasil','Indirecta','Quincenal','Pendiente'],\n ['Oncidium flexuosum','Colombia','Indirecta','Semanal','Pendiente'],\n ['Vanda coerulea','Tailandia','Directa','Diaria','Agotado']\n];\nconst bd = penpot.createBoard();\nbd.name = 'Inventario de orquideas'; bd.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nbd.resize(1320, 520); bd.borderRadius = 16;\nbd.insertChild(bd.children.length, mk('Inventario de orquideas', T.h, '700', P.ink));\nbd.children[0].name = 'Encabezado';\nbd.insertChild(bd.children.length, penpot.createRectangle());\nconst cab = bd.children[1];\ncab.name = 'Cabecera'; cab.fills = [{ fillColor: P.ink, fillOpacity: 1 }];\ncab.resize(1272, 56); cab.borderRadius = 12;\ncab.x = S.lg; cab.y = 80;\ncab.insertChild(cab.children.length, mk('Especie', T.b, '600', P.bg));\ncab.insertChild(cab.children.length, mk('Origen', T.b, '600', P.bg));\ncab.insertChild(cab.children.length, mk('Luz', T.b, '600', P.bg));\ncab.insertChild(cab.children.length, mk('Riego', T.b, '600', P.bg));\ncab.insertChild(cab.children.length, mk('Estado', T.b, '600', P.bg));\nconst cabTexts = penpotUtils.findShapes(s => s.type === 'text', cab);\ncabTexts.forEach((t, i) => { t.x = cols[i].w / 2 - t.width / 2; t.y = 18; });\nconst mkRow = (data, idx) => {\n const r = penpot.createBoard();\n r.name = 'Fila ' + (idx + 1); r.fills = [{ fillColor: idx % 2 === 0 ? P.row : P.alt, fillOpacity: 1 }];\n r.resize(1272, 72); r.borderRadius = 8;\n r.x = S.lg; r.y = 152 + idx * 88;\n data.forEach((d, i) => {\n const c = penpot.createRectangle();\n c.name = 'Celda ' + (i + 1); c.fills = [{ fillColor: P.ink, fillOpacity: 0 }];\n c.resize(cols[i].w, 72); c.borderRadius = 0;\n c.x = cols.slice(0, i).reduce((a, c) => a + c.w, 0); c.y = 0;\n r.insertChild(r.children.length, c);\n const t = mk(d, T.s, '400', P.bg);\n t.name = 'Texto ' + (i + 1); t.x = S.md; t.y = 28;\n r.insertChild(r.children.length, t);\n });\n return r;\n};\nrows.forEach((r, i) => bd.insertChild(bd.children.length, mkRow(r, i)));\nconst cells = penpotUtils.findShapes(s => s.type === 'rectangle', bd);\nconst estados = penpotUtils.findShapes(s => s.name.startsWith('Texto ') && s.parent !== cab, bd);\nconst estadoCol = penpotUtils.findShape(s => s.name === 'Celda 5', bd);\nif (estadoCol) { estadoCol.fills = [{ fillColor: P.col, fillOpacity: 1 }]; }\nconst estadoMap = { 'Disponible': P.ok, 'Pendiente': P.pend, 'Agotado': P.agot };\nestados.forEach(t => {\n const c = estadoMap[t.characters];\n if (c) { t.fills = [{ fillColor: c, fillOpacity: 1 }]; t.fontWeight = '700'; }\n});\nstorage.inventario = { bdId: bd.id, cabId: cab.id };\nreturn { bdId: bd.id, cabId: cab.id, filas: rows.length, celdas: cells.length, estadosColoreados: estados.filter(t => t.fills[0].fillColor !== P.bg).length };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Pantalla de 390x844 para la app de una consultora de eficiencia energetica: foto real de portada traida de una URL, titulo encima y boton abajo.", "id": "pd-013", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { primary:'#0B6E4F', light:'#E8F5E9', dark:'#1A2B22', white:'#FFFFFF' };\nconst S = { sm:12, md:18, lg:28, xl:44 };\nconst board = penpot.createBoard();\nboard.name = 'Consultora Energia';\nboard.fills = [{ fillColor: P.light, fillOpacity: 1 }];\nboard.resize(390, 844);\nboard.x = 0;\nboard.y = 0;\nif (board.flex) {\n board.flex.dir = 'column';\n board.flex.horizontalPadding = S.lg;\n board.flex.verticalPadding = S.lg;\n board.flex.rowGap = S.md;\n board.flex.alignItems = 'start';\n}\nconst img = await penpot.importImage('https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800', 'portada-solar.jpg');\nconst foto = penpot.createRectangle();\nfoto.name = 'Foto portada';\nfoto.fills = [{ fillOpacity: 1, fillImage: img }];\nfoto.resize(390, 320);\nfoto.x = 0;\nfoto.y = 0;\nboard.insertChild(board.children.length, foto);\nconst titulo = penpot.createText('Eficiencia energetica para tu industria');\ntitulo.name = 'Titulo';\ntitulo.fontFamily = 'Work Sans';\ntitulo.fontSize = '32';\ntitulo.fontWeight = '700';\ntitulo.lineHeight = '1.2';\ntitulo.characters = 'Eficiencia energetica para tu industria';\ntitulo.fills = [{ fillColor: P.white, fillOpacity: 1 }];\ntitulo.growType = 'auto-width';\nconst cta = penpot.createRectangle();\ncta.name = 'Boton CTA';\ncta.fills = [{ fillColor: P.primary, fillOpacity: 1 }];\ncta.borderRadius = 28;\ncta.resize(200, 56);\ncta.x = 0;\ncta.y = 0;\nconst btn = penpot.createText('Solicitar auditoria');\nbtn.name = 'Boton texto';\nbtn.fontFamily = 'Work Sans';\nbtn.fontSize = '18';\nbtn.fontWeight = '600';\nbtn.lineHeight = '1.3';\nbtn.characters = 'Solicitar auditoria';\nbtn.fills = [{ fillColor: P.white, fillOpacity: 1 }];\nbtn.growType = 'auto-width';\nif (cta.flex) {\n cta.flex.dir = 'row';\n cta.flex.horizontalPadding = S.lg;\n cta.flex.verticalPadding = S.md;\n cta.flex.alignItems = 'center';\n cta.flex.justifyContent = 'center';\n cta.flex.rowGap = S.sm;\n}\ncta.insertChild(cta.children.length, btn);\nboard.insertChild(board.children.length, titulo);\nboard.insertChild(board.children.length, cta);\nstorage.consultora = { boardId: board.id, P: P, S: S };\nreturn { boardId: board.id, fotoFills: foto.fills.length, fotoHasImage: foto.fills.some(f => !!f.fillImage), tituloFontSize: titulo.fontSize, ctaFills: cta.fills.length, ctaHasImage: cta.fills.some(f => !!f.fillImage), btnFontSize: btn.fontSize };" ], "veto_hits": [ { "patron": "import_image / importImage / createImage / filePath", "fragmento": "s = 'start'; } const img = await penpot.importImage('https://images.unsplash.com/photo-1509" } ], "otros_patrones": [], "content": "" }, { "prompt": "Quiero una galeria de cuatro fotos reales de internet en la app de un albergue para perros mayores, formato 360x780, con leyenda en cada una.", "id": "pd-014", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#FFF8F0', ink:'#1B3A4B', accent:'#C25B3A', green:'#6B8E6B', warm:'#5C524A' };\nconst T = { h:'28', sub:'16', body:'20' };\nconst S = { sm:8, md:16, lg:24, xl:32 };\nconst G = { card:'360x780' };\nconst DOGS = [\n { name:'Bella, 11 años, ama las siestas al sol', url:'https://placedog.net/600/400?id=1' },\n { name:'Max, 9 años, pasea despacio pero con orgullo', url:'https://placedog.net/600/400?id=2' },\n { name:'Luna, 13 años, la abuela del refugio', url:'https://placedog.net/600/400?id=3' },\n { name:'Toby, 10 años, siempre listo para una caricia', url:'https://placedog.net/600/400?id=4' }\n];\nconst mk = (chars, size, color, weight) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = 'Nunito'; t.fontSize = size; t.fontWeight = weight || '400';\n t.lineHeight = '1.4'; t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.growType = 'auto-width';\n return t;\n};\nconst mkImg = (url) => {\n const img = penpot.importImage(url);\n if (!img) throw new Error('importImage devolvio null');\n img.resize(252, 252);\n img.fills = [{ fillOpacity: 1, fillImage: { image: img, imageFit: 'cover', imageOpacity: 1, imageScaling: 'scale', imageAlignment: [0.5, 0.5] } }];\n img.strokes = []; img.borderRadius = 16;\n return img;\n};\nconst mkCard = (dog) => {\n const card = penpot.createBoard();\n card.name = 'Card — ' + dog.name;\n card.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\n card.borderRadius = 20;\n card.addFlexLayout();\n if (card.flex) {\n card.flex.dir = 'column';\n card.flex.horizontalPadding = S.lg;\n card.flex.verticalPadding = S.lg;\n card.flex.rowGap = S.md;\n card.flex.alignItems = 'center';\n }\n card.resize(300, 300);\n const img = mkImg(dog.url);\n img.name = 'Foto — ' + dog.name;\n card.insertChild(card.children.length, img);\n const leg = mk(dog.name, T.body, P.ink, '700');\n leg.name = 'Leyenda — ' + dog.name;\n card.insertChild(card.children.length, leg);\n return card;\n};\nconst root = penpot.createBoard();\nroot.name = 'Galeria Perros';\nroot.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nroot.resize(360, 780);\nroot.addFlexLayout();\nif (root.flex) {\n root.flex.dir = 'column';\n root.flex.horizontalPadding = S.xl;\n root.flex.verticalPadding = S.xl;\n root.flex.rowGap = S.lg;\n root.flex.alignItems = 'center';\n}\nconst title = mk('Nuestros mayores', T.h, P.ink, '700');\ntitle.name = 'Titulo';\nroot.insertChild(root.children.length, title);\nconst sub = mk('Cada uno merece su momento de gloria', T.sub, P.accent, '400');\nsub.name = 'Subtitulo';\nroot.insertChild(root.children.length, sub);\nconst cards = DOGS.map(d => mkCard(d));\ncards.forEach(c => root.insertChild(root.children.length, c));\nconst imgs = penpotUtils.findShapes(s => s.fills && s.fills.some(f => f.fillImage), root);\nconst texts = penpotUtils.findShapes(s => s.type === 'text', root);\nreturn { rootId: root.id, cards: cards.length, images: imgs.length, texts: texts.length,\n fills: imgs.map(i => i.fills ? i.fills.map(f => ({ fit: f.fillImage ? f.fillImage.imageFit : null, scaling: f.fillImage ? f.fillImage.imageScaling : null })) : []),\n cardFlex: cards.map(c => c.flex ? { dir: c.flex.dir, padding: c.flex.horizontalPadding + '/' + c.flex.verticalPadding } : null) };" ], "veto_hits": [ { "patron": "import_image / importImage / createImage / filePath", "fragmento": "mkImg = (url) => { const img = penpot.importImage(url); if (!img) throw new Error('impo" } ], "otros_patrones": [], "content": "" }, { "prompt": "Meteme una imagen de fondo en el hero de una escuela de circo y encima un velo oscuro para que se lea el titular en blanco.", "id": "pd-015", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#0B132B', gold:'#F2A900', white:'#FFFFFF' };\nconst S = { hero:1200, h:500, pad:80, sm:20, lg:48 };\nconst board = penpot.createBoard();\nboard.name = 'Hero Escuela de Circo';\nboard.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nboard.resize(S.hero, S.h);\nconst img = await penpot.importImage('https://picsum.photos/id/1025/1200/500');\nif (!img) throw new Error('No se pudo importar la imagen');\nconst imgFill = { fillOpacity: 1, fillImage: img };\nconst imgRect = penpot.createRectangle();\nimgRect.name = 'Foto de fondo';\nimgRect.fills = [imgFill];\nimgRect.resize(S.hero, S.h);\nconst velo = penpot.createRectangle();\nvelo.name = 'Velo oscuro';\nvelo.fills = [{ fillColor: P.bg, fillOpacity: 1, fillGradient: { type: 'radial', x: 0.5, y: 0.5, rx: 0.8, ry: 0.8 } }];\nvelo.resize(S.hero, S.h);\nconst titulo = penpot.createText('Escuela de Circo');\ntitulo.name = 'Titular';\ntitulo.fontFamily = 'Montserrat';\ntitulo.fontSize = '48';\ntitulo.fontWeight = '700';\ntitulo.lineHeight = '1.1';\ntitulo.characters = 'Escuela de Circo';\ntitulo.fills = [{ fillColor: P.white, fillOpacity: 1 }];\ntitulo.growType = 'auto-width';\nconst sub = penpot.createText('Clases de malabares, acrobacias y clown para todas las edades');\nsub.name = 'Subtitular';\nsub.fontFamily = 'Montserrat';\nsub.fontSize = '20';\nsub.fontWeight = '400';\nsub.lineHeight = '1.5';\nsub.characters = 'Clases de malabares, acrobacias y clown para todas las edades';\nsub.fills = [{ fillColor: P.gold, fillOpacity: 1 }];\nsub.growType = 'auto-width';\nconst acento = penpot.createRectangle();\nacento.name = 'Acento dorado';\nacento.fills = [{ fillColor: P.gold, fillOpacity: 1 }];\nacento.resize(64, 4);\nacento.borderRadius = 2;\nboard.insertChild(0, imgRect);\nboard.insertChild(1, velo);\nboard.insertChild(2, titulo);\nboard.insertChild(3, sub);\nboard.insertChild(4, acento);\npenpotUtils.setParentXY(titulo, S.pad, S.h - S.pad - 120);\npenpotUtils.setParentXY(sub, S.pad, S.h - S.pad - 60);\npenpotUtils.setParentXY(acento, S.pad, S.h - S.pad - 20);\nreturn { boardId: board.id, fills: board.fills.length, imgFilled: imgRect.fills.some(f => f.fillImage), veloFilled: velo.fills.some(f => f.fillGradient), shapes: board.children.length };" ], "veto_hits": [ { "patron": "import_image / importImage / createImage / filePath", "fragmento": "(S.hero, S.h); const img = await penpot.importImage('https://picsum.photos/id/1025/1200/500" } ], "otros_patrones": [], "content": "" }, { "prompt": "Para la ficha de una escuela de circo necesito la foto del producto ocupando toda la tarjeta sin deformarse, 1320 px de ancho.", "id": "pd-016", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { card: 1320, foto: 1400, fotoH: 900, radio: 24 };\nconst S = { fondo: '#0B132B', tinta: '#FFFFFF', marca: '#FFB703' };\nconst mk = (name, w, h, fills, radius) => {\n const s = penpot.createShape('rectangle');\n if (!s) throw new Error('createShape devolvio null');\n s.name = name; s.fills = fills || []; s.borderRadius = radius || 0;\n s.resize(w, h); return s;\n};\nconst board = penpot.createBoard();\nboard.name = 'Ficha escuela de circo';\nboard.fills = [{ fillColor: S.fondo, fillOpacity: 1 }];\nboard.resize(1320, 740);\nconst cont = mk('Contenedor foto', P.card, P.fotoH, [], P.radio);\ncont.x = 0; cont.y = 0;\nconst foto = penpot.createShape('image');\nif (!foto) throw new Error('createShape devolvio null para image');\nfoto.name = 'Foto escuela';\nfoto.fills = [{ fillOpacity: 1, fillImage: { id: 'img-escuela-circo', src: 'https://picsum.photos/id/1025/1400/900', width: 1400, height: 900 } }];\nfoto.resize(P.foto, P.fotoH);\nfoto.x = (P.card - P.foto) / 2; foto.y = 0;\nconst mascara = mk('Mascara', P.card, P.fotoH, [{ fillColor: '#000000', fillOpacity: 1 }], P.radio);\nmascara.x = 0; mascara.y = 0;\nboard.insertChild(board.children.length, cont);\nboard.insertChild(board.children.length, mascara);\nboard.insertChild(board.children.length, foto);\nstorage.ficha = { boardId: board.id, contId: cont.id, fotoId: foto.id, mascaraId: mascara.id };\nreturn { boardId: board.id, contId: cont.id, fotoId: foto.id, mascaraId: mascara.id,\n fotoDentro: penpotUtils.isContainedIn(foto, cont),\n zOrden: board.children.map(c => c.name) };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Fila de tres tarjetas de un estudio de doblaje que se repartan el ancho de 1180 px en partes iguales, con separacion pareja entre ellas.", "id": "pd-017", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg: '#0F172A', card: '#1E293B', accent: '#F59E0B', text: '#F8FAFC', sub: '#94A3B8' };\nconst S = { sm: 16, md: 24, lg: 32 };\nconst board = penpot.createBoard();\nboard.name = 'Estudio de doblaje';\nboard.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nboard.resize(1180, 360);\nboard.addFlexLayout();\nif (board.flex) {\n board.flex.dir = 'row';\n board.flex.horizontalPadding = S.lg;\n board.flex.verticalPadding = S.lg;\n board.flex.columnGap = S.md;\n board.flex.alignItems = 'center';\n}\nconst cards = ['Doblaje', 'Adecuacion', 'Locucion'].map((t, i) => {\n const c = penpot.createRectangle();\n c.name = 'Tarjeta ' + (i + 1);\n c.fills = [{ fillColor: P.card, fillOpacity: 1, fillOpacity: 1 }];\n c.strokes = [{ strokeColor: P.accent, strokeWidth: 2, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\n c.borderRadius = 12;\n c.resize(360, 280);\n c.addFlexLayout();\n if (c.flex) {\n c.flex.dir = 'column';\n c.flex.horizontalPadding = S.md;\n c.flex.verticalPadding = S.md;\n c.flex.rowGap = S.sm;\n c.flex.alignItems = 'start';\n }\n const titulo = penpot.createText('Servicios');\n titulo.name = 'Titulo ' + (i + 1);\n titulo.fontFamily = 'Inter';\n titulo.fontSize = 20;\n titulo.fontWeight = '700';\n titulo.lineHeight = '1.3';\n titulo.fills = [{ fillColor: P.accent, fillOpacity: 1 }];\n c.insertChild(c.children.length, titulo);\n const desc = penpot.createText(t);\n desc.name = 'Descripcion ' + (i + 1);\n desc.fontFamily = 'Inter';\n desc.fontSize = 15;\n desc.fontWeight = '400';\n desc.lineHeight = '1.5';\n desc.growType = 'auto-height';\n desc.fills = [{ fillColor: P.text, fillOpacity: 1 }];\n c.insertChild(c.children.length, desc);\n return c;\n});\ncards.forEach(c => board.appendChild(c));\nconst ids = cards.map(c => c.id);\nreturn { boardId: board.id, boardWidth: board.width, boardHeight: board.height, dir: board.flex ? board.flex.dir : null, columnGap: board.flex ? board.flex.columnGap : null, cardIds: ids, cardWidths: cards.map(c => c.width), cardNames: cards.map(c => c.name) };" ], "veto_hits": [ { "patron": "fontSize/fontWeight/lineHeight/letterSpacing numerico", "fragmento": " titulo.fontFamily = 'Inter'; titulo.fontSize = 20; titulo.fontWeight = '700'; titulo" } ], "otros_patrones": [], "content": "" }, { "prompt": "Grilla de 1180 px con seis casillas para el catalogo de un servicio de reparacion de relojes, tres por fila, todas del mismo alto.", "id": "pd-018", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { brand:'#0B1D3A', card:'#1A5276', ink:'#0B1D3A', muted:'#5A6B7A', bg:'#F4F7FA', accent:'#16A34A' };\nconst T = { xl:'28', lg:'18', sm:'14' };\nconst S = { sm:8, md:16, lg:24 };\nconst mk = (name, w, h, fills) => { const s = penpot.createRectangle(); s.name = name; s.resize(w, h); s.fills = fills || []; s.borderRadius = 12; return s; };\nconst mkText = (name, chars, size, color, weight) => { const t = penpot.createText(); t.name = name; t.characters = chars; t.fontFamily = 'Inter'; t.fontSize = size; t.fontWeight = weight || '400'; t.lineHeight = '1.4'; t.fills = [{ fillColor: color, fillOpacity: 1 }]; t.growType = 'auto-width'; return t; };\nconst board = penpot.createBoard();\nboard.name = 'Catalogo relojes';\nboard.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nboard.resize(1180, 620);\nboard.insertChild(board.children.length, mk('Encabezado', 1180, 100, [{ fillColor: P.brand, fillOpacity: 1 }]));\nboard.insertChild(board.children.length, mk('Fila 1', 1180, 200));\nboard.insertChild(board.children.length, mk('Fila 2', 1180, 200));\nboard.insertChild(board.children.length, mk('Fila 3', 1180, 200));\nconst add = (parent, shape) => { parent.insertChild(parent.children.length, shape); };\nconst items = [\n { name:'Reloj de pulsera', price:'$ 12.500', badge:'Disponible' },\n { name:'Reloj de pared', price:'$ 8.900', badge:'Disponible' },\n { name:'Reloj de bolsillo', price:'$ 15.200', badge:'Disponible' },\n { name:'Reloj de cocina', price:'$ 6.400', badge:'Disponible' },\n { name:'Reloj de escritorio', price:'$ 9.800', badge:'Disponible' },\n { name:'Reloj de pared grande', price:'$ 18.000', badge:'Disponible' }\n];\nconst cols = 3;\nconst rowH = 200;\nconst gap = S.md;\nconst pad = S.lg;\nconst cardW = (1180 - pad * 2 - gap * (cols - 1)) / cols;\nconst rowShapes = [];\nfor (let r = 0; r < 3; r++) {\n const row = penpotUtils.findShape(s => s.name === 'Fila ' + (r + 1));\n if (!row) throw new Error('Fila ' + (r + 1) + ' no encontrada');\n rowShapes.push(row);\n for (let c = 0; c < cols; c++) {\n const idx = r * cols + c;\n const card = mk('Card ' + (idx + 1), cardW, rowH, [{ fillColor: P.card, fillOpacity: 1 }]);\n add(row, card);\n const x = pad + c * (cardW + gap);\n penpotUtils.setParentXY(card, x, pad);\n add(card, mkText('Card ' + (idx + 1) + ' / Icono', '⌚', '40', P.bg));\n add(card, mkText('Card ' + (idx + 1) + ' / Nombre', items[idx].name, T.lg, P.bg, '600'));\n add(card, mkText('Card ' + (idx + 1) + ' / Precio', items[idx].price, T.xl, P.accent, '700'));\n add(card, mk('Card ' + (idx + 1) + ' / Badge', 100, 28, [{ fillColor: P.accent, fillOpacity: 1 }]));\n penpotUtils.analyzeDescendants(card, (root, shape) => {\n if (shape.type === 'text') {\n if (shape.name.includes('Icono')) penpotUtils.setParentXY(shape, pad, pad);\n else if (shape.name.includes('Nombre')) penpotUtils.setParentXY(shape, pad, pad + 56);\n else if (shape.name.includes('Precio')) penpotUtils.setParentXY(shape, pad, pad + 100);\n else penpotUtils.setParentXY(shape, pad, pad + 148);\n }\n if (shape.name.includes('Badge')) penpotUtils.setParentXY(shape, pad + cardW - 108, pad + 148);\n });\n }\n}\nconst enc = penpotUtils.findShape(s => s.name === 'Encabezado');\nif (enc) {\n add(enc, mkText('Encabezado / Titulo', 'Catalogo de relojes', T.xl, P.bg, '700'));\n add(enc, mkText('Encabezado / Sub', 'Servicio de reparacion', T.sm, '#A0B8D0'));\n}\nconst structure = penpotUtils.shapeStructure(board, 3);\nconst fixes = penpotUtils.analyzeDescendants(board, (root, shape) => {\n const xMod = shape.parentX % 4;\n if (xMod !== 0) return () => penpotUtils.setParentXY(shape, Math.round(shape.parentX / 4) * 4, shape.parentY);\n});\nreturn { boardId: board.id, cardWidth: cardW, rowHeight: rowH, rows: rowShapes.length, cells: rowShapes.length * cols,\n encTitle: enc ? penpotUtils.findShape(s => s.name === 'Encabezado / Titulo', enc) : null,\n structure, fixesApplied: fixes.length };" ], "veto_hits": [], "otros_patrones": [ { "patron": "createText sin argumento util", "fragmento": "e, color, weight) => { const t = penpot.createText(); t.name = name; t.characters = chars; t" } ], "content": "" }, { "prompt": "Una columna de una consultora de eficiencia energetica donde el titulo va arriba, el texto en el medio y el boton abajo del todo, y el contenedor se ajusta al contenido.", "id": "pd-019", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { brand: '#0B6B4A', ink: '#1A2B3C', paper: '#FFFFFF', surface: '#E8F5EE', accent: '#22C55E' };\nconst T = { xl: '36', lg: '18', sm: '14' };\nconst S = { col: 48, row: 32 };\nconst page = penpot.createBoard();\npage.name = 'Columna consultora';\npage.fills = [{ fillColor: P.surface, fillOpacity: 1 }];\npage.resize(480, 720);\npage.addFlexLayout();\nif (page.flex) {\n page.flex.dir = 'column';\n page.flex.horizontalPadding = S.col;\n page.flex.verticalPadding = S.col;\n page.flex.rowGap = S.row;\n page.flex.alignItems = 'start';\n}\nconst card = penpot.createRectangle();\ncard.name = 'Card';\ncard.fills = [{ fillColor: P.paper, fillOpacity: 1 }];\ncard.strokes = [{ strokeColor: P.brand, strokeWidth: 2, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\ncard.borderRadius = 20;\ncard.resize(384, 560);\ncard.shadows = [{ style: 'drop-shadow', offsetX: 0, offsetY: 8, blur: 24, spread: 0, color: { color: P.brand, opacity: 0.12 } }];\npage.insertChild(page.children.length, card);\nif (card.flex) {\n card.flex.dir = 'column';\n card.flex.horizontalPadding = 0;\n card.flex.verticalPadding = 0;\n card.flex.rowGap = S.row;\n card.flex.alignItems = 'start';\n}\nconst mk = (chars, size, weight, color, lh) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.name = chars.split(' ')[0];\n t.fontFamily = 'Work Sans';\n t.fontSize = size;\n t.fontWeight = weight;\n t.lineHeight = lh;\n t.letterSpacing = '0';\n t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.growType = 'auto-height';\n return t;\n};\nconst titulo = mk('Eficiencia energetica a medida', T.xl, '700', P.ink, '1.22');\nconst cuerpo = mk('Diagnosticamos tu consumo, disenamos el plan de reduccion y te acompanamos hasta la implementacion.', T.lg, '400', P.ink, '1.56');\nconst cta = mk('Solicitar diagnostico', T.sm, '600', P.paper, '1.43');\nconst btn = penpot.createRectangle();\nbtn.name = 'Boton CTA';\nbtn.fills = [{ fillColor: P.accent, fillOpacity: 1 }];\nbtn.borderRadius = 40;\nbtn.resize(220, 48);\nbtn.shadows = [{ style: 'drop-shadow', offsetX: 0, offsetY: 4, blur: 12, spread: 0, color: { color: P.accent, opacity: 0.35 } }];\npage.insertChild(page.children.length, btn);\npage.insertChild(page.children.length, card);\ncard.insertChild(card.children.length, titulo);\ncard.insertChild(card.children.length, cuerpo);\ncard.insertChild(card.children.length, btn);\ncard.insertChild(card.children.length, cta);\nif (card.flex) {\n card.flex.appendChild(titulo);\n card.flex.appendChild(cuerpo);\n card.flex.appendChild(btn);\n card.flex.appendChild(cta);\n}\nstorage.columna = { pageId: page.id, cardId: card.id };\nreturn { pageId: page.id, cardId: card.id, cardFlex: !!card.flex, cardDir: card.flex ? card.flex.dir : null, cardGap: card.flex ? card.flex.rowGap : null, cardPadding: card.flex ? card.flex.horizontalPadding : null, cardChildren: card.children.length, cardGrow: card.growType, cardX: card.x, cardY: card.y, cardWidth: card.width, cardHeight: card.height, cardParentX: card.parentX, cardParentY: card.parentY, cardContained: penpotUtils.isContainedIn(card, page), cardDescendants: penpotUtils.analyzeDescendants(card, (r, s) => ({ name: s.name, type: s.type, fills: s.fills?.length, strokes: s.strokes?.length, fontSize: s.type === 'text' ? (s as any).fontSize : null })).length };" ], "veto_hits": [ { "patron": "flex.appendChild", "fragmento": "en.length, cta); if (card.flex) { card.flex.appendChild(titulo); card.flex.appendChild(cuerpo)" } ], "otros_patrones": [], "content": "" }, { "prompt": "Pie de pagina de 1180 px para una cooperativa de miel con cuatro columnas de enlaces y una linea final de derechos.", "id": "pd-020", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#FFF8E7', brand:'#F5C542', dark:'#2B1810', ink:'#D49A1A', white:'#FFFFFF' };\nconst S = { sm:14, md:16, lg:22 };\nconst T = { heading:'Montserrat', body:'Open Sans' };\nconst W = 1180, H = 480, COL = 260, GAP = 20;\nconst mk = (chars, size, color, weight, family) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText returned null');\n t.fontFamily = family; t.fontSize = size; t.fontWeight = weight; t.lineHeight = '1.5';\n t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.growType = 'auto-width';\n return t;\n};\nconst board = penpot.createBoard();\nboard.name = 'Footer Miel';\nboard.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nboard.resize(W, H);\nboard.addFlexLayout();\nif (board.flex) {\n board.flex.dir = 'column';\n board.flex.horizontalPadding = 60;\n board.flex.verticalPadding = 40;\n board.flex.rowGap = 30;\n board.flex.alignItems = 'start';\n}\nconst cols = penpot.createBoard();\ncols.name = 'Columnas';\ncols.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\ncols.resize(W, 320);\ncols.addFlexLayout();\nif (cols.flex) {\n cols.flex.dir = 'row';\n cols.flex.horizontalPadding = 0;\n cols.flex.verticalPadding = 0;\n cols.flex.rowGap = GAP;\n cols.flex.columnGap = GAP;\n cols.flex.alignItems = 'start';\n}\nconst links = [\n { titulo:'Nuestra miel', items:['Variedades','Origen','Proceso'] },\n { titulo:'Tienda', items:['Packs','Regalos','Suscripcion'] },\n { titulo:'Aprende', items:['Recetas','Apicultura','Blog'] },\n { titulo:'Contacto', items:['WhatsApp','Email','Visita'] }\n];\nconst columnas = links.map(l => {\n const c = penpot.createBoard();\n c.name = l.titulo;\n c.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\n c.resize(COL, 280);\n c.addFlexLayout();\n if (c.flex) {\n c.flex.dir = 'column';\n c.flex.horizontalPadding = 0;\n c.flex.verticalPadding = 0;\n c.flex.rowGap = 12;\n c.flex.alignItems = 'start';\n }\n c.appendChild(mk(l.titulo, S.lg, P.dark, '700', T.heading));\n l.items.forEach(i => c.appendChild(mk(i, S.md, P.ink, '400', T.body)));\n return c;\n});\ncolumnas.children.forEach(c => c.layoutChild && (c.layoutChild.horizontalSizing = 'fixed'));\ncolumnas.children.forEach(c => { if (c.flex) c.flex.alignItems = 'start'; });\nboard.appendChild(cols);\nboard.appendChild(mk('© 2025 Cooperativa Miel Dorada — Todos los derechos reservados', S.sm, P.ink, '400', T.body));\nstorage.footer = { boardId: board.id, cols: columnas.map(c => c.id) };\nreturn { boardId: board.id, cols: columnas.length, links: columnas.reduce((a, c) => a + c.children.length, 0) - columnas.length };" ], "veto_hits": [], "otros_patrones": [ { "patron": "layoutChild.horizontalSizing = 'fixed' o 'fit-content'", "fragmento": "ildren.forEach(c => c.layoutChild && (c.layoutChild.horizontalSizing = 'fixed')); columnas.children.forEach(c => { if " } ], "content": "" }, { "prompt": "Pantalla de 412x915 de una tienda de kayaks con una lista vertical de siete items, cada uno con icono a la izquierda y texto a la derecha.", "id": "pd-021", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = {\n deep:'#0B3D5E', med:'#1A6B8A', light:'#E8F4F8', coral:'#FF6B35',\n white:'#FFFFFF', ink:'#1E293B', gray:'#64748B'\n};\nconst S = { sm:14, md:18, lg:26 };\nconst T = { heading:'Paddle & Co.', sub:'Nuestros modelos', items:[\n {n:'Kayak Touring',d:'Para recorridos largos'},\n {n:'Kayak Oceano',d:'Diseñado para olas'},\n {n:'Kayak Plegable',d:'Lleva tu kayak a cualquier lado'},\n {n:'Kayak Inflable',d:'Sin garage, sin complicaciones'},\n {n:'Kayak Turistico',d:'Para grupos y familias'},\n {n:'Kayak de Pesca',d:'Estable y con espacio'},\n {n:'Kayak de Mar',d:'Para expediciones costeras'}\n]};\nconst mk = (chars, size, color, weight) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText returned null');\n t.fontFamily = 'Inter'; t.fontSize = size; t.fontWeight = weight || '400';\n t.lineHeight = '1.35'; t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.growType = 'auto-height'; return t;\n};\nconst bd = penpot.createBoard();\nbd.name = 'Kayaks'; bd.fills = [{ fillColor: P.light, fillOpacity: 1 }];\nbd.resize(412, 915); bd.addFlexLayout();\nif (bd.flex) {\n bd.flex.dir = 'column'; bd.flex.horizontalPadding = 24; bd.flex.verticalPadding = 24;\n bd.flex.rowGap = 12; bd.flex.alignItems = 'start';\n}\nconst header = penpot.createBoard();\nheader.name = 'Header'; header.fills = [{ fillColor: P.deep, fillOpacity: 1 }];\nheader.resize(364, 120); header.borderRadius = 16;\nheader.addFlexLayout();\nif (header.flex) {\n header.flex.dir = 'column'; header.flex.horizontalPadding = 24; header.flex.verticalPadding = 24;\n header.flex.rowGap = 4; header.flex.alignItems = 'start';\n}\nheader.appendChild(mk(T.heading, S.lg, P.white, '700'));\nheader.appendChild(mk(T.sub, S.md, P.coral, '600'));\nbd.appendChild(header);\nconst mkCard = (item) => {\n const c = penpot.createBoard();\n c.name = 'Card ' + item.n; c.fills = [{ fillColor: P.white, fillOpacity: 1 }];\n c.resize(364, 116); c.borderRadius = 12; c.strokes = [{ strokeColor: P.med, strokeWidth: 1, strokeStyle: 'solid', strokeOpacity: 0.3 }];\n c.addFlexLayout();\n if (c.flex) { c.flex.dir = 'row'; c.flex.horizontalPadding = 20; c.flex.verticalPadding = 20; c.flex.rowGap = 16; c.flex.alignItems = 'center'; }\n const ic = penpot.createRectangle();\n ic.name = 'Icono ' + item.n; ic.fills = [{ fillColor: P.med, fillOpacity: 1 }];\n ic.resize(56, 56); ic.borderRadius = 14;\n c.appendChild(ic);\n const col = penpot.createBoard();\n col.name = 'Texto ' + item.n; col.addFlexLayout();\n if (col.flex) { col.flex.dir = 'column'; col.flex.rowGap = 4; col.flex.alignItems = 'start'; }\n col.appendChild(mk(item.n, S.md, P.ink, '600'));\n col.appendChild(mk(item.d, S.sm, P.gray, '400'));\n c.appendChild(col);\n return c;\n};\nT.items.forEach(i => bd.appendChild(mkCard(i)));\nreturn { boardId: bd.id, headerId: header.id, cardCount: bd.children.length,\n headerFlex: !!header.flex, headerDir: header.flex ? header.flex.dir : null,\n cardFlexes: bd.children.filter(s => s.name.startsWith('Card')).map(s => ({ name: s.name, flex: !!s.flex, dir: s.flex ? s.flex.dir : null })) };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "En la pagina hay un board de una escuela de circo que quedo con puros rectangulos grises y sin una sola palabra. Convertilo en un diseno de verdad.", "id": "pd-022", "tool_calls": [ "execute_code" ], "code_payloads": [ "const board = penpotUtils.findShape(s => s.type === 'board' && s.fills && s.fills.length > 0, penpot.root);\nif (!board) return { found: false };\nconst structure = penpotUtils.shapeStructure(board, 3);\nconst texts = penpotUtils.findShapes(s => s.type === 'text', board);\nconst fills = board.fills ? board.fills.map(f => ({ type: f.fillOpacity, color: f.color, fillOpacity: f.fillOpacity })) : [];\nreturn { found: true, id: board.id, name: board.name, width: board.width, height: board.height, fills, textCount: texts.length, structure };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Este board de una fabrica de velas de soja tiene todos los textos del mismo tamano y todo el mismo gris. Dale jerarquia y una paleta.", "id": "pd-023", "tool_calls": [ "execute_code" ], "code_payloads": [ "const board = penpotUtils.findShape(s => s.name === 'Caja de regalo', penpot.root);\nif (!board) return { error: 'no encontre el board' };\nconst texts = penpotUtils.findShapes(s => s.type === 'text', board);\nconst sizes = texts.map(t => t.fontSize);\nreturn { boardId: board.id, boardName: board.name, textos: texts.length, tamanios: [...new Set(sizes)], tamaniosUnicos: sizes.length === new Set(sizes).size };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Revisa el board de un albergue para perros mayores que arme yo, decime que esta mal de contraste y arreglalo.", "id": "pd-024", "tool_calls": [ "execute_code" ], "code_payloads": [ "const board = penpotUtils.findShape(s => s.name === 'Albergue Perros');\nif (!board) return { found: false };\nconst fills = penpotUtils.analyzeDescendants(board, (root, shape) => {\n if (shape.type === 'text') {\n const bg = shape.fills?.find(f => f.fillOpacity > 0 && f.fillColor);\n if (!bg) return null;\n const wc = penpotUtils.contrastRatio(bg.fillColor, shape.fills?.[0]?.fillColor || '#000000');\n return { name: shape.name, bg: bg.fillColor, fg: shape.fills?.[0]?.fillColor || '#000000', ratio: wc };\n }\n return null;\n});\nreturn { found: true, boardId: board.id, shapes: fills };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "El hero de un laboratorio de analisis de suelos tiene el texto ilegible sobre la foto. Arreglalo sin cambiar la foto.", "id": "pd-025", "tool_calls": [ "execute_code" ], "code_payloads": [ "const structure = penpotUtils.shapeStructure(penpot.root, 3);\nconst hero = penpotUtils.findShape(s => s.name.includes('hero') || s.name.includes('banner') || s.name.includes('portada'), penpot.root);\nreturn { structure, heroId: hero ? hero.id : null, heroName: hero ? hero.name : null };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Antes de tocar nada, fijate en la documentacion de la API como se crea un texto en Penpot y despues poneme un titulo de un albergue para perros mayores en 48 px.", "id": "pd-026", "tool_calls": [ "penpot_api_info" ], "code_payloads": [], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Como hago para que un hijo de un board con layout ocupe todo el ancho disponible? Mostramelo aplicado a una seccion de una fabrica de velas de soja.", "id": "pd-027", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { brand: '#2B6B3A', crema: '#FFF8F0', tinta: '#1A1A1A', acento: '#D4A373' };\nconst T = { h1: '32', h2: '22', body: '15' };\nconst S = { sm: 12, md: 20, lg: 32 };\nconst mk = (name, w, h, fills, radius) => {\n const s = penpot.createRectangle();\n s.name = name; s.resize(w, h);\n s.fills = fills.map(c => ({ fillColor: c, fillOpacity: 1 }));\n if (radius) s.borderRadius = radius;\n return s;\n};\nconst txt = (name, chars, size, color, weight, lineH) => {\n const t = penpot.createText(chars);\n t.name = name; t.fontFamily = 'Work Sans'; t.fontSize = size; t.fontWeight = weight;\n t.lineHeight = lineH; t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.growType = 'auto-width';\n return t;\n};\nconst board = penpot.createBoard();\nboard.name = 'Cuidado de velas';\nboard.resize(720, 480);\nboard.fills = [{ fillColor: P.crema, fillOpacity: 1 }];\nboard.addGridLayout();\nif (!board.grid) throw new Error('grid no se creo');\nboard.grid.rows = 'auto auto';\nboard.grid.columns = '1fr 1fr';\nboard.grid.rowGap = S.lg;\nboard.grid.columnGap = S.lg;\nboard.grid.horizontalPadding = S.lg;\nboard.grid.verticalPadding = S.lg;\nconst hero = mk('Hero', 656, 160, [P.brand], 16);\nboard.appendChild(hero);\nhero.layoutCell = { row: 1, column: 1, columnSpan: 2 };\nconst titulo = txt('Titulo', 'Cuidado de tus velas de soja', T.h1, P.crema, '700', '1.2');\nhero.appendChild(titulo);\npenpotUtils.setParentXY(titulo, S.md, S.md);\nconst nota = txt('Nota', 'Apaga la mecha antes de que la cera se consuma del todo.', T.body, P.acento, '400', '1.5');\nhero.appendChild(nota);\npenpotUtils.setParentXY(nota, S.md, 96);\nconst celda = board.grid.findCell(hero);\nreturn {\n boardId: board.id,\n gridOk: !!board.grid,\n heroSpan: celda ? celda.columnSpan : null,\n heroColumn: celda ? celda.column : null,\n heroRow: celda ? celda.row : null,\n heroWidth: hero.width,\n heroHeight: hero.height,\n heroParentX: hero.parentX,\n heroParentY: hero.parentY,\n heroLayoutCell: hero.layoutCell ? { row: hero.layoutCell.row, column: hero.layoutCell.column, columnSpan: hero.layoutCell.columnSpan } : null\n};" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Quiero usar la tipografia Karla en el titulo de una cooperativa de miel. Verifica que quede realmente aplicada, no solo seteada.", "id": "pd-028", "tool_calls": [ "execute_code" ], "code_payloads": [ "const page = penpotUtils.getPageByName('Cooperativa');\nif (!page) {\n const p = penpot.createPage();\n p.name = 'Cooperativa';\n penpotUtils.getPageByName('Cooperativa').root.appendChild(p);\n return { pageId: p.id, created: true };\n}\nreturn { pageId: page.id, created: false };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Guarda la paleta de una academia de tango como colores de la biblioteca del archivo y despues aplicalos a dos formas.", "id": "pd-029", "tool_calls": [ "high_level_overview" ], "code_payloads": [], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Exportame el board de una tienda de kayaks para verlo, y decime si algo quedo fuera de los limites.", "id": "pd-030", "tool_calls": [ "execute_code" ], "code_payloads": [ "const board = penpotUtils.findShape(s => s.name === 'Tienda de Kayaks');\nif (!board) return { error: 'board no encontrado' };\nstorage.tienda = { boardId: board.id };\nconst violations = penpotUtils.analyzeDescendants(board, (root, shape) => {\n return !penpotUtils.isContainedIn(shape, root) ? 'outside-bounds' : null;\n});\nreturn { boardId: board.id, boardWidth: board.width, boardHeight: board.height, violationsCount: violations.length, violations: violations.map(v => ({ id: v.shape.id, name: v.shape.name, type: v.shape.type })) };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Hazme una landing completa de una fabrica de velas de soja, 1320 px de ancho, con encabezado, hero, tres beneficios, testimonios y pie. Ponele color, nada de cajas grises.", "id": "pd-031", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#FFF8F0', ink:'#1A1A1A', verde:'#2D5A3D', terracota:'#C46A3A', dorado:'#D4A853', crema:'#F5E6D3' };\nconst T = { h1:'48', h2:'32', h3:'20', body:'16', small:'13' };\nconst S = { sm:16, md:24, lg:40, xl:64 };\nconst mk = (chars, size, color, weight, family) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText returned null');\n t.fontFamily = family || 'Work Sans';\n t.fontSize = size;\n t.fontWeight = weight || '400';\n t.lineHeight = '1.4';\n t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.growType = 'auto-width';\n return t;\n};\nconst mkBtn = (chars, bg, fg) => {\n const b = penpot.createRectangle();\n if (!b) throw new Error('createRectangle returned null');\n b.name = 'Boton';\n b.fills = [{ fillColor: bg, fillOpacity: 1 }];\n b.borderRadius = 8;\n b.resize(200, 52);\n b.insertChild(b.children.length, mkBtn(chars, bg, fg));\n return b;\n};\nconst mkSeccion = (nombre, ancho, alto, bg) => {\n const s = penpot.createBoard();\n if (!s) throw new Error('createBoard returned null');\n s.name = nombre;\n s.fills = [{ fillColor: bg, fillOpacity: 1 }];\n s.resize(ancho, alto);\n return s;\n};\nconst board = mkSeccion('Landing Cera Viva', 1320, 2200, P.bg);\nconst add = (s, shape) => { s.insertChild(s.children.length, shape); };\nconst addTexto = (s, chars, size, color, weight) => { const t = mk(chars, size, color, weight); add(s, t); return t; };\nconst enc = mkSeccion('Encabezado', 1320, 72, P.verde);\nenc.insertChild(enc.children.length, mk('Cera Viva', '18', P.crema, '700'));\nenc.insertChild(enc.children.length, mk('Tienda', '14', P.crema, '400'));\nenc.insertChild(enc.children.length, mk('Contacto', '14', P.crema, '400'));\nconst hero = mkSeccion('Hero', 1320, 520, P.verde);\nhero.insertChild(hero.children.length, mk('Velas de soja hechas a mano', '56', P.crema, '700'));\nhero.insertChild(hero.children.length, mk('Aromas naturales que transforman tu hogar', '20', P.dorado, '400'));\nhero.insertChild(hero.children.length, mkBtn('Ver coleccion', P.terracota, P.crema));\nconst beneficios = mkSeccion('Beneficios', 1320, 480, P.crema);\nconst items = [\n { titulo:'100% soja natural', desc:'Sin parafinas ni quimicos, queman limpia y mas tiempo.' },\n { titulo:'Hechas a mano', desc:'Cada vela se vierte en pequenos lotes con cuidado.' },\n { titulo:'Aromas originales', desc:'Fragancias creadas por perfumistas argentinos.' }\n];\nconst cols = items.map(i => {\n const c = penpot.createBoard();\n c.name = 'Beneficio ' + i.titulo;\n c.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\n c.borderRadius = 12;\n c.resize(340, 320);\n c.insertChild(c.children.length, mk(i.titulo, '20', P.verde, '700'));\n c.insertChild(c.children.length, mk(i.desc, '15', P.ink, '400'));\n return c;\n});\nconst test = mkSeccion('Testimonios', 1320, 400, P.verde);\nconst tests = [\n { quote:'Quedan encendidas horas y la casa huele a verano.', autor:'Marta L.' },\n { quote:'Regale tres y volvieron a pedir mas.', autor:'Javier R.' },\n { quote:'Por fin velas que no marean.', autor:'Sofia M.' }\n];\nconst pies = mkSeccion('Pie', 1320, 280, P.ink);\npies.insertChild(pies.children.length, mk('Cera Viva', '24', P.crema, '700'));\npies.insertChild(pies.children.length, mk('Talleres en Palermo · Envios a todo el pais', '14', P.dorado, '400'));\nconst shapes = [enc, hero, beneficios, test, pies];\nshapes.forEach(s => board.insertChild(board.children.length, s));\nconst result = {\n boardId: board.id,\n secciones: shapes.length,\n alto: board.height,\n textos: penpotUtils.findShapes(s => s.type === 'text', board).length,\n botones: penpotUtils.findShapes(s => s.type === 'rectangle' && s.fills?.some(f => f.fillColor === P.terracota), board).length,\n paleta: Object.keys(P).length,\n estructura: penpotUtils.shapeStructure(board, 2)\n};\nstorage.ceraViva = { boardId: board.id, P: P, T: T };\nreturn result;" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Necesito la home de un vivero de orquideas en 900 px: hero con foto de fondo, una franja de numeros y un bloque final de contacto. Paleta a tu criterio, que sea vibrante.", "id": "pd-032", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { verde: '#0B6B3A', lima: '#A3D84A', crema: '#FFF8E7', oscuro: '#063D22', blanco: '#FFFFFF' };\nconst T = { hero: '48', marca: '28', numero: '40', label: '16', cta: '18' };\nconst S = { sm: 12, md: 20, lg: 32 };\nconst penpot = globalThis.penpot;\nconst root = penpot.createBoard();\nroot.name = 'Vivero Orquideas - Home';\nroot.fills = [{ fillColor: P.crema, fillOpacity: 1 }];\nroot.resize(900, 900);\nroot.addFlexLayout();\nif (root.flex) { root.flex.dir = 'column'; root.flex.rowGap = 0; root.flex.horizontalPadding = S.lg; root.flex.verticalPadding = S.lg; root.flex.alignItems = 'start'; }\nconst mk = (name, fills, fontSize, fontWeight, lineHeight, color, growType) => {\n const t = penpot.createText(name);\n if (!t) throw new Error('createText returned null');\n t.fontFamily = 'Work Sans'; t.fontSize = fontSize; t.fontWeight = fontWeight; t.lineHeight = lineHeight; t.letterSpacing = '0'; t.align = 'left'; t.growType = growType || 'auto-width';\n t.fills = fills; t.strokes = []; t.shadows = [];\n return t;\n};\nconst hero = penpot.createBoard();\nhero.name = 'Hero';\nhero.fills = [{ fillColor: P.verde, fillOpacity: 1 }];\nhero.resize(900, 480);\nhero.borderRadius = S.md;\nroot.insertChild(root.children.length, hero);\nif (hero.flex) { hero.flex.dir = 'column'; hero.flex.rowGap = S.md; hero.flex.horizontalPadding = S.lg; hero.flex.verticalPadding = S.lg; hero.flex.alignItems = 'start'; }\nconst foto1 = penpot.createRectangle();\nfoto1.name = 'Foto orquidea 1';\nfoto1.fills = [{ fillColor: '#FF6B35', fillOpacity: 1, fillImageFit: 'cover' }];\nfoto1.resize(280, 200);\nfoto1.borderRadius = S.sm;\nhero.insertChild(hero.children.length, foto1);\nconst foto2 = penpot.createRectangle();\nfoto2.name = 'Foto orquidea 2';\nfoto2.fills = [{ fillColor: '#FFD166', fillOpacity: 1, fillImageFit: 'cover' }];\nfoto2.resize(280, 200);\nfoto2.borderRadius = S.sm;\nhero.insertChild(hero.children.length, foto2);\nconst titulo = mk('Orquideas con alma', [P.crema], T.hero, '700', '1.15', 'auto-width');\nhero.insertChild(hero.children.length, titulo);\nconst sub = mk('Cultivo propio, envio a todo el pais', [P.lima], T.cta, '400', '1.5', 'auto-width');\nhero.insertChild(hero.children.length, sub);\nconst barra = penpot.createBoard();\nbarra.name = 'Numeros';\nbarra.fills = [{ fillColor: P.verde, fillOpacity: 1 }];\nbarra.resize(900, 160);\nbarra.borderRadius = S.md;\nroot.insertChild(root.children.length, barra);\nif (barra.flex) { barra.flex.dir = 'row'; barra.flex.rowGap = S.md; barra.flex.horizontalPadding = S.lg; barra.flex.verticalPadding = S.md; barra.flex.alignItems = 'center'; }\nconst stats = [{ n: '120', l: 'Especies' }, { n: '3.400', l: 'Plantas vendidas' }, { n: '15', l: 'Anos' }];\nstats.forEach(s => {\n const card = penpot.createRectangle();\n card.name = 'Card ' + s.l;\n card.fills = [{ fillColor: P.lima, fillOpacity: 1 }];\n card.resize(240, 100);\n card.borderRadius = S.sm;\n barra.insertChild(barra.children.length, card);\n if (card.flex) { card.flex.dir = 'column'; card.flex.rowGap = 4; card.flex.horizontalPadding = S.md; card.flex.verticalPadding = S.md; card.flex.alignItems = 'start'; }\n card.insertChild(card.children.length, mk(s.n, [P.oscuro], T.numero, '700', '1.1', 'auto-width'));\n card.insertChild(card.children.length, mk(s.l, [P.oscuro], T.label, '400', '1.5', 'auto-width'));\n});\nconst contacto = penpot.createBoard();\ncontacto.name = 'Contacto';\ncontacto.fills = [{ fillColor: P.crema, fillOpacity: 1 }];\ncontacto.resize(900, 200);\ncontacto.borderRadius = S.md;\nroot.insertChild(root.children.length, contacto);\nif (contacto.flex) { contacto.flex.dir = 'column'; contacto.flex.rowGap = S.md; contacto.flex.horizontalPadding = S.lg; contacto.flex.verticalPadding = S.md; contacto.flex.alignItems = 'start'; }\ncontacto.insertChild(contacto.children.length, mk('Contacto', [P.verde], T.marca, '700', '1.2', 'auto-width'));\nconst items = [{ i: 'Email', v: 'hola@orquideas.bio' }, { i: 'Telefono', v: '+54 11 5555-0123' }, { i: 'Taller', v: 'Sabados 10 a 14' }];\nitems.forEach(it => {\n const row = penpot.createRectangle();\n row.name = 'Item ' + it.i;\n row.fills = [{ fillColor: P.verde, fillOpacity: 1 }];\n row.resize(800, 44);\n row.borderRadius = S.sm;\n contacto.insertChild(contacto.children.length, row);\n if (row.flex) { row.flex.dir = 'row'; row.flex.rowGap = S.sm; row.flex.horizontalPadding = S.md; row.flex.verticalPadding = S.sm; row.flex.alignItems = 'center'; }\n const dot = penpot.createEllipse();\n dot.name = 'Dot ' + it.i;\n dot.fills = [{ fillColor: P.lima, fillOpacity: 1 }];\n dot.resize(12, 12);\n row.insertChild(row.children.length, dot);\n row.insertChild(row.children.length, mk(it.v, [P.crema], T.cta, '400', '1.5', 'auto-width'));\n});\nreturn { rootId: root.id, heroId: hero.id, barraId: barra.id, contactoId: contacto.id, heroFotos: [foto1.id, foto2.id].length, statsCards: stats.length, contactItems: items.length, heroFills: hero.fills.length };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Armame algo lindo para una tienda de kayaks, una pagina de presentacion. Vos elegis todo.", "id": "pd-033", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { primary:'#0B3D5C', accent:'#00B4D8', sand:'#F4E8D1', ink:'#0A192F', white:'#FFFFFF' };\nconst T = { display:'48', section:'28', body:'16', label:'12' };\nconst G = { col:80, gut:40, row:24 };\nconst S = { sm:8, md:16, lg:24, xl:40, xxl:56, xxxl:80 };\nconst mk = (name, w, h, fills) => { const s = penpot.createShape('rectangle'); s.name = name; s.fills = fills; s.resize(w, h); s.borderRadius = 0; return s; };\nconst tx = (chars, size, color, weight, lineH) => { const t = penpot.createText(chars); t.fontFamily = 'Inter'; t.fontSize = size; t.fontWeight = weight; t.lineHeight = lineH; t.fills = [{ fillColor: color, fillOpacity: 1 }]; t.growType = 'auto-width'; return t; };\nconst bd = (w, h, fills, radius) => { const s = penpot.createShape('rectangle'); s.name = fills[0].fillColor === P.white ? 'Card' : 'Section'; s.fills = fills; s.resize(w, h); s.borderRadius = radius || 0; s.strokes = []; s.shadows = []; return s; };\nconst root = penpot.root;\nroot.fills = [{ fillColor: P.sand, fillOpacity: 1 }];\nroot.resize(1200, 1080);\nconst nav = bd(1200, 64, [P.primary], 0);\nroot.insertChild(root.children.length, nav);\nconst hero = bd(1200, 440, [P.accent], 0);\nroot.insertChild(root.children.length, hero);\nconst prods = bd(1200, 400, [P.sand], 0);\nroot.insertChild(root.children.length, prods);\nconst ft = bd(1200, 176, [P.primary], 0);\nroot.insertChild(root.children.length, ft);\n[nav, hero, prods, ft].forEach(b => { b.addFlexLayout(); b.flex.dir = 'column'; b.flex.horizontalPadding = S.xxl; b.flex.verticalPadding = S.xxl; b.flex.rowGap = S.xl; });\nconst mkBtn = (label, bg) => { const b = bd(140, 44, [bg], 22); b.addFlexLayout(); b.flex.dir = 'row'; b.flex.alignItems = 'center'; b.flex.justifyContent = 'center'; b.flex.horizontalPadding = S.lg; b.appendChild(tx(label, T.body, P.white, '600', '24')); return b; };\nconst mkCard = (name, price, img) => { const c = bd(320, 320, [P.white], 16); c.shadows = [{ style: 'drop-shadow', offsetX: 0, offsetY: 8, blur: 24, spread: 0, color: { color: P.ink, opacity: 0.12 } }]; c.strokes = []; c.addFlexLayout(); c.flex.dir = 'column'; c.flex.rowGap = S.md; c.flex.horizontalPadding = S.lg; c.flex.verticalPadding = S.lg; c.flex.alignItems = 'start'; c.appendChild(img); c.appendChild(tx(name, T.section, P.ink, '700', '36')); c.appendChild(tx(price, T.body, P.accent, '600', '24')); return c; };\nconst mkCol = (name, price, img) => { const c = mkCard(name, price, img); c.layoutChild = { rowSpan: 1, columnSpan: 1 }; return c; };\nconst mkRow = (chars, size, color, weight, lineH, align) => { const t = tx(chars, size, color, weight, lineH); t.alignHorizontal = align || 'left'; t.growType = 'auto-width'; return t; };\nconst mkGrid = (cols, rows, gaps) => { const g = penpot.createGridLayout(); g.columns = cols; g.rows = rows; g.columnGap = gaps; g.rowGap = gaps; return g; };\nconst mkGuide = (type, pos, color) => { const g = penpot.createRulerGuide(type, pos); g.color = color; g.opacity = 0.5; return g; };\nconst mkImg = (name, w, h, color) => { const i = penpot.createShape('rectangle'); i.name = name; i.fills = [{ fillColor: color, fillOpacity: 1 }]; i.resize(w, h); i.borderRadius = 0; return i; };\nconst mkIcon = (name, size, color) => { const i = penpot.createShape('ellipse'); i.name = name; i.fills = [{ fillColor: color, fillOpacity: 1 }]; i.resize(size, size); i.strokes = []; return i; };\nconst mkLine = (name, w, h, color) => { const l = penpot.createShape('rectangle'); l.name = name; l.fills = [{ fillColor: color, fillOpacity: 1 }]; l.resize(w, h); l.borderRadius = 0; return l; };\nconst mkArrow = (name, size, color) => { const a = penpot.createShape('path'); a.name = name; a.fills = [{ fillColor: color, fillOpacity: 1 }]; a.path = { commands: [{ type: 'M', x: 0, y: size/2 }, { type: 'L', x: size, y: size/2 }, { type: 'M', x: size*0.6, y: size*0.25 }, { type: 'L', x: size, y: size/2 }, { type: 'M', x: size*0.6, y: size*1.75 }, { type: 'L', size, size/2 }], closed: false, fillRule: 'nonzero' }; a.strokes = []; a.resize(size, size); return a; };\nconst mkDots = (name, size, color) => { const d = penpot.createShape('group'); d.name = name; d.fills = []; d.strokes = []; d.resize(size, size); for (let i = 0; i < 3; i++) { const dot = mkIcon('Dot', 6, color); penpotUtils.setParentXY(dot, 10 + i * 20, 12); d.appendChild(dot); } return d; };\nconst mkStar = (name, size, color) => { const s = penpot.createShape('path'); s.name = name; s.fills = [{ fillColor: color, fillOpacity: 1 }]; s.path = { commands: [{ type: 'M', x: size/2, y: 0 }, { type: 'L', x: size*0.6, y: size*0.4 }, { type: 'L', x: size, y: size*0.4 }, { type: 'L', x: size*0.7, y: size*0.7 }, { type: 'L', x: size*0.8, y: size }, { type: 'L', x: size*0.5, y: size*0.85 }, { type: 'L', x: size*0.2, y: size }, { type: 'L', x: size*0.3, y: size*0.7 }, { type: 'L', 0, size*0.4 }, { type: 'L', x: size*0.4, y: size*0.4 }], closed: true, fillRule: 'nonzero' }, s.strokes = []; s.resize(size, size); return s; };\nconst mkCheck = (name, size, color) => { const c = penpot.createShape('path'); c.name = name; c.fills = [{ fillColor: color, fillOpacity: 1 }]; c.path = { commands: [{ type: 'M', x: 0, y: size*0.5 }, { type: 'L', x: size*0.35, y: size }, { type: 'L', x: size, y: 0 }], closed: false, fillRule: 'nonzero' }, c.strokes = []; c.resize(size, size); return c; };\nconst mkCircle = (name, size, color) => { const c = penpot.createShape('ellipse'); c.name = name; c.fills = [{ fillColor: color, fillOpacity: 1 }]; c.resize(size, size); c.strokes = []; return c; };\nconst mkTriangle = (name, size, color) => { const t = penpot.createShape('path'); t.name = name; t.fills = [{ fillColor: color, fillOpacity: 1 }]; t.path = { commands: [{ type: 'M', x: size/2, y: 0 }, { type: 'L', x: size, y: size }, { type: 'L', 0, size }], closed: true, fillRule: 'nonzero' }, t.strokes = []; t.resize(size, size); return t; };\nconst mkDiamond = (name, size, color) => { const d = penpot.createShape('path'); d.name = name; d.fills = [{ fillColor: color, fillOpacity: 1 }]; d.path = { commands: [{ type: 'M', x: size/2, y: 0 }, { type: 'L', x: size, y: size/2 }, { type: 'L', x: size/2, y: size }, { type: 'L', 0, size/2 }], closed: true, fillRule: 'nonzero' }, d.strokes = []; d.resize(size, size); return d; };\nconst mkCross = (name, size, color) => { const c = penpot.createShape('path'); c.name = name; c.fills = [{ fillColor: color, fillOpacity: 1 }]; c.path = { commands: [{ type: 'M', x: 0, y: 0 }, { type: 'L', x: size, y: size }, { type: 'M', x: size, y: 0 }, { type: 'L', 0, size }], closed: false, fillRule: 'nonzero' }, c.strokes = []; c.resize(size, size); return c; };\nconst mkSquare = (name, size, color) => { const s = penpot.createShape('rectangle'); s.name = name; s.fills = [{ fillColor: color, fillOpacity: 1 }]; s.resize(size, size); s.borderRadius = 0; s.strokes = []; return s; };\nconst mkHexagon = (name, size, color) => { const h = penpot.createShape('path'); h.name = name; h.fills = [{ fillColor: color, fillOpacity: 1 }]; h.path = { commands: [{ type: 'M', x: size*0.25, y: 0 }, { type: 'L', x: size*0.75, y: 0 }, { type: 'L', size, y: size*0.5 }, { type: 'L', x: size*0.75, y: size }, { type: 'L', x: size*0.25, y: size }, { type: 'L', 0, y: size*0.5 }], closed: true, fillRule: 'nonzero' }, h.strokes = []; h.resize(size, size); return h; };\nconst mkWave = (name, size, color) => { const w = penpot.createShape('path'); w.name = name; w.fills = [{ fillColor: color, fillOpacity: 1 }]; w.path = { commands: [{ type: 'M', x: 0, y: size*0.5 }, { type: 'C', x: size*0.25, y: 0, x2: size*0.25, y2: size }, { type: 'C', x: size*0.75, y: 0, x2: size*0.75, y2: size }, { type: 'L', x: size, y: size*0.5 }, { type: 'Z' }], closed: true, fillRule: 'nonzero' }, w.strokes = []; w.resize(size, size); return w; };\nconst mkZigzag = (name, size, color) => { const z = penpot.createShape('path'); z.name = name; z.fills = [{ fillColor: color, fillOpacity: 1 }]; z.path = { commands: [{ type: 'M', x: 0, y: size }, { type: 'L', x: size*0.2, y: 0 }, { type: 'L', x: size*0.4, y: size }, { type: 'L', x: size*0.6, y: 0 }, { type: 'L', x: size*0.8, y: size }, { type: 'L', x: size, y: 0 }], closed: false, fillRule: 'nonzero' }, z.strokes = []; z.resize(size, size); return z; };\nconst mkSpiral = (name, size, color) => { const sp = penpot.createShape('path'); sp.name = name; sp.fills = [{ fillColor: color, fillOpacity: 1 }]; sp.path = { commands: [{ type: 'M', x: size/2, y: size/2 }, { type: 'C', x: size, y: size/2, x2: size, y2: 0 }, { type: 'C', x: size, y2: size, x2: 0, y2: size }, { type: 'C', x: 0, y2: 0, x2: size*0.5, y2: size*0.5 }], closed: false, fillRule: 'nonzero' }, sp.strokes = []; sp.resize(size, size); return sp; };\nconst mkPacman = (name, size, color) => { const p = penpot.createShape('path'); p.name = name; p.fills = [{ fillColor: color, fillOpacity: 1 }]; p.path = { commands: [{ type: 'M', x: size/2, y: size/2 }, { type: 'A', x: size/2, y: size/2, rx: size/2, ry: size/2, xAxisRotation: 0, largeArc: 1, sweep: 1, x: size/2, y: size/2 }], closed: false, fillRule: 'nonzero' }, p.strokes = []; p.resize(size, size); return p; };\nconst mkBow = (name, size, color) => { const b = penpot.createShape('path'); b.name = name; b.fills = [{ fillColor: color, fillOpacity: 1 }]; b.path = { commands: [{ type: 'M', x: size/2, y: size/2 }, { type: 'C', x: size, y: 0, x2: size, y2: size }, { type: 'M', x: size/2, y: size/2 }, { type: 'C', x: 0, y: size, x2: 0, y2: 0 }], closed: false, fillRule: 'nonzero' }, b.strokes = []; b.resize(size, size); return b; };\nconst mkHeart = (name, size, color) => { const h = penpot.createShape('path'); h.name = name; h.fills = [{ fillColor: color, fillOpacity: 1 }]; h.path = { commands: [{ type: 'M', x: size/2, y: size }, { type: 'C', x: 0, y: size*0.7, x2: 0, y2: 0, x: size*0.4, y: 0 }, { type: 'C', x: size*0.6, y: 0, x2: size, y2: 0, x: size, y: size*0.7 }, { type: 'C', x: size, y2: size, x2: size/2, y2: size, x: size/2, y: size }], closed: true, fillRule: 'nonzero' }, h.strokes = []; h.resize(size, size); return h; };\nconst mkCloud = (name, size, color) => { const c = penpot.createShape('path'); c.name = name; c.fills = [{ fillColor: color, fillOpacity: 1 }]; c.path = { commands: [{ type: 'M', x: size*0.2, y: size }, {" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Pagina de precios de una cooperativa de miel de 1320 px: encabezado, tres planes con el del medio destacado en #3b0764, preguntas frecuentes y pie.", "id": "pd-034", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = {\n bg:'#fff8f0', ink:'#1a0a2e', brand:'#3b0764', gold:'#d4a017',\n card:'#ffffff', line:'#f0d9b5', soft:'#f5e6d3', accent:'#6b21a8'\n};\nconst T = {\n xl:{fontSize:48,fontWeight:'700',lineHeight:'1.15',letterSpacing:'-0.5'},\n lg:{fontSize:28,fontWeight:'600',lineWidth:'1.2'},\n md:{fontSize:18,fontWeight:'500',lineWidth:'1.4'},\n sm:{fontSize:15,fontWeight:'400',lineWidth:'1.5'},\n xs:{fontSize:13,fontWeight:'400',lineWidth:'1.5'}\n};\nconst mk = (chars, style, fills, size) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText returned null');\n t.fontFamily = 'Work Sans';\n Object.assign(t.fontSize, style.fontSize);\n t.fontWeight = style.fontWeight;\n t.lineHeight = style.lineWidth;\n if (style.letterSpacing) t.letterSpacing = style.letterSpacing;\n t.fills = fills;\n t.growType = 'auto-width';\n if (size) t.resize(size[0], size[1]);\n return t;\n};\nconst mkBtn = (label, fill, textFill, size) => {\n const b = penpot.createRectangle();\n if (!b) throw new Error('createRectangle returned null');\n b.name = 'Boton ' + label;\n b.fills = [{ fillColor: fill, fillOpacity: 1 }];\n b.borderRadius = 28;\n b.resize(size[0], size[1]);\n b.addFlexLayout();\n if (b.flex) {\n b.flex.dir = 'row';\n b.flex.horizontalPadding = 32;\n b.flex.verticalPadding = 16;\n b.flex.alignItems = 'center';\n b.flex.justifyContent = 'center';\n }\n b.appendChild(mkBtn(label, fill, textFill, [1, 1]));\n return b;\n};\nconst mkCard = (name, price, desc, items, highlight) => {\n const c = penpot.createBoard();\n if (!c) throw new Error('createBoard returned null');\n c.name = 'Plan ' + name;\n c.fills = [{ fillColor: highlight ? P.brand : P.card, fillOpacity: 1 }];\n c.resize(360, 480);\n c.borderRadius = 20;\n c.strokes = highlight ? [] : [{ strokeColor: P.line, strokeWidth: 2, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\n c.shadows = highlight ? [] : [{ style: 'drop-shadow', offsetX: 0, offsetY: 8, blur: 24, spread: 0, color: { color: '#1a0a2e', opacity: 0.12 } }];\n c.addFlexLayout();\n if (c.flex) {\n c.flex.dir = 'column';\n c.flex.rowGap = 20;\n c.flex.horizontalPadding = 32;\n c.flex.verticalPadding = 32;\n c.flex.alignItems = 'center';\n }\n c.appendChild(mk(name, T.lg, highlight ? [P.bg] : [P.ink], [1, 1]));\n c.appendChild(mk(price, T.xl, highlight ? [P.gold] : [P.brand], [1, 1]));\n c.appendChild(mk(desc, T.sm, highlight ? [P.soft] : [P.ink], [1, 1]));\n c.appendChild(mk('Incluye:', T.md, highlight ? [P.bg] : [P.ink], [1, 1]));\n items.forEach(i => c.appendChild(mk('\\u2022 ' + i, T.sm, highlight ? [P.soft] : [P.ink], [1, 1])));\n return c;\n};\nconst mkFaq = (q, a) => {\n const r = penpot.createBoard();\n if (!r) throw new Error('createBoard returned null');\n r.name = 'FAQ ' + q;\n r.fills = [{ fillColor: P.card, fillOpacity: 1 }];\n r.resize(960, 1);\n r.borderRadius = 12;\n r.strokes = [{ strokeColor: P.line, strokeWidth: 1, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\n r.addFlexLayout();\n if (r.flex) {\n r.flex.dir = 'column';\n r.flex.rowGap = 12;\n r.flex.horizontalPadding = 32;\n r.flex.verticalPadding = 24;\n }\n r.appendChild(mk(q, T.md, [P.ink], [1, 1]));\n r.appendChild(mk(a, T.sm, [P.ink], [1, 1]));\n return r;\n};\nconst mkSection = (title, body, fills, size) => {\n const s = penpot.createBoard();\n if (!s) throw new Error('createBoard returned null');\n s.name = 'Seccion ' + title;\n s.fills = fills;\n s.resize(1320, 1);\n s.addFlexLayout();\n if (s.flex) {\n s.flex.dir = 'column';\n s.flex.rowGap = 16;\n s.flex.horizontalPadding = 80;\n s.flex.verticalPadding = 64;\n s.flex.alignItems = 'center';\n }\n s.appendChild(mk(title, T.lg, [P.ink], [1, 1]));\n body.forEach(b => s.appendChild(mk(b, T.sm, [P.ink], [1, 1])));\n return s;\n};\nconst mkFooter = () => {\n const f = penpot.createBoard();\n if (!f) throw new Error('createBoard returned null');\n f.name = 'Pie';\n f.fills = [{ fillColor: P.ink, fillOpacity: 1 }];\n f.resize(1320, 1);\n f.addFlexLayout();\n if (f.flex) {\n f.flex.dir = 'row';\n f.flex.rowGap = 24;\n f.flex.horizontalPadding = 80;\n f.flex.verticalPadding = 32;\n f.flex.alignItems = 'center';\n f.flex.justifyContent = 'space-between';\n }\n f.appendChild(mk('Cooperativa Miel Dorada', T.md, [P.bg], [1, 1]));\n f.appendChild(mk('contacto@mieldorada.coop', T.sm, [P.soft], [1, 1]));\n return f;\n};\nconst page = penpot.createPage();\npage.name = 'Precios';\nconst root = page.root;\nroot.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nroot.resize(1320, 1);\nconst header = mkSection('Miel de abeja pura, sin intermediarios', [\n 'Tres planes de suscripcion mensual. Cancela cuando quieras.'\n], [P.bg], [1, 1]);\nroot.insertChild(root.children.length, header);\nconst plans = penpot.createBoard();\nplans.name = 'Planes';\nplans.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nplans.resize(1320, 560);\nplans.addFlexLayout();\nif (plans.flex) {\n plans.flex.dir = 'row';\n plans.flex.rowGap = 24;\n plans.flex.horizontalPadding = 80;\n plans.flex.verticalPadding = 48;\n plans.flex.alignItems = 'center';\n plans.flex.justifyContent = 'center';\n}\nconst cards = [\n mkCard('Miel de abeja pura', '$45.000', '1 kg mensual de miel cruda', ['Cosecha local', 'Envio gratis', 'Etiqueta personalizada']),\n mkCard('Miel de abeja pura', '$78.000', '2 kg mensual + propoleo', ['Todo lo del plan anterior', 'Propoleo en spray', 'Recetario estacional']),\n mkCard('Miel de abeja pura', '$120.000', '3 kg mensual + polen', ['Todo lo del plan anterior', 'Polen de abeja', 'Visita a la colmena'])\n];\ncards.forEach(c => plans.appendChild(c));\nroot.insertChild(root.children.length, plans);\nconst faqs = mkSection('Preguntas frecuentes', [\n mkFaq('Cada cuanto llega la miel?', 'Cada 30 dias, el primer lunes del mes.'),\n mkFaq('Puedo cambiar de plan?', 'Si, desde tu cuenta antes del 20 de cada mes.'),\n mkFaq('Es miel 100% natural?', 'Si, sin aditivos ni calentar, directo de la colmena.')\n], [P.bg], [1, 1]);\nroot.insertChild(root.children.length, faqs);\nconst footer = mkFooter();\nroot.insertChild(root.children.length, footer);\nconst all = penpotUtils.findShapes(s => s.type === 'text', root);\nconst sizes = [...new Set(all.map(t => t.fontSize))];\nreturn { pageId: page.id, rootId: root.id, sections: root.children.length,\n plans: plans.children.length, faqs: faqs.children.length,\n fonts: sizes.length, buttons: 3,\n planPrices: cards.map(c => c.children.find(t => t.fontSize === '48')?.characters),\n planNames: cards.map(c => c.children.find(t => t.fontSize === '28')?.characters),\n planDescs: cards.map(c => c.children.find(t => t.fontSize === '15')?.characters),\n faqQuestions: faqs.children.filter(t => t.fontSize === '18').map(t => t.characters),\n faqAnswers: faqs.children.filter(t => t.fontSize === '15').map(t => t.characters),\n footerLinks: footer.children.filter(t => t.fontSize === '15').length,\n planFills: cards.map(c => c.fills[0].fillColor),\n planStrokes: cards.map(c => c.strokes.length),\n planShadows: cards.map(c => c.shadows.length),\n planFlex: cards.map(c => !!c.flex),\n planFlexDir: cards.map(c => c.flex ? c.flex.dir : null),\n planFlexGap: cards.map(c => c.flex ? c.flex.rowGap : null),\n planFlexPad: cards.map(c => c.flex ? { h: c.flex.horizontalPadding, v: c.flex.verticalPadding } : null),\n planFlexAlign: cards.map(c => c.flex ? c.flex.alignItems : null),\n planFlexJustify: cards.map(c => c.flex ? c.flex.justifyContent : null),\n planSizes: cards.map(c => c.width + 'x' + c.height),\n planNamesOk: cards.map(c => c.children.filter(t => t.fills[0].fillColor === (c.fills[0].fillColor === P.brand ? P.bg : P.ink)).length > 0),\n planPricesOk: cards.map(c => c.children.filter(t => t.fills[0].fillColor === (c.fills[0].fillColor === P.brand ? P.gold : P.brand)).length > 0),\n planDescsOk: cards.map(c => c.children.filter(t => t.fills[0].fillColor === (c.fills[0].fillColor === P.brand ? P.soft : P.ink)).length > 0),\n planItemsOk: cards.map(c => c.children.filter(t => t.characters.startsWith('\\u2022')).length),\n planBtns: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).length),\n planBtnFills: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.fills[0].fillColor)),\n planBtnLabels: cards.map(c => c.children.filter(s => s.type === 'text' && s.fills.length > 0 && s.borderRadius > 0).map(t => t.characters)),\n planBtnSizes: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.width + 'x' + s.height)),\n planBtnFlex: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => !!s.flex)),\n planBtnFlexDir: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.dir : null)),\n planBtnFlexPad: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? { h: s.flex.horizontalPadding, v: s.flex.verticalPadding } : null)),\n planBtnFlexAlign: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.alignItems : null)),\n planBtnFlexJustify: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.justifyContent : null)),\n planBtnFlexGap: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.rowGap : null)),\n planBtnFlexHGap: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.columnGap : null)),\n planBtnFlexItems: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.children.length : null)),\n planBtnFlexAlignSelf: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.layoutChild ? s.layoutChild.alignSelf : null)),\n planBtnFlexJustifySelf: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.layoutChild ? s.layoutChild.justifySelf : null)),\n planBtnFlexOrder: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.layoutChild ? s.layoutChild.order : null)),\n planBtnFlexGrow: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.layoutChild ? s.layoutChild.grow : null)),\n planBtnFlexShrink: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.layoutChild ? s.layoutChild.shrink : null)),\n planBtnFlexBasis: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.layoutChild ? s.layoutChild.basis : null)),\n planBtnFlexWrap: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.wrap : null)),\n planBtnFlexOverflow: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.overflow : null)),\n planBtnFlexAlignContent: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.alignContent : null)),\n planBtnFlexAlignItems: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.alignItems : null)),\n planBtnFlexJustifyContent: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.justifyContent : null)),\n planBtnFlexRowGap: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.rowGap : null)),\n planBtnFlexColumnGap: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.columnGap : null)),\n planBtnFlexHorizontalPadding: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.horizontalPadding : null)),\n planBtnFlexVerticalPadding: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.verticalPadding : null)),\n planBtnFlexTopPadding: cards.map(c => c.children.filter(s => s.fills.length > 0 && s.borderRadius > 0).map(s => s.flex ? s.flex.topPadding : null" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Quiero la pantalla principal del panel interno de un laboratorio de analisis de suelos, 900 px, con barra lateral, seis tarjetas de indicadores y una tabla de movimientos.", "id": "pd-035", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#F1F5F9', side:'#0B3D91', card:'#FFFFFF', ink:'#0F172A', muted:'#64748B', line:'#E2E8F0', ok:'#16A34A', warn:'#F59E0B' };\nconst T = { h:'22', s:'15', b:'13' };\nconst S = { sm:8, md:16, lg:24 };\nconst mk = (chars, size, weight, color, align) => {\n const t = penpot.createText(chars);\n if (!t) return null;\n t.fontFamily = 'Inter'; t.fontSize = size; t.fontWeight = weight; t.lineHeight = '1.4';\n t.fills = [{ fillColor: color, fillOpacity: 1 }]; t.growType = 'auto-width';\n if (align) t.textAlign = align;\n return t;\n};\nconst mkCard = (title, value, delta, icon) => {\n const c = penpot.createRectangle();\n if (!c) return null;\n c.name = title; c.resize(130, 100); c.fills = [{ fillColor: P.card, fillOpacity: 1 }];\n c.strokes = [{ strokeColor: P.line, strokeWidth: 1, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\n c.borderRadius = 8;\n const g = penpot.createGroup();\n if (!g) return null;\n g.name = 'card-inner'; g.resize(130, 100);\n g.fills = [{ fillColor: P.card, fillOpacity: 1 }];\n g.insertChild(0, mk(icon, T.b, '700', P.ink));\n g.insertChild(1, mk(value, T.h, '700', P.ink));\n g.insertChild(2, mk(delta, T.s, '400', delta.startsWith('+') ? P.ok : P.warn));\n c.insertChild(0, g);\n return c;\n};\nconst mkRow = (cells, bg) => {\n const r = penpot.createRectangle();\n if (!r) return null;\n r.name = 'row'; r.resize(620, 36); r.fills = [{ fillColor: bg, fillOpacity: 1 }];\n r.borderRadius = 4;\n cells.forEach((c, i) => { const t = mk(c, T.b, '400', P.ink); if (t) { t.x = 12 + i * 124; r.insertChild(r.children.length, t); } });\n return r;\n};\nconst board = penpot.createBoard();\nboard.name = 'Lab Suelos'; board.resize(900, 680); board.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nconst side = penpot.createRectangle();\nside.name = 'Sidebar'; side.resize(180, 680); side.fills = [{ fillColor: P.side, fillOpacity: 1 }];\nboard.insertChild(0, side);\nboard.insertChild(1, mk('Lab Suelos', T.h, '700', P.ink));\nboard.insertChild(2, mk('Panel de indicadores', T.s, '400', P.muted));\nconst cards = [mkCard('Muestras', '142', '+18 hoy', '🧪'), mkCard('Pendientes', '23', '-5', '⏳'), mkCard('Aprobadas', '119', '+12', '✅'), mkCard('Rechazadas', '8', '+2', '⚠️'), mkCard('Tiempo medio', '3.2 d', '-0.4', '⏱️'), mkCard('Costo/muestra', '$185', '-$12', '💰')];\ncards.forEach(c => { if (c) board.insertChild(board.children.length, c); });\nconst tabla = penpot.createRectangle();\ntabla.name = 'Tabla'; tabla.resize(620, 200); tabla.fills = [{ fillColor: P.card, fillOpacity: 1 }]; tabla.borderRadius = 8;\ntabla.strokes = [{ strokeColor: P.line, strokeWidth: 1, strokeAlignment: 'inner', strokeOpacity: 1 }];\nboard.insertChild(board.children.length, tabla);\nconst filas = [\n ['Muestra', 'Método', 'Estado', 'Fecha', 'Técnico'],\n ['S-2024-087', 'pH + NPK', 'Aprobada', '12/06', 'M. López'],\n ['S-2024-088', 'Metales pesados', 'En curso', '13/06', 'A. Ruiz'],\n ['S-2024-089', 'Materia orgánica', 'Pendiente', '14/06', 'M. López'],\n ['S-2024-090', 'Granulometría', 'Rechazada', '14/06', 'C. Vega']\n];\nfilas.forEach((f, i) => { const r = mkRow(f, i === 0 ? P.side : (i % 2 === 0 ? P.card : '#F8FAFC')); if (r) tabla.insertChild(tabla.children.length, r); });\nconst layout = board.addFlexLayout();\nif (layout) {\n layout.dir = 'column'; layout.rowGap = S.lg; layout.horizontalPadding = S.lg; layout.verticalPadding = S.lg;\n layout.alignItems = 'start'; layout.justifyContent = 'start';\n}\nconst cardsRow = penpot.createBoard();\ncardsRow.name = 'Tarjetas'; cardsRow.resize(690, 100); cardsRow.fills = [];\ncards.forEach(c => { if (c) cardsRow.appendChild(c); });\nif (cardsRow.flex) { cardsRow.flex.dir = 'row'; cardsRow.flex.rowGap = S.md; cardsRow.flex.alignItems = 'center'; }\nboard.insertChild(board.children.length, cardsRow);\nconst tablaPos = penpotUtils.setParentXY(tabla, S.lg, 300);\nconst cardPos = penpotUtils.setParentXY(cardsRow, S.lg, 140);\nreturn { boardId: board.id, sideW: side.width, cards: cards.length, filas: filas.length, layoutType: layout ? layout.dir : null };" ], "veto_hits": [], "otros_patrones": [ { "patron": "textAlign", "fragmento": ".growType = 'auto-width'; if (align) t.textAlign = align; return t; }; const mkCard = (t" } ], "content": "" }, { "prompt": "Landing de un evento de un servicio de reparacion de relojes: fecha grande, cuenta regresiva, grilla de oradores y formulario de inscripcion, 900 px de ancho.", "id": "pd-036", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = {\n bg: '#0B0F14', card: '#2A2F36', gold: '#C9A24E', cream: '#F5F0E6',\n goldDark: '#8B6914', border: '#3D444D'\n};\nconst T = {\n display: { family: 'Playfair Display', size: 64, weight: '700', line: '72', color: P.gold },\n h2: { family: 'Playfair Display', size: 28, weight: '600', line: '40', color: P.cream },\n body: { family: 'Inter', size: 16, weight: '400', line: '24', color: P.cream },\n label: { family: 'Inter', size: 13, weight: '600', line: '18', color: P.gold }\n};\nconst S = { xs: 8, sm: 16, md: 24, lg: 40, xl: 64 };\nconst W = 900;\nconst penpot = globalThis.penpot;\nconst root = penpot.root;\nconst pages = penpotUtils.getPages();\nconst page = pages.length ? penpotUtils.getPageById(pages[0].id) : null;\nif (!page) return { error: 'No page found', pages: pages.map(p => p.name) };\nconst mk = (name, w, h, fills) => {\n const s = penpot.createRectangle();\n s.name = name; s.fills = fills || []; s.borderRadius = 0;\n s.resize(w, h);\n if (s.fills.length) s.fills[0].opacity = 1;\n return s;\n};\nconst mkText = (chars, style, align) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText returned null');\n t.fontFamily = style.family; t.fontSize = style.size; t.fontWeight = style.weight;\n t.lineHeight = style.line; t.fillColor = style.color; t.growType = 'auto-width';\n if (align) t.textAlign = align;\n return t;\n};\nconst mkBtn = (label, w, h) => {\n const b = mk(label, w, h, [{ fillColor: P.gold, fillOpacity: 1 }]);\n b.borderRadius = 4;\n const t = mkBtnText(label, b);\n if (t) { b.insertChild(b.children.length, t); }\n return b;\n};\nconst mkBtnText = (chars, parent) => {\n const t = mkText(chars, { family: 'Inter', size: 15, weight: '700', line: '20', color: P.bg });\n if (!t) return null;\n t.horizontalAlign = 'center'; t.verticalAlign = 'center';\n t.resize(parent.width, parent.height);\n return t;\n};\nconst mkCard = (chars, style) => {\n const c = mk(chars, 380, 160, [{ fillColor: P.card, fillOpacity: 1 }]);\n c.borderRadius = 8;\n c.strokes = [{ style: { width: 1, color: P.border, opacity: 1 }, strokeType: 'solid' }];\n const t = mkText(chars, style);\n if (t) { c.insertChild(c.children.length, t); }\n return c;\n};\nconst mkInput = (placeholder, w, h) => {\n const i = mk(placeholder, w, h, [{ fillColor: P.card, fillOpacity: 1 }]);\n i.borderRadius = 4;\n i.strokes = [{ style: { width: 1, color: P.border, opacity: 1 }, strokeType: 'solid' }];\n const t = mkText(placeholder, { family: 'Inter', size: 14, weight: '400', line: '20', color: '#6B7280' });\n if (t) { i.insertChild(0, t); }\n return i;\n};\nconst mkSection = (name, w, h, fills) => {\n const b = penpot.createBoard();\n b.name = name; b.fills = fills || []; b.borderRadius = 0;\n b.resize(w, h);\n if (b.fills.length) b.fills[0].opacity = 1;\n b.addFlexLayout();\n if (b.flex) {\n b.flex.dir = 'column'; b.flex.rowGap = S.md;\n b.flex.horizontalPadding = S.lg; b.flex.verticalPadding = S.lg;\n b.flex.alignItems = 'center';\n }\n return b;\n};\nconst add = (board, ...shapes) => shapes.forEach(s => board.appendChild(s));\nconst sections = [];\nconst hero = mkSection('Hero', W, 480, [{ fillColor: P.bg, fillOpacity: 1 }]);\nadd(hero, mkText('RELOJERÍA SOTOMAYOR', T.h2, 'center'));\nadd(hero, mkText('17 DE AGOSTO · 2025', T.display, 'center'));\nadd(hero, mkText('Encuentro anual de relojeros, coleccionistas y artesanos', T.body, 'center'));\nconst countdown = mkSection('Cuenta regresiva', W, 120, [{ fillColor: P.bg, fillOpacity: 1 }]);\nconst cd = mk('Cuenta regresiva', 320, 60, [{ fillColor: P.gold, fillOpacity: 1 }]);\ncd.borderRadius = 4;\nadd(cd, mkText('00:00:00', T.label, 'center'));\nadd(countdown, cd);\nconst oradores = mkSection('Oradores', W, 440, [{ fillColor: P.bg, fillOpacity: 1 }]);\nadd(oradores, mkText('Oradores', T.h2, 'center'));\nconst grid = mk('Grilla de oradores', W, 300);\ngrid.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nconst speakers = [\n { name: 'Elena Sotomayor', role: 'Relojera maestra' },\n { name: 'Marco Viana', role: 'Historiador de la relojería' },\n { name: 'Aisha Ndiaye', role: 'Diseñadora de mecanismos' },\n { name: 'Tomás Herrera', role: 'Restaurador certificado' }\n];\nconst cols = 2;\nconst cardW = (W - S.lg * 2 - S.md * (cols - 1)) / cols;\nspeakers.forEach((sp, i) => {\n const card = mkCard(sp.name + ' — ' + sp.role, T.body);\n card.borderRadius = 8;\n card.resize(cardW, 120);\n if (card.fills.length) card.fills[0].opacity = 1;\n grid.appendChild(card);\n});\nadd(oradores, grid);\nconst inscripcion = mkSection('Inscripción', W, 400, [{ fillColor: P.bg, fillOpacity: 1 }]);\nadd(inscripcion, mkText('Inscripción', T.h2, 'center'));\nadd(inscripcion, mkText('Cupos limitados. Reserva tu lugar.', T.body, 'center'));\nconst form = mk('Formulario', W - S.lg * 2, 240);\nform.fills = [{ fillColor: P.card, fillOpacity: 1 }];\nform.borderRadius = 8;\nform.strokes = [{ style: { width: 1, color: P.border, opacity: 1 }, strokeType: 'solid' }];\nform.addFlexLayout();\nif (form.flex) {\n form.flex.dir = 'column'; form.flex.rowGap = S.sm;\n form.flex.horizontalPadding = S.md; form.flex.verticalPadding = S.md;\n form.flex.alignItems = 'start';\n}\nconst fields = ['Nombre completo', 'Correo electrónico', 'Teléfono'];\nfields.forEach(f => {\n const label = mkText(f, T.label);\n const input = mkInput('Escribe aquí...', form.width - S.md * 2, 44);\n add(form, label, input);\n});\nadd(inscripcion, form);\nadd(inscripcion, mkBtn('Reservar mi lugar', form.width - S.md * 2, 48));\nconst footer = mkSection('Footer', W, 100, [{ fillColor: P.bg, fillOpacity: 1 }]);\nadd(footer, mkText('© 2025 Relojería Sotomayor · Todos los derechos reservados', T.label, 'center'));\nconst boards = [hero, countdown, oradores, inscripcion, footer];\nboards.forEach(b => page.root.appendChild(b));\nconst boardIds = boards.map(b => b.id);\nconst heroIds = penpotUtils.findShapes(() => true, hero).map(s => s.id);\nconst gridIds = penpotUtils.findShapes(() => true, grid).map(s => s.id);\nconst formIds = penpotUtils.findShapes(() => true, form).map(s => s.id);\nstorage.relojeria = { heroId: hero.id, countdownId: countdown.id, oradoresId: oradores.id, inscripcionId: inscripcion.id, footerId: footer.id, boardIds, heroIds, gridIds, formIds };\nreturn {\n page: page.name,\n sections: boards.length,\n heroHeight: hero.height,\n countdownHeight: countdown.height,\n oradoresHeight: oradores.height,\n inscripcionHeight: inscripcion.height,\n footerHeight: footer.height,\n speakers: speakers.length,\n formFields: fields.length,\n heroShapes: heroIds.length,\n gridShapes: gridIds.length,\n formShapes: formIds.length,\n allContained: penpotUtils.analyzeDescendants(page.root, (r, s) => !penpotUtils.isContainedIn(s, r) ? 'outside' : null).length === 0\n};" ], "veto_hits": [], "otros_patrones": [ { "patron": "textAlign", "fragmento": ".growType = 'auto-width'; if (align) t.textAlign = align; return t; }; const mkBtn = (la" } ], "content": "" }, { "prompt": "Un boton primario y uno secundario para una consultora de eficiencia energetica, ambos con estados normal y deshabilitado, usando #155e75 como acento.", "id": "pd-037", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = {\n accent: '#155e75',\n accentHover: '#0e4254',\n accentDisabled: '#2a7a94',\n bgSecondary: '#f0f7fa',\n borderSecondary: '#155e75',\n textPrimary: '#ffffff',\n textSecondary: '#155e75',\n textDisabled: '#8bbfd0'\n};\nconst S = { sm: 12, md: 14, lg: 16 };\nconst T = { primary: 'Montserrat', secondary: 'Open Sans' };\nconst mkBtn = (label, variant) => {\n const b = penpot.createBoard();\n b.name = 'Boton ' + label;\n b.fills = [{ fillColor: variant === 'primary' ? P.accent : P.bgSecondary, fillOpacity: 1 }];\n b.resize(200, 56);\n b.borderRadius = 8;\n b.strokes = variant === 'primary' ? [] : [{ strokeColor: P.borderSecondary, strokeWidth: 2, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\n b.shadows = variant === 'primary' ? [{ style: 'drop-shadow', offsetX: 0, offsetY: 2, blur: 4, spread: 0, color: { color: '#000000', opacity: 0.2 } }] : [];\n return b;\n};\nconst mkState = (label, variant) => {\n const s = penpot.createBoard();\n s.name = 'Estado ' + label;\n s.fills = [{ fillColor: variant === 'primary' ? (label === 'deshabilitado' ? P.accentDisabled : P.accent) : P.bgSecondary, fillOpacity: 1 }];\n s.resize(160, 40);\n s.borderRadius = 6;\n s.strokes = variant === 'primary' ? [] : [{ strokeColor: P.borderSecondary, strokeWidth: 2, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\n const t = penpot.createText(label);\n t.name = 'Texto ' + label;\n t.fontFamily = T.primary;\n t.fontSize = S.md;\n t.fontWeight = '600';\n t.lineHeight = '1.4';\n t.growType = 'auto-width';\n t.fills = [{ fillColor: variant === 'primary' ? P.textPrimary : P.textSecondary, fillOpacity: 1 }];\n t.align = 'center';\n s.insertChild(s.children.length, t);\n if (t.x !== undefined) { penpotUtils.setParentXY(t, (160 - t.width) / 2, (40 - t.height) / 2); }\n return s;\n};\nconst page = penpotUtils.getPageByName('Componentes');\nif (!page) { return { error: 'pagina no encontrada' }; }\nconst prim = mkBtn('Solicitar auditoria', 'primary');\nconst sec = mkBtn('Ver servicios', 'secondary');\nconst primN = mkState('normal', 'primary');\nconst primD = mkState('deshabilitado', 'primary');\nconst secN = mkState('normal', 'secondary');\nconst secD = mkState('deshabilitado', 'secondary');\npage.insertChild(page.children.length, prim);\npage.insertChild(page.children.length, sec);\npage.insertChild(page.children.length, primN);\npage.insertChild(page.children.length, primD);\npage.insertChild(page.children.length, secN);\npage.insertChild(page.children.length, secD);\nconst btns = penpotUtils.findShapes(s => s.type === 'board', page.root);\nconst textos = penpotUtils.findShapes(s => s.type === 'text', page.root);\nreturn {\n pagina: page.name,\n botones: btns.length,\n textos: textos.length,\n primario: { id: prim.id, fills: prim.fills.length, strokes: prim.strokes.length, shadows: prim.shadows.length, texto: prim.children.length },\n secundario: { id: sec.id, fills: sec.fills.length, strokes: sec.strokes.length, shadows: sec.shadows.length, texto: sec.children.length },\n estados: [primN, primD, secN, secD].map(e => ({ nombre: e.name, texto: e.children.length })),\n textosInfo: textos.map(t => ({ nombre: t.name, fontSize: t.fontSize, fontWeight: t.fontWeight, fills: t.fills.length }))\n};" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Card de producto para una cooperativa de miel: foto arriba, titulo en Libre Baskerville, precio tachado y precio final, y un boton. 320 de ancho.", "id": "pd-038", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { miel:'#F5C542', oscuro:'#D49A2A', crema:'#FFF8E7', tinta:'#2C1810', gris:'#F0F0F0' };\nconst S = { sm:12, md:16, lg:24, xl:32 };\nconst T = { h:'28', sub:'16', body:'14' };\nconst mk = (w,h,f,b,r) => { const s = penpot.createRectangle(); s.name = f; s.fills = [{ fillColor: b, fillOpacity: 1, fillImageScaling: 'cover' }]; s.resize(w, h); s.borderRadius = r; return s; };\nconst tx = (c, s, f, b, w) => { const t = penpot.createText(); t.characters = c; t.fontFamily = f; t.fontSize = s; t.fontWeight = w; t.lineHeight = '1.3'; t.fills = [{ fillColor: b, fillOpacity: 1 }]; t.growType = 'auto-height'; return t; };\nconst root = penpot.createBoard();\nroot.name = 'Miel de abeja';\nroot.fills = [{ fillColor: P.crema, fillOpacity: 1, fillImageScaling: 'cover' }];\nroot.resize(320, 520);\nroot.addFlexLayout();\nif (root.flex) { root.flex.dir = 'column'; root.flex.horizontalPadding = S.xl; root.flex.verticalPadding = S.xl; root.flex.rowGap = S.md; root.flex.alignItems = 'start'; }\nconst foto = mk(256, 200, 'Foto', P.miel, 16);\nfoto.strokes = [{ strokeColor: P.miel, strokeWidth: 4, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\nroot.insertChild(root.children.length, foto);\nroot.insertChild(root.children.length, tx('Miel de abeja', T.h, 'Libre Baskerville', P.tinta, '700'));\nroot.insertChild(root.children.length, tx('Cosecha de primavera, 500 g', T.body, 'Inter', P.tinta, '400'));\nconst precio = mk(256, 40, 'Precio', P.gris, 8);\nroot.insertChild(root.children.length, precio);\nconst precioLine = tx('Antes 12,90', T.sub, 'Inter', P.tinta, '400');\nprecioLine.strikethrough = true;\nprecioLine.lineHeight = '1';\nprecio.insertChild(precio.children.length, precioLine);\nconst precioFinal = tx('9,90', T.sub, 'Inter', P.oscuro, '700');\nprecioFinal.lineHeight = '1';\nprecio.insertChild(precio.children.length, precioFinal);\nconst btn = mk(256, 48, 'Boton', P.oscuro, 24);\nbtn.fills = [{ fillColor: P.oscuro, fillOpacity: 1, fillImageScaling: 'cover' }];\nroot.insertChild(root.children.length, btn);\nbtn.insertChild(btn.children.length, tx('Agregar al carrito', T.md, 'Inter', P.crema, '600'));\nbtn.children.forEach(c => { if (c.type === 'text') { c.alignHorizontal = 'center'; c.alignVertical = 'center'; } });\nstorage.miel = { root: root.id, P: P, S: S, T: T };\nreturn { rootId: root.id, children: root.children.length, flex: !!root.flex };" ], "veto_hits": [], "otros_patrones": [ { "patron": "createText sin argumento util", "fragmento": "= (c, s, f, b, w) => { const t = penpot.createText(); t.characters = c; t.fontFamily = f; t." } ], "content": "" }, { "prompt": "Hazme una barra de navegacion de un laboratorio de analisis de suelos de 1180x88 con logo, cinco enlaces y un boton de accion a la derecha.", "id": "pd-039", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#0B2545', text:'#FFFFFF', accent:'#00B4D8' };\nconst S = { sm:14, md:16 };\nconst T = { family:'Inter', weight:'600', color:P.text };\nconst nav = penpot.createBoard();\nnav.name = 'Navbar Laboratorio';\nnav.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nnav.resize(1180, 88);\nnav.addFlexLayout();\nif (nav.flex) {\n nav.flex.dir = 'row';\n nav.flex.rowGap = 0;\n nav.flex.columnGap = 32;\n nav.flex.alignItems = 'center';\n nav.flex.horizontalPadding = 40;\n}\nconst mk = (chars, size, weight) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.name = chars;\n t.fontFamily = T.family;\n t.fontSize = size;\n t.fontWeight = weight;\n t.lineHeight = '1.3';\n t.fills = [{ fillColor: T.color, fillOpacity: 1 }];\n t.growType = 'auto-width';\n return t;\n};\nconst logo = penpot.createBoard();\nlogo.name = 'Logo';\nlogo.fills = [{ fillColor: P.accent, fillOpacity: 1 }];\nlogo.resize(160, 40);\nlogo.borderRadius = 8;\nlogo.insertChild(logo.children.length, mk('SUELOLAB', 18, '700'));\nif (logo.children[1]) {\n logo.children[1].x = 12;\n logo.children[1].y = 11;\n}\nconst links = ['Analisis', 'Metodologia', 'Certificados', 'Contacto', 'Laboratorio'].map(n => mk(n, S.md, T.weight));\nlinks.forEach(l => {\n nav.insertChild(nav.children.length, l);\n});\nconst cta = penpot.createRectangle();\ncta.name = 'CTA';\ncta.fills = [{ fillColor: P.accent, fillOpacity: 1 }];\ncta.resize(140, 40);\ncta.borderRadius = 20;\ncta.insertChild(cta.children.length, mk('Solicitar analisis', S.sm, '700'));\nif (cta.children[1]) {\n cta.children[1].x = 12;\n cta.children[1].y = 11;\n}\nnav.insertChild(nav.children.length, cta);\nreturn { navId: nav.id, links: links.length, cta: cta.name, flexDir: nav.flex ? nav.flex.dir : null };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Un aviso de cookies para el sitio de un estudio de doblaje: texto corto, dos botones y fondo #581c87.", "id": "pd-040", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#581c87', ink:'#ffffff', accent:'#f59e0b' };\nconst S = { bar:60, sm:16, md:24, lg:40 };\nconst T = { sm:'14', md:'18', lg:'26' };\nconst mk = (chars, size, weight, color, align) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = 'Inter'; t.fontSize = size; t.fontWeight = weight;\n t.lineHeight = '1.4'; t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.growType = 'auto-width';\n if (align) t.textAlign = align;\n return t;\n};\nconst btn = (label, bg, fg) => {\n const b = penpot.createRectangle();\n if (!b) throw new Error('createRectangle devolvio null');\n b.name = 'Boton ' + label; b.fills = [{ fillColor: bg, fillOpacity: 1 }];\n b.borderRadius = 8; b.resize(160, 40);\n b.strokes = [{ strokeColor: fg, strokeWidth: 2, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\n b.insertChild(0, mk(label, '14', '700', fg, 'center'));\n return b;\n};\nconst board = penpot.createBoard();\nboard.name = 'Aviso de cookies'; board.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nboard.resize(1280, 60);\nconst inner = penpot.createGroup();\ninner.name = 'Contenido'; inner.fills = []; inner.resize(1200, 28);\ninner.x = (1280 - 1200) / 2; inner.y = (60 - 28) / 2;\nboard.insertChild(board.children.length, inner);\ninner.insertChild(inner.children.length, mk('Este sitio usa cookies para mejorar tu experiencia. Al continuar, aceptas su uso.', T.md, '400', P.ink, 'left'));\ninner.insertChild(inner.children.length, btn('Aceptar', P.accent, P.ink));\ninner.insertChild(inner.children.length, btn('Configurar', P.bg, P.ink));\nconst children = inner.children;\nchildren[1].x = S.lg; children[2].x = 1200 - S.md - 160; children[3].x = 1200 - S.sm - 160;\nconst texts = penpotUtils.findShapes(s => s.type === 'text', board);\nconst buttons = penpotUtils.findShapes(s => s.type === 'rectangle' && s.name.startsWith('Boton'), board);\nreturn { boardId: board.id, innerId: inner.id,\n texts: texts.length, buttons: buttons.length,\n buttonFills: buttons.map(b => b.fills[0]?.fillColor),\n textColors: texts.map(t => t.fills[0]?.fillColor),\n buttonSizes: buttons.map(b => ({ w: b.width, h: b.height })),\n innerXY: { x: inner.x, y: inner.y },\n childrenCount: inner.children.length };" ], "veto_hits": [], "otros_patrones": [ { "patron": "textAlign", "fragmento": ".growType = 'auto-width'; if (align) t.textAlign = align; return t; }; const btn = (labe" } ], "content": "" }, { "prompt": "Necesito una tarjeta de perfil de una fabrica de velas de soja con avatar circular, nombre, rol y tres datos de contacto.", "id": "pd-041", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#FFF8F0', ink:'#1A1410', accent:'#C25B3A', sage:'#6B8E6B', line:'#E8D5C4' };\nconst T = { sm:'14', md:'18', lg:'28' };\nconst S = { xs:8, sm:16, md:24, lg:32 };\nconst mk = (chars, size, color, weight, family) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = family || 'Work Sans';\n t.fontSize = size;\n t.fontWeight = weight || '400';\n t.lineHeight = '1.35';\n t.growType = 'auto-width';\n t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.align = 'center';\n return t;\n};\nconst board = penpot.createBoard();\nboard.name = 'Tarjeta / Fabrica de velas';\nboard.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nboard.resize(400, 520);\nboard.borderRadius = 24;\nboard.shadows = [{ style: 'drop-shadow', offsetX: 0, offsetY: 8, blur: 24, spread: 0, color: { color: '#1A1410', opacity: 0.12 } }];\nconst mkCirc = (d, fill) => {\n const c = penpot.createEllipse();\n c.name = fill ? 'avatar' : 'icono';\n c.fills = [{ fillColor: fill, fillOpacity: 1 }];\n c.resize(d, d);\n return c;\n};\nconst avatar = mkCirc(120, P.sage);\navatar.strokes = [{ strokeColor: P.accent, strokeWidth: 3, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\nconst iniciales = mk('S', '40', '#FFF8F0', '700');\niniciales.align = 'center';\nboard.insertChild(board.children.length, avatar);\nboard.insertChild(board.children.length, iniciales);\npenpotUtils.setParentXY(iniciales, 160, 160);\nconst nombre = mk('Cera y Raiz', T.lg, P.ink, '700');\nnombre.align = 'center';\nconst rol = mk('Fabrica de velas de soja', T.md, P.accent, '400');\nrol.align = 'center';\nconst datos = [\n { icon: 'tel', label: '+54 11 4567-8900' },\n { icon: 'mail', label: 'hola@cerayraiz.com' },\n { icon: 'web', label: 'cerayraiz.com' }\n];\nconst items = datos.map(d => {\n const row = penpot.createBoard();\n row.name = 'dato / ' + d.label;\n row.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\n row.resize(336, 48);\n row.borderRadius = 12;\n row.strokes = [{ strokeColor: P.line, strokeWidth: 1, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\n const ic = mkCirc(32, P.accent);\n const lb = mk(d.label, T.sm, P.ink, '400');\n row.insertChild(row.children.length, ic);\n row.insertChild(row.children.length, lb);\n penpotUtils.setParentXY(lb, 48, 8);\n return row;\n});\nboard.insertChild(board.children.length, nombre);\nboard.insertChild(board.children.length, rol);\nitems.forEach(i => board.insertChild(board.children.length, i));\nconst flex = board.addFlexLayout();\nflex.dir = 'column';\nflex.rowGap = S.md;\nflex.alignItems = 'center';\nflex.horizontalPadding = S.lg;\nflex.verticalPadding = S.lg;\nstorage.tarjeta = { boardId: board.id };\nreturn { boardId: board.id, avatarDiametro: avatar.width, inicialesDentro: penpotUtils.isContainedIn(iniciales, avatar), datos: items.length, flexDir: flex.dir, rowGap: flex.rowGap };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Necesito una tabla de 1180 px para una academia de tango: encabezado oscuro, cinco filas con fondo alternado y una columna de estado con etiquetas de color.", "id": "pd-042", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#0B1D3A', ink:'#FFFFFF', row:'#F8FAFC', alt:'#EEF2F7', state:'#1A3A6B', stateInk:'#FFFFFF', ok:'#10B981', okInk:'#0B1D3A', line:'#D1D5DB' };\nconst T = { xl:'28', lg:'16', sm:'13' };\nconst S = { sm:12, md:20, lg:32 };\nconst mk = (chars, size, weight, color, align) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = 'Inter'; t.fontSize = size; t.fontWeight = weight; t.lineHeight = '1.3';\n t.fills = [{ fillColor: color, fillOpacity: 1 }]; t.growType = 'auto-width';\n if (align) t.textAlign = align;\n return t;\n};\nconst bd = penpot.createBoard();\nbd.name = 'Tabla de clases'; bd.fills = [{ fillColor: P.bg, fillOpacity: 1 }]; bd.resize(1180, 520); bd.borderRadius = 16;\nbd.insertChild(bd.children.length, mk('Clases de tango', T.xl, '700', P.ink));\nbd.insertChild(bd.children.length, mk('Horarios y cupos disponibles', T.lg, '400', P.ink));\nbd.insertChild(bd.children.length, mk('Lunes a sabado', T.sm, '400', P.ink));\nbd.insertChild(bd.children.length, mk('Inscripcion abierta', T.sm, '700', P.ok, 'center'));\nconst filas = [\n { dia:'Lunes', hora:'19:00', nivel:'Basico', cupos:'12/20', estado:'Activo' },\n { dia:'Martes', hora:'20:30', nivel:'Intermedio', cupos:'8/15', estado:'Activo' },\n { dia:'Miercoles', hora:'19:00', nivel:'Avanzado', cupos:'5/10', estado:'Cupo lleno' },\n { dia:'Jueves', hora:'21:00', nivel:'Basico', cupos:'18/20', estado:'Activo' },\n { dia:'Sabado', hora:'16:00', nivel:'Social', cupos:'0/25', estado:'Cancelado' }\n];\nconst estados = { 'Activo': P.ok, 'Cupo lleno': '#F59E0B', 'Cancelado': '#EF4444' };\nconst cols = ['Dia', 'Hora', 'Nivel', 'Cupos', 'Estado'];\nconst cab = penpot.createBoard();\ncab.name = 'Cabecera'; cab.fills = [{ fillColor: P.row, fillOpacity: 1 }]; cab.resize(1180, 56); cab.borderRadius = 16;\ncols.forEach(c => cab.insertChild(cab.children.length, mk(c, T.sm, '700', P.state)));\nconst filasB = penpot.createBoard();\nfilasB.name = 'Filas'; filasB.fills = [{ fillColor: P.row, fillOpacity: 1 }]; filasB.resize(1180, 360);\nfilas.forEach((f, i) => {\n const r = penpot.createBoard();\n r.name = 'Fila ' + (i + 1); r.fills = [{ fillColor: i % 2 === 0 ? P.row : P.alt, fillOpacity: 1 }]; r.resize(1180, 72);\n r.insertChild(r.children.length, mk(f.dia, T.lg, '600', P.bg));\n r.insertChild(r.children.length, mk(f.hora, T.lg, '400', P.bg));\n r.insertChild(r.children.length, mk(f.nivel, T.lg, '400', P.bg));\n r.insertChild(r.children.length, mk(f.cupos, T.lg, '400', P.bg));\n const badge = penpot.createRectangle();\n badge.name = 'Badge ' + f.estado; badge.fills = [{ fillColor: estados[f.estado], fillOpacity: 1 }];\n badge.resize(110, 32); badge.borderRadius = 16;\n badge.insertChild(badge.children.length, mk(f.estado, T.sm, '700', estados[f.estado] === P.ok ? P.okInk : P.ink, 'center'));\n r.insertChild(r.children.length, badge);\n filasB.insertChild(filasB.children.length, r);\n});\nconst lineas = penpotUtils.findShapes(s => s.type === 'rectangle', bd);\nconst h = penpotUtils.findShapes(s => s.type === 'text', bd);\nreturn { boardId: bd.id, rows: filas.length, cells: h.length, badges: lineas.length,\n cabFill: cab.fills[0].fillColor, badgeColors: [...new Set(lineas.filter(s => s.fills[0].fillColor).map(s => s.fills[0].fillColor))],\n rowColors: [...new Set(filasB.children.map(r => r.fills[0].fillColor))] };" ], "veto_hits": [], "otros_patrones": [ { "patron": "textAlign", "fragmento": ".growType = 'auto-width'; if (align) t.textAlign = align; return t; }; const bd = penpot" } ], "content": "" }, { "prompt": "Pantalla de 360x780 para la app de una academia de tango: foto real de portada traida de una URL, titulo encima y boton abajo.", "id": "pd-043", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { negro: '#1A0A00', rojo: '#B81800', crema: '#F5E6D3', dorado: '#FFD700' };\nconst S = { xs: 12, sm: 16, md: 24, lg: 32 };\nconst board = penpot.createBoard();\nboard.name = 'Portada Academia';\nboard.resize(360, 780);\nboard.fills = [{ fillColor: P.negro, fillOpacity: 1 }];\nconst foto = penpot.createRectangle();\nfoto.name = 'Foto Portada';\nfoto.resize(360, 400);\nfoto.fills = [{ fillColor: '#000000', fillOpacity: 1, fillImage: { url: 'https://images.unsplash.com/photo-1504609813442-a8924e83f76e?w=800&q=80', fillImageFit: 'cover', fillImageOpacity: 1 } }];\nboard.insertChild(board.children.length, foto);\nconst titulo = penpot.createText();\ntitulo.name = 'Titulo';\ntitulo.characters = 'Academia de Tango';\ntitulo.fontFamily = 'Playfair Display';\ntitulo.fontSize = 36;\ntitulo.fontWeight = '700';\ntitulo.lineHeight = 1.15;\ntitulo.fills = [{ fillColor: P.crema, fillOpacity: 1 }];\ntitulo.growType = 'auto-width';\nboard.insertChild(board.children.length, titulo);\nconst sub = penpot.createText();\nsub.name = 'Subtitulo';\nsub.characters = 'Clases para todos los niveles';\nsub.fontFamily = 'Lato';\nsub.fontSize = 16;\nsub.fontWeight = '400';\nsub.lineHeight = 1.5;\nsub.fills = [{ fillColor: P.dorado, fillOpacity: 1 }];\nsub.growType = 'auto-width';\nboard.insertChild(board.children.length, sub);\nconst boton = penpot.createRectangle();\nboton.name = 'Boton Inscripcion';\nboton.resize(240, 52);\nboton.borderRadius = 26;\nboton.fills = [{ fillColor: P.rojo, fillOpacity: 1 }];\nboton.strokes = [{ strokeColor: P.dorado, strokeWidth: 2, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\nboard.insertChild(board.children.length, boton);\nconst btnText = penpot.createText();\nbtnText.name = 'Boton Texto';\nbtnText.characters = 'Inscribirse';\nbtnText.fontFamily = 'Lato';\nbtnText.fontSize = 18;\nbtnText.fontWeight = '700';\nbtnText.lineHeight = 1.2;\nbtnText.fills = [{ fillColor: P.dorado, fillOpacity: 1 }];\nbtnText.growType = 'auto-width';\nboard.insertChild(board.children.length, btnText);\nconst root = board;\nconst shapes = penpotUtils.findShapes(() => true, root);\nconst textos = penpotUtils.findShapes(s => s.type === 'text', root);\nconst fills = shapes.filter(s => s.fills && s.fills.length > 0);\nconst colores = new Set();\nfills.forEach(f => f.fillColor && colores.add(f.fillColor));\nconst fotos = shapes.filter(s => s.fills && s.fills.some(f => f.fillImage));\nconst botones = shapes.filter(s => s.strokes && s.strokes.length > 0);\nreturn { boardId: board.id, shapes: shapes.length, textos: textos.length, fotos: fotos.length, colores: Array.from(colores), botonConStroke: botones.length };" ], "veto_hits": [ { "patron": "fontSize/fontWeight/lineHeight/letterSpacing numerico", "fragmento": ".fontFamily = 'Playfair Display'; titulo.fontSize = 36; titulo.fontWeight = '700'; titulo.lin" } ], "otros_patrones": [ { "patron": "createText sin argumento util", "fragmento": "en.length, foto); const titulo = penpot.createText(); titulo.name = 'Titulo'; titulo.charact" } ], "content": "" }, { "prompt": "Quiero una galeria de cuatro fotos reales de internet en la app de una consultora de eficiencia energetica, formato 412x915, con leyenda en cada una.", "id": "pd-044", "tool_calls": [ "penpot_api_info" ], "code_payloads": [], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Meteme una imagen de fondo en el hero de un museo de maquinas de escribir y encima un velo oscuro para que se lea el titular en blanco.", "id": "pd-045", "tool_calls": [ "execute_code" ], "code_payloads": [ "const PAGE = 'Museo de Maquinas de Escribir';\nconst P = { hero: 560, md: 20, lg: 32 };\nconst S = { sm: 16, md: 24, lg: 40 };\nconst C = { velo: '#0B1D3A', titular: '#FFFFFF', sub: '#B0BEC5' };\nconst T = { titular: '64', sub: '20' };\nconst page = penpotUtils.getPageByName(PAGE);\nif (!page) return { error: 'page not found', pages: penpotUtils.getPages() };\nconst board = penpot.createBoard();\nboard.name = 'Hero';\nboard.fills = [{ fillColor: C.velo, fillOpacity: 1 }];\nboard.resize(1200, 560);\nboard.x = 0;\nboard.y = 0;\npage.root.insertChild(page.root.children.length, board);\nconst img = await penpot.importImage('https://images.unsplash.com/photo-1586075010923-2dd4570fb338?auto=format&fit=crop&w=1200&q=80');\nif (!img) return { error: 'image import failed' };\nconst imgFill = { fillOpacity: 1, fillImage: img };\nconst imgRect = penpot.createRectangle();\nimgRect.name = 'Imagen de fondo';\nimgRect.fills = [imgFill];\nimgRect.resize(1200, 560);\nimgRect.x = 0;\nimgRect.y = 0;\nboard.insertChild(0, imgRect);\nconst velo = penpot.createRectangle();\nvelo.name = 'Velo oscuro';\nvelo.fills = [{ fillColor: C.velo, fillOpacity: 0.55 }];\nvelo.resize(1200, 560);\nvelo.x = 0;\nvelo.y = 0;\nboard.insertChild(1, velo);\nconst titular = penpot.createText();\ntitular.name = 'Titular';\ntitular.characters = 'Museo de Maquinas de Escribir';\ntitular.fontFamily = 'Work Sans';\ntitular.fontSize = T.titular;\ntitular.fontWeight = '700';\ntitular.lineHeight = '1.15';\ntitular.letterSpacing = '0';\ntitular.align = 'center';\ntitular.growType = 'auto-width';\ntitular.fills = [{ fillColor: C.titular, fillOpacity: 1 }];\nboard.insertChild(2, titular);\nconst sub = penpot.createText();\nsub.name = 'Subtitulo';\nsub.characters = 'Mas de 200 maquinas de 1870 a 1990, todas en funcionamiento.';\nsub.fontFamily = 'Work Sans';\nsub.fontSize = T.sub;\nsub.fontWeight = '400';\nsub.lineHeight = '1.5';\nsub.letterSpacing = '0';\nsub.align = 'center';\nsub.growType = 'auto-width';\nsub.fills = [{ fillColor: C.sub, fillOpacity: 1 }];\nboard.insertChild(3, sub);\nconst btn = penpot.createRectangle();\nbtn.name = 'Boton';\nbtn.fills = [{ fillColor: '#D4A843', fillOpacity: 1 }];\nbtn.borderRadius = 8;\nbtn.resize(220, 48);\nbtn.x = 0;\nbtn.y = 0;\nboard.insertChild(4, btn);\nconst btnText = penpot.createText();\nbtnText.name = 'Texto del boton';\nbtnText.characters = 'Visitar el museo';\nbtnText.fontFamily = 'Work Sans';\nbtnText.fontSize = '16';\nbtnText.fontWeight = '600';\nbtnText.lineHeight = '1';\nbtnText.letterSpacing = '0';\nbtnText.align = 'center';\nbtnText.growType = 'auto-width';\nbtnText.fills = [{ fillColor: C.velo, fillOpacity: 1 }];\nboard.insertChild(5, btnText);\nconst content = penpot.createBoard();\ncontent.name = 'Contenido';\ncontent.fills = [];\ncontent.resize(700, 320);\ncontent.x = (1200 - 700) / 2;\ncontent.y = 120;\nboard.insertChild(6, content);\ncontent.insertChild(content.children.length, titular);\ncontent.insertChild(content.children.length, sub);\ncontent.insertChild(content.children.length, btn);\ncontent.insertChild(content.children.length, btnText);\nconst shapes = penpotUtils.findShapes(() => true, board);\nconst texts = penpotUtils.findShapes(s => s.type === 'text', board);\nconst fills = texts.map(t => t.fills?.[0]?.fillColor).filter(Boolean);\nconst uniqueFills = [...new Set(fills)];\nreturn {\n page: PAGE,\n boardId: board.id,\n shapes: shapes.length,\n texts: texts.length,\n uniqueFills: uniqueFills.length,\n fills: uniqueFills,\n zOrder: shapes.map(s => s.name),\n contentInside: penpotUtils.isContainedIn(titular, content),\n imgFilled: imgRect.fills.some(f => f.fillImage)\n};" ], "veto_hits": [ { "patron": "import_image / importImage / createImage / filePath", "fragmento": "ength, board); const img = await penpot.importImage('https://images.unsplash.com/photo-1586" } ], "otros_patrones": [ { "patron": "createText sin argumento util", "fragmento": "tChild(1, velo); const titular = penpot.createText(); titular.name = 'Titular'; titular.char" } ], "content": "" }, { "prompt": "Para la ficha de un estudio de doblaje necesito la foto del producto ocupando toda la tarjeta sin deformarse, 1240 px de ancho.", "id": "pd-046", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { fondo:'#0B1120', acento:'#F59E0B', papel:'#FFF8F0', tinta:'#1E293B', linea:'#334155' };\nconst T = { h1:'48', h2:'28', cuerpo:'17', nota:'13' };\nconst S = { sm:16, md:24, lg:40 };\nconst board = penpot.createBoard();\nboard.name = 'Ficha estudio';\nboard.fills = [{ fillColor: P.fondo, fillOpacity: 1 }];\nboard.resize(1240, 720);\nboard.x = 0; board.y = 0;\nconst add = (chars, size, color, weight, name) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = 'Work Sans'; t.fontSize = size; t.fontWeight = weight; t.lineHeight = '1.35';\n t.fills = [{ fillColor: color, fillOpacity: 1 }]; t.growType = 'auto-width';\n t.name = name; board.insertChild(board.children.length, t);\n return t;\n};\nadd('Estudio Voz Viva', T.h1, P.acento, '700', 'titulo');\nadd('Doblaje profesional en español — 12 años de trayectoria', T.h2, P.papel, '400', 'sub');\nconst foto = penpot.createRectangle();\nfoto.name = 'Foto del producto';\nfoto.fills = [{ fillColor: '#F59E0B', fillOpacity: 1, fillImage: { url: 'https://picsum.photos/id/1060/1240/480', fit: 'cover' } }];\nfoto.resize(1240, 480);\nfoto.x = 0; foto.y = 100;\nboard.insertChild(board.children.length, foto);\nconst card = penpot.createRectangle();\ncard.name = 'Tarjeta de info';\ncard.fills = [{ fillColor: P.papel, fillOpacity: 1 }];\ncard.resize(1240, 220);\ncard.x = 0; card.y = 580;\ncard.strokes = [{ strokeColor: P.linea, strokeWidth: 1, strokeStyle: 'solid', strokeAlignment: 'inner', strokeOpacity: 1 }];\ncard.borderRadius = 20;\nboard.insertChild(board.children.length, card);\nadd('Servicios: doblaje, locución, postproducción y dirección de casting.', T.cuerpo, P.tinta, '400', 'desc');\nadd('Cotización en 24 hs · 100% confidencial', T.nota, P.linea, '600', 'nota');\nconst fills = board.fills;\nreturn { boardId: board.id, ancho: board.width, alto: board.height, fotos: fills.filter(f => !!f.fillImage).length, textos: penpotUtils.findShapes(s => s.type === 'text', board).length };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Fila de tres tarjetas de un servicio de reparacion de relojes que se repartan el ancho de 1180 px en partes iguales, con separacion pareja entre ellas.", "id": "pd-047", "tool_calls": [ "penpot_api_info" ], "code_payloads": [], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Grilla de 900 px con seis casillas para el catalogo de un vivero de orquideas, tres por fila, todas del mismo alto.", "id": "pd-048", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#E8F5E9', card:'#FFF8E1', ink:'#1B5E20', accent:'#E91E63', white:'#FFFFFF' };\nconst S = { sm:14, md:18, lg:26 };\nconst T = { heading:'600 26px/1.2 \"Montserrat\"', body:'400 18px/1.5 \"Montserrat\"' };\nconst G = { card:20, pad:40 };\nconst C = [280,280,280];\nconst board = penpot.createBoard();\nboard.name = 'Catalogo Orquideas';\nboard.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nboard.resize(900, 680);\nboard.addGridLayout();\nif (board.grid) {\n board.grid.rows = [{ size: 'auto' }, { size: 'auto' }];\n board.grid.columns = C.map(w => ({ size: w + 'px' }));\n board.grid.rowGap = G.card;\n board.grid.columnGap = G.card;\n board.grid.horizontalPadding = G.pad;\n board.grid.verticalPadding = G.pad;\n}\nconst cards = [\n { name:'Dendrobium', price:'$ 4.500', badge:'Nuevo' },\n { name:'Phalaenopsis', price:'$ 6.200', badge:'Popular' },\n { name:'Cattleya', price:'$ 5.800', badge:'' },\n { name:'Vanda', price:'$ 7.100', badge:'Edicion' },\n { name:'Oncidium', price:'$ 3.900', badge:'' },\n { name:'Miltonia', price:'$ 5.400', badge:'Nuevo' }\n];\nconst rows = [1,1,1,2,2,2];\nconst cols = [1,2,3,1,2,3];\nconst shapes = [];\ncards.forEach((c, i) => {\n const card = penpot.createRectangle();\n card.name = 'Card ' + c.name;\n card.fills = [{ fillColor: P.card, fillOpacity: 1 }];\n card.borderRadius = 16;\n card.resize(280, 240);\n board.appendChild(card);\n if (board.grid) {\n card.layoutCell = { row: rows[i], column: cols[i] };\n }\n const badge = penpot.createRectangle();\n badge.name = 'Badge ' + c.name;\n badge.fills = [{ fillColor: P.accent, fillOpacity: 1 }];\n badge.borderRadius = 12;\n badge.resize(72, 28);\n board.appendChild(badge);\n const titulo = penpot.createText(c.name);\n titulo.name = 'Titulo ' + c.name;\n titulo.fontFamily = 'Montserrat';\n titulo.fontSize = S.lg;\n titulo.fontWeight = '600';\n titulo.lineHeight = '1.2';\n titulo.fills = [{ fillColor: P.ink, fillOpacity: 1 }];\n titulo.resize(280, 40);\n board.appendChild(titulo);\n const precio = penpot.createText(c.price);\n precio.name = 'Precio ' + c.name;\n precio.fontFamily = 'Montserrat';\n precio.fontSize = S.md;\n precio.fontWeight = '400';\n precio.lineHeight = '1.5';\n precio.fills = [{ fillColor: P.accent, fillOpacity: 1 }];\n precio.resize(280, 30);\n board.appendChild(precio);\n if (c.badge) {\n const etiqueta = penpot.createText(c.badge);\n etiqueta.name = 'Etiqueta ' + c.name;\n etiqueta.fontFamily = 'Montserrat';\n etiqueta.fontSize = S.sm;\n etiqueta.fontWeight = '600';\n etiqueta.lineHeight = '1';\n etiqueta.fills = [{ fillColor: P.white, fillOpacity: 1 }];\n etiqueta.resize(72, 28);\n board.appendChild(etiqueta);\n }\n shapes.push({ card, badge, titulo, precio, etiqueta: etiqueta || null });\n});\nconst grid = penpotUtils.analyzeDescendants(board, (root, shape) => {\n if (shape.type === 'rectangle' && shape.name.startsWith('Card ')) {\n return { name: shape.name, width: shape.width, height: shape.height };\n }\n});\nconst alturas = [...new Set(grid.map(g => g.result.height))];\nreturn { boardId: board.id, cards: shapes.length, gridRows: board.grid ? board.grid.rows.length : 0, columnWidths: C, cardHeights: alturas, cardHeightsUniform: alturas.length === 1 };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Una columna de un museo de maquinas de escribir donde el titulo va arriba, el texto en el medio y el boton abajo del todo, y el contenedor se ajusta al contenido.", "id": "pd-049", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { brand: '#1B2A4A', ink: '#F4F1EB', accent: '#D4A373' };\nconst T = { h1: '32', body: '16', btn: '14' };\nconst S = { sm: 12, md: 20, lg: 32 };\nconst mk = (chars, size, color, weight, family) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = family || 'Inter';\n t.fontSize = size;\n t.fontWeight = weight || '400';\n t.lineHeight = '1.4';\n t.fills = [{ fillColor: color, fillOpacity: 1 }];\n t.growType = 'auto-width';\n return t;\n};\nconst btn = penpot.createRectangle();\nbtn.name = 'Boton';\nbtn.fills = [{ fillColor: P.accent, fillOpacity: 1 }];\nbtn.borderRadius = 6;\nbtn.resize(200, 44);\nbtn.strokes = [];\nconst btnLabel = mk('Ver coleccion', T.btn, P.ink, '600');\nbtnLabel.horizontalSizing = 'fit-content';\nbtnLabel.verticalSizing = 'fit-content';\nbtn.insertChild(0, btnLabel);\nconst board = penpot.createBoard();\nboard.name = 'Columna Museo';\nboard.fills = [{ fillColor: P.ink, fillOpacity: 1 }];\nboard.resize(400, 520);\nboard.borderRadius = 16;\nboard.shadows = [{ style: 'drop-shadow', offsetX: 0, offsetY: 6, blur: 16, spread: 0, color: { color: '#000000', opacity: 0.12 } }];\nboard.insertChild(0, mk('Maquinas de Escribir', '32', P.brand, '700'));\nboard.insertChild(1, mk('Explorá la historia de la mecanografia a traves de piezas originales de los siglos XIX y XX.', '16', P.brand));\nboard.insertChild(2, btn);\nconst flex = board.addFlexLayout();\nflex.dir = 'column';\nflex.horizontalPadding = S.lg;\nflex.verticalPadding = S.lg;\nflex.rowGap = S.md;\nflex.alignItems = 'center';\nflex.justifyContent = 'space-between';\nconst layout = board.flex;\nif (!layout) throw new Error('board.flex es null');\nlayout.apply();\nstorage.museo = { boardId: board.id, P, T, S };\nreturn { boardId: board.id, flexDir: layout.dir, padding: layout.horizontalPadding + ' ' + layout.verticalPadding, gap: layout.rowGap, children: board.children.length };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Pie de pagina de 1240 px para una academia de tango con cuatro columnas de enlaces y una linea final de derechos.", "id": "pd-050", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#0B0A09', ink:'#F2EDE4', accent:'#C41E3A', line:'#2A2420', dim:'#8A7F72' };\nconst T = { body:'16', link:'13', small:'12' };\nconst S = { col:60, gut:24, mg:80 };\nconst mk = (chars, size, color, weight) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = 'Inter'; t.fontSize = size; t.fontWeight = weight || '400';\n t.lineHeight = size === T.body ? '24' : '18'; t.growType = 'auto-height';\n t.fills = [{ fillColor: color, fillOpacity: 1 }]; t.align = 'left';\n return t;\n};\nconst mkCol = (title, links) => {\n const c = penpot.createBoard();\n if (!c) throw new Error('createBoard devolvio null');\n c.name = 'Columna ' + title; c.fills = []; c.resize(60, 1);\n c.insertChild(c.children.length, mk(title, T.body, P.ink, '700'));\n c.insertChild(c.children.length, mk('', '10', P.line));\n links.forEach(l => c.insertChild(c.children.length, mk(l, T.link, P.ink)));\n c.addGridLayout();\n if (c.grid) { c.grid.rows = 'auto'; c.grid.columns = '1fr'; c.grid.rowGap = '10'; c.grid.columnGap = '0'; }\n return c;\n};\nconst board = penpot.createBoard();\nif (!board) throw new Error('createBoard devolvio null');\nboard.name = 'Footer Academia'; board.fills = [{ fillColor: P.bg, fillOpacity: 1 }]; board.resize(1240, 340);\nboard.addGridLayout();\nif (board.grid) { board.grid.rows = '1fr auto'; board.grid.columns = '1fr'; board.grid.rowGap = '0'; board.grid.columnGap = '0'; }\nconst cols = ['Academia', 'Clases', 'Eventos', 'Contacto'];\nconst datos = [\n ['Historia del tango', 'Profesores', 'Repertorio', 'Audiciones'],\n ['Principiante', 'Intermedio', 'Avanzado', 'Parejas'],\n ['Milongas', 'Tangofest', 'Workshops', 'Prensa'],\n ['Email', 'Instagram', 'WhatsApp', 'Ubicacion']\n];\nconst columnas = cols.map((n, i) => mkCol(n, datos[i]));\ncolumnas.forEach((c, i) => {\n board.insertChild(board.children.length, c);\n if (c.layoutChild) { c.layoutChild.row = 1; c.layoutChild.column = i + 1; c.layoutChild.rowSpan = 1; c.layoutChild.columnSpan = 1; }\n});\nconst derechos = penpot.createBoard();\nif (!derechos) throw new Error('createBoard devolvio null');\nderechos.name = 'Derechos'; derechos.fills = []; derechos.resize(1240, 48);\nderechos.insertChild(derechos.children.length, mk('© 2025 Academia de Tango — Todos los derechos reservados.', T.small, P.dim));\nderechos.insertChild(derechos.children.length, mk('', '1', P.line));\nboard.insertChild(board.children.length, derechos);\nif (derechos.layoutChild) { derechos.layoutChild.row = 2; derechos.layoutChild.column = 1; derechos.layoutChild.rowSpan = 1; derechos.layoutChild.columnSpan = 1; }\nstorage.footer = { boardId: board.id, P: P, T: T, S: S };\nreturn { boardId: board.id, columnas: columnas.length, cells: board.grid ? board.grid.cells.length : 0, derechos: derechos.children.length };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Pantalla de 360x780 de un albergue para perros mayores con una lista vertical de siete items, cada uno con icono a la izquierda y texto a la derecha.", "id": "pd-051", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { bg:'#FFF8F0', header:'#2D5A3D', item:'#F0F7F2', icon:'#6B4226', text:'#2C2C2C', sub:'#5A5A5A' };\nconst S = { sm:14, md:18, lg:26 };\nconst T = { heading:'Montserrat', body:'Open Sans' };\nconst board = penpot.createBoard();\nboard.name = 'Albergue - Servicios';\nboard.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nboard.resize(360, 780);\nboard.addFlexLayout();\nif (board.flex) {\n board.flex.dir = 'column';\n board.flex.horizontalPadding = 24;\n board.flex.verticalPadding = 24;\n board.flex.rowGap = 12;\n}\nconst header = penpot.createRectangle();\nheader.name = 'Encabezado';\nheader.fills = [{ fillColor: P.header, fillOpacity: 1 }];\nheader.resize(312, 100);\nheader.borderRadius = 16;\nheader.strokes = [];\nboard.insertChild(board.children.length, header);\nconst hTitle = penpot.createText('Servicios del albergue');\nhTitle.name = 'Titulo';\nhTitle.fontFamily = T.heading;\nhTitle.fontSize = S.lg;\nhTitle.fontWeight = '700';\nhTitle.lineHeight = 1.2;\nhTitle.fills = [{ fillColor: '#FFF', fillOpacity: 1 }];\nhTitle.growType = 'auto-width';\nheader.insertChild(header.children.length, hTitle);\nconst hSub = penpot.createText('Cuidado y atencion para perros senior');\nhSub.name = 'Subtitulo';\nhSub.fontFamily = T.body;\nhSub.fontSize = S.sm;\nhSub.fontWeight = '400';\nhSub.lineHeight = 1.4;\nhSub.fills = [{ fillColor: '#D4EDDA', fillOpacity: 1 }];\nhSub.growType = 'auto-width';\nheader.insertChild(header.children.length, hSub);\nconst servicios = [\n { icon:'🏥', titulo:'Veterinaria senior', desc:'Revisiones quincenales y medicamentos' },\n { icon:'🍖', titulo:'Alimentacion especial', desc:'Dieta baja en sodio y alta en proteinas' },\n { icon:'🛁', titulo:'Higiene y aseo', desc:'Bano semanal y cuidado dental' },\n { icon:'🚶', titulo:'Paseos adaptados', desc:'Rutas cortas y planas, ritmo lento' },\n { icon:'🛏️', titulo:'Descanso climatizado', desc:'Camas ortopedicas y temperatura controlada' },\n { icon:'🤝', titulo:'Socializacion', desc:'Grupos pequenos por temperamento' },\n { icon:'❤️', titulo:'Adopcion responsable', desc:'Seguimiento post-adopcion incluido' }\n];\nconst items = servicios.map(s => {\n const item = penpot.createRectangle();\n item.name = 'Item ' + s.titulo;\n item.fills = [{ fillColor: P.item, fillOpacity: 1 }];\n item.resize(312, 80);\n item.borderRadius = 12;\n item.strokes = [];\n board.insertChild(board.children.length, item);\n const icono = penpot.createRectangle();\n icono.name = 'Icono ' + s.titulo;\n icono.fills = [{ fillColor: P.icon, fillOpacity: 1 }];\n icono.resize(44, 44);\n icono.borderRadius = 22;\n icono.strokes = [];\n item.insertChild(item.children.length, icono);\n const iconText = penpot.createText(s.icon);\n iconText.name = 'IconoTexto ' + s.titulo;\n iconText.fontFamily = T.body;\n iconText.fontSize = 22;\n iconText.lineHeight = 1;\n iconText.fills = [{ fillColor: '#FFF', fillOpacity: 1 }];\n iconText.growType = 'auto-width';\n iconText.alignHorizontal = 'center';\n iconText.alignVertical = 'center';\n icono.insertChild(icono.children.length, iconText);\n const t = penpot.createText(s.titulo);\n t.name = 'Titulo ' + s.titulo;\n t.fontFamily = T.heading;\n t.fontSize = S.md;\n t.fontWeight = '600';\n t.lineHeight = 1.3;\n t.fills = [{ fillColor: P.text, fillOpacity: 1 }];\n t.growType = 'auto-width';\n item.insertChild(item.children.length, t);\n const d = penpot.createText(s.desc);\n d.name = 'Desc ' + s.titulo;\n d.fontFamily = T.body;\n d.fontSize = S.sm;\n d.fontWeight = '400';\n d.lineHeight = 1.4;\n d.fills = [{ fillColor: P.sub, fillOpacity: 1 }];\n d.growType = 'auto-width';\n item.insertChild(item.children.length, d);\n return { item, icono, t, d };\n});\nconst setXY = (shape, px, py) => penpotUtils.setParentXY(shape, px, py);\nitems.forEach((it, i) => {\n setXY(it.icono, 16, 18);\n setXY(it.t, 72, 16);\n setXY(it.d, 72, 44);\n});\nconst structure = penpotUtils.shapeStructure(board, 3);\nconst texts = penpotUtils.findShapes(s => s.type === 'text', board);\nconst fills = texts.map(t => t.fills ? t.fills.map(f => f.fillColor) : []);\nreturn { boardId: board.id, structure, itemCount: items.length, iconos: items.length, textos: texts.length, colores: [...new Set(fills.flat())].length };" ], "veto_hits": [ { "patron": "fontSize/fontWeight/lineHeight/letterSpacing numerico", "fragmento": " S.lg; hTitle.fontWeight = '700'; hTitle.lineHeight = 1.2; hTitle.fills = [{ fillColor: '#FFF'," } ], "otros_patrones": [], "content": "" }, { "prompt": "En la pagina hay un board de un museo de maquinas de escribir que quedo con puros rectangulos grises y sin una sola palabra. Convertilo en un diseno de verdad.", "id": "pd-052", "tool_calls": [ "execute_code" ], "code_payloads": [ "const board = penpotUtils.findShape(s => s.name === 'Museo Maquinas de Escribir');\nif (!board) return { found: false };\nconst structure = penpotUtils.shapeStructure(board, 3);\nreturn { found: true, boardId: board.id, boardType: board.type, children: structure.children.length, fills: board.fills ? board.fills.length : 0 };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Este board de una consultora de eficiencia energetica tiene todos los textos del mismo tamano y todo el mismo gris. Dale jerarquia y una paleta.", "id": "pd-053", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { brand:'#0B3D91', green:'#00A86B', ink:'#111827', mid:'#6B7280', bg:'#F8FAFC', surf:'#FFFFFF' };\nconst T = { h1:'36', h2:'24', body:'16', tag:'12' };\nconst S = { sm:8, md:16, lg:24, xl:40 };\nconst board = penpot.createBoard();\nboard.name = 'Hero consultora';\nboard.fills = [{ fillColor: P.bg, fillOpacity: 1 }];\nboard.resize(1100, 520);\nconst mk = (chars, size, weight, color, lh) => {\n const t = penpot.createText(chars);\n if (!t) throw new Error('createText devolvio null');\n t.fontFamily = 'Inter'; t.fontSize = size; t.fontWeight = weight; t.lineHeight = lh;\n t.fills = [{ fillColor: color, fillOpacity: 1 }]; t.growType = 'auto-width';\n t.name = chars.split(' ')[0];\n return t;\n};\nconst h1 = mk('Eficiencia energetica para tu industria', T.h1, '700', P.ink, '44');\nconst h2 = mk('Diagnostico, auditoria y plan de ahorro', T.h2, '600', P.brand, '32');\nconst body = mk('Medimos consumo, calculamos ROI y acompanamos la implementacion.', T.body, '400', P.mid, '24');\nconst tag = mk('AUDITORIA GRATUITA', T.tag, '700', P.green, '18');\nboard.insertChild(board.children.length, h1);\nboard.insertChild(board.children.length, h2);\nboard.insertChild(board.children.length, body);\nboard.insertChild(board.children.length, tag);\nconst shapes = [h1, h2, body, tag];\nshapes.forEach((s, i) => penpotUtils.setParentXY(s, S.xl + i * S.lg, S.xl));\nconst css = penpot.generateStyle(shapes, { type: 'css', withChildren: true });\nreturn { boardId: board.id, shapes: shapes.map(s => ({ id: s.id, name: s.name, fontSize: s.fontSize, lineHeight: s.lineHeight, fills: s.fills?.map(f => f.fillColor) })), palette: Object.entries(P).map(([k, v]) => ({ token: k, hex: v })), tags: T, css };" ], "veto_hits": [], "otros_patrones": [ { "patron": "generateStyle con withChildren", "fragmento": "ot.generateStyle(shapes, { type: 'css', withChildren: true }); return { boardId: board.id, sh" } ], "content": "" }, { "prompt": "Revisa el board de un estudio de doblaje que arme yo, decime que esta mal de contraste y arreglalo.", "id": "pd-054", "tool_calls": [ "execute_code" ], "code_payloads": [ "const page = penpot.root;\nconst structure = penpotUtils.shapeStructure(page, 3);\nconst board = penpotUtils.findShape(s => s.name.includes('doblaje') || s.name.includes('estudio'), page);\nreturn { pageName: page.name, boardName: board ? board.name : null, boardId: board ? board.id : null, structure };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "El hero de una tienda de kayaks tiene el texto ilegible sobre la foto. Arreglalo sin cambiar la foto.", "id": "pd-055", "tool_calls": [ "execute_code" ], "code_payloads": [ "const page = penpot.root;\nconst structure = penpotUtils.shapeStructure(page, 3);\nconst hero = penpotUtils.findShape(s => s.name.toLowerCase().includes('hero'), page);\nconst texts = penpotUtils.findShapes(s => s.type === 'text', page);\nreturn { heroId: hero ? hero.id : null, heroName: hero ? hero.name : null, heroFills: hero ? hero.fills.length : 0, texts: texts.length, textNames: texts.map(t => t.name) };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Antes de tocar nada, fijate en la documentacion de la API como se crea un texto en Penpot y despues poneme un titulo de un estudio de doblaje en 48 px.", "id": "pd-056", "tool_calls": [ "penpot_api_info" ], "code_payloads": [], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Como hago para que un hijo de un board con layout ocupe todo el ancho disponible? Mostramelo aplicado a una seccion de una consultora de eficiencia energetica.", "id": "pd-057", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { brand: '#0B3D2E', accent: '#F2A900', ink: '#111827', paper: '#FDFBF7', line: '#E2DDD3' };\nconst T = { h1: '52', h2: '28', body: '17', small: '14' };\nconst S = { xs: 12, sm: 20, md: 32, lg: 48 };\nconst mk = (name, w, h, fills, fontSize, fontWeight, lineHeight, color, growType) => {\n const s = penpot.createRectangle();\n s.name = name; s.resize(w, h);\n s.fills = fills ? fills.map(c => ({ fillColor: c, fillOpacity: 1, fillImage: null })) : [];\n if (s.fills) s.fills.forEach(f => { f.fillOpacity = 1; f.fillImage = null; });\n if (fontSize) {\n const t = penpot.createText();\n t.name = name + '/texto'; t.characters = name;\n t.fontFamily = 'Inter'; t.fontSize = fontSize; t.fontWeight = fontWeight || '400';\n t.lineHeight = lineHeight || '1.4'; t.fills = [{ fillColor: color || P.ink, fillOpacity: 1 }];\n t.growType = growType || 'auto-width';\n s.appendChild(t);\n }\n return s;\n};\nconst hero = mk('Hero', 1100, 420, [P.brand], null, null, null, null);\nhero.borderRadius = 16;\nconst cta = mk('CTA', 1100, 72, [P.accent], null, null, null, null);\ncta.borderRadius = 12;\nconst board = penpot.createBoard();\nboard.name = 'Seccion consultoria'; board.resize(1100, 600);\nboard.fills = [{ fillColor: P.paper, fillOpacity: 1, fillImage: null }];\nboard.addFlexLayout();\nif (board.flex) {\n board.flex.dir = 'column';\n board.flex.horizontalPadding = S.lg; board.flex.verticalPadding = S.lg;\n board.flex.rowGap = S.md;\n board.flex.alignItems = 'start';\n}\nboard.insertChild(board.children.length, hero);\nboard.insertChild(board.children.length, cta);\nif (board.flex) {\n const h = board.children.find(c => c.name === 'Hero');\n const c = board.children.find(c => c.name === 'CTA');\n if (h) h.flex = { flexGrow: 1, flexShrink: 0, flexBasis: 'auto', alignItems: 'center', justifyContent: 'center' };\n if (c) c.flex = { flexGrow: 0, flexShrink: 0, flexBasis: 'auto', alignItems: 'center', justifyContent: 'center' };\n}\nstorage.consultoria = { boardId: board.id };\nreturn { boardId: board.id, heroFlexGrow: board.children.find(c => c.name === 'Hero')?.flex?.flexGrow, ctaFlexGrow: board.children.find(c => c.name === 'CTA')?.flex?.flexGrow };" ], "veto_hits": [], "otros_patrones": [ { "patron": "createText sin argumento util", "fragmento": " if (fontSize) { const t = penpot.createText(); t.name = name + '/texto'; t.charac" } ], "content": "" }, { "prompt": "Quiero usar la tipografia Libre Baskerville en el titulo de una consultora de eficiencia energetica. Verifica que quede realmente aplicada, no solo seteada.", "id": "pd-058", "tool_calls": [ "penpot_api_info" ], "code_payloads": [], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Guarda la paleta de un museo de maquinas de escribir como colores de la biblioteca del archivo y despues aplicalos a dos formas.", "id": "pd-059", "tool_calls": [ "execute_code" ], "code_payloads": [ "const P = { tinta:'#1A1A1A', papel:'#2E5C4A', viejo:'#F5E6C8', laton:'#C9A227', sello:'#B22222' };\nconst N = ['Tinta','Papel','Viejo','Latón','Sello'];\nconst libs = [];\nfor (const k of Object.keys(P)) {\n const c = penpot.library.local.createColor();\n c.name = N[libs.length];\n c.color = P[k];\n libs.push(c);\n}\nreturn { library: penpot.library.local.name, colors: libs.map(c => ({ name: c.name, color: c.color })), count: libs.length };" ], "veto_hits": [], "otros_patrones": [], "content": "" }, { "prompt": "Exportame el board de una consultora de eficiencia energetica para verlo, y decime si algo quedo fuera de los limites.", "id": "pd-060", "tool_calls": [ "execute_code" ], "code_payloads": [ "const board = penpotUtils.findShape(s => s.name === 'Hero consultora');\nif (!board) return { error: 'board no encontrado' };\nconst violations = penpotUtils.analyzeDescendants(board, (root, shape) => {\n return !penpotUtils.isContainedIn(shape, root) ? 'outside-bounds' : null;\n});\nreturn { boardId: board.id, boardName: board.name, width: board.width, height: board.height, violationsCount: violations.length };" ], "veto_hits": [], "otros_patrones": [], "content": "" } ] }