{"context":{"domain":"lucca-office"},"layers":{"ApplicationLayer":{"reference":"ApplicationLayer","availableInstances":[{"reference":"ComponentsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@DataSet","reference":"SystemsDataSet","params":{"filter":{"domain":"data2"},"pagination":{"size":1},"datasource":{"component":"data2-core-types@ProcessReference","params":{"reference":"SystemsDataSource"}}}},{"reference":"ExternalComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"SystemsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"5e3b796d6228227ca637d0f4","name":"data2","domain":"data2","organization":"62292184611f480b3191c37c","visibility":"public","updatedAt":"2026-07-27T14:19:36.810Z"}]}},{"reference":"OrganizationsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"DataComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@DataSet","reference":"OneFileComponentInstancesDataSet","params":{"pagination":{"size":1},"master":{"dataset":{"component":"data2-core-types@ProcessReference","params":{"reference":"SystemsDataSet"}},"localField":"system","masterField":"_id"},"datasource":{"component":"data2-core-types@ProcessReference","params":{"reference":"FileComponentInstancesDataSource"}}}},{"reference":"ApplicationsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"ComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"ServiceComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@Data2TableDataSource","reference":"FileComponentInstancesDataSource","params":{"table":"fileComponentInstances","schema":[{"component":"data2-core-types@Field","params":{"key":"component","title":"Component","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"system","title":"System","component":"data2-core-primitives@ObjectId"}},{"component":"data2-core-types@Field","params":{"key":"displayName","title":"Display Name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"params","title":"Params","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"reference","title":"Reference","component":"data2-core-primitives@String"}}]}},{"component":"data2-core@MemoryDataSource","reference":"EmptyDataSource","params":{"initialData":[]}},{"reference":"ComponentsDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"UserComponentsDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"DataComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ServiceComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ExternalComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ApplicationsDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}}],"processesParams":{}},"HostApplicationLayer":{"reference":"HostApplicationLayer","availableInstances":[{"_id":"5f15ff190528b92601890f71","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"SystemsTableDataSource"}},"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"domain"}}},"pk":"_id","fields":[{"component":"data2-core-types@FieldReference","params":{"key":"_id"}},{"component":"data2-core-types@FieldReference","params":{"key":"name"}},{"component":"data2-core-types@FieldReference","params":{"key":"domain"}},{"component":"data2-core-types@FieldReference","params":{"key":"visibility"}},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"customDomains"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization._id"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization.name"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization.billingStatus"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization.publicTrial.active"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"settings.defaultAccessPolicy"}}},"break":true}],"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":1}}},"staticPagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":1}}},"policies":[{"component":"data2-core-auth@Policy","params":{"allow":{"params":{"actions":[{"params":{"components":[{"params":{"query":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$params.query"}}}},"field":{"component":"data2-core-primitives@String","params":{"content":"_id"}},"fieldType":"string"},"component":"data2@7KkQq0vK4a7D2gg6IVJT7"},{"params":{"query":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$params.query"}}}},"field":{"component":"data2-core-primitives@String","params":{"content":"domain"}},"fieldType":"string"},"component":"data2@7KkQq0vK4a7D2gg6IVJT7"},{"params":{"query":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$params.query"}}}},"field":{"component":"data2-core-primitives@String","params":{"content":"customDomains"}},"fieldType":"string"},"component":"data2@7KkQq0vK4a7D2gg6IVJT7"}]},"component":"data2-core-logical-operators@Or"}]},"component":"data2-core-flow@Series"}}}]},"reference":"SystemsDataSet","updatedAt":"2026-08-07T21:51:24.037Z","createdAt":"2020-07-20T20:31:21.838Z"},{"_id":"5f1601ce0528b92601890f7c","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":1000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentsTableDataSource"}},"sort":{"params.package":1,"params.component":1},"pk":"_id","autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"staticFilter":{"system":{"exists":{"params":{"content":false},"component":"data2-core-primitives@Boolean"}}},"filter":{"system":{"exists":{"params":{"content":false},"component":"data2-core-primitives@Boolean"}}}},"reference":"ComponentsDataSet","updatedAt":"2025-11-05T19:37:01.935Z","createdAt":"2020-07-20T20:42:54.898Z"},{"_id":"5f9041dbc8da12be8b94aea4","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":2000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentsTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"UserComponentsDataSet","updatedAt":"2025-11-06T21:06:58.095Z","createdAt":"2020-07-20T20:44:09.350Z"},{"_id":"65d8c4fabc9dbb8f94de8d53","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ExternalComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ExternalComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:23.742Z","createdAt":"2024-02-23T16:17:01.816Z"},{"reference":"SystemsTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a11143bae205cd35b1d5de0","name":"Lucca Office","domain":"lucca-office","visibility":"public","settings":{"defaultAccessPolicy":"private"},"updatedAt":"2026-08-14T20:00:20.536Z","createdAt":"2026-05-23T02:43:07.343Z"}]}},{"reference":"ComponentsTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"5f52a278a575399a45d239b4","component":"data2-core-types@Component","params":{"package":"data2-core-primitives","component":"Router","interactions":{"component":"data2-core-types@FieldSet","params":{"fields":{"goBack":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-types@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@This","params":{}},"prop":"history.goBack","args":[]}}}},"icon":{"component":"data2-core-primitives@String","params":{"content":"arrow-left"}},"visibility":"public"}},"goTo":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@This","params":{}},"prop":"history.push","args":[{"component":"data2-core-types@Reference","params":{"reference":"url"}},{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"preserveQuery"}}}}]}},"parameters":[{"component":"data2-core-primitives@String","params":{"content":"url"}},{"component":"data2-core-primitives@String","params":{"content":"preserveQuery"}}]},"break":true},"icon":{"component":"data2-core-primitives@String","params":{"content":"share"}},"visibility":"public","arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Path to route"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Path to redirect user"}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"},{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Boolean"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Preserve Query"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Preserve query string parameters  preserve query parameters on navigating."}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"}]}},"open":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-types@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@This","params":{}},"prop":"history.push","args":[{"pathname":{"component":"data2-core-types@Reference","params":{"reference":"value"}}}]}},"parameters":["value"]}},"icon":{"component":"data2-core-primitives@String","params":{"content":"share"}},"visibility":"public","arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Link to open"}},"description":{"component":"data2-core-primitives@String","params":{"content":""}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"},{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Target"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Where to open the link"}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"oneOf":[{"params":{"content":"_blank"},"component":"data2-core-primitives@String"},{"params":{"content":"_self"},"component":"data2-core-primitives@String"},{"params":{"content":"_parent"},"component":"data2-core-primitives@String"},{"params":{"content":"_top"},"component":"data2-core-primitives@String"}]},"component":"data2-core-types@ComponentParameter"}]}},"close":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-types@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"params":{},"component":"data2-core-web@Window"},"prop":"close"}},"parameters":["value"]}},"icon":{"component":"data2-core-primitives@String","params":{"content":"times"}},"visibility":"public","arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Link to open"}},"description":{"component":"data2-core-primitives@String","params":{"content":""}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"},{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Target"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Where to open the link"}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"oneOf":[{"params":{"content":"_blank"},"component":"data2-core-primitives@String"},{"params":{"content":"_self"},"component":"data2-core-primitives@String"},{"params":{"content":"_parent"},"component":"data2-core-primitives@String"},{"params":{"content":"_top"},"component":"data2-core-primitives@String"}]},"component":"data2-core-types@ComponentParameter"}],"description":{"component":"data2-core-primitives@String","params":{"content":"Closes a window that was opened via router.open."}}}}}}}}},{"_id":"620d28658271aa4129919d89","component":"data2-core-types@Component","params":{"package":"data2-interface-services","component":"DeviceController","interactions":{"params":{"fields":{"postMessage":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":"data2-core-primitives@String","title":{"component":"data2-core-primitives@String","params":{"content":"data"}}}}]},"component":"data2-core-types@ComponentParameter"},"print":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Object"}},"flat":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"parameters":{"zoom":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Number"}},"title":{"component":"data2-core-primitives@String","params":{"content":"zoom"}},"visibility":"public"}}},"visibility":"public"},"component":"data2-core-types@ComponentParameter"}],"visibility":"public"},"component":"data2-core-types@ComponentParameter"},"share":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}}},"component":"data2-core-types@ComponentParameter"},"clipboard.copy":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}}}}]},"component":"data2-core-types@ComponentParameter"},"clipboard.paste":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}}}}]},"component":"data2-core-types@ComponentParameter"},"download":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Filename"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Content"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Object"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Options"}},"parameters":{"params":{"fields":{"type":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Mimetype"}}}}}},"component":"data2-core-types@FieldSet"}}}]},"component":"data2-core-types@ComponentParameter"},"downloadUrl":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Filename"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Url"}}}}]},"component":"data2-core-types@ComponentParameter"},"promptFiles":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":"data2-core-primitives@Object","flat":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"parameters":{"params":{"fields":{"multiple":{"component":"data2-core-types@ComponentParameter","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Multiple"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Whether or not to allow selecting multiple files"}},"component":"data2-core-primitives@Boolean"}},"acceptedFormats":{"component":"data2-core-types@ComponentParameter","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Accepted Formats"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Accepted file formats"}},"component":"data2-core-primitives@String","list":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}},"timeout":{"component":"data2-core-types@ComponentParameter","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Timeout"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Max milliseconds to wait for file upload"}},"component":"data2-core-primitives@Number"}}}},"component":"data2-core-types@FieldSet"}}}],"visibility":"public"},"component":"data2-core-types@ComponentParameter"},"meetingRecord":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","definition":{"params":{"actions":[{"params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"meeting"}},"script":{"component":"data2-core-primitives@String","params":{"content":"(function() {\n  let mediaRecorder;\n  let audioChunks = [];\n  let combinedStream;\n  let audioContext;\n  \n  window.meetingCapture = async function() {\n    try {\n      // Solicitar acesso ao áudio da guia (e vídeo, que será ignorado depois)\n      const tabStream = await navigator.mediaDevices.getDisplayMedia({ \n        video: true, // Necessário para alguns navegadores\n        audio: true\n      });\n      \n      // Remover a trilha de vídeo\n      const tabAudioTrack = tabStream.getAudioTracks()[0];\n      \n      // Solicitar acesso ao microfone\n      const micStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n      const micAudioTrack = micStream.getAudioTracks()[0];\n      \n      // Criar um novo MediaStream com ambas as trilhas de áudio\n      combinedStream = new MediaStream([tabAudioTrack, micAudioTrack]);\n      \n      // Criar um contexto de áudio para misturar os streams\n      audioContext = new AudioContext();\n      const destination = audioContext.createMediaStreamDestination();\n      \n      // Conectar a trilha de áudio da guia\n      const tabSource = audioContext.createMediaStreamSource(new MediaStream([tabAudioTrack]));\n      tabSource.connect(destination);\n      \n      // Conectar a trilha de áudio do microfone\n      const micSource = audioContext.createMediaStreamSource(new MediaStream([micAudioTrack]));\n      micSource.connect(destination);\n      \n      // Iniciar gravação com o stream misturado\n      mediaRecorder = new MediaRecorder(destination.stream);\n      audioChunks = [];\n      mediaRecorder.ondataavailable = (e) => audioChunks.push(e.data);\n      mediaRecorder.start();\n      \n      console.log('Gravação iniciada. Chame meetingCaptureStop() para finalizar.');\n    } catch (error) {\n      console.error('Erro ao iniciar a captura:', error);\n    }\n  };\n  \n  window.meetingCaptureStop = function() {\n    return new Promise((resolve, reject) => {\n      if (!mediaRecorder) {\n        reject('Nenhuma gravação em andamento.');\n        return;\n      }\n      \n      mediaRecorder.onstop = () => {\n        const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });\n        combinedStream.getTracks().forEach(track => track.stop());\n        audioContext.close();\n        \n        resolve(audioBlob);\n        console.log('Gravação finalizada. O arquivo de áudio está pronto para upload.');\n      };\n      \n      mediaRecorder.stop();\n    });\n  };\n})();"}}},"component":"data2-core-web@Script"},{"component":"data2-core-types@Getter","params":{"module":{"params":{},"component":"data2-core-web@Window"},"prop":{"component":"data2-core-primitives@String","params":{"content":"meetingCapture"}}}}]},"component":"data2-core-flow@Series"}},"component":"data2-core-types@ComponentParameter"},"meetingRecordStop":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Filename"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Content"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Object"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Options"}},"parameters":{"params":{"fields":{"type":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Mimetype"}}}}}},"component":"data2-core-types@FieldSet"}}}],"definition":{"params":{"actions":[{"component":"data2-core-types@Getter","params":{"module":{"params":{},"component":"data2-core-web@Window"},"prop":{"component":"data2-core-primitives@String","params":{"content":"meetingCaptureStop"}}}}]},"component":"data2-core-flow@Series"}},"component":"data2-core-types@ComponentParameter"},"notify":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"private","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":"data2-core-primitives@Object","parameters":{"title":{"params":{"component":"data2-core-primitives@String","index":{"component":"data2-core-primitives@Number","params":{"content":0}},"title":{"component":"data2-core-primitives@String","params":{"content":"title"}}},"component":"data2-core-types@ComponentParameter"},"sound":{"params":{"component":"data2-core-types@FileComponentUrlReference","index":{"component":"data2-core-primitives@Number","params":{"content":1}},"title":{"component":"data2-core-primitives@String","params":{"content":"sound"}}},"component":"data2-core-types@ComponentParameter"}},"flat":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}}]},"component":"data2-core-types@ComponentParameter"}}},"component":"data2-core-types@FieldSet"}}},{"_id":"6a602c613a42b34509a62927","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"interactive-muni-map","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const UF_CODE={RO:'11',AC:'12',AM:'13',RR:'14',PA:'15',AP:'16',TO:'17',MA:'21',PI:'22',CE:'23',RN:'24',PB:'25',PE:'26',AL:'27',SE:'28',BA:'29',MG:'31',ES:'32',RJ:'33',SP:'35',PR:'41',SC:'42',RS:'43',MS:'50',MT:'51',GO:'52',DF:'53'};\nconst UF_BORDERS={AC:['AM','RO'],AL:['BA','PE','SE'],AM:['AC','RO','RR','PA','MT'],AP:['PA'],\n BA:['AL','SE','PE','PI','TO','GO','MG','ES'],CE:['PI','PB','PE','RN'],DF:['GO','MG'],\n ES:['BA','MG','RJ'],GO:['DF','MT','MS','MG','BA','TO'],MA:['PA','TO','PI'],\n MG:['BA','GO','DF','MS','SP','RJ','ES'],MS:['MT','GO','MG','SP','PR'],MT:['RO','AM','PA','TO','GO','MS'],\n PA:['AP','AM','MT','TO','MA','RR'],PB:['RN','CE','PE'],PE:['PB','CE','PI','BA','AL'],\n PI:['MA','TO','BA','PE','CE'],PR:['SP','MS','SC'],RJ:['ES','MG','SP'],RN:['CE','PB'],\n RO:['AC','AM','MT'],RR:['AM','PA'],RS:['SC'],SC:['PR','RS'],SE:['BA','AL'],\n SP:['MG','RJ','PR','MS'],TO:['PA','MA','PI','BA','GO','MT']};\nconst PALETTE=['#8b7fd6','#d1b048','#67bd5b','#5aa9d6','#d67fa8','#c98a4b'];\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%;box-sizing:border-box}\n    .wrap{position:relative;height:100%;display:flex;flex-direction:column;font:12px -apple-system,BlinkMacSystemFont,\"Segoe UI\",Inter,sans-serif;background:#f4f4f8}\n    .stage{flex:1;min-height:0;position:relative;cursor:grab}\n    .stage:active{cursor:grabbing}\n    svg{width:100%;height:100%;display:block}\n    .muni{stroke:#0b0d12;stroke-width:1.1;transition:filter .1s}\n    .muni.ctx{cursor:default}\n    .muni:not(.ctx){cursor:pointer}\n    .muni:not(.ctx):hover{filter:brightness(1.25)}\n    .bg{fill:#ffffff;stroke:#1a1a1a;stroke-width:.5;opacity:.5}\n    .mlabel{fill:#1a1a1a;font-size:9px;font-weight:700;pointer-events:none;user-select:none;text-anchor:middle;paint-order:stroke;stroke:rgba(255,255,255,.6);stroke-width:0.5px}\n    .legend{display:flex;gap:10px;flex-wrap:wrap;padding:8px 10px;border-top:1px solid #e7e7ef;background:#fff}\n    .legend span{display:inline-flex;align-items:center;gap:5px}\n    .legend b{width:10px;height:10px;border-radius:3px;display:inline-block}\n    .load{position:absolute;inset:0;background:rgba(244,244,248,.92);display:flex;align-items:center;justify-content:center;z-index:5;color:#71717f;text-align:center;padding:16px;cursor:default}\n    .load.err{cursor:pointer;text-decoration:underline;color:#c8104e}\n    .load.hidden{display:none}\n    .tip{position:fixed;pointer-events:none;background:#000;color:#fff;border-radius:7px;padding:6px 8px;font-size:11px;opacity:0;transition:opacity .1s;z-index:9;max-width:220px}\n  `,\n  render(){\n    return `<div class=\"wrap\">\n      <div class=\"stage\"><svg id=\"map\" viewBox=\"0 0 700 500\" preserveAspectRatio=\"xMidYMid meet\"></svg></div>\n      <div class=\"legend\" id=\"legend\"></div>\n      <div class=\"tip\" id=\"tip\"></div>\n      <div class=\"load\" id=\"loader\">carregando malhas…</div>\n    </div>`;\n  },\n  mounted(d2, root){\n\n    const $ = s => root.querySelector(s);\n    const NS = 'http://www.w3.org/2000/svg';\n    const norm = s => (s||'').normalize('NFKD').replace(/[̀-ͯ]/g,'').toUpperCase().replace(/[^A-Z0-9]+/g,' ').trim();\n    const esc = s => (s==null?'':String(s)).replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));\n    const splitUfs = s => (s||'').split(',').map(x=>x.trim().toUpperCase()).filter(Boolean);\n\n    async function geoUF(uf){\n      const code = UF_CODE[uf] || uf;\n      const r = await fetch('https://raw.githubusercontent.com/tbrugz/geodata-br/master/geojson/geojs-'+code+'-mun.json');\n      if (!r.ok) throw new Error('geo ' + uf);\n      return r.json();\n    }\n    function buildAdj(features){\n      const pts = f => {\n        const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates], s=new Set();\n        ps.forEach(poly=>poly.forEach(ring=>ring.forEach(p=>s.add(Math.round(p[0]*1000)/1000+','+Math.round(p[1]*1000)/1000))));\n        return s;\n      };\n      const feat = {}, idx = {};\n      features.forEach(f => { const nm = norm(f.properties.name || f.properties.NOME || f.properties.nome); feat[nm] = { f, pts: pts(f) }; });\n      for (const nm in feat) feat[nm].pts.forEach(p => (idx[p] = idx[p] || []).push(nm));\n      const nbrs = nm => {\n        const c = {};\n        feat[nm].pts.forEach(p => idx[p].forEach(o => { if (o !== nm) c[o] = (c[o] || 0) + 1; }));\n        return Object.entries(c).filter(([, n]) => n >= 2).map(([o]) => o);\n      };\n      return { feat, nbrs };\n    }\n    function project(feats){\n      let mnx=1/0,mny=1/0,mxx=-1/0,mxy=-1/0;\n      const each = cb => feats.forEach(f=>{const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];ps.forEach(p=>p.forEach(r=>r.forEach(cb)));});\n      each(p=>{mnx=Math.min(mnx,p[0]);mxx=Math.max(mxx,p[0]);mny=Math.min(mny,p[1]);mxy=Math.max(mxy,p[1]);});\n      const mlat=(mny+mxy)/2, kx=Math.cos(mlat*Math.PI/180), W=700,H=500,pad=22;\n      const gw=(mxx-mnx)*kx, gh=(mxy-mny), sc=Math.min((W-2*pad)/gw,(H-2*pad)/gh);\n      const ox=(W-gw*sc)/2, oy=(H-gh*sc)/2;\n      const pr = p=>[ox+(p[0]-mnx)*kx*sc, oy+(mxy-p[1])*sc];\n      pr.scale = sc;\n      return pr;\n    }\n    function pathFor(f,pr){\n      const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n      return ps.map(poly=>poly.map(r=>'M'+r.map(p=>{const q=pr(p);return q[0].toFixed(1)+','+q[1].toFixed(1);}).join('L')+'Z').join('')).join('');\n    }\n    function cxy(f,pr){\n      let x=0,y=0,n=0; const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n      ps.forEach(poly=>poly[0].forEach(p=>{const q=pr(p);x+=q[0];y+=q[1];n++;}));\n      return [x/n, y/n];\n    }\n    const loader = (on, t, isErr) => {\n      const l = $('#loader');\n      l.classList.toggle('hidden', !on);\n      l.classList.toggle('err', !!isErr);\n      if (t != null) l.textContent = t;\n    };\n\n    let S = { assign: { ...(d2.param('assignments', {}) || {}) }, targets: new Set(), clickable: new Set(), closeFeats: {}, nbrFeats: [], labels: {}, pr: null, view: { scale: 1, tx: 0, ty: 0 } };\n    let dragStart = null, dragMoved = false;\n\n    // Live sector for a município: the Store (if bound) is the source of truth —\n    // reads are tracked, so an external change to the Store re-renders this map.\n    // `S.assign` is only the bootstrap default (from `assignments`) before the\n    // Store has an entry, or the fallback when no `store` param is bound at all.\n    function getAssign(key){\n      const storeRef = d2.param('store');\n      return storeRef ? d2.store(storeRef).get(key, S.assign[key]) : S.assign[key];\n    }\n\n    function svgPoint(evt){\n      const svg = $('#map');\n      const pt = svg.createSVGPoint();\n      pt.x = evt.clientX; pt.y = evt.clientY;\n      const ctm = svg.getScreenCTM();\n      if (!ctm) return { x: 0, y: 0 };\n      const p = pt.matrixTransform(ctm.inverse());\n      return { x: p.x, y: p.y };\n    }\n    function applyTransform(){\n      const g = $('#viewport');\n      if (g) g.setAttribute('transform', `translate(${S.view.tx} ${S.view.ty}) scale(${S.view.scale})`);\n    }\n    d2.on('.stage', 'wheel', e => {\n      e.preventDefault();\n      const p = svgPoint(e);\n      const factor = e.deltaY < 0 ? 1.15 : 1 / 1.15;\n      const next = Math.min(8, Math.max(0.15, S.view.scale * factor));\n      const k = next / S.view.scale;\n      S.view.tx = p.x - k * (p.x - S.view.tx);\n      S.view.ty = p.y - k * (p.y - S.view.ty);\n      S.view.scale = next;\n      applyTransform();\n    });\n    d2.on('.stage', 'mousedown', e => {\n      if (e.button !== 0) return;\n      const p = svgPoint(e);\n      dragStart = { x: p.x, y: p.y, tx: S.view.tx, ty: S.view.ty };\n      dragMoved = false;\n    });\n    d2.on(window, 'mousemove', e => {\n      if (!dragStart) return;\n      const p = svgPoint(e);\n      const dx = p.x - dragStart.x, dy = p.y - dragStart.y;\n      if (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5) dragMoved = true;\n      S.view.tx = dragStart.tx + dx;\n      S.view.ty = dragStart.ty + dy;\n      applyTransform();\n    });\n    d2.on(window, 'mouseup', () => { dragStart = null; });\n\n    // This component paints imperatively (mounted-driven SVG, not the reactive\n    // render() string), so a Store read here isn't auto-tracked the way it would\n    // be inside render(). Poll the bound Store's sector values every frame and\n    // redraw when any of them changed — this is what makes an EXTERNAL write to\n    // the Store (not just this map's own clicks) show up on the map.\n    let lastAssignSnapshot = {};\n    d2.raf(() => {\n      const storeRef = d2.param('store');\n      if (!storeRef) return;\n      let changed = false;\n      Object.keys(S.closeFeats).forEach(key => {\n        const v = getAssign(key);\n        if (lastAssignSnapshot[key] !== v) { changed = true; lastAssignSnapshot[key] = v; }\n      });\n      if (changed) drawMap();\n    });\n\n    function drawMap(){\n      const svg = $('#map'); svg.innerHTML = '';\n      const viewport = document.createElementNS(NS, 'g');\n      viewport.setAttribute('id', 'viewport');\n      svg.appendChild(viewport);\n      applyTransform();\n      const groups = d2.param('groups', []);\n      const colors = d2.param('colors', {});\n      const colorFor = g => colors[g] || PALETTE[groups.indexOf(g) % PALETTE.length] || '#94a3b8';\n      // Base label size tracks the projection's own fit scale, so labels stay proportionate\n      // to the shapes whether the auto-fit is tight (one neighborhood) or wide (distant targets).\n      const labelFontSize = Math.max(4, Math.min(14, 9 * ((S.pr.scale || 1000) / 1000)));\n\n      S.nbrFeats.forEach(f => {\n        const p = document.createElementNS(NS, 'path');\n        p.setAttribute('d', pathFor(f, S.pr));\n        p.setAttribute('class', 'bg');\n        p.setAttribute('data-key', 'bg-' + (f.properties.name || f.properties.NOME || f.properties.nome || ''));\n        viewport.appendChild(p);\n      });\n\n      Object.entries(S.closeFeats).forEach(([key, f]) => {\n        const clickable = S.clickable.has(key);\n        const p = document.createElementNS(NS, 'path');\n        p.setAttribute('d', pathFor(f, S.pr));\n        p.setAttribute('data-key', key);\n        p.setAttribute('class', ['muni', clickable ? '' : 'ctx'].filter(Boolean).join(' '));\n        const currentSector = getAssign(key);\n        p.setAttribute('fill', currentSector ? colorFor(currentSector) : (clickable ? '#94a3b8' : '#dcdfe8'));\n        if (clickable) {\n          p.onclick = () => {\n            if (dragMoved) { dragMoved = false; return; }\n            const gs = d2.param('groups', []);\n            if (!gs.length) return;\n            const next = gs[(gs.indexOf(getAssign(key)) + 1) % gs.length];\n            S.assign[key] = next;\n            const storeRef = d2.param('store');\n            if (storeRef) d2.store(storeRef).set(key, next);\n            drawMap();\n            d2.emit('onChange', { municipio: key, setor: next });\n          };\n        }\n        p.onmousemove = e => {\n          const tip = $('#tip');\n          tip.style.opacity = 1; tip.style.left = (e.clientX + 12) + 'px'; tip.style.top = (e.clientY + 12) + 'px';\n          tip.innerHTML = `<b>${esc(S.labels[key] || key)}</b><br>${esc(getAssign(key) || (clickable ? 'sem destino' : 'apenas contexto'))}`;\n        };\n        p.onmouseleave = () => { $('#tip').style.opacity = 0; };\n        viewport.appendChild(p);\n\n        const [cx, cy] = cxy(f, S.pr);\n        const t = document.createElementNS(NS, 'text');\n        t.setAttribute('class', 'mlabel'); t.setAttribute('x', cx); t.setAttribute('y', cy);\n        t.style.fontSize = labelFontSize + 'px';\n        t.textContent = (S.labels[key] || key).slice(0, 13);\n        viewport.appendChild(t);\n      });\n\n      $('#legend').innerHTML = groups.map(g => `<span><b style=\"background:${colorFor(g)}\"></b>${esc(g)}</span>`).join('');\n    }\n\n    async function load(){\n      loader(true, 'carregando malhas…', false);\n      try {\n        const ufs = splitUfs(d2.param('ufs', 'PR'));\n        if (!ufs.length) throw new Error('informe ao menos uma UF em \"ufs\"');\n        const manualNbr = splitUfs(d2.param('neighborUfs', ''));\n        const autoNbr = ufs.flatMap(u => UF_BORDERS[u] || []);\n        const nbrUfs = [...new Set(manualNbr.length ? manualNbr : autoNbr)].filter(u => !ufs.includes(u));\n        const allUfs = [...new Set([...ufs, ...nbrUfs])];\n        const geos = await Promise.all(allUfs.map(u => geoUF(u).catch(() => null)));\n\n        const closeUfSet = new Set(ufs);\n        const allFeats = [];\n        const stateNames = [];\n        allUfs.forEach((u, i) => {\n          const g = geos[i]; if (!g) return;\n          const inClose = closeUfSet.has(u);\n          g.features.forEach(f => {\n            allFeats.push(f);\n            if (inClose) stateNames.push(norm(f.properties.name || f.properties.NOME || f.properties.nome || ''));\n          });\n        });\n        if (!allFeats.length) throw new Error('nenhuma malha carregada para essas UFs');\n\n        const adj = buildAdj(allFeats);\n\n        const targetsRaw = (d2.param('municipio', '') || '').split(',').map(x => x.trim()).filter(Boolean);\n        const targetNames = targetsRaw.map(norm);\n        const foundTargets = targetNames.filter(t => adj.feat[t]);\n        if (targetNames.length && !foundTargets.length) throw new Error('município(s) não encontrado(s) na(s) UF(s) informada(s): ' + targetsRaw.join(', '));\n        S.targets = new Set(foundTargets);\n\n        // Interactive scope (\"close\"): each target município (comma-separated in `municipio`)\n        // + its direct neighbors — NOT the whole state. Falls back to the whole state when no\n        // `municipio` is set.\n        const neighborhoodNames = foundTargets.length\n          ? [...new Set(foundTargets.flatMap(t => [t, ...adj.nbrs(t)]))]\n          : stateNames;\n\n        // A município already carrying a sector assignment joins the interactive set even when\n        // it's not adjacent to the target — this is what `neighborUfs` is for: letting a sector\n        // reach into a município from a bordering state, as long as that UF was loaded.\n        const assignedSet = new Set(Object.keys(S.assign).map(norm));\n        const assignedLoaded = [...assignedSet].filter(n => adj.feat[n]);\n        const closingNames = [...new Set([...neighborhoodNames, ...assignedLoaded])];\n        const closingSet = new Set(closingNames);\n\n        S.closeFeats = {}; S.labels = {};\n        closingNames.forEach(n => { if (adj.feat[n]) { S.closeFeats[n] = adj.feat[n].f; S.labels[n] = adj.feat[n].f.properties.name || n; } });\n\n        S.clickable = new Set(closingNames.filter(n => assignedSet.has(n)));\n\n        // Everything else loaded (rest of the state / neighbor states) is faint background context.\n        const restNames = new Set();\n        allFeats.forEach(f => {\n          const n = norm(f.properties.name || f.properties.NOME || f.properties.nome || '');\n          if (adj.feat[n] && !closingSet.has(n)) restNames.add(n);\n        });\n        S.nbrFeats = [...restNames].map(n => adj.feat[n].f);\n\n        const zoomFeats = Object.values(S.closeFeats);\n        S.pr = project(zoomFeats.length ? zoomFeats : allFeats);\n        S.view = { scale: 1, tx: 0, ty: 0 };\n        drawMap();\n        loader(false);\n      } catch (e) {\n        loader(true, 'erro: ' + e.message + ' (clique p/ tentar de novo)', true);\n      }\n    }\n\n    $('#loader').onclick = () => {\n      if (!$('#loader').classList.contains('err')) return;\n      S.closeFeats = {}; S.nbrFeats = []; S.labels = {};\n      load();\n    };\n\n    load();\n  },\n};"}},"assignments":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"assignments"}}}},"colors":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"colors"}}}},"groups":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"groups"}}}},"municipio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"municipio"}}}},"neighborUfs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neighborUfs"}}}},"onChange":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"onChange"}}}},"store":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"store"}}}},"ufs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ufs"}}}}}}}}}},{"_id":"6a622469425e2477b774cbed","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"matriz-asis-table","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const fmt = n => Math.round(n).toLocaleString('pt-BR');\nconst esc = s => (s == null ? '' : String(s)).replace(/[&<>]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));\nconst isNum = v => typeof v === 'number' && Number.isFinite(v);\nconst cellText = v => {\n  if (v == null) return '';\n  if (typeof v === 'object') return esc(JSON.stringify(v));\n  return isNum(v) ? fmt(v) : esc(v);\n};\n\n// Accepts '#rgb', '#rrggbb', 'rgb(...)' or 'rgba(...)' — returns \"r,g,b\" or null.\nfunction parseRgb(input) {\n  const s = (input == null ? '' : String(input)).trim();\n  let m = s.match(/^#([0-9a-f]{3})$/i);\n  if (m) return m[1].split('').map(c => parseInt(c + c, 16)).join(',');\n  m = s.match(/^#([0-9a-f]{6})$/i);\n  if (m) return [m[1].slice(0, 2), m[1].slice(2, 4), m[1].slice(4, 6)].map(h => parseInt(h, 16)).join(',');\n  m = s.match(/^rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)/i);\n  if (m) return `${m[1]},${m[2]},${m[3]}`;\n  return null;\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%;--panel:#ffffff;--line:#e7e7ef;--ink:#1c1c2b;--mut:#71717f}\n    *{box-sizing:border-box}\n    .wrap{width:100%;height:100%;display:flex;flex-direction:column;background:var(--panel);font:12px -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;color:var(--ink)}\n    .scroll{flex:1;min-height:0;overflow:auto}\n    .empty{padding:16px;color:var(--mut)}\n    table{border-collapse:collapse;width:100%;font-size:11.5px}\n    th,td{padding:5px 8px;text-align:left;border-bottom:1px solid var(--line);white-space:nowrap}\n    th.num,td.num{text-align:right;font-variant-numeric:tabular-nums}\n    th.bold,td.bold{font-weight:700}\n    thead th{color:var(--mut);font-weight:600;font-size:10.5px;position:sticky;top:0;background:var(--panel)}\n  `,\n  render(d2) {\n    const rows = d2.param('rows', []) || [];\n    const heatmapCols = new Set(d2.param('heatmap', []) || []);\n    const heatmapRgb = parseRgb(d2.param('heatmapColor')) || '229,181,63';\n    const firstCol = d2.param('first');\n    const lastCol = d2.param('last');\n\n    if (!rows.length) {\n      return `<div class=\"wrap\"><div class=\"empty\">Sem dados — passe uma lista de objetos em <code>rows</code>; cada propriedade vira uma coluna.</div></div>`;\n    }\n\n    // Column order = key order as properties first appear across the rows.\n    const cols = [];\n    const seen = new Set();\n    rows.forEach(r => {\n      Object.keys(r || {}).forEach(k => {\n        if (!seen.has(k)) { seen.add(k); cols.push(k); }\n      });\n    });\n\n    // `first`/`last` pin a column to either end of the table.\n    if (firstCol && cols.includes(firstCol)) {\n      cols.splice(cols.indexOf(firstCol), 1);\n      cols.unshift(firstCol);\n    }\n    if (lastCol && cols.includes(lastCol)) {\n      cols.splice(cols.indexOf(lastCol), 1);\n      cols.push(lastCol);\n    }\n\n    // A column is numeric only if every value present in it is a number.\n    const numericCol = {};\n    cols.forEach(c => {\n      numericCol[c] = rows.every(r => r[c] == null || isNum(r[c]));\n    });\n\n    // Heatmap intensity is scaled GLOBALLY: one shared max across every value in\n    // every column named in `heatmap`, so intensity is comparable column-to-column\n    // (a 200 in one heatmap column looks exactly as intense as a 200 in another).\n    let heatMax = 1;\n    if (heatmapCols.size) {\n      rows.forEach(r => {\n        heatmapCols.forEach(c => {\n          if (isNum(r[c]) && r[c] > heatMax) heatMax = r[c];\n        });\n      });\n    }\n\n    const head = `<tr>${cols.map(c => `<th class=\"${numericCol[c] ? 'num' : ''}${c === lastCol ? ' bold' : ''}\">${esc(c)}</th>`).join('')}</tr>`;\n\n    const body = rows\n      .map((r, i) => {\n        const key = r[cols[0]] != null ? esc(r[cols[0]]) : i;\n        const cells = cols\n          .map(c => {\n            const v = r[c];\n            let bg = '';\n            if (heatmapCols.has(c) && isNum(v) && v > 0) {\n              const ratio = v / heatMax;\n              bg = ` style=\"background:rgba(${heatmapRgb},${(0.08 + ratio * 0.5).toFixed(2)})\"`;\n            }\n            return `<td class=\"${numericCol[c] ? 'num' : ''}${c === lastCol ? ' bold' : ''}\"${bg}>${cellText(v)}</td>`;\n          })\n          .join('');\n        return `<tr data-key=\"${key}\">${cells}</tr>`;\n      })\n      .join('');\n\n    return `<div class=\"wrap\">\n      <div class=\"scroll\">\n        <table>\n          <thead>${head}</thead>\n          <tbody>${body}</tbody>\n        </table>\n      </div>\n    </div>`;\n  },\n};\n"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"heatmap":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"heatmap"}}}},"heatmapColor":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"heatmapColor"}}}},"first":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"first"}}}},"last":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"last"}}}}}}}}}},{"_id":"6a623156e0a2e35d92bba09a","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"before-after-chart","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const PALETTE = ['#8b7fd6', '#d1b048', '#67bd5b', '#5aa9d6', '#d67fa8', '#c98a4b'];\nconst fmt = n => Math.round(n).toLocaleString('pt-BR');\nconst esc = s => (s == null ? '' : String(s)).replace(/[&<>]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));\n\nfunction validateRow(r, i) {\n  const where = `linha ${i + 1}`;\n  if (r == null || typeof r !== 'object' || Array.isArray(r)) {\n    return { ok: false, error: `${where}: esperado um objeto { label, before, after, color? }, recebido ${JSON.stringify(r)}` };\n  }\n  if (r.label == null || (typeof r.label !== 'string' && typeof r.label !== 'number')) {\n    return { ok: false, error: `${where}: \"label\" ausente ou inválido (esperado string/number)` };\n  }\n  const before = r.before == null ? 0 : Number(r.before);\n  if (!Number.isFinite(before)) {\n    return { ok: false, error: `${where} (\"${r.label}\"): \"before\" não é um número válido (${JSON.stringify(r.before)})` };\n  }\n  const after = r.after == null ? 0 : Number(r.after);\n  if (!Number.isFinite(after)) {\n    return { ok: false, error: `${where} (\"${r.label}\"): \"after\" não é um número válido (${JSON.stringify(r.after)})` };\n  }\n  if (r.color != null && typeof r.color !== 'string') {\n    return { ok: false, error: `${where} (\"${r.label}\"): \"color\" deveria ser uma string (ex: \"#8b7fd6\")` };\n  }\n  return { ok: true, row: { label: String(r.label), before, after, color: r.color || null } };\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%}\n    *{box-sizing:border-box}\n    .wrap{width:100%;height:100%;display:flex;flex-direction:column;background:#ffffff;font:12px -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;padding:8px}\n    .empty{padding:16px;color:#71717f}\n    .empty.error{color:#c8104e}\n    .errbar{flex:none;background:#fdecef;color:#c8104e;border:1px solid #f0c2cb;border-radius:6px;padding:6px 9px;font-size:11px;margin-bottom:6px;line-height:1.5}\n    svg{width:100%;flex:1;min-height:0;display:block}\n  `,\n  render(d2) {\n    const rowsParam = d2.param('rows', []);\n\n    if (!Array.isArray(rowsParam)) {\n      return `<div class=\"wrap\"><div class=\"empty error\">\"rows\" deveria ser uma lista de objetos { label, before, after, color? } — recebido tipo \"${esc(typeof rowsParam)}\".</div></div>`;\n    }\n    if (!rowsParam.length) {\n      return `<div class=\"wrap\"><div class=\"empty\">Sem dados — passe uma lista de objetos em <code>rows</code> (label, before, after, color?).</div></div>`;\n    }\n\n    const data = [];\n    const errors = [];\n    rowsParam.forEach((r, i) => {\n      const v = validateRow(r, i);\n      if (v.ok) data.push({ ...v.row, color: v.row.color || PALETTE[data.length % PALETTE.length] });\n      else errors.push(v.error);\n    });\n\n    if (!data.length) {\n      return `<div class=\"wrap\"><div class=\"empty error\">Nenhuma linha válida em \"rows\":<br>${errors.map(esc).join('<br>')}</div></div>`;\n    }\n\n    const max = Math.max(...data.flatMap(d => [d.before, d.after]), 1);\n    const W = 720, H = 290, base = 238, top = 42, maxH = base - top;\n    const n = data.length, gw = (W - 16) / n;\n\n    let g = '';\n    data.forEach((d, i) => {\n      const cx = 8 + gw * i + gw / 2;\n      const bw = Math.min(30, gw * 0.3);\n      const ah = (d.before / max) * maxH;\n      const dh = (d.after / max) * maxH;\n      const ax = cx - bw - 2;\n      const dx = cx + 2;\n      const dp = d.before ? ((d.after - d.before) / d.before) * 100 : (d.after ? 100 : 0);\n\n      g += `<rect x=\"${ax.toFixed(1)}\" y=\"${(base - ah).toFixed(1)}\" width=\"${bw.toFixed(1)}\" height=\"${ah.toFixed(1)}\" rx=\"2.5\" fill=\"${d.color}\" opacity=\"0.35\"/>`;\n      g += `<rect x=\"${dx.toFixed(1)}\" y=\"${(base - dh).toFixed(1)}\" width=\"${bw.toFixed(1)}\" height=\"${dh.toFixed(1)}\" rx=\"2.5\" fill=\"${d.color}\"/>`;\n      g += `<text x=\"${(ax + bw / 2).toFixed(1)}\" y=\"${(base - ah - 4).toFixed(1)}\" text-anchor=\"middle\" font-size=\"9\" fill=\"#9a9aa8\">${fmt(d.before)}</text>`;\n      g += `<text x=\"${(dx + bw / 2).toFixed(1)}\" y=\"${(base - dh - 4).toFixed(1)}\" text-anchor=\"middle\" font-size=\"9.5\" font-weight=\"700\" fill=\"${d.color}\">${fmt(d.after)}</text>`;\n      g += `<text x=\"${cx.toFixed(1)}\" y=\"16\" text-anchor=\"middle\" font-size=\"10.5\" font-weight=\"700\" fill=\"${dp >= 0 ? '#1f9d57' : '#c8104e'}\">${dp > 0 ? '+' : ''}${dp.toFixed(0)}%</text>`;\n      g += `<text x=\"${cx.toFixed(1)}\" y=\"${base + 16}\" text-anchor=\"middle\" font-size=\"10.5\" font-weight=\"700\" fill=\"#1c1c2b\">${esc(d.label)}</text>`;\n    });\n\n    const errorBanner = errors.length\n      ? `<div class=\"errbar\">${errors.length} linha(s) de \"rows\" ignorada(s): ${errors.map(esc).join(' · ')}</div>`\n      : '';\n\n    return `<div class=\"wrap\">${errorBanner}<svg viewBox=\"0 0 ${W} ${H}\" preserveAspectRatio=\"xMidYMid meet\"><line x1=\"8\" y1=\"${base}\" x2=\"${W - 8}\" y2=\"${base}\" stroke=\"#e7e7ef\"/>${g}</svg></div>`;\n  },\n};\n"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}}}}}}}},{"_id":"6a63f0db4eab3e4ed9fd4da7","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"leaders-table","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const PALETTE = ['#8b7fd6', '#d1b048', '#67bd5b', '#5aa9d6', '#d67fa8', '#c98a4b'];\nconst fmt = n => Math.round(n).toLocaleString('pt-BR');\nconst esc = s => (s == null ? '' : String(s)).replace(/[&<>]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));\n\nfunction validateRow(r, i) {\n  const where = `linha ${i + 1}`;\n  if (r == null || typeof r !== 'object' || Array.isArray(r)) {\n    return { ok: false, error: `${where}: esperado um objeto { label, total, home, bySetor, desloc? }, recebido ${JSON.stringify(r)}` };\n  }\n  if (r.label == null || (typeof r.label !== 'string' && typeof r.label !== 'number')) {\n    return { ok: false, error: `${where}: \"label\" ausente ou inválido (esperado string/number)` };\n  }\n  const total = r.total == null ? 0 : Number(r.total);\n  if (!Number.isFinite(total)) {\n    return { ok: false, error: `${where} (\"${r.label}\"): \"total\" não é um número válido (${JSON.stringify(r.total)})` };\n  }\n  if (r.bySetor != null && (typeof r.bySetor !== 'object' || Array.isArray(r.bySetor))) {\n    return { ok: false, error: `${where} (\"${r.label}\"): \"bySetor\" deveria ser um objeto { setor: cbs }` };\n  }\n  const desloc = r.desloc == null ? 0 : Number(r.desloc);\n  if (!Number.isFinite(desloc)) {\n    return { ok: false, error: `${where} (\"${r.label}\"): \"desloc\" não é um número válido (${JSON.stringify(r.desloc)})` };\n  }\n  return { ok: true, row: { label: String(r.label), total, home: r.home != null ? String(r.home) : null, bySetor: r.bySetor || {}, desloc } };\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%;--panel:#ffffff;--line:#e7e7ef;--ink:#1c1c2b;--mut:#71717f;--closing:#c8104e}\n    *{box-sizing:border-box}\n    .wrap{width:100%;height:100%;display:flex;flex-direction:column;background:var(--panel);font:12px -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;color:var(--ink)}\n    .scroll{flex:1;min-height:0;overflow:auto}\n    .empty{padding:16px;color:var(--mut)}\n    .empty.error{color:var(--closing)}\n    .errbar{flex:none;background:#fdecef;color:var(--closing);border:1px solid #f0c2cb;border-radius:6px;padding:6px 9px;font-size:11px;margin:8px;line-height:1.5}\n    table{border-collapse:collapse;width:100%;font-size:11.5px}\n    th,td{padding:5px 8px;text-align:right;border-bottom:1px solid var(--line);white-space:nowrap;font-variant-numeric:tabular-nums}\n    th:first-child,td:first-child{text-align:left}\n    thead th{color:var(--mut);font-weight:600;font-size:10.5px;position:sticky;top:0;background:var(--panel)}\n    .home{display:inline-block;width:9px;height:9px;border-radius:2px;margin-right:4px}\n    tr.total td{font-weight:700}\n  `,\n  render(d2) {\n    const rowsParam = d2.param('rows', []);\n    const sectors = d2.param('sectors', []) || [];\n    const colors = d2.param('colors', {}) || {};\n    const showTotal = d2.param('showTotal', true);\n    const truncateWords = d2.param('truncateNameWords', 2);\n    const colorFor = s => colors[s] || PALETTE[sectors.indexOf(s) % PALETTE.length] || '#94a3b8';\n    const nameOf = label => (truncateWords > 0 ? label.split(' ').slice(0, truncateWords).join(' ') : label);\n\n    if (!Array.isArray(rowsParam)) {\n      return `<div class=\"wrap\"><div class=\"empty error\">\"rows\" deveria ser uma lista de objetos { label, total, home, bySetor, desloc? } — recebido tipo \"${esc(typeof rowsParam)}\".</div></div>`;\n    }\n    if (!rowsParam.length) {\n      return `<div class=\"wrap\"><div class=\"empty\">Sem dados — passe uma lista de líderes em <code>rows</code> e a ordem dos setores em <code>sectors</code>.</div></div>`;\n    }\n\n    const data = [];\n    const errors = [];\n    rowsParam.forEach((r, i) => {\n      const v = validateRow(r, i);\n      if (v.ok) data.push(v.row);\n      else errors.push(v.error);\n    });\n\n    if (!data.length) {\n      return `<div class=\"wrap\"><div class=\"empty error\">Nenhuma linha válida em \"rows\":<br>${errors.map(esc).join('<br>')}</div></div>`;\n    }\n\n    const head = `<tr><th>Líder</th><th>CBs</th><th>Home</th>${sectors.map(s => `<th>${esc(s)}</th>`).join('')}<th>Desloc.</th></tr>`;\n\n    const body = data.map((r, i) => {\n      const homeColor = r.home ? colorFor(r.home) : '#94a3b8';\n      const sectorCells = sectors.map(s => {\n        const v = r.bySetor[s];\n        const isHome = s === r.home;\n        const style = isHome ? ` style=\"color:${colorFor(s)};font-weight:700\"` : '';\n        return `<td${style}>${v != null ? fmt(v) : '·'}</td>`;\n      }).join('');\n      const deslocStyle = r.desloc ? ` style=\"color:var(--closing);font-weight:700\"` : ` style=\"color:var(--mut)\"`;\n      return `<tr data-key=\"${esc(r.label)}-${i}\">\n        <td>${esc(nameOf(r.label))}</td>\n        <td>${fmt(r.total)}</td>\n        <td>${r.home ? `<span class=\"home\" style=\"background:${homeColor}\"></span>${esc(r.home)}` : '–'}</td>\n        ${sectorCells}\n        <td${deslocStyle}>${fmt(r.desloc)}</td>\n      </tr>`;\n    }).join('');\n\n    let footer = '';\n    if (showTotal) {\n      const totalCbs = data.reduce((a, r) => a + r.total, 0);\n      const totalDesloc = data.reduce((a, r) => a + r.desloc, 0);\n      const sectorTotals = sectors.map(s => data.reduce((a, r) => a + (r.bySetor[s] || 0), 0));\n      footer = `<tr class=\"total\"><td>TOTAL</td><td>${fmt(totalCbs)}</td><td></td>${sectorTotals.map(v => `<td>${fmt(v)}</td>`).join('')}<td style=\"color:var(--closing)\">${fmt(totalDesloc)}</td></tr>`;\n    }\n\n    const errorBanner = errors.length\n      ? `<div class=\"errbar\">${errors.length} linha(s) de \"rows\" ignorada(s): ${errors.map(esc).join(' · ')}</div>`\n      : '';\n\n    return `<div class=\"wrap\">${errorBanner}<div class=\"scroll\"><table><thead>${head}</thead><tbody>${body}${footer}</tbody></table></div></div>`;\n  },\n};\n"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"sectors":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sectors"}}}},"colors":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"colors"}}}},"showTotal":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"showTotal"}}}},"truncateNameWords":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"truncateNameWords"}}}}}}}}}},{"_id":"6a696da4dc1fded10b04d48f","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"NoQh9Rv3EeK6K8WBWkpPO","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const UF_CODE={RO:'11',AC:'12',AM:'13',RR:'14',PA:'15',AP:'16',TO:'17',MA:'21',PI:'22',CE:'23',RN:'24',PB:'25',PE:'26',AL:'27',SE:'28',BA:'29',MG:'31',ES:'32',RJ:'33',SP:'35',PR:'41',SC:'42',RS:'43',MS:'50',MT:'51',GO:'52',DF:'53'};\nconst UF_BORDERS={AC:['AM','RO'],AL:['BA','PE','SE'],AM:['AC','RO','RR','PA','MT'],AP:['PA'],\n BA:['AL','SE','PE','PI','TO','GO','MG','ES'],CE:['PI','PB','PE','RN'],DF:['GO','MG'],\n ES:['BA','MG','RJ'],GO:['DF','MT','MS','MG','BA','TO'],MA:['PA','TO','PI'],\n MG:['BA','GO','DF','MS','SP','RJ','ES'],MS:['MT','GO','MG','SP','PR'],MT:['RO','AM','PA','TO','GO','MS'],\n PA:['AP','AM','MT','TO','MA','RR'],PB:['RN','CE','PE'],PE:['PB','CE','PI','BA','AL'],\n PI:['MA','TO','BA','PE','CE'],PR:['SP','MS','SC'],RJ:['ES','MG','SP'],RN:['CE','PB'],\n RO:['AC','AM','MT'],RR:['AM','PA'],RS:['SC'],SC:['PR','RS'],SE:['BA','AL'],\n SP:['MG','RJ','PR','MS'],TO:['PA','MA','PI','BA','GO','MT']};\nconst PALETTE=['#8b7fd6','#d1b048','#67bd5b','#5aa9d6','#d67fa8','#c98a4b'];\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%;box-sizing:border-box}\n    .wrap{position:relative;height:100%;display:flex;flex-direction:column;font:12px -apple-system,BlinkMacSystemFont,\"Segoe UI\",Inter,sans-serif;background:#f4f4f8}\n    .stage{flex:1;min-height:0;position:relative;cursor:grab}\n    .stage:active{cursor:grabbing}\n    svg{width:100%;height:100%;display:block}\n    .muni{stroke:#0b0d12;stroke-width:1.1;transition:filter .1s}\n    .muni.ctx{cursor:default}\n    .muni:not(.ctx){cursor:pointer}\n    .muni:not(.ctx):hover{filter:brightness(1.25)}\n    .wedge{cursor:pointer;stroke:#fff;stroke-width:0.6;transition:filter .1s}\n    .wedge:hover{filter:brightness(1.25)}\n    .bg{fill:#ffffff;stroke:#1a1a1a;stroke-width:.5;opacity:.5}\n    .mlabel{fill:#1a1a1a;font-size:9px;font-weight:700;pointer-events:none;user-select:none;text-anchor:middle;paint-order:stroke;stroke:rgba(255,255,255,.6);stroke-width:0.5px}\n    .legend{display:flex;gap:10px;flex-wrap:wrap;padding:8px 10px;border-top:1px solid #e7e7ef;background:#fff}\n    .legend span{display:inline-flex;align-items:center;gap:5px}\n    .legend b{width:10px;height:10px;border-radius:3px;display:inline-block}\n    .load{position:absolute;inset:0;background:rgba(244,244,248,.92);display:flex;align-items:center;justify-content:center;z-index:5;color:#71717f;text-align:center;padding:16px;cursor:default}\n    .load.err{cursor:pointer;text-decoration:underline;color:#c8104e}\n    .load.hidden{display:none}\n    .tip{position:fixed;pointer-events:none;background:#000;color:#fff;border-radius:7px;padding:6px 8px;font-size:11px;opacity:0;transition:opacity .1s;z-index:9;max-width:220px}\n  `,\n  render(){\n    return `<div class=\"wrap\">\n      <div class=\"stage\"><svg id=\"map\" viewBox=\"0 0 700 500\" preserveAspectRatio=\"xMidYMid meet\"></svg></div>\n      <div class=\"legend\" id=\"legend\"></div>\n      <div class=\"tip\" id=\"tip\"></div>\n      <div class=\"load\" id=\"loader\">carregando malhas…</div>\n    </div>`;\n  },\n  mounted(d2, root){\n\n    const $ = s => root.querySelector(s);\n    const NS = 'http://www.w3.org/2000/svg';\n    const norm = s => (s||'').normalize('NFKD').replace(/[̀-ͯ]/g,'').toUpperCase().replace(/[^A-Z0-9]+/g,' ').trim();\n    const esc = s => (s==null?'':String(s)).replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));\n    const splitUfs = s => (s||'').split(',').map(x=>x.trim().toUpperCase()).filter(Boolean);\n\n    async function geoUF(uf){\n      const code = UF_CODE[uf] || uf;\n      const r = await fetch('https://raw.githubusercontent.com/tbrugz/geodata-br/master/geojson/geojs-'+code+'-mun.json');\n      if (!r.ok) throw new Error('geo ' + uf);\n      return r.json();\n    }\n    function buildAdj(features){\n      const pts = f => {\n        const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates], s=new Set();\n        ps.forEach(poly=>poly.forEach(ring=>ring.forEach(p=>s.add(Math.round(p[0]*1000)/1000+','+Math.round(p[1]*1000)/1000))));\n        return s;\n      };\n      const feat = {}, idx = {};\n      features.forEach(f => { const nm = norm(f.properties.name || f.properties.NOME || f.properties.nome); feat[nm] = { f, pts: pts(f) }; });\n      for (const nm in feat) feat[nm].pts.forEach(p => (idx[p] = idx[p] || []).push(nm));\n      const nbrs = nm => {\n        const c = {};\n        feat[nm].pts.forEach(p => idx[p].forEach(o => { if (o !== nm) c[o] = (c[o] || 0) + 1; }));\n        return Object.entries(c).filter(([, n]) => n >= 2).map(([o]) => o);\n      };\n      return { feat, nbrs };\n    }\n    function project(feats){\n      let mnx=1/0,mny=1/0,mxx=-1/0,mxy=-1/0;\n      const each = cb => feats.forEach(f=>{const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];ps.forEach(p=>p.forEach(r=>r.forEach(cb)));});\n      each(p=>{mnx=Math.min(mnx,p[0]);mxx=Math.max(mxx,p[0]);mny=Math.min(mny,p[1]);mxy=Math.max(mxy,p[1]);});\n      const mlat=(mny+mxy)/2, kx=Math.cos(mlat*Math.PI/180), W=700,H=500,pad=22;\n      const gw=(mxx-mnx)*kx, gh=(mxy-mny), sc=Math.min((W-2*pad)/gw,(H-2*pad)/gh);\n      const ox=(W-gw*sc)/2, oy=(H-gh*sc)/2;\n      const pr = p=>[ox+(p[0]-mnx)*kx*sc, oy+(mxy-p[1])*sc];\n      pr.scale = sc;\n      return pr;\n    }\n    function pathFor(f,pr){\n      const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n      return ps.map(poly=>poly.map(r=>'M'+r.map(p=>{const q=pr(p);return q[0].toFixed(1)+','+q[1].toFixed(1);}).join('L')+'Z').join('')).join('');\n    }\n    function cxy(f,pr){\n      let x=0,y=0,n=0; const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n      ps.forEach(poly=>poly[0].forEach(p=>{const q=pr(p);x+=q[0];y+=q[1];n++;}));\n      return [x/n, y/n];\n    }\n    // Clips a projected ring (screen-space [x,y] points) against a single straight half-plane\n    // (side(x,y) >= 0 is kept) via Sutherland-Hodgman. Unlike a wedge radiating from one apex —\n    // which only covers a shape the apex can \"see\" the whole border of — a straight-line cut is\n    // exact for ANY simple polygon, convex or concave, so stacking these never leaves a gap.\n    function clipHalfPlane(ring, side){\n      const out = [];\n      const n = ring.length;\n      for (let i = 0; i < n; i++){\n        const curr = ring[i], prev = ring[(i - 1 + n) % n];\n        const sCurr = side(curr[0], curr[1]);\n        const sPrev = side(prev[0], prev[1]);\n        const currIn = sCurr >= -1e-9;\n        const prevIn = sPrev >= -1e-9;\n        if (currIn) {\n          if (!prevIn) out.push(intersectHalfPlane(prev, curr, side));\n          out.push(curr);\n        } else if (prevIn) {\n          out.push(intersectHalfPlane(prev, curr, side));\n        }\n      }\n      return out;\n    }\n    function intersectHalfPlane(p1, p2, side){\n      const s1 = side(p1[0], p1[1]), s2 = side(p2[0], p2[1]);\n      const t = s1 / (s1 - s2);\n      return [p1[0] + t * (p2[0] - p1[0]), p1[1] + t * (p2[1] - p1[1])];\n    }\n    // Full projected bounding box of a município — used to lay bands across its ENTIRE geometry\n    // (all MultiPolygon parts included), so the bands always tile the whole shape, islands too.\n    function bboxOf(f, pr){\n      let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n      const g = f.geometry, ps = g.type === 'MultiPolygon' ? g.coordinates : [g.coordinates];\n      ps.forEach(poly => poly.forEach(ring => ring.forEach(p => {\n        const q = pr(p);\n        minX = Math.min(minX, q[0]); maxX = Math.max(maxX, q[0]);\n        minY = Math.min(minY, q[1]); maxY = Math.max(maxY, q[1]);\n      })));\n      return [minX, minY, maxX, maxY];\n    }\n    // A vertical band [x0,x1) of the município, clipped against its REAL border on both sides.\n    function stripeClipPath(f, pr, x0, x1){\n      const g = f.geometry, ps = g.type === 'MultiPolygon' ? g.coordinates : [g.coordinates];\n      let d = '';\n      ps.forEach(poly => poly.forEach(ring => {\n        let pts = ring.map(p => pr(p));\n        pts = clipHalfPlane(pts, (x) => x - x0);\n        if (pts.length) pts = clipHalfPlane(pts, (x) => x1 - x);\n        if (pts.length >= 3) d += 'M' + pts.map(q => q[0].toFixed(1) + ',' + q[1].toFixed(1)).join('L') + 'Z';\n      }));\n      return d;\n    }\n    const loader = (on, t, isErr) => {\n      const l = $('#loader');\n      l.classList.toggle('hidden', !on);\n      l.classList.toggle('err', !!isErr);\n      if (t != null) l.textContent = t;\n    };\n\n    let S = { assign: { ...(d2.param('assignments', {}) || {}) }, targets: new Set(), clickable: new Set(), closeFeats: {}, nbrFeats: [], labels: {}, pr: null, view: { scale: 1, tx: 0, ty: 0 }, groupAssign: {} };\n    let dragStart = null, dragMoved = false;\n\n    // Live sector for a município: the Store (if bound) is the source of truth —\n    // reads are tracked, so an external change to the Store re-renders this map.\n    // `S.assign` is only the bootstrap default (from `assignments`) before the\n    // Store has an entry, or the fallback when no `store` param is bound at all.\n    function getAssign(key){\n      const storeRef = d2.param('store');\n      if (!storeRef) return S.assign[key];\n      const prop = d2.param('prop');\n      if (prop) {\n        const obj = d2.store(storeRef).get(prop, {}) || {};\n        return key in obj ? obj[key] : S.assign[key];\n      }\n      return d2.store(storeRef).get(key, S.assign[key]);\n    }\n\n    // Writes back through the same `prop` indirection as getAssign(): when `prop` is set,\n    // values land on store[prop][key] (a nested object) instead of store[key] directly —\n    // the same shape a Setter targeting `@Store.prop` would produce.\n    function setAssign(key, value){\n      S.assign[key] = value;\n      const storeRef = d2.param('store');\n      if (!storeRef) return;\n      const prop = d2.param('prop');\n      if (prop) {\n        const obj = { ...(d2.store(storeRef).get(prop, {}) || {}) };\n        obj[key] = value;\n        d2.store(storeRef).set(prop, obj);\n      } else {\n        d2.store(storeRef).set(key, value);\n      }\n    }\n\n    // A município can hold MULTIPLE sectors at once — stored as an array instead of\n    // a single string. cycleAt() advances just one slot of that array (one pie wedge)\n    // through `groups`, padding the array if the clicked index didn't exist yet.\n    // `groups` is now an array of { sector, municipio } — sector is the value cycled\n    // through on click; municipio (comma-separated) statically pins that sector's color\n    // onto municípios that aren't part of the interactive store-backed assignment.\n    function sectorNames(){\n      return (d2.param('groups', []) || []).map(g => g.sector);\n    }\n\n    function cycleAt(key, idx, arrLen){\n      const gs = sectorNames();\n      if (!gs.length) return null;\n      let arr = getAssign(key);\n      arr = Array.isArray(arr) ? [...arr] : [];\n      while (arr.length < arrLen) arr.push('');\n      const next = gs[(gs.indexOf(arr[idx]) + 1) % gs.length];\n      arr[idx] = next;\n      setAssign(key, arr);\n      return next;\n    }\n\n    function svgPoint(evt){\n      const svg = $('#map');\n      const pt = svg.createSVGPoint();\n      pt.x = evt.clientX; pt.y = evt.clientY;\n      const ctm = svg.getScreenCTM();\n      if (!ctm) return { x: 0, y: 0 };\n      const p = pt.matrixTransform(ctm.inverse());\n      return { x: p.x, y: p.y };\n    }\n    function applyTransform(){\n      const g = $('#viewport');\n      if (g) g.setAttribute('transform', `translate(${S.view.tx} ${S.view.ty}) scale(${S.view.scale})`);\n    }\n    d2.on('.stage', 'wheel', e => {\n      e.preventDefault();\n      const p = svgPoint(e);\n      const factor = e.deltaY < 0 ? 1.15 : 1 / 1.15;\n      const next = Math.min(8, Math.max(0.15, S.view.scale * factor));\n      const k = next / S.view.scale;\n      S.view.tx = p.x - k * (p.x - S.view.tx);\n      S.view.ty = p.y - k * (p.y - S.view.ty);\n      S.view.scale = next;\n      applyTransform();\n    });\n    d2.on('.stage', 'mousedown', e => {\n      if (e.button !== 0) return;\n      const p = svgPoint(e);\n      dragStart = { x: p.x, y: p.y, tx: S.view.tx, ty: S.view.ty };\n      dragMoved = false;\n    });\n    d2.on(window, 'mousemove', e => {\n      if (!dragStart) return;\n      const p = svgPoint(e);\n      const dx = p.x - dragStart.x, dy = p.y - dragStart.y;\n      if (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5) dragMoved = true;\n      S.view.tx = dragStart.tx + dx;\n      S.view.ty = dragStart.ty + dy;\n      applyTransform();\n    });\n    d2.on(window, 'mouseup', () => { dragStart = null; });\n\n    // This component paints imperatively (mounted-driven SVG, not the reactive\n    // render() string), so a Store read here isn't auto-tracked the way it would\n    // be inside render(). Poll the bound Store's sector values every frame and\n    // redraw when any of them changed — this is what makes an EXTERNAL write to\n    // the Store (not just this map's own clicks) show up on the map.\n    let lastAssignSnapshot = {};\n    d2.raf(() => {\n      const storeRef = d2.param('store');\n      if (!storeRef) return;\n      let changed = false;\n      Object.keys(S.closeFeats).forEach(key => {\n        const v = getAssign(key);\n        // Arrays (multi-sector municípios) are compared by a joined signature, not by\n        // reference — a store that clones on read would otherwise look \"changed\" every frame.\n        const sig = Array.isArray(v) ? v.join('\u0001') : v;\n        if (lastAssignSnapshot[key] !== sig) { changed = true; lastAssignSnapshot[key] = sig; }\n      });\n      if (changed) drawMap();\n    });\n\n    function drawMap(){\n      const svg = $('#map'); svg.innerHTML = '';\n      const viewport = document.createElementNS(NS, 'g');\n      viewport.setAttribute('id', 'viewport');\n      svg.appendChild(viewport);\n      applyTransform();\n      const groupDefs = d2.param('groups', []) || [];\n      const sectors = groupDefs.map(g => g.sector);\n      const colors = d2.param('colors', {});\n      const colorFor = g => colors[g] || PALETTE[sectors.indexOf(g) % PALETTE.length] || '#94a3b8';\n      // Base label size tracks the projection's own fit scale, so labels stay proportionate\n      // to the shapes whether the auto-fit is tight (one neighborhood) or wide (distant targets).\n      const labelFontSize = Math.max(4, Math.min(14, 9 * ((S.pr.scale || 1000) / 1000)));\n\n      S.nbrFeats.forEach(f => {\n        const p = document.createElementNS(NS, 'path');\n        p.setAttribute('d', pathFor(f, S.pr));\n        p.setAttribute('class', 'bg');\n        p.setAttribute('data-key', 'bg-' + (f.properties.name || f.properties.NOME || f.properties.nome || ''));\n        viewport.appendChild(p);\n      });\n\n      Object.entries(S.closeFeats).forEach(([key, f]) => {\n        const clickable = S.clickable.has(key);\n        const groupSectors = !clickable ? S.groupAssign[key] : null;\n        const groupSector = groupSectors ? groupSectors[0] : null;\n        const currentVal = getAssign(key);\n        const arrVal = Array.isArray(currentVal) ? currentVal : null;\n        const isPie = !!(arrVal && arrVal.length >= 2 && clickable);\n        // Static group pins can also stack more than one sector onto the same município\n        // (e.g. two `groups[]` entries both listing it) — split those into a pie too.\n        const isGroupPie = !!(groupSectors && groupSectors.length >= 2);\n        const soloSector = arrVal ? arrVal[0] : currentVal;\n        const p = document.createElementNS(NS, 'path');\n        p.setAttribute('d', pathFor(f, S.pr));\n        p.setAttribute('data-key', key);\n        p.setAttribute('class', ['muni', clickable ? '' : 'ctx'].filter(Boolean).join(' '));\n\n        if (isPie || isGroupPie) {\n          // Multiple sectors share this município — the shape itself is a neutral\n          // safety-net backdrop; the wedges below (clipped to the REAL border, one per\n          // array entry) carry the actual colors and fully cover the município's area.\n          const pieSectors = isPie ? arrVal : groupSectors;\n          p.setAttribute('fill', '#eef0f5');\n          p.onmousemove = e => {\n            const tip = $('#tip');\n            tip.style.opacity = 1; tip.style.left = (e.clientX + 12) + 'px'; tip.style.top = (e.clientY + 12) + 'px';\n            const names = pieSectors.map(v => v || 'sem destino').join(', ');\n            tip.innerHTML = `<b>${esc(S.labels[key] || key)}</b><br>${pieSectors.length} setores: ${esc(names)}`;\n          };\n          p.onmouseleave = () => { $('#tip').style.opacity = 0; };\n        } else {\n          if (groupSector) {\n            p.setAttribute('fill', colorFor(groupSector));\n            p.setAttribute('fill-opacity', '0.3');\n          } else {\n            p.setAttribute('fill', soloSector ? colorFor(soloSector) : (clickable ? '#94a3b8' : '#dcdfe8'));\n          }\n          if (clickable) {\n            p.onclick = () => {\n              if (dragMoved) { dragMoved = false; return; }\n              let next;\n              if (arrVal) {\n                next = cycleAt(key, 0, arrVal.length || 1);\n              } else {\n                const gs = sectorNames();\n                if (!gs.length) return;\n                next = gs[(gs.indexOf(getAssign(key)) + 1) % gs.length];\n                setAssign(key, next);\n              }\n              drawMap();\n              d2.emit('onChange', arrVal ? { municipio: key, sectorIndex: 0, setor: next } : { municipio: key, setor: next });\n            };\n          }\n          p.onmousemove = e => {\n            const tip = $('#tip');\n            tip.style.opacity = 1; tip.style.left = (e.clientX + 12) + 'px'; tip.style.top = (e.clientY + 12) + 'px';\n            const label = groupSector || soloSector || (clickable ? 'sem destino' : 'apenas contexto');\n            tip.innerHTML = `<b>${esc(S.labels[key] || key)}</b><br>${esc(label)}`;\n          };\n          p.onmouseleave = () => { $('#tip').style.opacity = 0; };\n        }\n        viewport.appendChild(p);\n\n        const [cx, cy] = cxy(f, S.pr);\n\n        if (isPie || isGroupPie) {\n          const wedgeSectors = isPie ? arrVal : groupSectors;\n          const n = wedgeSectors.length;\n          // Bands across the FULL bounding box of this município, not wedges around a center —\n          // a center point can't always \"see\" a concave shape's whole border, which used to leave\n          // slivers uncovered; straight parallel cuts always tile the entire shape exactly.\n          const [bx0, , bx1] = bboxOf(f, S.pr);\n          const stripeW = (bx1 - bx0) / n;\n          wedgeSectors.forEach((sector, i) => {\n            const d = stripeClipPath(f, S.pr, bx0 + i * stripeW, bx0 + (i + 1) * stripeW);\n            if (!d) return;\n            const wedge = document.createElementNS(NS, 'path');\n            wedge.setAttribute('d', d);\n            wedge.setAttribute('class', 'wedge');\n            wedge.setAttribute('fill', sector ? colorFor(sector) : '#94a3b8');\n            // Static group pins (`groups[]`) aren't interactable — keep them at ~30% opacity so\n            // they read as context, distinct from a real Store-backed sector assignment.\n            wedge.setAttribute('fill-opacity', isPie ? '1' : '0.3');\n            wedge.style.cursor = isPie ? 'pointer' : 'default';\n            if (isPie) {\n              wedge.onclick = () => {\n                if (dragMoved) { dragMoved = false; return; }\n                const next = cycleAt(key, i, n);\n                drawMap();\n                d2.emit('onChange', { municipio: key, sectorIndex: i, setor: next });\n              };\n            }\n            wedge.onmousemove = e => {\n              const tip = $('#tip');\n              tip.style.opacity = 1; tip.style.left = (e.clientX + 12) + 'px'; tip.style.top = (e.clientY + 12) + 'px';\n              tip.innerHTML = `<b>${esc(S.labels[key] || key)}</b> — setor ${i + 1}/${n}<br>${esc(sector || 'sem destino')}`;\n            };\n            wedge.onmouseleave = () => { $('#tip').style.opacity = 0; };\n            viewport.appendChild(wedge);\n          });\n        }\n\n        const t = document.createElementNS(NS, 'text');\n        t.setAttribute('class', 'mlabel'); t.setAttribute('x', cx); t.setAttribute('y', cy);\n        t.style.fontSize = labelFontSize + 'px';\n        t.textContent = (S.labels[key] || key).slice(0, 13);\n        viewport.appendChild(t);\n      });\n\n      $('#legend').innerHTML = sectors.map(g => `<span><b style=\"background:${colorFor(g)}\"></b>${esc(g)}</span>`).join('');\n    }\n\n    async function load(){\n      loader(true, 'carregando malhas…', false);\n      try {\n        const ufs = splitUfs(d2.param('ufs', 'PR'));\n        if (!ufs.length) throw new Error('informe ao menos uma UF em \"ufs\"');\n        const manualNbr = splitUfs(d2.param('neighborUfs', ''));\n        const autoNbr = ufs.flatMap(u => UF_BORDERS[u] || []);\n        const nbrUfs = [...new Set(manualNbr.length ? manualNbr : autoNbr)].filter(u => !ufs.includes(u));\n        const allUfs = [...new Set([...ufs, ...nbrUfs])];\n        const geos = await Promise.all(allUfs.map(u => geoUF(u).catch(() => null)));\n\n        const closeUfSet = new Set(ufs);\n        const allFeats = [];\n        const stateNames = [];\n        allUfs.forEach((u, i) => {\n          const g = geos[i]; if (!g) return;\n          const inClose = closeUfSet.has(u);\n          g.features.forEach(f => {\n            allFeats.push(f);\n            if (inClose) stateNames.push(norm(f.properties.name || f.properties.NOME || f.properties.nome || ''));\n          });\n        });\n        if (!allFeats.length) throw new Error('nenhuma malha carregada para essas UFs');\n\n        const adj = buildAdj(allFeats);\n\n        const targetsRaw = (d2.param('municipio', '') || '').split(',').map(x => x.trim()).filter(Boolean);\n        const targetNames = targetsRaw.map(norm);\n        const foundTargets = targetNames.filter(t => adj.feat[t]);\n        if (targetNames.length && !foundTargets.length) throw new Error('município(s) não encontrado(s) na(s) UF(s) informada(s): ' + targetsRaw.join(', '));\n        S.targets = new Set(foundTargets);\n\n        // Interactive scope (\"close\"): each target município (comma-separated in `municipio`)\n        // + its direct neighbors — NOT the whole state. Falls back to the whole state when no\n        // `municipio` is set.\n        const neighborhoodNames = foundTargets.length\n          ? [...new Set(foundTargets.flatMap(t => [t, ...adj.nbrs(t)]))]\n          : stateNames;\n\n        // Statically pins a sector's color onto whatever municípios are listed in that\n        // group's `municipio` field — these are display-only, not interactively assignable.\n        // A município can be listed by more than one group (e.g. Astorga under both Setor A\n        // and Setor B) — keep every sector that claims it so drawMap() can split it into a pie.\n        const groupAssign = {};\n        (d2.param('groups', []) || []).forEach(g => {\n          (g.municipio || '').split(',').map(x => x.trim()).filter(Boolean).forEach(n => {\n            const key = norm(n);\n            (groupAssign[key] = groupAssign[key] || []).push(g.sector);\n          });\n        });\n\n        // A município already carrying a sector assignment joins the interactive set even when\n        // it's not adjacent to the target — this is what `neighborUfs` is for: letting a sector\n        // reach into a município from a bordering state, as long as that UF was loaded.\n        const assignedSet = new Set(Object.keys(S.assign).map(norm));\n        const assignedLoaded = [...assignedSet].filter(n => adj.feat[n]);\n        const groupLoaded = Object.keys(groupAssign).filter(n => adj.feat[n]);\n        const closingNames = [...new Set([...neighborhoodNames, ...assignedLoaded, ...groupLoaded])];\n        const closingSet = new Set(closingNames);\n\n        S.closeFeats = {}; S.labels = {};\n        closingNames.forEach(n => { if (adj.feat[n]) { S.closeFeats[n] = adj.feat[n].f; S.labels[n] = adj.feat[n].f.properties.name || n; } });\n\n        S.clickable = new Set(closingNames.filter(n => assignedSet.has(n)));\n        S.groupAssign = groupAssign;\n\n        // Everything else loaded (rest of the state / neighbor states) is faint background context.\n        const restNames = new Set();\n        allFeats.forEach(f => {\n          const n = norm(f.properties.name || f.properties.NOME || f.properties.nome || '');\n          if (adj.feat[n] && !closingSet.has(n)) restNames.add(n);\n        });\n        S.nbrFeats = [...restNames].map(n => adj.feat[n].f);\n\n        const zoomFeats = Object.values(S.closeFeats);\n        S.pr = project(zoomFeats.length ? zoomFeats : allFeats);\n        S.view = { scale: 1, tx: 0, ty: 0 };\n        drawMap();\n        loader(false);\n      } catch (e) {\n        loader(true, 'erro: ' + e.message + ' (clique p/ tentar de novo)', true);\n      }\n    }\n\n    $('#loader').onclick = () => {\n      if (!$('#loader').classList.contains('err')) return;\n      S.closeFeats = {}; S.nbrFeats = []; S.labels = {};\n      load();\n    };\n\n    load();\n  },\n};"}},"assignments":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"assignments"}}}},"colors":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"colors"}}}},"groups":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"groups"}}}},"municipio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"municipio"}}}},"neighborUfs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neighborUfs"}}}},"onChange":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"onChange"}}}},"store":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"store"}}}},"ufs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ufs"}}}},"prop":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"prop"}}}}}}}}}},{"_id":"6a698066dc1fded10b04f6ba","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"get_neighboring_municipios","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const UF_CODE={RO:'11',AC:'12',AM:'13',RR:'14',PA:'15',AP:'16',TO:'17',MA:'21',PI:'22',CE:'23',RN:'24',PB:'25',PE:'26',AL:'27',SE:'28',BA:'29',MG:'31',ES:'32',RJ:'33',SP:'35',PR:'41',SC:'42',RS:'43',MS:'50',MT:'51',GO:'52',DF:'53'};\nconst UF_BORDERS={AC:['AM','RO'],AL:['BA','PE','SE'],AM:['AC','RO','RR','PA','MT'],AP:['PA'],\n BA:['AL','SE','PE','PI','TO','GO','MG','ES'],CE:['PI','PB','PE','RN'],DF:['GO','MG'],\n ES:['BA','MG','RJ'],GO:['DF','MT','MS','MG','BA','TO'],MA:['PA','TO','PI'],\n MG:['BA','GO','DF','MS','SP','RJ','ES'],MS:['MT','GO','MG','SP','PR'],MT:['RO','AM','PA','TO','GO','MS'],\n PA:['AP','AM','MT','TO','MA','RR'],PB:['RN','CE','PE'],PE:['PB','CE','PI','BA','AL'],\n PI:['MA','TO','BA','PE','CE'],PR:['SP','MS','SC'],RJ:['ES','MG','SP'],RN:['CE','PB'],\n RO:['AC','AM','MT'],RR:['AM','PA'],RS:['SC'],SC:['PR','RS'],SE:['BA','AL'],\n SP:['MG','RJ','PR','MS'],TO:['PA','MA','PI','BA','GO','MT']};\n\nconst norm = s => (s||'').normalize('NFKD').replace(/[̀-ͯ]/g,'').toUpperCase().replace(/[^A-Z0-9]+/g,' ').trim();\nconst splitUfs = s => (s||'').split(',').map(x=>x.trim().toUpperCase()).filter(Boolean);\n\nasync function geoUF(uf){\n  const code = UF_CODE[uf] || uf;\n  const r = await fetch('https://raw.githubusercontent.com/tbrugz/geodata-br/master/geojson/geojs-'+code+'-mun.json');\n  if (!r.ok) throw new Error('geo ' + uf);\n  return r.json();\n}\n\n// Same trick the map uses: a município's boundary points, rounded to ~110m, are\n// (near-)identical to its neighbor's along a shared edge. 2+ shared points = a real\n// edge (not just a corner-touch), which is what \"neighboring\" means here.\nfunction buildAdj(features){\n  const pts = f => {\n    const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates], s=new Set();\n    ps.forEach(poly=>poly.forEach(ring=>ring.forEach(p=>s.add(Math.round(p[0]*1000)/1000+','+Math.round(p[1]*1000)/1000))));\n    return s;\n  };\n  const feat = {}, idx = {};\n  features.forEach(f => { const nm = norm(f.properties.name || f.properties.NOME || f.properties.nome); feat[nm] = { f, pts: pts(f) }; });\n  for (const nm in feat) feat[nm].pts.forEach(p => (idx[p] = idx[p] || []).push(nm));\n  const nbrs = nm => {\n    const c = {};\n    feat[nm].pts.forEach(p => idx[p].forEach(o => { if (o !== nm) c[o] = (c[o] || 0) + 1; }));\n    return Object.entries(c).filter(([, n]) => n >= 2).map(([o]) => o);\n  };\n  return { feat, nbrs };\n}\n\nexport default {\n  css: `:host{display:none}`,\n  render(){\n    return '';\n  },\n  mounted(d2){\n    const writeResult = (neighbors) => {\n      const storeRef = d2.param('store');\n      if (storeRef) d2.store(storeRef).set('neighbors', neighbors);\n      d2.emit('onChange', { neighbors });\n    };\n    (async () => {\n      const targetsRaw = (d2.param('municipio', '') || '').split(',').map(x => x.trim()).filter(Boolean);\n      const ufs = splitUfs(d2.param('ufs', ''));\n      if (!targetsRaw.length || !ufs.length) { writeResult([]); return; }\n\n      const manualNbr = splitUfs(d2.param('neighborUfs', ''));\n      const autoNbr = ufs.flatMap(u => UF_BORDERS[u] || []);\n      const nbrUfs = [...new Set(manualNbr.length ? manualNbr : autoNbr)].filter(u => !ufs.includes(u));\n      const allUfs = [...new Set([...ufs, ...nbrUfs])];\n\n      const geos = await Promise.all(allUfs.map(u => geoUF(u).catch(() => null)));\n      const allFeats = [];\n      geos.forEach(g => { if (g) allFeats.push(...g.features); });\n      if (!allFeats.length) { writeResult([]); return; }\n\n      const adj = buildAdj(allFeats);\n      const targets = targetsRaw.map(norm).filter(t => adj.feat[t]);\n      if (!targets.length) { writeResult([]); return; }\n\n      const targetSet = new Set(targets);\n      const result = new Set();\n      targets.forEach(t => adj.nbrs(t).forEach(n => { if (!targetSet.has(n)) result.add(n); }));\n\n      const neighbors = [...result].map(n => adj.feat[n].f.properties.name || adj.feat[n].f.properties.NOME || adj.feat[n].f.properties.nome || n);\n      writeResult(neighbors);\n    })();\n  }\n};"}},"municipio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"municipio"}}}},"ufs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ufs"}}}},"neighborUfs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neighborUfs"}}}},"store":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"store"}}}},"onChange":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"onChange"}}}}}}}}}},{"_id":"6a6c50bb88f670e6c118e310","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"sector-closing-map","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const UF_CODE={RO:'11',AC:'12',AM:'13',RR:'14',PA:'15',AP:'16',TO:'17',MA:'21',PI:'22',CE:'23',RN:'24',PB:'25',PE:'26',AL:'27',SE:'28',BA:'29',MG:'31',ES:'32',RJ:'33',SP:'35',PR:'41',SC:'42',RS:'43',MS:'50',MT:'51',GO:'52',DF:'53'};\nconst PALETTE=['#8b7fd6','#d1b048','#67bd5b','#5aa9d6','#d67fa8','#c98a4b'];\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%;box-sizing:border-box}\n    .wrap{position:relative;height:100%;display:flex;flex-direction:column;font:12px -apple-system,BlinkMacSystemFont,\"Segoe UI\",Inter,sans-serif;background:#f4f4f8}\n    .stage{flex:1;min-height:0;position:relative;cursor:grab}\n    .stage:active{cursor:grabbing}\n    svg{width:100%;height:100%;display:block}\n    .muni{stroke:#0b0d12;stroke-width:1.1;transition:filter .1s}\n    .muni.ctx{cursor:default}\n    .muni:not(.ctx){cursor:pointer}\n    .muni:not(.ctx):hover{filter:brightness(1.25)}\n    .wedge{cursor:pointer;stroke:#fff;stroke-width:0.6;transition:filter .1s}\n    .wedge:hover{filter:brightness(1.25)}\n    .bg{fill:#ffffff;stroke:#1a1a1a;stroke-width:.5;opacity:.5}\n    .mlabel{fill:#1a1a1a;font-size:9px;font-weight:700;pointer-events:none;user-select:none;text-anchor:middle;paint-order:stroke;stroke:rgba(255,255,255,.6);stroke-width:0.5px}\n    .legend{display:flex;gap:10px;flex-wrap:wrap;padding:8px 10px;border-top:1px solid #e7e7ef;background:#fff}\n    .legend span{display:inline-flex;align-items:center;gap:5px}\n    .legend b{width:10px;height:10px;border-radius:3px;display:inline-block}\n    .legend .closing{font-style:italic;opacity:.75}\n    .load{position:absolute;inset:0;background:rgba(244,244,248,.92);display:flex;align-items:center;justify-content:center;z-index:5;color:#71717f;text-align:center;padding:16px;cursor:default}\n    .load.err{cursor:pointer;text-decoration:underline;color:#c8104e}\n    .load.hidden{display:none}\n    .tip{position:fixed;pointer-events:none;background:#000;color:#fff;border-radius:7px;padding:6px 8px;font-size:11px;opacity:0;transition:opacity .1s;z-index:9;max-width:220px}\n  `,\n  render(){\n    return `<div class=\"wrap\">\n      <div class=\"stage\"><svg id=\"map\" viewBox=\"0 0 700 500\" preserveAspectRatio=\"xMidYMid meet\"></svg></div>\n      <div class=\"legend\" id=\"legend\"></div>\n      <div class=\"tip\" id=\"tip\"></div>\n      <div class=\"load\" id=\"loader\">carregando malhas…</div>\n    </div>`;\n  },\n  mounted(d2, root){\n\n    const $ = s => root.querySelector(s);\n    const NS = 'http://www.w3.org/2000/svg';\n    const norm = s => (s||'').normalize('NFKD').replace(/[̀-ͯ]/g,'').toUpperCase().replace(/[^A-Z0-9]+/g,' ').trim();\n    const esc = s => (s==null?'':String(s)).replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));\n    const splitUfs = s => (s||'').split(',').map(x=>x.trim().toUpperCase()).filter(Boolean);\n\n    async function geoUF(uf){\n      const code = UF_CODE[uf] || uf;\n      const r = await fetch('https://raw.githubusercontent.com/tbrugz/geodata-br/master/geojson/geojs-'+code+'-mun.json');\n      if (!r.ok) throw new Error('geo ' + uf);\n      return r.json();\n    }\n    function project(feats){\n      let mnx=1/0,mny=1/0,mxx=-1/0,mxy=-1/0;\n      const each = cb => feats.forEach(f=>{const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];ps.forEach(p=>p.forEach(r=>r.forEach(cb)));});\n      each(p=>{mnx=Math.min(mnx,p[0]);mxx=Math.max(mxx,p[0]);mny=Math.min(mny,p[1]);mxy=Math.max(mxy,p[1]);});\n      const mlat=(mny+mxy)/2, kx=Math.cos(mlat*Math.PI/180), W=700,H=500,pad=22;\n      const gw=(mxx-mnx)*kx, gh=(mxy-mny), sc=Math.min((W-2*pad)/gw,(H-2*pad)/gh);\n      const ox=(W-gw*sc)/2, oy=(H-gh*sc)/2;\n      const pr = p=>[ox+(p[0]-mnx)*kx*sc, oy+(mxy-p[1])*sc];\n      pr.scale = sc;\n      return pr;\n    }\n    function pathFor(f,pr){\n      const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n      return ps.map(poly=>poly.map(r=>'M'+r.map(p=>{const q=pr(p);return q[0].toFixed(1)+','+q[1].toFixed(1);}).join('L')+'Z').join('')).join('');\n    }\n    function cxy(f,pr){\n      let x=0,y=0,n=0; const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n      ps.forEach(poly=>poly[0].forEach(p=>{const q=pr(p);x+=q[0];y+=q[1];n++;}));\n      return [x/n, y/n];\n    }\n    // Clips a projected ring against a single straight half-plane (side(x,y) >= 0 is kept)\n    // via Sutherland-Hodgman — exact for ANY simple polygon, convex or concave, so stacking\n    // these to slice a município into N vertical bands never leaves a gap.\n    function clipHalfPlane(ring, side){\n      const out = [];\n      const n = ring.length;\n      for (let i = 0; i < n; i++){\n        const curr = ring[i], prev = ring[(i - 1 + n) % n];\n        const sCurr = side(curr[0], curr[1]);\n        const sPrev = side(prev[0], prev[1]);\n        const currIn = sCurr >= -1e-9;\n        const prevIn = sPrev >= -1e-9;\n        if (currIn) {\n          if (!prevIn) out.push(intersectHalfPlane(prev, curr, side));\n          out.push(curr);\n        } else if (prevIn) {\n          out.push(intersectHalfPlane(prev, curr, side));\n        }\n      }\n      return out;\n    }\n    function intersectHalfPlane(p1, p2, side){\n      const s1 = side(p1[0], p1[1]), s2 = side(p2[0], p2[1]);\n      const t = s1 / (s1 - s2);\n      return [p1[0] + t * (p2[0] - p1[0]), p1[1] + t * (p2[1] - p1[1])];\n    }\n    // Full projected bounding box of a município (all MultiPolygon parts included), used to\n    // lay bands across its ENTIRE geometry so the bands always tile the whole shape, islands too.\n    function bboxOf(f, pr){\n      let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n      const g = f.geometry, ps = g.type === 'MultiPolygon' ? g.coordinates : [g.coordinates];\n      ps.forEach(poly => poly.forEach(ring => ring.forEach(p => {\n        const q = pr(p);\n        minX = Math.min(minX, q[0]); maxX = Math.max(maxX, q[0]);\n        minY = Math.min(minY, q[1]); maxY = Math.max(maxY, q[1]);\n      })));\n      return [minX, minY, maxX, maxY];\n    }\n    // A vertical band [x0,x1) of the município, clipped against its REAL border on both sides.\n    function stripeClipPath(f, pr, x0, x1){\n      const g = f.geometry, ps = g.type === 'MultiPolygon' ? g.coordinates : [g.coordinates];\n      let d = '';\n      ps.forEach(poly => poly.forEach(ring => {\n        let pts = ring.map(p => pr(p));\n        pts = clipHalfPlane(pts, (x) => x - x0);\n        if (pts.length) pts = clipHalfPlane(pts, (x) => x1 - x);\n        if (pts.length >= 3) d += 'M' + pts.map(q => q[0].toFixed(1) + ',' + q[1].toFixed(1)).join('L') + 'Z';\n      }));\n      return d;\n    }\n    const loader = (on, t, isErr) => {\n      const l = $('#loader');\n      l.classList.toggle('hidden', !on);\n      l.classList.toggle('err', !!isErr);\n      if (t != null) l.textContent = t;\n    };\n\n    // Bootstrap `sectors` snapshot — used only to know which municípios to load geometry\n    // for. Live membership (incl. which municípios are still under a closing sector, which\n    // is permanent — see cycleMuni) is always read fresh from the Store, never from here.\n    const bootstrapSectors = JSON.parse(JSON.stringify(d2.param('sectors', []) || []));\n\n    let S = { sectorsLocal: JSON.parse(JSON.stringify(bootstrapSectors)), mapFeats: {}, bgFeats: [], labels: {}, pr: null, view: { scale: 1, tx: 0, ty: 0 } };\n    let dragStart = null, dragMoved = false;\n    let lastSig = null;\n\n    // Live sectors: the Store (if `store`+`prop` are bound) is the source of truth —\n    // reads are tracked, so an external change to the Store re-renders this map.\n    // `S.sectorsLocal` is only the bootstrap default before the Store has a value, or the\n    // fallback when no `store` param is bound at all.\n    function getLiveSectors(){\n      const storeRef = d2.param('store');\n      const prop = d2.param('prop');\n      if (!storeRef || !prop) return S.sectorsLocal;\n      const val = d2.store(storeRef).get(prop, null);\n      return Array.isArray(val) ? val : S.sectorsLocal;\n    }\n    function setLiveSectors(next){\n      S.sectorsLocal = next;\n      const storeRef = d2.param('store');\n      const prop = d2.param('prop');\n      if (!storeRef || !prop) return;\n      d2.store(storeRef).set(prop, next);\n    }\n\n    // Every sector's own `municipios` list is FIXED forever — a closing sector never loses\n    // or gains territory, and a non-closing sector's data is never touched by a click. What\n    // a click changes is metadata living ONLY on the closing sector's own entry: a `targets`\n    // map from município → the non-closing sector code it's currently pointed at. Rendering\n    // then paints that município using the TARGET's color/code instead of the closing\n    // sector's own — the closing sector still \"owns\" the município (it's still in its\n    // `municipios` list, still permanently clickable), it just visually partakes in\n    // whichever destination sector's identity is currently assigned.\n    function cycleMuni(muniKey, closingCode){\n      const live = getLiveSectors();\n      const sectorByCode = {};\n      live.forEach(s => { sectorByCode[s.code] = s; });\n      const closingSector = sectorByCode[closingCode];\n      if (!closingSector) return null;\n      const nonClosingCycle = live.filter(s => !s.closing_sector).map(s => s.code);\n      if (!nonClosingCycle.length) return null;\n\n      // Every code CURRENTLY shown on this município via a DIFFERENT sector's slot — a\n      // static non-closing membership, or another closing sector's own current pick — must\n      // never be picked again, so this slot's new color is always visibly distinct.\n      const otherDisplayed = new Set();\n      live.forEach(s => {\n        if (s.code === closingCode) return;\n        if (!(s.municipios||[]).some(m => norm(m) === muniKey)) return;\n        if (s.closing_sector) {\n          const t = s.targets && s.targets[muniKey];\n          otherDisplayed.add((t && sectorByCode[t]) ? t : s.code);\n        } else {\n          otherDisplayed.add(s.code);\n        }\n      });\n\n      const currentTarget = closingSector.targets && closingSector.targets[muniKey];\n      const startIdx = nonClosingCycle.indexOf(currentTarget);\n      let nextTarget = null;\n      for (let step = 1; step <= nonClosingCycle.length; step++) {\n        const candidate = nonClosingCycle[(startIdx + step) % nonClosingCycle.length];\n        if (!otherDisplayed.has(candidate)) { nextTarget = candidate; break; }\n      }\n      if (nextTarget == null) return null;\n\n      const updated = live.map(s => s.code === closingCode\n        ? { ...s, targets: { ...(s.targets||{}), [muniKey]: nextTarget } }\n        : s);\n      setLiveSectors(updated);\n      return { fromSector: currentTarget || null, toSector: nextTarget };\n    }\n\n    function svgPoint(evt){\n      const svg = $('#map');\n      const pt = svg.createSVGPoint();\n      pt.x = evt.clientX; pt.y = evt.clientY;\n      const ctm = svg.getScreenCTM();\n      if (!ctm) return { x: 0, y: 0 };\n      const p = pt.matrixTransform(ctm.inverse());\n      return { x: p.x, y: p.y };\n    }\n    function applyTransform(){\n      const g = $('#viewport');\n      if (g) g.setAttribute('transform', `translate(${S.view.tx} ${S.view.ty}) scale(${S.view.scale})`);\n    }\n    d2.on('.stage', 'wheel', e => {\n      e.preventDefault();\n      const p = svgPoint(e);\n      const factor = e.deltaY < 0 ? 1.15 : 1 / 1.15;\n      const next = Math.min(8, Math.max(0.15, S.view.scale * factor));\n      const k = next / S.view.scale;\n      S.view.tx = p.x - k * (p.x - S.view.tx);\n      S.view.ty = p.y - k * (p.y - S.view.ty);\n      S.view.scale = next;\n      applyTransform();\n    });\n    d2.on('.stage', 'mousedown', e => {\n      if (e.button !== 0) return;\n      const p = svgPoint(e);\n      dragStart = { x: p.x, y: p.y, tx: S.view.tx, ty: S.view.ty };\n      dragMoved = false;\n    });\n    d2.on(window, 'mousemove', e => {\n      if (!dragStart) return;\n      const p = svgPoint(e);\n      const dx = p.x - dragStart.x, dy = p.y - dragStart.y;\n      if (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5) dragMoved = true;\n      S.view.tx = dragStart.tx + dx;\n      S.view.ty = dragStart.ty + dy;\n      applyTransform();\n    });\n    d2.on(window, 'mouseup', () => { dragStart = null; });\n\n    // This component paints imperatively (mounted-driven SVG, not the reactive render()\n    // string), so a Store read here isn't auto-tracked the way it would be inside render().\n    // Poll the bound Store's sectors value every frame and redraw when it changed — this is\n    // what makes an EXTERNAL write to the Store (not just this map's own clicks) show up.\n    d2.raf(() => {\n      const storeRef = d2.param('store');\n      if (!storeRef) return;\n      const sig = JSON.stringify(getLiveSectors());\n      if (sig !== lastSig) { lastSig = sig; if (Object.keys(S.mapFeats).length) drawMap(); }\n    });\n\n    function drawMap(){\n      const svg = $('#map'); svg.innerHTML = '';\n      const viewport = document.createElementNS(NS, 'g');\n      viewport.setAttribute('id', 'viewport');\n      svg.appendChild(viewport);\n      applyTransform();\n\n      const live = getLiveSectors();\n      const sectorByCode = {};\n      live.forEach(s => { sectorByCode[s.code] = s; });\n      const codeList = live.map(s => s.code);\n      const colorFor = code => (sectorByCode[code] && sectorByCode[code].color) || PALETTE[codeList.indexOf(code) % PALETTE.length] || '#94a3b8';\n\n      const partsByMuni = {};\n      Object.keys(S.mapFeats).forEach(key => {\n        const parts = [];\n        live.forEach(s => {\n          if (!(s.municipios||[]).some(m => norm(m) === key)) return;\n          if (s.closing_sector) {\n            const t = s.targets && s.targets[key];\n            const displayCode = (t && sectorByCode[t]) ? t : s.code;\n            parts.push({ displayCode, closingAnchor: s.code });\n          } else {\n            parts.push({ displayCode: s.code, closingAnchor: null });\n          }\n        });\n        partsByMuni[key] = parts;\n      });\n\n      // Base label size tracks the projection's own fit scale, so labels stay proportionate\n      // to the shapes whether the auto-fit is tight (few municípios) or wide (many, spread out).\n      const labelFontSize = Math.max(4, Math.min(14, 9 * ((S.pr.scale || 1000) / 1000)));\n\n      S.bgFeats.forEach(f => {\n        const p = document.createElementNS(NS, 'path');\n        p.setAttribute('d', pathFor(f, S.pr));\n        p.setAttribute('class', 'bg');\n        viewport.appendChild(p);\n      });\n\n      const handleClick = (key, part) => {\n        const result = cycleMuni(key, part.closingAnchor);\n        if (!result) return;\n        drawMap();\n        d2.emit('onChange', { municipio: key, closingSector: part.closingAnchor, fromSector: result.fromSector, toSector: result.toSector });\n      };\n\n      Object.entries(S.mapFeats).forEach(([key, f]) => {\n        const parts = partsByMuni[key] || [];\n        const isPie = parts.length >= 2;\n        const p = document.createElementNS(NS, 'path');\n        p.setAttribute('d', pathFor(f, S.pr));\n        p.setAttribute('data-key', key);\n\n        if (isPie) {\n          // Multiple sectors claim this município at once — the shape itself is a neutral\n          // safety-net backdrop; the wedges below (clipped to the REAL border, one per\n          // sector) carry the actual colors and fully cover the município's area.\n          p.setAttribute('class', 'muni ctx');\n          p.setAttribute('fill', '#eef0f5');\n          p.onmousemove = e => {\n            const tip = $('#tip');\n            tip.style.opacity = 1; tip.style.left = (e.clientX + 12) + 'px'; tip.style.top = (e.clientY + 12) + 'px';\n            tip.innerHTML = `<b>${esc(S.labels[key] || key)}</b><br>${parts.length} setores: ${esc(parts.map(pt => pt.displayCode).join(', '))}`;\n          };\n          p.onmouseleave = () => { $('#tip').style.opacity = 0; };\n          viewport.appendChild(p);\n\n          const [bx0, , bx1] = bboxOf(f, S.pr);\n          const n = parts.length;\n          const stripeW = (bx1 - bx0) / n;\n          parts.forEach((part, i) => {\n            const d = stripeClipPath(f, S.pr, bx0 + i * stripeW, bx0 + (i + 1) * stripeW);\n            if (!d) return;\n            const wedge = document.createElementNS(NS, 'path');\n            wedge.setAttribute('d', d);\n            wedge.setAttribute('class', 'wedge');\n            wedge.setAttribute('fill', colorFor(part.displayCode));\n            // A wedge is interactive iff it's the closing sector's OWN slot — regardless of\n            // which sector's color it's currently borrowing for display.\n            const clickable = part.closingAnchor != null;\n            wedge.style.cursor = clickable ? 'pointer' : 'default';\n            if (clickable) wedge.onclick = () => { if (dragMoved) { dragMoved = false; return; } handleClick(key, part); };\n            wedge.onmousemove = e => {\n              const tip = $('#tip');\n              tip.style.opacity = 1; tip.style.left = (e.clientX + 12) + 'px'; tip.style.top = (e.clientY + 12) + 'px';\n              tip.innerHTML = `<b>${esc(S.labels[key] || key)}</b> — setor ${i + 1}/${n}<br>${esc(part.displayCode)}${clickable ? ' (clique p/ reatribuir)' : ''}`;\n            };\n            wedge.onmouseleave = () => { $('#tip').style.opacity = 0; };\n            viewport.appendChild(wedge);\n          });\n        } else {\n          const part = parts[0] || null;\n          // A solo tile is interactive iff it's the closing sector's own slot — whether it's\n          // still showing the closing sector's own color (undecided) or already borrowing a\n          // target's color/code, it always stays clickable.\n          const clickable = !!(part && part.closingAnchor != null);\n          p.setAttribute('class', ['muni', clickable ? '' : 'ctx'].filter(Boolean).join(' '));\n          p.setAttribute('fill', part ? colorFor(part.displayCode) : '#dcdfe8');\n          if (clickable) p.onclick = () => { if (dragMoved) { dragMoved = false; return; } handleClick(key, part); };\n          p.onmousemove = e => {\n            const tip = $('#tip');\n            tip.style.opacity = 1; tip.style.left = (e.clientX + 12) + 'px'; tip.style.top = (e.clientY + 12) + 'px';\n            const label = part ? part.displayCode : 'sem setor';\n            tip.innerHTML = `<b>${esc(S.labels[key] || key)}</b><br>${esc(label)}${clickable ? ' (clique p/ reatribuir)' : ''}`;\n          };\n          p.onmouseleave = () => { $('#tip').style.opacity = 0; };\n          viewport.appendChild(p);\n        }\n\n        const [cx, cy] = cxy(f, S.pr);\n        const t = document.createElementNS(NS, 'text');\n        t.setAttribute('class', 'mlabel'); t.setAttribute('x', cx); t.setAttribute('y', cy);\n        t.style.fontSize = labelFontSize + 'px';\n        t.textContent = (S.labels[key] || key).slice(0, 13);\n        viewport.appendChild(t);\n      });\n\n      $('#legend').innerHTML = live.map(s => `<span><b style=\"background:${colorFor(s.code)}\"></b>${esc(s.code)}${s.closing_sector ? '<i class=\"closing\"> (fechando)</i>' : ''}</span>`).join('');\n    }\n\n    async function load(){\n      loader(true, 'carregando malhas…', false);\n      try {\n        const ufs = splitUfs(d2.param('ufs', ''));\n        if (!ufs.length) throw new Error('informe ao menos uma UF em \"ufs\"');\n        const nbrUfs = splitUfs(d2.param('neighborUfs', '')).filter(u => !ufs.includes(u));\n        const allUfs = [...new Set([...ufs, ...nbrUfs])];\n        const geos = await Promise.all(allUfs.map(u => geoUF(u).catch(() => null)));\n\n        const allFeats = [];\n        allUfs.forEach((u, i) => { const g = geos[i]; if (g) allFeats.push(...g.features); });\n        if (!allFeats.length) throw new Error('nenhuma malha carregada para essas UFs');\n\n        const idx = {};\n        allFeats.forEach(f => { const nm = norm(f.properties.name || f.properties.NOME || f.properties.nome); idx[nm] = f; });\n\n        const declaredNames = new Set();\n        bootstrapSectors.forEach(s => (s.municipios||[]).forEach(m => declaredNames.add(norm(m))));\n        if (!declaredNames.size) throw new Error('nenhum município declarado em \"sectors\"');\n\n        S.mapFeats = {}; S.labels = {};\n        declaredNames.forEach(n => {\n          if (idx[n]) { S.mapFeats[n] = idx[n]; S.labels[n] = idx[n].properties.name || idx[n].properties.NOME || idx[n].properties.nome || n; }\n        });\n        const missing = [...declaredNames].filter(n => !idx[n]);\n        if (!Object.keys(S.mapFeats).length) throw new Error('município(s) não encontrado(s) na(s) UF(s) informada(s): ' + missing.join(', '));\n\n        // Everything else loaded (rest of the state(s) / neighborUfs) is faint background context.\n        S.bgFeats = allFeats.filter(f => {\n          const n = norm(f.properties.name || f.properties.NOME || f.properties.nome || '');\n          return n && !S.mapFeats[n];\n        });\n\n        const zoomFeats = Object.values(S.mapFeats);\n        S.pr = project(zoomFeats.length ? zoomFeats : allFeats);\n        S.view = { scale: 1, tx: 0, ty: 0 };\n        drawMap();\n        lastSig = JSON.stringify(getLiveSectors());\n        loader(false);\n      } catch (e) {\n        loader(true, 'erro: ' + e.message + ' (clique p/ tentar de novo)', true);\n      }\n    }\n\n    $('#loader').onclick = () => {\n      if (!$('#loader').classList.contains('err')) return;\n      S.mapFeats = {}; S.bgFeats = []; S.labels = {};\n      load();\n    };\n\n    load();\n  },\n};\n"}},"sectors":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sectors"}}}},"ufs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ufs"}}}},"neighborUfs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neighborUfs"}}}},"store":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"store"}}}},"prop":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"prop"}}}},"onChange":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"onChange"}}}}}}}}}},{"_id":"6a6c67f495b4a2c9973aacd2","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"UYcDX4v5ky74c0Eqq_U2l","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n  :host{display:block;height:100%;--bg:#f4f4f8;--panel:#ffffff;--panel2:#f7f7fb;--line:#e7e7ef;--ink:#1c1c2b;--mut:#71717f;--acc:#c8104e; --closing:#c8104e;--ok:#1f9d57;--grad:linear-gradient(100deg,#c8104e,#8f0b39)}\n  *{box-sizing:border-box}\n  .simroot{position:relative;height:100%;overflow:auto;font:13.5px/1.45 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;background:var(--bg);color:var(--ink)}\n  header{padding:14px 20px;background:var(--grad);color:#fff;display:flex;align-items:center;gap:12px;flex-wrap:wrap;box-shadow:0 2px 14px rgba(140,11,57,.25)}\n  header h1{font-size:16px;margin:0;font-weight:700;letter-spacing:.2px}\n  header select,header input{background:rgba(255,255,255,.16);border-color:rgba(255,255,255,.3);color:#fff}\n  header select option{color:#1c1c2b}\n  header .status{color:rgba(255,255,255,.85)}\n  .howto{padding:9px 20px;background:#fff;border-bottom:1px solid var(--line);display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--mut);align-items:center}\n  .howto b{color:var(--ink)} .howto span{max-width:340px}\n  .pill{font-size:11px;padding:3px 9px;border-radius:99px;border:1px solid rgba(255,255,255,.35);color:rgba(255,255,255,.92);background:rgba(255,255,255,.12)}\n  .pill.live{color:#fff}.pill.ro{color:#fff}\n  header button.act{background:#fff;color:var(--acc)}\n  header button.ghost{background:rgba(255,255,255,.14);color:#fff;border-color:rgba(255,255,255,.3)}\n  .status{font-size:11px;color:var(--mut)}.status.err{color:var(--closing)}\n  select,input,textarea,button{font:inherit}\n  select,input{background:var(--panel2);border:1px solid var(--line);color:var(--ink);border-radius:7px;padding:6px 8px}\n  button.act{background:var(--acc);color:#fff;border:0;border-radius:8px;padding:7px 12px;font-weight:700;cursor:pointer}\n  button.ghost{background:transparent;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:6px 11px;cursor:pointer}\n  button.ghost.on{border-color:var(--acc);color:var(--acc)}\n  .wrap{display:grid;grid-template-columns:minmax(360px,1fr) 430px;gap:14px;padding:14px 18px;align-items:start}\n  @media(max-width:1150px){.wrap{grid-template-columns:1fr}}\n  .card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:14px 16px;margin-bottom:14px;box-shadow:0 1px 3px rgba(20,20,40,.05),0 6px 20px rgba(20,20,40,.04)}\n  .card h2{font-size:11.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--mut);margin:0 0 9px;font-weight:700}\n  svg{width:100%;height:auto;display:block}\n  .muni{stroke:#0b0d12;stroke-width:1.1;cursor:pointer;transition:filter .1s}.muni:hover{filter:brightness(1.28)}\n  .muni.sel{stroke:#fff;stroke-width:2.3}\n  .nbr{fill:#e6e6ee;stroke:#cfcfda;stroke-width:.8;opacity:.95}\n  .mlabel{fill:#0d0f14;font-size:10px;font-weight:700;pointer-events:none;text-anchor:middle;paint-order:stroke;stroke:rgba(255,255,255,.55);stroke-width:2.4px}\n  .nlabel{fill:#6b7183;font-size:8.5px;pointer-events:none;text-anchor:middle}\n  .legend{display:flex;gap:12px;flex-wrap:wrap;margin-top:8px;font-size:12px}\n  .legend b{display:inline-block;width:11px;height:11px;border-radius:3px;margin-right:5px;vertical-align:-1px}\n  .kpis{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-bottom:12px}\n  .kpi{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:9px 11px}\n  .kpi .n{font-size:20px;font-weight:700}.kpi .l{font-size:10.5px;color:var(--mut);margin-top:2px}\n  .kpi.warn{border-color:#f0c2cb}.kpi.warn .n{color:var(--closing)}.kpi.ok{border-color:#bfe6cd}.kpi.ok .n{color:var(--ok)}\n  .bar-row{display:flex;align-items:center;gap:8px;margin:6px 0}\n  .bar-row .tag{width:42px;font-weight:700;font-size:12px}\n  .bar-track{flex:1;height:19px;background:var(--panel2);border-radius:5px;overflow:hidden;display:flex}\n  .bar-base{height:100%;opacity:.4}.bar-inc{height:100%}\n  .bar-val{width:132px;text-align:right;font-size:11px;color:var(--mut);font-variant-numeric:tabular-nums}\n  table{border-collapse:collapse;width:100%;font-size:11.5px}\n  th,td{padding:5px 6px;text-align:right;border-bottom:1px solid var(--line);font-variant-numeric:tabular-nums}\n  th:first-child,td:first-child{text-align:left}\n  thead th{color:var(--mut);font-weight:600;font-size:10.5px;position:sticky;top:0;background:var(--panel)}\n  .home{display:inline-block;width:9px;height:9px;border-radius:2px;margin-right:4px}\n  .muni-item{display:flex;align-items:center;gap:8px;background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:5px 8px;margin-bottom:6px}\n  .muni-item .nm{flex:1;font-weight:600;font-size:12px}.muni-item .ct{color:var(--mut);font-size:11px}\n  .seg{display:flex;border:1px solid var(--line);border-radius:7px;overflow:hidden}\n  .seg button{border:0;background:transparent;color:var(--mut);padding:4px 8px;font-size:11px;font-weight:700;cursor:pointer}\n  .seg button.on{color:#fff}\n  .toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap}\n  .toggle{display:flex;border:1px solid var(--line);border-radius:8px;overflow:hidden;font-size:12px}\n  .toggle button{background:transparent;border:0;color:var(--mut);padding:6px 10px;cursor:pointer;font-weight:600}\n  .toggle button.on{background:var(--panel2);color:var(--ink)}\n  .note{color:var(--mut);font-size:10.5px;margin-top:7px;line-height:1.5}\n  .cell{font-variant-numeric:tabular-nums}\n  .scroll{max-height:240px;overflow:auto}\n  .tip{position:fixed;pointer-events:none;background:#000;color:#fff;border-radius:8px;padding:7px 9px;font-size:11px;opacity:0;transition:opacity .1s;z-index:9;max-width:230px}\n  .delta{font-size:10px}.delta.up{color:var(--ok)}.delta.dn{color:var(--closing)}\n  .tabs{display:flex;gap:6px;margin-bottom:10px}\n  .tabs button{background:transparent;border:1px solid var(--line);border-radius:8px;color:var(--mut);padding:6px 12px;cursor:pointer;font-weight:600}\n  .tabs button.on{background:var(--panel2);color:var(--ink);border-color:var(--acc)}\n  .hidden{display:none}\n  .load{position:absolute;inset:0;background:rgba(248,248,251,.85);display:flex;align-items:center;justify-content:center;z-index:20;font-size:14px;color:var(--acc);font-weight:600}\n  .load.hidden{display:none}\n  .badge{font-size:10px;padding:1px 6px;border-radius:5px;background:var(--panel2);border:1px solid var(--line);color:var(--mut)}\n`,\n  render(d2){\n    return `<div class=\"simroot\"><header>\n  <h1>Simulador de Realocação de Setores</h1>\n  <select id=\"sectorPick\" style=\"min-width:230px\"></select>\n  <button class=\"ghost\" id=\"rand\">🎲 aleatório</button>\n  <button class=\"act\" id=\"go\">Fechar setor ▶</button>\n  <span class=\"pill live\" id=\"liveState\">carregando…</span>\n  <span class=\"pill ro\">read-only · nunca escreve</span>\n  <span class=\"status\" id=\"ds\"></span>\n</header>\n\n<div class=\"howto\">\n  <b>Como usar &nbsp;→&nbsp;</b>\n  <span><b>1.</b> Escolha um setor pra <b>fechar</b> (ou 🎲).</span>\n  <span><b>2.</b> O simulador cruza o snapshot local (arquivos carregados, sem Databricks), acha os <b>setores vizinhos</b> (geografia, cruzando estados) e sugere o destino de cada município.</span>\n  <span><b>3.</b> Clique <b style=\"color:var(--acc)\">★ Modelo ÓTIMO</b> p/ minimizar quebra de relação, ou ajuste no <b>mapa</b>/botões.</span>\n  <span><b>4.</b> Veja o impacto <b>antes → depois</b> no relatório.</span>\n</div>\n<div class=\"wrap\">\n  <div>\n    <div class=\"card\">\n      <div style=\"display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:9px\">\n        <h2 id=\"mapTitle\" style=\"margin:0\">Mapa — clique num município p/ trocar o setor destino</h2>\n        <div class=\"toggle\" id=\"viewToggle\" title=\"ASIS = estrutura atual (setor inteiro) · TOBE = após a redistribuição\">\n          <button data-v=\"asis\">◧ ASIS (hoje)</button>\n          <button data-v=\"tobe\" class=\"on\">◨ TOBE (simulação)</button>\n        </div>\n      </div>\n      <svg id=\"map\" viewBox=\"0 0 700 500\" preserveAspectRatio=\"xMidYMid meet\"></svg>\n      <div class=\"legend\" id=\"legend\"></div>\n      <div class=\"note\">Coloridos = municípios do setor fechado (cor = destino). Cinza = vizinhos (contexto). Shapes reais IBGE/tbrugz.</div>\n    </div>\n    <div class=\"card\">\n      <div class=\"tabs\">\n        <button data-tab=\"matrix\" class=\"on\">Matriz ASIS</button>\n        <button data-tab=\"report\">Relatório de impacto (antes/depois)</button>\n      </div>\n      <div id=\"tab-matrix\"><div class=\"scroll\"><table id=\"matrix\"></table></div></div>\n      <div id=\"tab-report\" class=\"hidden\"></div>\n    </div>\n  </div>\n\n  <div>\n    <div class=\"card\">\n      <div class=\"toolbar\" style=\"margin-bottom:11px\">\n        <button class=\"ghost\" id=\"optim\" title=\"testa todas alocações contíguas e escolhe a que equilibra a carga entre setores + minimiza a quebra (evita empilhar tudo num setor)\">★ Modelo ÓTIMO (equilíbrio + quebra)</button>\n        <button class=\"ghost\" id=\"autoTobe\" title=\"preenche cada município pelo setor vizinho mais próximo (geografia)\">TOBE por adjacência</button>\n        <span class=\"badge\" id=\"optBadge\"></span>\n      </div>\n      <div class=\"note\" style=\"margin:-4px 0 10px\"><b style=\"color:var(--ink)\">★ ÓTIMO</b> = equilibra a carga entre os setores <b>e</b> minimiza a quebra (contíguo; não empilha tudo num setor). <b style=\"color:var(--ink)\">TOBE</b> = sugestão por vizinhança. Indicadores atualizam ao vivo.</div>\n      <div class=\"kpis\" id=\"kpis\"></div>\n      <div id=\"contig\"></div>\n      <h2>Span por setor (CBs) — base + entrantes</h2>\n      <div id=\"spans\"></div>\n      <div style=\"display:flex;justify-content:space-between;align-items:center;margin:14px 0 7px\">\n        <h2 style=\"margin:0\">CBs deslocadas</h2>\n        <div class=\"toggle\" id=\"policy\">\n          <button data-p=\"fora\" class=\"on\">= fora do setor</button>\n          <button data-p=\"quebra\">= quebra de relação</button>\n        </div>\n      </div>\n      <div class=\"note\" id=\"policyNote\"></div>\n    </div>\n\n    <div class=\"card\">\n      <h2>Municípios → setor destino</h2>\n      <div class=\"note\" style=\"margin:-4px 0 9px\">Clique no <b>código do setor</b> pra mandar o município <b>inteiro</b> pra lá (regra de negócio). A cor acompanha o mapa.</div>\n      <div id=\"muniList\"></div>\n    </div>\n\n    <div class=\"card\">\n      <h2>Líderes — destino e CBs deslocadas</h2>\n      <div class=\"note\" style=\"margin:-4px 0 9px\"><b>Home</b> = setor que ficou com a maioria das CBs da líder. <b style=\"color:var(--closing)\">Deslocadas</b> = CBs dela que caíram noutro setor (viram \"fora do setor\" ou \"quebra de relação\").</div>\n      <div class=\"scroll\"><table id=\"leaders\"></table></div>\n    </div>\n\n    <div class=\"card\">\n      <div class=\"toolbar\" style=\"justify-content:space-between\">\n        <h2 style=\"margin:0\">Plano de migração</h2>\n        <div><button class=\"ghost\" id=\"copyPlan\">Copiar</button> <button class=\"ghost\" id=\"csvPlan\">CSV</button></div>\n      </div>\n      <div id=\"plan\" class=\"note\"></div>\n    </div>\n  </div>\n</div>\n\n<div class=\"tip\" id=\"tip\"></div>\n<div class=\"load hidden\" id=\"loader\">carregando…</div></div>`;\n  },\n  mounted(d2, root){\n\nconst PALETTE=['#8b7fd6','#d1b048','#67bd5b','#5aa9d6','#d67fa8','#c98a4b'];\nconst CLOSE_COLOR='#475569';\nconst UF_CODE={RO:'11',AC:'12',AM:'13',RR:'14',PA:'15',AP:'16',TO:'17',MA:'21',PI:'22',CE:'23',RN:'24',PB:'25',PE:'26',AL:'27',SE:'28',BA:'29',MG:'31',ES:'32',RJ:'33',SP:'35',PR:'41',SC:'42',RS:'43',MS:'50',MT:'51',GO:'52',DF:'53'};\nconst UF_BORDERS={AC:['AM','RO'],AL:['BA','PE','SE'],AM:['AC','RO','RR','PA','MT'],AP:['PA'],\n BA:['AL','SE','PE','PI','TO','GO','MG','ES'],CE:['PI','PB','PE','RN'],DF:['GO','MG'],\n ES:['BA','MG','RJ'],GO:['DF','MT','MS','MG','BA','TO'],MA:['PA','TO','PI'],\n MG:['BA','GO','DF','MS','SP','RJ','ES'],MS:['MT','GO','MG','SP','PR'],MT:['RO','AM','PA','TO','GO','MS'],\n PA:['AP','AM','MT','TO','MA','RR'],PB:['RN','CE','PE'],PE:['PB','CE','PI','BA','AL'],\n PI:['MA','TO','BA','PE','CE'],PR:['SP','MS','SC'],RJ:['ES','MG','SP'],RN:['CE','PB'],\n RO:['AC','AM','MT'],RR:['AM','PA'],RS:['SC'],SC:['PR','RS'],SE:['BA','AL'],\n SP:['MG','RJ','PR','MS'],TO:['PA','MA','PI','BA','GO','MT']};\nconst $=s=>root.querySelector(s);\nconst fmt=n=>n==null?'–':Math.round(+n).toLocaleString('pt-BR');\nconst money=n=>{n=+n||0;return n>=1e6?'R$'+(n/1e6).toFixed(1)+'M':n>=1e3?'R$'+(n/1e3).toFixed(0)+'k':'R$'+Math.round(n)};\nconst norm=s=>(s||'').normalize('NFKD').replace(/[̀-ͯ]/g,'').toUpperCase().replace(/[^A-Z0-9]+/g,' ').trim();\nconst NIVEIS=['Bronze','Cristal','Prata','Ouro','Diamante','Diamante+'];\n\nlet DATA=null;\nasync function loadData(){\n  const res=await d2.rush('secDataFilesProxy').invoke({});\n  DATA=res && res.data ? res.data : res;\n}\n\nlet S={sector:null, matrix:[], munis:{}, leaders:[], color:{}, dest:[], assign:{}, allowed:{},\n       geoFeat:{}, nbrFeat:[], baseMetrics:{}, baseLevel:{}, levelByMuni:{},\n       policy:'fora', selected:null, sectorsList:[], view:'tobe'};\nconst closingCbs=()=>Object.entries(S.munis).filter(([m])=>m!=='__OUTROS__').reduce((a,[,o])=>a+o.cbs,0);\n\nasync function geoUF(uf){const code=UF_CODE[uf]||uf;const r=await fetch('https://raw.githubusercontent.com/tbrugz/geodata-br/master/geojson/geojs-'+code+'-mun.json');if(!r.ok)throw new Error('geo '+uf);return r.json();}\nconst loader=(on,t)=>{const l=$('#loader');l.classList.toggle('hidden',!on);if(t)l.textContent=t;};\n\nfunction buildAdj(features){\n  const pts=f=>{const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates],s=new Set();\n    ps.forEach(poly=>poly.forEach(ring=>ring.forEach(p=>s.add(Math.round(p[0]*1000)/1000+','+Math.round(p[1]*1000)/1000))));return s;};\n  const feat={},idx={};\n  features.forEach(f=>{const nm=norm(f.properties.name||f.properties.NOME||f.properties.nome);feat[nm]={f,pts:pts(f)};});\n  for(const nm in feat) feat[nm].pts.forEach(p=>(idx[p]=idx[p]||[]).push(nm));\n  const nbrs=nm=>{const c={};feat[nm].pts.forEach(p=>idx[p].forEach(o=>{if(o!==nm)c[o]=(c[o]||0)+1;}));\n    return Object.entries(c).filter(([,n])=>n>=2).map(([o,n])=>({o,w:n}));};\n  return {feat,nbrs};\n}\nfunction centroidLL(f){let x=0,y=0,n=0;const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n  ps.forEach(poly=>poly[0].forEach(p=>{x+=p[0];y+=p[1];n++;}));return[x/n,y/n];}\n\nasync function closeSector(sec){\n  loader(true,'fechando setor '+sec+' — cruzando snapshot local…');\n  $('#liveState').textContent='● processando';$('#ds').classList.remove('err');\n  try{\n    S.sector=sec;\n    const summary=DATA.summary[sec];\n    const muniGrupoRows=DATA.muniGrupo[sec]||[];\n    const muniNivelRows=DATA.muniNivel[sec]||[];\n\n    S.matrix=muniGrupoRows.map(r=>({municipio:r.municipio, grupo:r.grupo, lider:r.lider, cbs:r.cbs, receita:r.receita, pontos:r.pontos, ativas:r.ativas}));\n    S.levelByMuni={};\n    muniNivelRows.forEach(r=>{ (S.levelByMuni[r.municipio]=S.levelByMuni[r.municipio]||{})[r.nivel]=r.cbs; });\n\n    const domUF=summary?.uf||'PR';\n\n    const ufs=[domUF,...(UF_BORDERS[domUF]||[])];\n    loader(true,'malhas '+ufs.join('/')+' + adjacência…');\n    const geos=await Promise.all(ufs.map(u=>geoUF(u).catch(()=>null)));\n    const allFeat=geos.filter(Boolean).flatMap(g=>g.features);\n    const adj=buildAdj(allFeat);\n    const totBy={}; S.matrix.forEach(r=>{const n=norm(r.municipio);totBy[n]=(totBy[n]||0)+ +r.cbs;});\n    const totalSec=Object.values(totBy).reduce((a,b)=>a+b,0);\n    const floor=Math.max(10, totalSec*0.008);\n    const footprint=new Set(Object.entries(totBy).filter(([,c])=>c>=floor).sort((a,b)=>b[1]-a[1]).slice(0,15).map(([m])=>m));\n    const closingNames=[...footprint].filter(n=>adj.feat[n]);\n    const closingSet=new Set(closingNames);\n    S.geoFeat={};closingNames.forEach(n=>S.geoFeat[n]=adj.feat[n].f);\n    const extVotes={}; const nbrNames=new Set();\n    closingNames.forEach(n=>{adj.nbrs(n).forEach(({o,w})=>{ if(!closingSet.has(o)){ nbrNames.add(o); (extVotes[n]=extVotes[n]||[]).push({o,w}); } });});\n    S.nbrFeat=[...nbrNames].map(n=>adj.feat[n].f);\n\n    const domSecOf={};\n    nbrNames.forEach(nb=>{\n      const cands=(DATA.muniSetor[nb]||[]).filter(r=>String(r.setor)!==String(sec));\n      if(cands.length) domSecOf[nb]=String(cands[0].setor);\n    });\n\n    S.allowed={}; const secWeight={};\n    closingNames.forEach(n=>{const v={};(extVotes[n]||[]).forEach(({o,w})=>{const s=domSecOf[o];if(s){v[s]=(v[s]||0)+w;secWeight[s]=(secWeight[s]||0)+w;}});S.allowed[n]=v;});\n    S.dest=Object.keys(secWeight).sort((a,b)=>secWeight[b]-secWeight[a]).slice(0,6);\n    if(!S.dest.length){\n      S.dest=[...new Set(Object.values(domSecOf))].slice(0,6);\n      closingNames.forEach(n=>{if(!Object.keys(S.allowed[n]).length){S.dest.forEach(s=>S.allowed[n][s]=1);}});\n    }\n    S.color={};S.dest.forEach((s,i)=>S.color[s]=PALETTE[i%PALETTE.length]);\n    const acc={};Object.entries(domSecOf).forEach(([nb,s])=>{if(!S.dest.includes(s))return;const f=adj.feat[nb]?.f;if(!f)return;const c=centroidLL(f);acc[s]=acc[s]||[0,0,0];acc[s][0]+=c[0];acc[s][1]+=c[1];acc[s][2]++;});\n    S.secCentroid={};S.dest.forEach(s=>{if(acc[s])S.secCentroid[s]=[acc[s][0]/acc[s][2],acc[s][1]/acc[s][2]];});\n    S.muniCentroid={};closingNames.forEach(n=>{S.muniCentroid[n]=centroidLL(adj.feat[n].f);});\n    S.closeAdj={};closingNames.forEach(n=>{S.closeAdj[n]=adj.nbrs(n).filter(({o})=>closingSet.has(o)).map(({o})=>o);});\n    const comp={};let ci=0;closingNames.forEach(n=>{if(comp[n]!=null)return;const st=[n];comp[n]=ci;while(st.length){const x=st.pop();(S.closeAdj[x]||[]).forEach(o=>{if(comp[o]==null){comp[o]=ci;st.push(o);}});}ci++;});\n    const compAllowed={};closingNames.forEach(n=>{const cid=comp[n];(compAllowed[cid]=compAllowed[cid]||new Set());Object.keys(S.allowed[n]||{}).forEach(s=>compAllowed[cid].add(s));});\n    S.reachable={};closingNames.forEach(n=>{S.reachable[n]=[...(compAllowed[comp[n]]||[])].filter(s=>S.dest.includes(s));});\n\n    S.baseMetrics={}; S.baseLevel={};\n    S.dest.forEach(s=>{\n      const b=DATA.summary[s];\n      S.baseMetrics[s]=b?{cbs:b.cbs,ativas:b.ativas,receita:b.receita,pontos:b.pontos}:{cbs:0,ativas:0,receita:0,pontos:0};\n      const lv={};(DATA.nivel[s]||[]).forEach(r=>{lv[r.nivel]=r.cbs;});\n      S.baseLevel[s]=lv;\n    });\n\n    aggregate();\n    S.assign=optimize();\n    S.selected=null;\n    const dt=new Date().toLocaleTimeString('pt-BR');\n    $('#ds').textContent=`setor ${sec} · UF ${domUF} · ${closingNames.length} munic. · snapshot local · ${dt}`;\n    $('#liveState').textContent='● pronto (offline)';\n    $('#mapTitle').textContent=`Fechando setor ${sec} — clique num município p/ trocar destino`;\n    renderAll();\n  }catch(e){ $('#ds').textContent='erro: '+e.message; $('#ds').classList.add('err'); $('#liveState').textContent='● erro'; }\n  finally{ loader(false); }\n}\n\nfunction aggregate(){\n  const munis={}, leaders={};\n  const acc=(o,r)=>{o.cbs+=+r.cbs;o.receita+=+r.receita||0;o.pontos+=+r.pontos||0;o.ativas+=+r.ativas||0;};\n  const blank=()=>({cbs:0,receita:0,pontos:0,ativas:0});\n  S.matrix.forEach(r=>{\n    const m=norm(r.municipio); if(!S.geoFeat[m]) {r._out=true;}\n    const mm=r._out?'__OUTROS__':m;\n    munis[mm]=munis[mm]||{...blank(),name:mm}; acc(munis[mm],r);\n    const g=String(r.grupo); leaders[g]=leaders[g]||{grupo:g,lider:r.lider,total:0,byMuni:{}};\n    leaders[g].total+=+r.cbs; leaders[g].byMuni[mm]=(leaders[g].byMuni[mm]||0)+ +r.cbs;\n  });\n  S.munis=munis; S.leaders=Object.values(leaders).sort((a,b)=>b.total-a.total);\n}\nfunction autoAssign(){\n  const a={};\n  Object.keys(S.munis).forEach(m=>{ if(m==='__OUTROS__')return;\n    const v=S.allowed[m]||{}; const votes=Object.keys(v);\n    if(votes.length){ let best=votes[0],bw=-1; for(const s of votes) if(v[s]>bw){bw=v[s];best=s;} a[m]=best; }\n    else { const c=S.muniCentroid[m]; let best=S.dest[0],bd=Infinity;\n      for(const s of S.dest){ const sc=S.secCentroid[s]; if(!sc)continue; const d=(c[0]-sc[0])**2+(c[1]-sc[1])**2; if(d<bd){bd=d;best=s;} }\n      a[m]=best; }\n  });\n  return a;\n}\nfunction deslocFor(assign){\n  let d=0;\n  S.leaders.forEach(L=>{const byS={};let tot=0;\n    for(const [m,c] of Object.entries(L.byMuni)){if(m==='__OUTROS__')continue;const s=assign[m];if(s){byS[s]=(byS[s]||0)+c;tot+=c;}}\n    const home=S.dest.reduce((a,s)=>(byS[s]||0)>(byS[a]||0)?s:a,S.dest[0]);\n    d+=tot-(byS[home]||0);});\n  return d;\n}\nfunction spanSpread(assign){\n  const inc={};S.dest.forEach(s=>inc[s]=0);\n  Object.entries(S.munis).forEach(([m,o])=>{if(m==='__OUTROS__')return;if(assign[m])inc[assign[m]]+=o.cbs;});\n  const sp=S.dest.map(s=>(+ (S.baseMetrics[s]?.cbs||0))+inc[s]);\n  return Math.max(...sp)-Math.min(...sp);\n}\nfunction contigBad(assign){\n  const bySec={}; for(const m in assign){(bySec[assign[m]]=bySec[assign[m]]||[]).push(m);}\n  const bad=[];\n  for(const s in bySec){const grp=bySec[s],inGrp=new Set(grp);\n    const seeds=grp.filter(m=>(S.allowed[m]||{})[s]);\n    if(!seeds.length){bad.push(s);continue;}\n    const seen=new Set(seeds),st=[...seeds];\n    while(st.length){const m=st.pop();(S.closeAdj[m]||[]).forEach(o=>{if(inGrp.has(o)&&!seen.has(o)){seen.add(o);st.push(o);}});}\n    if(seen.size!==grp.length)bad.push(s);}\n  return bad;\n}\nfunction maxSpanOf(a){const inc={};S.dest.forEach(s=>inc[s]=0);Object.entries(S.munis).forEach(([m,o])=>{if(m==='__OUTROS__')return;if(a[m])inc[a[m]]+=o.cbs;});return Math.max(...S.dest.map(s=>(+ (S.baseMetrics[s]?.cbs||0))+inc[s]));}\nconst OPT_K=1.5;\nfunction optScore(a){return maxSpanOf(a)+OPT_K*deslocFor(a);}\nfunction optimize(){\n  const ms=Object.keys(S.munis).filter(m=>m!=='__OUTROS__');\n  const opts=ms.map(m=>{const al=(S.reachable[m]&&S.reachable[m].length)?S.reachable[m]:S.dest.slice();return al;});\n  const combos=opts.reduce((a,o)=>a*o.length,1);\n  if(combos>300000){ $('#optBadge').textContent='espaço grande ('+fmt(combos)+') — heurística'; return greedy(ms,opts); }\n  let best=null,bestSc=Infinity,bestD=Infinity,valid=0;\n  const rec=(i,cur)=>{ if(i===ms.length){ if(contigBad(cur).length) return; valid++; const sc=optScore(cur),d=deslocFor(cur); if(sc<bestSc||(sc===bestSc&&d<bestD)){bestSc=sc;bestD=d;best={...cur};}return;}\n    for(const s of opts[i]){cur[ms[i]]=s;rec(i+1,cur);} };\n  rec(0,{});\n  if(!best){ $('#optBadge').textContent='sem solução contígua — heurística'; return greedy(ms,opts); }\n  $('#optBadge').textContent=`ótimo · pico ${fmt(maxSpanOf(best))} CBs · quebra ${fmt(bestD)} · ${fmt(valid)}/${fmt(combos)} contíguas`;\n  return best;\n}\nfunction greedy(ms,opts){const a={};ms.forEach((m,i)=>{let best=opts[i][0],bd=Infinity;for(const s of opts[i]){a[m]=s;const d=optScore(a)+(contigBad(a).length?1e9:0);if(d<bd){bd=d;best=s;}}a[m]=best;});return a;}\n\nfunction compute(){\n  const inc={},gained={};S.dest.forEach(s=>{inc[s]=0;gained[s]=0;});\n  Object.entries(S.munis).forEach(([m,o])=>{if(m==='__OUTROS__')return;const s=S.assign[m];if(s)inc[s]+=o.cbs;});\n  let desloc=0;\n  const L2=S.leaders.map(L=>{const byS={};S.dest.forEach(s=>byS[s]=0);let tot=0;\n    for(const [m,c] of Object.entries(L.byMuni)){if(m==='__OUTROS__')continue;const s=S.assign[m];if(s){byS[s]+=c;tot+=c;}}\n    const home=S.dest.reduce((a,s)=>byS[s]>byS[a]?s:a,S.dest[0]);const dl=tot-byS[home];desloc+=dl;gained[home]++;\n    return {...L,byS,home,desloc:dl,assignedTotal:tot};});\n  const span={};S.dest.forEach(s=>span[s]=(+ (S.baseMetrics[s]?.cbs||0))+inc[s]);\n  return {inc,gained,desloc,leaders:L2,span};\n}\nfunction renderAll(){drawMap();renderKpis();renderContig();renderSpans();renderMuni();renderLeaders();renderMatrix();renderPolicy();renderReport();renderPlan();}\nfunction renderContig(){const el=$('#contig');if(!el)return;\n  if(S.view==='asis'){el.innerHTML='';return;}\n  const bad=contigBad(S.assign);\n  el.innerHTML=bad.length\n    ?`<div class=\"note\" style=\"color:var(--closing);font-weight:600;margin:-2px 0 6px\">⚠ Contiguidade quebrada em ${bad.join(', ')} — município(s) desconectado(s) do território do setor. Use ★ ÓTIMO ou reassocie no mapa.</div>`\n    :`<div class=\"note\" style=\"color:var(--ok);margin:-2px 0 6px\">✓ Contíguo — cada setor forma uma região conectada.</div>`;\n}\nfunction renderKpis(){const c=compute();\n  if(S.view==='asis'){const mc=Object.keys(S.munis).filter(m=>m!=='__OUTROS__').length;\n    $('#kpis').innerHTML=[\n      {n:fmt(closingCbs()),l:`CBs no setor ${S.sector}`,t:'Total de CBs cadastradas no setor hoje (estrutura atual).'},\n      {n:fmt(mc),l:'Municípios do setor',t:'Municípios que compõem o território do setor hoje.'},\n      {n:fmt(S.leaders.length),l:'Líderes (grupos)',t:'Líderes ativas dentro do setor hoje.'},\n      {n:fmt(S.dest.length),l:'Setores adjacentes',t:'Setores vizinhos que poderiam receber os municípios se o setor fechar.'},\n    ].map(k=>`<div class=\"kpi\" title=\"${k.t}\"><div class=\"n\">${k.n}</div><div class=\"l\">${k.l}</div></div>`).join('');\n    return;}\n  const sv=S.dest.map(s=>c.span[s]);const spread=Math.max(...sv)-Math.min(...sv);\n  const zero=S.dest.filter(s=>c.gained[s]===0);const totCbs=Object.entries(S.munis).filter(([m])=>m!=='__OUTROS__').reduce((a,[,o])=>a+o.cbs,0);\n  $('#kpis').innerHTML=[\n    {n:fmt(c.desloc),l:S.policy==='fora'?'CBs fora do setor':'CBs c/ quebra de relação',cls:'warn',t:'CBs cuja líder acabou noutro setor. Menor = menos ruptura. É o custo humano da migração.'},\n    {n:fmt(spread),l:'Desequilíbrio (máx−mín CBs)',cls:spread>800?'warn':'ok',t:'Diferença de tamanho entre o maior e o menor setor depois. Menor = carga mais equilibrada entre as GNs.'},\n    {n:fmt(totCbs),l:'CBs realocadas',cls:'',t:`Total de CBs do setor ${S.sector} sendo redistribuídas para os setores vizinhos.`},\n    {n:zero.length?zero.join(', '):'nenhum',l:'Setor sem líder herdada',cls:zero.length?'warn':'ok',t:'Setor que recebe município(s) mas nenhuma líder — as CBs de lá terão que se ligar a uma líder pré-existente do setor.'},\n  ].map(k=>`<div class=\"kpi ${k.cls}\" title=\"${k.t}\"><div class=\"n\">${k.n}</div><div class=\"l\">${k.l}</div></div>`).join('');\n}\nfunction renderSpans(){const c=compute();\n  if(S.view==='asis'){const items=[{s:S.sector,val:closingCbs(),color:CLOSE_COLOR}].concat(S.dest.map(s=>({s,val:+(S.baseMetrics[s]?.cbs||0),color:S.color[s]})));\n    const max=Math.max(...items.map(x=>x.val),1);\n    $('#spans').innerHTML=items.map(x=>`<div class=\"bar-row\"><div class=\"tag\" style=\"color:${x.color}\">${x.s}</div><div class=\"bar-track\">\n      <div class=\"bar-inc\" style=\"width:${x.val/max*100}%;background:${x.color}\"></div></div>\n      <div class=\"bar-val\">${fmt(x.val)}</div></div>`).join('')\n      +`<div class=\"note\">Estrutura atual — o setor <b>${S.sector}</b> ainda existe. Alterne pra <b>TOBE</b> pra ver a redistribuição.</div>`;\n    return;}\n  const max=Math.max(...S.dest.map(s=>c.span[s]),1);\n  $('#spans').innerHTML=S.dest.map(s=>{const base=+(S.baseMetrics[s]?.cbs||0),i=c.inc[s];\n    return `<div class=\"bar-row\"><div class=\"tag\" style=\"color:${S.color[s]}\">${s}</div><div class=\"bar-track\">\n      <div class=\"bar-base\" style=\"width:${base/max*100}%;background:${S.color[s]}\"></div>\n      <div class=\"bar-inc\" style=\"width:${i/max*100}%;background:${S.color[s]}\"></div></div>\n      <div class=\"bar-val\">${fmt(c.span[s])} <span style=\"opacity:.6\">(+${fmt(i)})</span></div></div>`;}).join('')\n    +`<div class=\"note\">Claro = base atual · cheio = CBs entrantes do ${S.sector}.</div>`;\n}\nfunction renderMuni(){const c=compute();const ms=Object.keys(S.munis).filter(m=>m!=='__OUTROS__').sort((a,b)=>S.munis[b].cbs-S.munis[a].cbs);\n  if(S.view==='asis'){$('#muniList').innerHTML=ms.map(m=>`<div class=\"muni-item\"><div class=\"nm\">${m}</div><div class=\"ct\">${fmt(S.munis[m].cbs)} CBs</div><span class=\"badge\" style=\"background:${CLOSE_COLOR};color:#fff;border-color:${CLOSE_COLOR}\">setor ${S.sector}</span></div>`).join('')\n      +`<div class=\"note\">Hoje todos pertencem ao setor <b>${S.sector}</b>. No <b>TOBE</b> cada um vira botões de destino.</div>`;return;}\n  $('#muniList').innerHTML=ms.map(m=>`<div class=\"muni-item\"><div class=\"nm\">${m}</div><div class=\"ct\">${fmt(S.munis[m].cbs)} CBs</div>\n    <div class=\"seg\" data-m=\"${m}\">${S.dest.map(s=>`<button data-s=\"${s}\" class=\"${S.assign[m]===s?'on':''}\" style=\"${S.assign[m]===s?'background:'+S.color[s]:''}\">${s}</button>`).join('')}</div></div>`).join('')\n    +(S.munis.__OUTROS__?`<div class=\"note\">+ ${fmt(S.munis.__OUTROS__.cbs)} CBs em municípios sem shape na UF (tratar à parte).</div>`:'');\n  $('#muniList').querySelectorAll('.seg button').forEach(b=>b.onclick=()=>{S.assign[b.parentElement.dataset.m]=b.dataset.s;renderAll();});\n}\nfunction renderLeaders(){const c=compute();\n  if(S.view==='asis'){$('#leaders').innerHTML=`<thead><tr><th>Líder</th><th>Grupo</th><th>CBs</th><th>Setor</th></tr></thead><tbody>`\n    +S.leaders.map(L=>`<tr><td>${(L.lider||'').split(' ').slice(0,2).join(' ')}</td><td>${L.grupo}</td><td>${fmt(L.total)}</td><td><span class=\"home\" style=\"background:${CLOSE_COLOR}\"></span>${S.sector}</td></tr>`).join('')\n    +`<tr style=\"font-weight:700\"><td>TOTAL</td><td></td><td>${fmt(S.leaders.reduce((a,L)=>a+L.total,0))}</td><td></td></tr></tbody>`;return;}\n  $('#leaders').innerHTML=`<thead><tr><th>Líder</th><th>CBs</th><th>Home</th>${S.dest.map(s=>`<th>${s}</th>`).join('')}<th>Desloc.</th></tr></thead><tbody>`\n   +c.leaders.map(L=>`<tr><td>${(L.lider||'').split(' ').slice(0,2).join(' ')}</td><td>${fmt(L.total)}</td>\n     <td><span class=\"home\" style=\"background:${S.color[L.home]}\"></span>${L.home}</td>\n     ${S.dest.map(s=>`<td class=\"cell\" style=\"${s===L.home?'color:'+S.color[s]+';font-weight:700':''}\">${L.byS[s]||'·'}</td>`).join('')}\n     <td style=\"color:${L.desloc?'var(--closing)':'var(--mut)'};font-weight:${L.desloc?700:400}\">${L.desloc||'0'}</td></tr>`).join('')\n   +`<tr style=\"font-weight:700\"><td>TOTAL</td><td>${fmt(c.leaders.reduce((a,L)=>a+L.total,0))}</td><td></td>${S.dest.map(s=>`<td>${fmt(c.leaders.reduce((a,L)=>a+L.byS[s],0))}</td>`).join('')}<td style=\"color:var(--closing)\">${fmt(c.desloc)}</td></tr></tbody>`;\n}\nfunction renderMatrix(){const c=compute();const ms=Object.keys(S.munis).filter(m=>m!=='__OUTROS__').sort((a,b)=>S.munis[b].cbs-S.munis[a].cbs);\n  const cell=(m,g)=>{const r=S.matrix.find(x=>(x._out?'__OUTROS__':norm(x.municipio))===m&&String(x.grupo)===g);return r?+r.cbs:0;};\n  const mx=Math.max(...ms.flatMap(m=>S.leaders.map(L=>cell(m,L.grupo))),1);\n  $('#matrix').innerHTML=`<thead><tr><th>Município</th><th>→</th>${S.leaders.map(L=>`<th title=\"${L.lider}\">${L.grupo}</th>`).join('')}<th>Tot</th></tr></thead><tbody>`\n   +ms.map(m=>`<tr><td>${m}</td><td><span class=\"home\" style=\"background:${S.color[S.assign[m]]}\"></span>${S.assign[m]||'–'}</td>`\n     +S.leaders.map(L=>{const v=cell(m,L.grupo);return `<td class=\"cell\" style=\"${v?`background:rgba(229,181,63,${.08+v/mx*.5})`:''}\">${v||'·'}</td>`;}).join('')\n     +`<td style=\"font-weight:700\">${fmt(S.munis[m].cbs)}</td></tr>`).join('')+`</tbody>`;\n}\nfunction renderPolicy(){const c=compute();\n  $('#policyNote').innerHTML=S.policy==='fora'\n    ?`<b style=\"color:var(--acc)\">Mantendo vínculo:</b> ${fmt(c.desloc)} CBs seguem com a líder mas ficam <b>fisicamente noutro setor</b>. Preserva relação; suja geografia.`\n    :`<b style=\"color:var(--acc)\">Quebrando vínculo:</b> ${fmt(c.desloc)} CBs <b>re-vinculam a uma líder do setor destino</b>. Geografia limpa; rompe relação.`;\n}\nfunction renderPlan(){const c=compute();const ms=Object.keys(S.munis).filter(m=>m!=='__OUTROS__').sort((a,b)=>a.localeCompare(b));\n  S._plan=ms.map(m=>({municipio:m,cbs:S.munis[m].cbs,destino:S.assign[m]}));\n  $('#plan').innerHTML=`Setor <b>${S.sector}</b> → ${ms.map(m=>`${m}→<b style=\"color:${S.color[S.assign[m]]}\">${S.assign[m]}</b>`).join(' · ')}<br>\n   Política: <b>${S.policy==='fora'?'manter vínculo (CBs fora do setor)':'quebrar relação'}</b> · ${fmt(c.desloc)} CBs impactadas.`;\n}\nfunction renderReport(){const c=compute();\n  S.metric=S.metric||'cbs';\n  const metrics=[['cbs','CBs'],['ativas','Ativas'],['pontos','Pontos'],['receita','Receita','$']];\n  const KEYS=metrics.map(m=>m[0]);\n  const incBy={};S.dest.forEach(s=>incBy[s]=Object.fromEntries(KEYS.map(k=>[k,0])));\n  Object.entries(S.munis).forEach(([m,o])=>{if(m==='__OUTROS__')return;const s=S.assign[m];if(!s)return;KEYS.forEach(k=>incBy[s][k]+=o[k]||0);});\n  const closeTot={};KEYS.forEach(k=>closeTot[k]=Object.entries(S.munis).filter(([m])=>m!=='__OUTROS__').reduce((a,[,o])=>a+(o[k]||0),0));\n  const chartHTML=mk=>{\n    const isM=mk==='receita'; const fN=isM?money:fmt;\n    const data=S.dest.map(s=>({label:s,color:S.color[s],antes:+(S.baseMetrics[s]?.[mk]||0),depois:+(S.baseMetrics[s]?.[mk]||0)+(incBy[s]?.[mk]||0)}));\n    data.push({label:S.sector+'✕',color:'#c8104e',antes:closeTot[mk]||0,depois:0});\n    const max=Math.max(...data.flatMap(d=>[d.antes,d.depois]),1);\n    const W=720,H=290,base=238,top=42,maxH=base-top,n=data.length,gw=(W-16)/n;\n    let g='';\n    data.forEach((d,i)=>{const cx=8+gw*i+gw/2,bw=Math.min(30,gw*0.30);\n      const ah=d.antes/max*maxH,dh=d.depois/max*maxH,ax=cx-bw-2,dx=cx+2;\n      g+=`<rect x=\"${ax.toFixed(1)}\" y=\"${(base-ah).toFixed(1)}\" width=\"${bw.toFixed(1)}\" height=\"${ah.toFixed(1)}\" rx=\"2.5\" fill=\"${d.color}\" opacity=\"0.35\"/>`;\n      g+=`<rect x=\"${dx.toFixed(1)}\" y=\"${(base-dh).toFixed(1)}\" width=\"${bw.toFixed(1)}\" height=\"${dh.toFixed(1)}\" rx=\"2.5\" fill=\"${d.color}\"/>`;\n      g+=`<text x=\"${(ax+bw/2).toFixed(1)}\" y=\"${(base-ah-4).toFixed(1)}\" text-anchor=\"middle\" font-size=\"9\" fill=\"#9a9aa8\">${fN(d.antes)}</text>`;\n      g+=`<text x=\"${(dx+bw/2).toFixed(1)}\" y=\"${(base-dh-4).toFixed(1)}\" text-anchor=\"middle\" font-size=\"9.5\" font-weight=\"700\" fill=\"${d.color}\">${fN(d.depois)}</text>`;\n      const dp=d.antes?((d.depois-d.antes)/d.antes*100):(d.depois?100:0);\n      g+=`<text x=\"${cx.toFixed(1)}\" y=\"16\" text-anchor=\"middle\" font-size=\"10.5\" font-weight=\"700\" fill=\"${dp>=0?'#1f9d57':'#c8104e'}\">${dp>0?'+':''}${dp.toFixed(0)}%</text>`;\n      g+=`<text x=\"${cx.toFixed(1)}\" y=\"${base+16}\" text-anchor=\"middle\" font-size=\"10.5\" font-weight=\"700\" fill=\"#1c1c2b\">${d.label}</text>`;});\n    return `<svg viewBox=\"0 0 ${W} ${H}\" style=\"width:100%\"><line x1=\"8\" y1=\"${base}\" x2=\"${W-8}\" y2=\"${base}\" stroke=\"#e7e7ef\"/>${g}</svg>`;\n  };\n  let h=`<div style=\"display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap\">\n      <h2 style=\"margin:0\">Impacto antes → depois por setor</h2>\n      <select id=\"metricSel\">${metrics.map(m=>`<option value=\"${m[0]}\" ${m[0]===S.metric?'selected':''}>${m[1]}</option>`).join('')}</select></div>\n    <div class=\"note\" style=\"margin:2px 0 4px\">Barra clara = <b>antes</b> · barra cheia = <b>depois</b> (base + entrantes do ${S.sector}) · <b style=\"color:var(--acc)\">${S.sector}✕</b> = setor que fecha · % = variação</div>\n    ${chartHTML(S.metric)}`;\n  h+=`<h2 style=\"margin-top:16px\">Antes → Depois por setor receptor (snapshot atual)</h2>\n    <div class=\"scroll\"><table><thead><tr><th>Setor</th>${metrics.map(m=>`<th>${m[1]}</th>`).join('')}</tr></thead><tbody>`;\n  S.dest.forEach(s=>{const b=S.baseMetrics[s]||{};const inc=incBy[s];\n    h+=`<tr><td><span class=\"home\" style=\"background:${S.color[s]}\"></span>${s}</td>`+\n      metrics.map(([k])=>{const bv=+b[k]||0,av=bv+ (inc[k]||0);const d=bv?((av-bv)/bv*100):0;const f=metrics.find(x=>x[0]===k)[2]==='$'?money:fmt;\n        return `<td>${f(av)}<div class=\"delta ${d>=0?'up':'dn'}\">${d>0?'+':''}${d.toFixed(0)}%</div></td>`;}).join('')+`</tr>`;});\n  h+=`<tr style=\"opacity:.8\"><td>${S.sector} (sai)</td>`+metrics.map(([k,,t])=>`<td>${(t==='$'?money:fmt)(closeTot[k]||0)}</td>`).join('')+`</tr>`;\n  h+=`</tbody></table></div>`;\n  const incLvl={};S.dest.forEach(s=>incLvl[s]={});\n  Object.keys(S.munis).forEach(m=>{if(m==='__OUTROS__')return;const s=S.assign[m];if(!s)return;const lv=S.levelByMuni[m]||{};for(const nv in lv)incLvl[s][nv]=(incLvl[s][nv]||0)+lv[nv];});\n  h+=`<h2 style=\"margin-top:18px\">Consultoras por nível — antes → depois por setor receptor</h2>\n    <div class=\"scroll\"><table><thead><tr><th>Setor</th>${NIVEIS.map(n=>`<th>${n}</th>`).join('')}<th>Total</th></tr></thead><tbody>`;\n  S.dest.forEach(s=>{const base=S.baseLevel[s]||{},inc=incLvl[s]||{};let tb=0,ta=0;\n    h+=`<tr><td><span class=\"home\" style=\"background:${S.color[s]}\"></span>${s}</td>`+\n      NIVEIS.map(nv=>{const b=base[nv]||0,i=inc[nv]||0,a=b+i;tb+=b;ta+=a;\n        return `<td>${a?fmt(a):'·'}${i?`<div class=\"delta up\">+${fmt(i)}</div>`:''}</td>`;}).join('')+\n      `<td style=\"font-weight:700\">${fmt(ta)}${ta-tb?`<div class=\"delta up\">+${fmt(ta-tb)}</div>`:''}</td></tr>`;});\n  h+=`</tbody></table></div><div class=\"note\">Número = CBs por nível <b>depois</b> da migração · <span style=\"color:var(--ok)\">+N</span> = entrantes do ${S.sector}. Base + entrantes por nível.</div>`;\n  $('#tab-report').innerHTML=h;\n  const sel=$('#metricSel'); if(sel) sel.onchange=()=>{S.metric=sel.value; renderReport();};\n}\nfunction project(feats){let mnx=1/0,mny=1/0,mxx=-1/0,mxy=-1/0;\n  const each=cb=>feats.forEach(f=>{const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];ps.forEach(p=>p.forEach(r=>r.forEach(cb)));});\n  each(p=>{mnx=Math.min(mnx,p[0]);mxx=Math.max(mxx,p[0]);mny=Math.min(mny,p[1]);mxy=Math.max(mxy,p[1]);});\n  const mlat=(mny+mxy)/2,kx=Math.cos(mlat*Math.PI/180),W=700,H=500,pad=22;\n  const gw=(mxx-mnx)*kx,gh=(mxy-mny),sc=Math.min((W-2*pad)/gw,(H-2*pad)/gh);\n  const ox=(W-gw*sc)/2,oy=(H-gh*sc)/2;\n  return p=>[ox+(p[0]-mnx)*kx*sc, oy+(mxy-p[1])*sc];\n}\nfunction pathFor(f,pr){const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n  return ps.map(poly=>poly.map(r=>'M'+r.map(p=>{const q=pr(p);return q[0].toFixed(1)+','+q[1].toFixed(1);}).join('L')+'Z').join('')).join('');}\nfunction cxy(f,pr){let x=0,y=0,n=0;const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];ps.forEach(poly=>poly[0].forEach(p=>{const q=pr(p);x+=q[0];y+=q[1];n++;}));return[x/n,y/n];}\nfunction drawMap(){const c=compute();const NS='http://www.w3.org/2000/svg';const svg=$('#map');svg.innerHTML='';\n  const feats=[...S.nbrFeat,...Object.values(S.geoFeat)];if(!feats.length)return;const pr=project(feats);\n  S.nbrFeat.forEach(f=>{const p=document.createElementNS(NS,'path');p.setAttribute('d',pathFor(f,pr));p.setAttribute('class','nbr');svg.appendChild(p);\n    const ct=cxy(f,pr);const t=document.createElementNS(NS,'text');t.setAttribute('class','nlabel');t.setAttribute('x',ct[0]);t.setAttribute('y',ct[1]);t.textContent=(f.properties.name||'').slice(0,14);svg.appendChild(t);});\n  const asis=S.view==='asis';\n  Object.entries(S.geoFeat).forEach(([m,f])=>{const p=document.createElementNS(NS,'path');p.setAttribute('d',pathFor(f,pr));\n    p.setAttribute('class','muni'+(S.selected===m?' sel':''));p.setAttribute('fill',asis?CLOSE_COLOR:(S.color[S.assign[m]]||'#444'));\n    if(!asis) p.onclick=()=>{const i=S.dest.indexOf(S.assign[m]);S.assign[m]=S.dest[(i+1)%S.dest.length];S.selected=m;renderAll();};\n    p.style.cursor=asis?'default':'pointer';\n    p.onmousemove=e=>{const t=$('#tip');t.style.opacity=1;t.style.left=(e.clientX+12)+'px';t.style.top=(e.clientY+12)+'px';\n      t.innerHTML=asis?`<b>${m}</b><br>${fmt(S.munis[m]?.cbs||0)} CBs · setor ${S.sector}`:`<b>${m}</b><br>${fmt(S.munis[m]?.cbs||0)} CBs → ${S.assign[m]}<br><span style=\"color:var(--mut)\">clique p/ trocar</span>`;};\n    p.onmouseleave=()=>$('#tip').style.opacity=0;svg.appendChild(p);\n    const ct=cxy(f,pr);const t=document.createElementNS(NS,'text');t.setAttribute('class','mlabel');t.setAttribute('x',ct[0]);t.setAttribute('y',ct[1]);\n    t.innerHTML=`${m.slice(0,13)}<tspan x=\"${ct[0]}\" dy=\"11\" font-size=\"8.5\">${fmt(S.munis[m]?.cbs||0)}${asis?'':'·'+S.assign[m]}</tspan>`;svg.appendChild(t);});\n  $('#legend').innerHTML=asis\n    ?`<span><b style=\"background:${CLOSE_COLOR}\"></b>Setor ${S.sector} (estrutura atual — ${Object.keys(S.geoFeat).length} municípios)</span>`\n    :S.dest.map(s=>`<span><b style=\"background:${S.color[s]}\"></b>Setor ${s} — ${c.gained[s]} líder(es)</span>`).join('');\n}\nfunction loadSectors(){\n  S.sectorsList=Object.values(DATA.summary).map(s=>({setor:String(s.setor),uf:s.uf,cbs:+s.cbs,munis:+s.munis})).sort((a,b)=>+a.setor-+b.setor);\n  const sel=$('#sectorPick');sel.innerHTML=S.sectorsList.map(s=>`<option value=\"${s.setor}\">${s.setor} · ${s.uf||'?'} · ${fmt(s.cbs)} CBs · ${s.munis} munic.</option>`).join('');\n  sel.value='3487';\n}\n$('#go').onclick=()=>closeSector($('#sectorPick').value);\n$('#rand').onclick=()=>{const cand=S.sectorsList.filter(s=>s.munis>=3&&s.munis<=10&&s.cbs>300);const pick=(cand.length?cand:S.sectorsList)[Math.floor(Math.random()*(cand.length?cand.length:S.sectorsList.length))];$('#sectorPick').value=pick.setor;closeSector(pick.setor);};\n$('#optim').onclick=()=>{S.assign=optimize();renderAll();};\n$('#autoTobe').onclick=()=>{S.assign=autoAssign();$('#optBadge').textContent='TOBE por adjacência';renderAll();};\n$('#viewToggle').querySelectorAll('button').forEach(b=>b.onclick=()=>{S.view=b.dataset.v;$('#viewToggle').querySelectorAll('button').forEach(x=>x.classList.toggle('on',x===b));renderAll();});\n$('#policy').querySelectorAll('button').forEach(b=>b.onclick=()=>{S.policy=b.dataset.p;$('#policy').querySelectorAll('button').forEach(x=>x.classList.toggle('on',x===b));renderKpis();renderPolicy();renderPlan();});\nroot.querySelectorAll('.tabs button').forEach(b=>b.onclick=()=>{root.querySelectorAll('.tabs button').forEach(x=>x.classList.toggle('on',x===b));\n  ['matrix','report'].forEach(t=>$('#tab-'+t).classList.toggle('hidden',t!==b.dataset.tab));});\n$('#copyPlan').onclick=()=>{navigator.clipboard.writeText(JSON.stringify({setor:S.sector,politica:S.policy,plano:S._plan},null,2));$('#copyPlan').textContent='copiado ✓';setTimeout(()=>$('#copyPlan').textContent='Copiar',1500);};\n$('#csvPlan').onclick=()=>{const csv='municipio,cbs,setor_destino\\n'+S._plan.map(p=>`${p.municipio},${p.cbs},${p.destino}`).join('\\n');\n  const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv'}));a.download=`plano_migracao_setor_${S.sector}.csv`;a.click();};\n\n(async()=>{loader(true,'carregando snapshot local (dados dos arquivos, sem Databricks)…');try{await loadData();loadSectors();await closeSector('3487');}catch(e){$('#ds').textContent='erro init: '+e.message;}loader(false);})();\n\n  }\n}\n"}}}}}}}},{"_id":"6a713b47f8f3c5135cec6fd6","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"mj32g2gcm5nQXhRqJIhw6","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const UF_CODE={RO:'11',AC:'12',AM:'13',RR:'14',PA:'15',AP:'16',TO:'17',MA:'21',PI:'22',CE:'23',RN:'24',PB:'25',PE:'26',AL:'27',SE:'28',BA:'29',MG:'31',ES:'32',RJ:'33',SP:'35',PR:'41',SC:'42',RS:'43',MS:'50',MT:'51',GO:'52',DF:'53'};\nconst UF_BORDERS={AC:['AM','RO'],AL:['BA','PE','SE'],AM:['AC','RO','RR','PA','MT'],AP:['PA'],\n BA:['AL','SE','PE','PI','TO','GO','MG','ES'],CE:['PI','PB','PE','RN'],DF:['GO','MG'],\n ES:['BA','MG','RJ'],GO:['DF','MT','MS','MG','BA','TO'],MA:['PA','TO','PI'],\n MG:['BA','GO','DF','MS','SP','RJ','ES'],MS:['MT','GO','MG','SP','PR'],MT:['RO','AM','PA','TO','GO','MS'],\n PA:['AP','AM','MT','TO','MA','RR'],PB:['RN','CE','PE'],PE:['PB','CE','PI','BA','AL'],\n PI:['MA','TO','BA','PE','CE'],PR:['SP','MS','SC'],RJ:['ES','MG','SP'],RN:['CE','PB'],\n RO:['AC','AM','MT'],RR:['AM','PA'],RS:['SC'],SC:['PR','RS'],SE:['BA','AL'],\n SP:['MG','RJ','PR','MS'],TO:['PA','MA','PI','BA','GO','MT']};\nconst UF_NAMES={RO:'Rondônia',AC:'Acre',AM:'Amazonas',RR:'Roraima',PA:'Pará',AP:'Amapá',TO:'Tocantins',\n MA:'Maranhão',PI:'Piauí',CE:'Ceará',RN:'Rio Grande do Norte',PB:'Paraíba',PE:'Pernambuco',AL:'Alagoas',\n SE:'Sergipe',BA:'Bahia',MG:'Minas Gerais',ES:'Espírito Santo',RJ:'Rio de Janeiro',SP:'São Paulo',\n PR:'Paraná',SC:'Santa Catarina',RS:'Rio Grande do Sul',MS:'Mato Grosso do Sul',MT:'Mato Grosso',\n GO:'Goiás',DF:'Distrito Federal'};\nconst REGION_UFS={N:['RO','AC','AM','RR','PA','AP','TO'],NE:['MA','PI','CE','RN','PB','PE','AL','SE','BA'],\n CO:['MS','MT','GO','DF'],SE:['MG','ES','RJ','SP'],S:['PR','SC','RS']};\nconst REGION_NAMES={N:'Norte',NE:'Nordeste',CO:'Centro-Oeste',SE:'Sudeste',S:'Sul'};\nconst PALETTE=['#8b7fd6','#d1b048','#67bd5b','#5aa9d6','#d67fa8','#c98a4b'];\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%;box-sizing:border-box}\n    .wrap{position:relative;height:100%;display:flex;flex-direction:column;font:13px -apple-system,BlinkMacSystemFont,\"SF Pro Text\",Inter,sans-serif;background:transparent;overflow:hidden;border-radius:14px;border:1px solid rgba(0,0,0,.08);box-shadow:0 6px 20px rgba(0,0,0,.12)}\n    .stage{flex:1;min-height:0;position:relative;cursor:grab;touch-action:none}\n    .stage:active{cursor:grabbing}\n    svg{width:100%;height:100%;display:block}\n    .area{stroke:rgba(0,0,0,.2);stroke-width:.85;stroke-linejoin:round;stroke-linecap:round}\n    .wedge{stroke:rgba(255,255,255,.7);stroke-width:.5;stroke-linejoin:round;cursor:pointer;transition:filter .15s ease}\n    .wedge:hover{filter:brightness(1.16)}\n    .wedge.touchy{stroke-width:.9}\n    .lbl{fill:#1D1D1F;font-size:9px;font-weight:600;pointer-events:none;user-select:none;text-anchor:middle;paint-order:stroke;stroke:rgba(255,255,255,.7);stroke-width:.6px}\n    .legend{display:none!important;position:absolute;left:10px;bottom:10px;max-width:64%;max-height:70%;overflow:auto;flex-direction:column;gap:8px;padding:10px 12px;border-radius:12px;background:rgba(255,255,255,.78);-webkit-backdrop-filter:blur(30px) saturate(180%);backdrop-filter:blur(30px) saturate(180%);border:1px solid rgba(0,0,0,.08);font-size:11px;box-shadow:0 8px 24px rgba(0,0,0,.20)}\n    .legend .grp{display:flex;flex-direction:column;gap:3px}\n    .legend .grpname{font-weight:700;color:rgba(0,0,0,.5);font-size:10px;text-transform:uppercase;letter-spacing:.06em}\n    .legend .row{display:flex;align-items:center;gap:6px;color:#1D1D1F}\n    .legend b{width:10px;height:10px;border-radius:3px;display:inline-block;flex:0 0 auto;box-shadow:inset 0 0 0 1px rgba(0,0,0,.1)}\n    .tip{display:none!important;position:fixed;pointer-events:none;background:rgba(255,255,252,.95);-webkit-backdrop-filter:blur(20px) saturate(150%);backdrop-filter:blur(20px) saturate(150%);color:#1D1D1F;border:1px solid rgba(0,0,0,.08);border-radius:6px;padding:6px 9px;font-size:11px;opacity:0;transition:opacity .12s ease;box-shadow:0 2px 8px rgba(0,0,0,.15);z-index:9;max-width:220px}\n    .infobar{display:none!important;position:absolute;left:0;right:0;bottom:0;background:rgba(246,246,247,.85);-webkit-backdrop-filter:blur(20px) saturate(180%);backdrop-filter:blur(20px) saturate(180%);border-top:1px solid rgba(0,0,0,.1);color:#1D1D1F;padding:10px 14px;font-size:13px;display:flex;justify-content:space-between;align-items:center;gap:10px;transform:translateY(100%);transition:transform .18s ease;z-index:8}\n    .infobar.show{transform:translateY(0)}\n    .infobar .x{opacity:.5;cursor:pointer;padding:2px 7px;border-radius:5px;transition:background .12s ease,opacity .12s ease}\n    .infobar .x:hover{opacity:.9;background:rgba(0,0,0,.06)}\n    .load{position:absolute;inset:0;background:rgba(236,236,236,.94);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:5;color:rgba(0,0,0,.5);text-align:center;padding:16px;cursor:default;font-size:13px}\n    .load.err{cursor:pointer;text-decoration:underline;color:#FF3B30}\n    .empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:rgba(0,0,0,.5);text-align:center;padding:24px;font-size:13px}\n    .hidden{display:none}\n  `,\n  render(){\n    return `<div class=\"wrap\">\n      <div class=\"stage\"><svg id=\"map\" viewBox=\"0 0 700 500\" preserveAspectRatio=\"xMidYMid meet\"></svg></div>\n      <div class=\"legend hidden\" id=\"legend\"></div>\n      <div class=\"tip\" id=\"tip\"></div>\n      <div class=\"infobar\" id=\"infobar\"><span id=\"infobarText\"></span><span class=\"x\" id=\"infobarClose\">✕</span></div>\n      <div class=\"load\" id=\"loader\">carregando malhas do Brasil…</div>\n      <div class=\"empty hidden\" id=\"empty\">Nenhuma localização declarada — informe country_list, region_list, state_list, city_list ou municipality_list.</div>\n    </div>`;\n  },\n  mounted(d2, root){\n\n    const $ = s => root.querySelector(s);\n    const NS = 'http://www.w3.org/2000/svg';\n    const norm = s => (s||'').normalize('NFKD').replace(/[̀-ͯ]/g,'').toUpperCase().replace(/[^A-Z0-9]+/g,' ').trim();\n    const esc = s => (s==null?'':String(s)).replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));\n    const toArr = v => Array.isArray(v) ? v : [];\n\n    function matchUf(idOrName){\n      if(!idOrName) return null;\n      const up = String(idOrName).trim().toUpperCase();\n      if (UF_CODE[up]) return up;\n      const n = norm(idOrName);\n      const hit = Object.keys(UF_NAMES).find(code => norm(UF_NAMES[code]) === n);\n      return hit || null;\n    }\n    function matchRegion(idOrName){\n      if(!idOrName) return null;\n      const up = String(idOrName).trim().toUpperCase();\n      if (REGION_UFS[up]) return up;\n      const n = norm(idOrName);\n      const hit = Object.keys(REGION_NAMES).find(code => norm(REGION_NAMES[code]) === n);\n      return hit || null;\n    }\n\n    async function geoUF(uf){\n      const code = UF_CODE[uf] || uf;\n      const r = await fetch('https://raw.githubusercontent.com/tbrugz/geodata-br/master/geojson/geojs-'+code+'-mun.json');\n      if (!r.ok) throw new Error('geo ' + uf);\n      return r.json();\n    }\n    function ptsOf(f){\n      const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates], s=new Set();\n      ps.forEach(poly=>poly.forEach(ring=>ring.forEach(p=>s.add(Math.round(p[0]*1000)/1000+','+Math.round(p[1]*1000)/1000))));\n      return s;\n    }\n    function buildIndex(features){\n      const recs = features.map(f => ({\n        name: norm(f.properties.name || f.properties.NOME || f.properties.nome || ''),\n        label: f.properties.name || f.properties.NOME || f.properties.nome || '',\n        uf: f.__uf, f, pts: ptsOf(f)\n      }));\n      const ptIdx = {};\n      recs.forEach((r,i) => r.pts.forEach(p => (ptIdx[p]=ptIdx[p]||[]).push(i)));\n      const nbrsOf = i => {\n        const c={};\n        recs[i].pts.forEach(p => ptIdx[p].forEach(j => { if (j!==i) c[j]=(c[j]||0)+1; }));\n        return Object.entries(c).filter(([,n])=>n>=2).map(([j])=>+j);\n      };\n      const byUfName = {};\n      recs.forEach((r,i) => { byUfName[r.uf]=byUfName[r.uf]||{}; byUfName[r.uf][r.name]=i; });\n      const anyName = {};\n      recs.forEach((r,i) => { (anyName[r.name]=anyName[r.name]||[]).push(i); });\n      return { recs, nbrsOf, byUfName, anyName };\n    }\n    function project(feats){\n      let mnx=1/0,mny=1/0,mxx=-1/0,mxy=-1/0;\n      const each = cb => feats.forEach(f=>{const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];ps.forEach(p=>p.forEach(r=>r.forEach(cb)));});\n      each(p=>{mnx=Math.min(mnx,p[0]);mxx=Math.max(mxx,p[0]);mny=Math.min(mny,p[1]);mxy=Math.max(mxy,p[1]);});\n      const mlat=(mny+mxy)/2, kx=Math.cos(mlat*Math.PI/180), W=700,H=500,pad=22;\n      const gw=(mxx-mnx)*kx, gh=(mxy-mny), sc=Math.min((W-2*pad)/gw,(H-2*pad)/gh);\n      const ox=(W-gw*sc)/2, oy=(H-gh*sc)/2;\n      const pr = p=>[ox+(p[0]-mnx)*kx*sc, oy+(mxy-p[1])*sc];\n      pr.scale = sc;\n      return pr;\n    }\n    function pathFor(f,pr){\n      const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n      return ps.map(poly=>poly.map(r=>'M'+r.map(p=>{const q=pr(p);return q[0].toFixed(1)+','+q[1].toFixed(1);}).join('L')+'Z').join('')).join('');\n    }\n    function cxy(f,pr){\n      let x=0,y=0,n=0; const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n      ps.forEach(poly=>poly[0].forEach(p=>{const q=pr(p);x+=q[0];y+=q[1];n++;}));\n      return [x/n, y/n];\n    }\n    function ringsOf(f){\n      const g = f.geometry, ps = g.type==='MultiPolygon' ? g.coordinates : [g.coordinates];\n      const rings = [];\n      ps.forEach(poly => poly.forEach(ring => rings.push(ring)));\n      return rings;\n    }\n    const quant = p => [Math.round(p[0]*1000)/1000, Math.round(p[1]*1000)/1000];\n    const keyOf = p => p[0] + ',' + p[1];\n    // Dissolves the municipio polygons in `idxs` into their outer boundary (+ any real holes)\n    // by cancelling every edge shared by two included polygons — the border between two\n    // adjacent included municipios disappears, leaving only the union's true edge. Exact for\n    // this dataset because neighboring municipio files share literal boundary coordinates (the\n    // same tolerance ptsOf already relies on for adjacency).\n    function dissolveRings(idxs, recs){\n      if (idxs.length === 1) return ringsOf(recs[idxs[0]].f);\n      const edgeCount = new Map(), edges = [];\n      idxs.forEach(i => {\n        ringsOf(recs[i].f).forEach(ring => {\n          const q = ring.map(quant);\n          for (let k = 0; k < q.length - 1; k++){\n            const a = q[k], b = q[k+1], ka = keyOf(a), kb = keyOf(b);\n            if (ka === kb) continue;\n            const uk = ka < kb ? ka+'|'+kb : kb+'|'+ka;\n            edgeCount.set(uk, (edgeCount.get(uk)||0) + 1);\n            edges.push({ a, b, ka, kb, uk });\n          }\n        });\n      });\n      const kept = edges.filter(e => edgeCount.get(e.uk) === 1);\n      const byStart = new Map();\n      kept.forEach(e => {\n        if (!byStart.has(e.ka)) byStart.set(e.ka, []);\n        byStart.get(e.ka).push(e);\n      });\n      const used = new Set(), rings = [];\n      kept.forEach(seed => {\n        if (used.has(seed)) return;\n        const ring = [seed.a];\n        let cur = seed, guard = 0;\n        while (guard++ < 50000) {\n          used.add(cur);\n          ring.push(cur.b);\n          if (cur.kb === seed.ka) break;\n          const next = (byStart.get(cur.kb) || []).find(e => !used.has(e));\n          if (!next) break;\n          cur = next;\n        }\n        if (ring.length >= 4 && keyOf(ring[0]) === keyOf(ring[ring.length-1])) rings.push(ring);\n      });\n      return rings;\n    }\n    function pathFromRings(rings, pr){\n      return rings.map(ring => 'M'+ring.map(p=>{const q=pr(p);return q[0].toFixed(1)+','+q[1].toFixed(1);}).join('L')+'Z').join('');\n    }\n    function stripeClipRings(rings, pr, x0, x1){\n      let d = '';\n      rings.forEach(ring => {\n        let pts = ring.map(p => pr(p));\n        pts = clipHalfPlane(pts, (x) => x - x0);\n        if (pts.length) pts = clipHalfPlane(pts, (x) => x1 - x);\n        if (pts.length >= 3) d += 'M' + pts.map(q => q[0].toFixed(1) + ',' + q[1].toFixed(1)).join('L') + 'Z';\n      });\n      return d;\n    }\n    function mergeCentroid(idxs, pr, recs){\n      let x=0,y=0,n=0;\n      idxs.forEach(i=>{ const [cx,cy]=cxy(recs[i].f,pr); x+=cx; y+=cy; n++; });\n      return n ? [x/n, y/n] : [0,0];\n    }\n    function mergedBbox(idxs, pr, recs){\n      let minX=Infinity,minY=Infinity,maxX=-Infinity,maxY=-Infinity;\n      idxs.forEach(i => {\n        const g=recs[i].f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n        ps.forEach(poly=>poly.forEach(ring=>ring.forEach(p=>{\n          const q=pr(p);\n          minX=Math.min(minX,q[0]); maxX=Math.max(maxX,q[0]);\n          minY=Math.min(minY,q[1]); maxY=Math.max(maxY,q[1]);\n        })));\n      });\n      return [minX,minY,maxX,maxY];\n    }\n    // Sutherland-Hodgman clip against a straight half-plane (side(x,y)>=0 kept). A straight-line\n    // cut is exact for ANY simple polygon, convex or concave, so stacking bands never leaves a gap\n    // — unlike an angular wedge radiating from one apex, which can't \"see\" a concave shape's whole border.\n    function clipHalfPlane(ring, side){\n      const out = [];\n      const n = ring.length;\n      for (let i = 0; i < n; i++){\n        const curr = ring[i], prev = ring[(i - 1 + n) % n];\n        const sCurr = side(curr[0], curr[1]);\n        const sPrev = side(prev[0], prev[1]);\n        const currIn = sCurr >= -1e-9;\n        const prevIn = sPrev >= -1e-9;\n        if (currIn) {\n          if (!prevIn) out.push(intersectHalfPlane(prev, curr, side));\n          out.push(curr);\n        } else if (prevIn) {\n          out.push(intersectHalfPlane(prev, curr, side));\n        }\n      }\n      return out;\n    }\n    function intersectHalfPlane(p1, p2, side){\n      const s1 = side(p1[0], p1[1]), s2 = side(p2[0], p2[1]);\n      const t = s1 / (s1 - s2);\n      return [p1[0] + t * (p2[0] - p1[0]), p1[1] + t * (p2[1] - p1[1])];\n    }\n\n    const loader = (on, t, isErr) => {\n      const l = $('#loader');\n      l.classList.toggle('hidden', !on);\n      l.classList.toggle('err', !!isErr);\n      if (t != null) l.textContent = t;\n    };\n\n    let IDX = null;\n    let S = { pr: null, view: { scale: 1, tx: 0, ty: 0 }, focus: [], ctx: [] };\n    let dragStart = null, dragMoved = false;\n    let pointers = new Map();\n    let pinchDist = null;\n\n    function svgPoint(evt){\n      const svg = $('#map');\n      const pt = svg.createSVGPoint();\n      pt.x = evt.clientX; pt.y = evt.clientY;\n      const ctm = svg.getScreenCTM();\n      if (!ctm) return { x: 0, y: 0 };\n      const p = pt.matrixTransform(ctm.inverse());\n      return { x: p.x, y: p.y };\n    }\n    function applyTransform(){\n      const g = $('#viewport');\n      if (g) g.setAttribute('transform', `translate(${S.view.tx} ${S.view.ty}) scale(${S.view.scale})`);\n    }\n    function zoomAt(p, factor){\n      const next = Math.min(8, Math.max(0.15, S.view.scale * factor));\n      const k = next / S.view.scale;\n      S.view.tx = p.x - k * (p.x - S.view.tx);\n      S.view.ty = p.y - k * (p.y - S.view.ty);\n      S.view.scale = next;\n      applyTransform();\n    }\n    d2.on('.stage', 'wheel', e => { e.preventDefault(); zoomAt(svgPoint(e), e.deltaY < 0 ? 1.15 : 1/1.15); });\n    d2.on('.stage', 'pointerdown', (e, el) => {\n      pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n      if (pointers.size === 1) {\n        const p = svgPoint(e);\n        dragStart = { x: p.x, y: p.y, tx: S.view.tx, ty: S.view.ty, pointerId: e.pointerId, el, sx: e.clientX, sy: e.clientY };\n        dragMoved = false;\n      } else {\n        dragStart = null;\n        pinchDist = null;\n      }\n    });\n    d2.on(window, 'pointermove', e => {\n      if (!pointers.has(e.pointerId)) return;\n      pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n      if (pointers.size === 2) {\n        const pts = [...pointers.values()];\n        const dist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);\n        if (pinchDist) {\n          const mid = { clientX: (pts[0].x + pts[1].x) / 2, clientY: (pts[0].y + pts[1].y) / 2 };\n          zoomAt(svgPoint(mid), dist / pinchDist);\n          dragMoved = true;\n        }\n        pinchDist = dist;\n        return;\n      }\n      if (!dragStart) return;\n      const p = svgPoint(e);\n      const dx = p.x - dragStart.x, dy = p.y - dragStart.y;\n      // Capture the pointer only once real drag movement is detected. Capturing eagerly on\n      // every pointerdown (the previous behavior) redirects the native `click` event's target\n      // to the capturing element (a browser click-synthesis quirk with setPointerCapture), so a\n      // plain tap on a color wedge never reached its own onclick. Deferring capture until the\n      // gesture crosses the drag threshold lets a no-movement click hit-test normally.\n      //\n      // The threshold is measured in SCREEN pixels (clientX/clientY), not viewBox units: a mouse\n      // cursor is sub-pixel precise, but a real finger tap naturally drifts several pixels, and\n      // the old 0.5-viewBox-unit threshold (~0.6 screen px, shrinking further when zoomed in)\n      // misread almost every touch tap as a drag — silently swallowing the color's onClick.\n      // Only widen the tolerance when the instance opts into touch_controls — that param is\n      // already the explicit signal this instance is being driven by touch, so gate on it\n      // instead of sniffing e.pointerType (unreliable on hybrid/stylus devices anyway).\n      if (!dragMoved) {\n        const sdx = e.clientX - dragStart.sx, sdy = e.clientY - dragStart.sy;\n        const touchMode = !!d2.param('touch_controls', false);\n        const threshold = touchMode ? 10 : 4;\n        if (Math.hypot(sdx, sdy) > threshold) {\n          dragMoved = true;\n          try { dragStart.el.setPointerCapture?.(dragStart.pointerId); } catch (_) {}\n        }\n      }\n      if (dragMoved) {\n        S.view.tx = dragStart.tx + dx; S.view.ty = dragStart.ty + dy;\n        applyTransform();\n      }\n    });\n    d2.on(window, 'pointerup', e => { pointers.delete(e.pointerId); pinchDist = null; if (!pointers.size) dragStart = null; });\n    d2.on(window, 'pointercancel', e => { pointers.delete(e.pointerId); pinchDist = null; dragStart = null; });\n\n    function showInfo(desc, evt){\n      const touch = !!d2.param('touch_controls', false);\n      const sub = desc.color ? esc(desc.color.color_label || desc.color.color_id) : 'apenas contexto';\n      if (touch) {\n        $('#infobarText').textContent = `${desc.label}${desc.color ? ' · ' + sub : ''}`;\n        $('#infobar').classList.add('show');\n      } else {\n        const tip = $('#tip');\n        if (evt) { tip.style.left = (evt.clientX + 12) + 'px'; tip.style.top = (evt.clientY + 12) + 'px'; }\n        tip.innerHTML = `<b>${esc(desc.label)}</b>${desc.color ? '<br>' + sub : ''}`;\n        tip.style.opacity = 1;\n      }\n    }\n    function hideInfo(){ $('#tip').style.opacity = 0; }\n    $('#infobarClose').onclick = () => $('#infobar').classList.remove('show');\n\n    function addCountry(list, focus, usedUfs){\n      list.forEach((entry, ei) => {\n        if (!entry) return;\n        if (String(entry.location_id||'').trim().toUpperCase() !== 'BR') {\n          d2.log('multiescala: country_list só suporta BR por enquanto (' + entry.location_id + ')');\n          return;\n        }\n        const ufs = Object.keys(UF_CODE);\n        ufs.forEach(u => usedUfs.add(u));\n        const idxs = ufs.flatMap(u => IDX.byUfName[u] ? Object.values(IDX.byUfName[u]) : []);\n        if (!idxs.length) return;\n        focus.push({ key: `country-${ei}`, idxs, level: 'country', entry, label: entry.location_label || 'Brasil', colors: toArr(entry.location_colors) });\n      });\n    }\n    function addRegion(list, focus, usedUfs, usedRegions){\n      list.forEach((entry, ei) => {\n        if (!entry) return;\n        const rc = matchRegion(entry.location_id);\n        if (!rc) { d2.log('multiescala: region_list id não reconhecido (' + entry.location_id + ')'); return; }\n        usedRegions.add(rc);\n        REGION_UFS[rc].forEach(u => usedUfs.add(u));\n        const idxs = REGION_UFS[rc].flatMap(u => IDX.byUfName[u] ? Object.values(IDX.byUfName[u]) : []);\n        if (!idxs.length) return;\n        focus.push({ key: `region-${ei}`, idxs, level: 'region', entry, label: entry.location_label || REGION_NAMES[rc], colors: toArr(entry.location_colors) });\n      });\n    }\n    function addState(list, focus, usedUfs){\n      list.forEach((entry, ei) => {\n        if (!entry) return;\n        const uf = matchUf(entry.location_id);\n        if (!uf) { d2.log('multiescala: state_list id não reconhecido (' + entry.location_id + ')'); return; }\n        usedUfs.add(uf);\n        const idxs = IDX.byUfName[uf] ? Object.values(IDX.byUfName[uf]) : [];\n        if (!idxs.length) return;\n        focus.push({ key: `state-${ei}`, idxs, level: 'state', entry, label: entry.location_label || UF_NAMES[uf] || uf, colors: toArr(entry.location_colors) });\n      });\n    }\n    function addLeaf(list, level, focus, usedUfs){\n      list.forEach((entry, ei) => {\n        if (!entry) return;\n        const n = norm(entry.location_id);\n        const matches = IDX.anyName[n];\n        if (!matches || !matches.length) { d2.log('multiescala: ' + level + ' não encontrado (' + entry.location_id + ')'); return; }\n        const i = matches[0];\n        usedUfs.add(IDX.recs[i].uf);\n        focus.push({ key: `${level}-${ei}`, idxs: [i], level, entry, label: entry.location_label || IDX.recs[i].label, colors: toArr(entry.location_colors) });\n      });\n    }\n\n    function buildScene(){\n      const countryList = toArr(d2.param('country_list', []));\n      const regionList = toArr(d2.param('region_list', []));\n      const stateList = toArr(d2.param('state_list', []));\n      const cityList = toArr(d2.param('city_list', []));\n      const muniList = toArr(d2.param('municipality_list', []));\n      const wantNeighbors = !!d2.param('should_render_neighbors', false);\n\n      const focus = [];\n      const usedUfs = new Set();\n      const usedRegions = new Set();\n      const lowest = (cityList.length || muniList.length) ? 'leaf'\n        : stateList.length ? 'state'\n        : regionList.length ? 'region'\n        : countryList.length ? 'country' : null;\n\n      addCountry(countryList, focus, usedUfs);\n      addRegion(regionList, focus, usedUfs, usedRegions);\n      addState(stateList, focus, usedUfs);\n      addLeaf(cityList, 'city', focus, usedUfs);\n      addLeaf(muniList, 'municipality', focus, usedUfs);\n\n      const ctx = [];\n      const focusIdxSet = new Set(focus.flatMap(s => s.idxs));\n      if (wantNeighbors && lowest) {\n        if (lowest === 'leaf') {\n          const nbrIdx = new Set();\n          focus.filter(s => s.level === 'municipality' || s.level === 'city')\n            .forEach(s => IDX.nbrsOf(s.idxs[0]).forEach(j => { if (!focusIdxSet.has(j)) nbrIdx.add(j); }));\n          if (nbrIdx.size) ctx.push({ idxs: [...nbrIdx] });\n        } else if (lowest === 'state') {\n          const nbrUfs = new Set([...usedUfs].flatMap(u => UF_BORDERS[u] || []).filter(u => !usedUfs.has(u)));\n          nbrUfs.forEach(u => { if (IDX.byUfName[u]) ctx.push({ idxs: Object.values(IDX.byUfName[u]) }); });\n        } else if (lowest === 'region') {\n          const nbrRegions = new Set();\n          [...usedUfs].forEach(u => (UF_BORDERS[u] || []).forEach(b => {\n            const br = Object.keys(REGION_UFS).find(r => REGION_UFS[r].includes(b));\n            if (br && !usedRegions.has(br)) nbrRegions.add(br);\n          }));\n          nbrRegions.forEach(r => {\n            const idxs = REGION_UFS[r].flatMap(u => IDX.byUfName[u] ? Object.values(IDX.byUfName[u]) : []);\n            if (idxs.length) ctx.push({ idxs });\n          });\n        }\n      }\n\n      S.focus = focus; S.ctx = ctx;\n\n      if (!focus.length && !ctx.length) {\n        $('#empty').classList.remove('hidden');\n        $('#legend').classList.add('hidden');\n        $('#map').innerHTML = '';\n        return;\n      }\n      $('#empty').classList.add('hidden');\n\n      const zoomIdxs = focus.length ? focus.flatMap(s => s.idxs) : ctx.flatMap(s => s.idxs);\n      S.pr = project(zoomIdxs.map(i => IDX.recs[i].f));\n      S.view = { scale: 1, tx: 0, ty: 0 };\n      drawMap();\n    }\n\n    function drawMap(){\n      const svg = $('#map'); svg.innerHTML = '';\n      const viewport = document.createElementNS(NS, 'g');\n      viewport.setAttribute('id', 'viewport');\n      svg.appendChild(viewport);\n      applyTransform();\n      const touch = !!d2.param('touch_controls', false);\n      const labelFontSize = Math.max(4, Math.min(14, 9 * ((S.pr && S.pr.scale || 1000) / 1000)));\n\n      S.ctx.forEach((c, i) => {\n        const p = document.createElementNS(NS, 'path');\n        p.setAttribute('d', pathFromRings(dissolveRings(c.idxs, IDX.recs), S.pr));\n        p.setAttribute('data-key', 'ctx-' + i);\n        p.setAttribute('class', 'area');\n        p.setAttribute('fill', '#dcdfe8');\n        p.setAttribute('opacity', '.55');\n        viewport.appendChild(p);\n      });\n\n      const legendGroups = [];\n      S.focus.forEach(item => {\n        const rings = dissolveRings(item.idxs, IDX.recs);\n        const dFull = pathFromRings(rings, S.pr);\n        const base = document.createElementNS(NS, 'path');\n        base.setAttribute('d', dFull);\n        base.setAttribute('data-key', item.key + '-base');\n        base.setAttribute('class', 'area');\n        base.setAttribute('fill', item.colors.length ? '#eef0f5' : '#94a3b8');\n        base.onmousemove = e => showInfo({ label: item.label, color: null }, e);\n        base.onmouseleave = hideInfo;\n        viewport.appendChild(base);\n\n        if (item.colors.length) {\n          const [bx0, , bx1] = mergedBbox(item.idxs, S.pr, IDX.recs);\n          const n = item.colors.length;\n          const stripeW = (bx1 - bx0) / n;\n          item.colors.forEach((c, ci) => {\n            const d = stripeClipRings(rings, S.pr, bx0 + ci * stripeW, bx0 + (ci + 1) * stripeW);\n            if (!d) return;\n            const wedge = document.createElementNS(NS, 'path');\n            wedge.setAttribute('d', d);\n            wedge.setAttribute('data-key', item.key + '-w' + ci);\n            wedge.setAttribute('class', 'wedge' + (touch ? ' touchy' : ''));\n            wedge.setAttribute('fill', c.color || PALETTE[ci % PALETTE.length]);\n            wedge.onclick = (e) => {\n              if (dragMoved) { dragMoved = false; return; }\n              if (touch) showInfo({ label: item.label, color: c });\n              // The ONLY click entry point — a color with no `onClick` just isn't actionable.\n              // Augment the REAL click event (not a bare payload object) so APIs that need a\n              // genuine DOM event as their first arg (e.g. @__DialogsService, which walks\n              // $event.parentElement to find the modal mount point) still work, while\n              // `$event.color_id` etc. remain readable.\n              if (typeof c.onClick === 'function') {\n                c.onClick(Object.assign(e, {\n                  level: item.level,\n                  location_id: item.entry.location_id,\n                  location_label: item.entry.location_label,\n                  color_id: c.color_id,\n                  color_label: c.color_label,\n                  color: c.color,\n                }));\n              }\n            };\n            if (!touch) {\n              wedge.onmousemove = e => showInfo({ label: item.label, color: c }, e);\n              wedge.onmouseleave = hideInfo;\n            }\n            viewport.appendChild(wedge);\n          });\n        }\n\n        const [cx, cy] = item.idxs.length > 1 ? mergeCentroid(item.idxs, S.pr, IDX.recs) : cxy(IDX.recs[item.idxs[0]].f, S.pr);\n        const t = document.createElementNS(NS, 'text');\n        t.setAttribute('class', 'lbl'); t.setAttribute('x', cx); t.setAttribute('y', cy);\n        t.style.fontSize = labelFontSize + 'px';\n        t.textContent = (item.label || '').slice(0, 16);\n        viewport.appendChild(t);\n\n        if (item.colors.length) legendGroups.push({ entry: item.entry, items: item.colors });\n      });\n\n      renderLegend(legendGroups);\n    }\n    function renderLegend(groups){\n      const el = $('#legend');\n      if (!groups.length) { el.classList.add('hidden'); return; }\n      el.classList.remove('hidden');\n      el.innerHTML = groups.map(g => {\n        const rows = g.items.map(c => `<div class=\"row\"><b style=\"background:${esc(c.color)}\"></b>${esc(c.color_label || c.color_id)}</div>`).join('');\n        return `<div class=\"grp\"><div class=\"grpname\">${esc(g.entry.location_label || g.entry.location_id || '')}</div>${rows}</div>`;\n      }).join('');\n    }\n\n    async function load(){\n      loader(true, 'carregando malhas do Brasil (27 UFs)…', false);\n      try {\n        const ufs = Object.keys(UF_CODE);\n        const geos = await Promise.all(ufs.map(u => geoUF(u).catch(() => null)));\n        const allFeat = [];\n        ufs.forEach((u, i) => { const g = geos[i]; if (!g) return; g.features.forEach(f => { f.__uf = u; allFeat.push(f); }); });\n        if (!allFeat.length) throw new Error('nenhuma malha carregada');\n        IDX = buildIndex(allFeat);\n        buildScene();\n        loader(false);\n      } catch (e) {\n        loader(true, 'erro: ' + e.message + ' (clique p/ tentar de novo)', true);\n      }\n    }\n    $('#loader').onclick = () => { if (!$('#loader').classList.contains('err')) return; IDX = null; load(); };\n\n    load();\n  },\n};"}},"country_list":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"country_list"}}}},"region_list":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"region_list"}}}},"state_list":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"state_list"}}}},"city_list":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"city_list"}}}},"municipality_list":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"municipality_list"}}}},"should_render_neighbors":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"should_render_neighbors"}}}},"touch_controls":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"touch_controls"}}}}}}}}}},{"_id":"6a7170c3a9d7cf0ea991a718","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"pq5nxhb0pbusiAlnbFhhO","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const UF_CODE={RO:'11',AC:'12',AM:'13',RR:'14',PA:'15',AP:'16',TO:'17',MA:'21',PI:'22',CE:'23',RN:'24',PB:'25',PE:'26',AL:'27',SE:'28',BA:'29',MG:'31',ES:'32',RJ:'33',SP:'35',PR:'41',SC:'42',RS:'43',MS:'50',MT:'51',GO:'52',DF:'53'};\nconst UF_BORDERS={AC:['AM','RO'],AL:['BA','PE','SE'],AM:['AC','RO','RR','PA','MT'],AP:['PA'],\n BA:['AL','SE','PE','PI','TO','GO','MG','ES'],CE:['PI','PB','PE','RN'],DF:['GO','MG'],\n ES:['BA','MG','RJ'],GO:['DF','MT','MS','MG','BA','TO'],MA:['PA','TO','PI'],\n MG:['BA','GO','DF','MS','SP','RJ','ES'],MS:['MT','GO','MG','SP','PR'],MT:['RO','AM','PA','TO','GO','MS'],\n PA:['AP','AM','MT','TO','MA','RR'],PB:['RN','CE','PE'],PE:['PB','CE','PI','BA','AL'],\n PI:['MA','TO','BA','PE','CE'],PR:['SP','MS','SC'],RJ:['ES','MG','SP'],RN:['CE','PB'],\n RO:['AC','AM','MT'],RR:['AM','PA'],RS:['SC'],SC:['PR','RS'],SE:['BA','AL'],\n SP:['MG','RJ','PR','MS'],TO:['PA','MA','PI','BA','GO','MT']};\nconst UF_NAMES={RO:'Rondônia',AC:'Acre',AM:'Amazonas',RR:'Roraima',PA:'Pará',AP:'Amapá',TO:'Tocantins',\n MA:'Maranhão',PI:'Piauí',CE:'Ceará',RN:'Rio Grande do Norte',PB:'Paraíba',PE:'Pernambuco',AL:'Alagoas',\n SE:'Sergipe',BA:'Bahia',MG:'Minas Gerais',ES:'Espírito Santo',RJ:'Rio de Janeiro',SP:'São Paulo',\n PR:'Paraná',SC:'Santa Catarina',RS:'Rio Grande do Sul',MS:'Mato Grosso do Sul',MT:'Mato Grosso',\n GO:'Goiás',DF:'Distrito Federal'};\nconst REGION_UFS={N:['RO','AC','AM','RR','PA','AP','TO'],NE:['MA','PI','CE','RN','PB','PE','AL','SE','BA'],\n CO:['MS','MT','GO','DF'],SE:['MG','ES','RJ','SP'],S:['PR','SC','RS']};\nconst REGION_NAMES={N:'Norte',NE:'Nordeste',CO:'Centro-Oeste',SE:'Sudeste',S:'Sul'};\nconst PALETTE=['#8b7fd6','#d1b048','#67bd5b','#5aa9d6','#d67fa8','#c98a4b'];\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%;box-sizing:border-box}\n    .wrap{position:relative;height:100%;display:flex;flex-direction:column;font:13px -apple-system,BlinkMacSystemFont,\"SF Pro Text\",Inter,sans-serif;background:transparent;overflow:hidden;border-radius:14px;border:1px solid rgba(0,0,0,.08);box-shadow:0 6px 20px rgba(0,0,0,.12)}\n    .stage{flex:1;min-height:0;position:relative;cursor:grab;touch-action:none}\n    .stage:active{cursor:grabbing}\n    .stage.globe::after{content:'';position:absolute;inset:0;pointer-events:none;z-index:4;border-radius:inherit;background:radial-gradient(circle at 50% 50%, rgba(255,255,255,0) 52%, rgba(0,0,0,.12) 100%);box-shadow:inset 0 0 40px rgba(0,0,0,.08)}\n    svg{width:100%;height:100%;display:block}\n    .area{stroke:rgba(0,0,0,.2);stroke-width:.85;stroke-linejoin:round;stroke-linecap:round}\n    .wedge{stroke:rgba(255,255,255,.7);stroke-width:.5;stroke-linejoin:round;cursor:pointer;transition:filter .15s ease}\n    .wedge:hover{filter:brightness(1.16)}\n    .wedge.touchy{stroke-width:.9}\n    .lbl{fill:#1D1D1F;font-size:9px;font-weight:600;pointer-events:none;user-select:none;text-anchor:middle;paint-order:stroke;stroke:rgba(255,255,255,.7);stroke-width:.6px}\n    .legend{display:none!important;position:absolute;left:10px;bottom:10px;max-width:64%;max-height:70%;overflow:auto;flex-direction:column;gap:8px;padding:10px 12px;border-radius:12px;background:rgba(255,255,255,.78);-webkit-backdrop-filter:blur(30px) saturate(180%);backdrop-filter:blur(30px) saturate(180%);border:1px solid rgba(0,0,0,.08);font-size:11px;box-shadow:0 8px 24px rgba(0,0,0,.20)}\n    .legend .grp{display:flex;flex-direction:column;gap:3px}\n    .legend .grpname{font-weight:700;color:rgba(0,0,0,.5);font-size:10px;text-transform:uppercase;letter-spacing:.06em}\n    .legend .row{display:flex;align-items:center;gap:6px;color:#1D1D1F}\n    .legend b{width:10px;height:10px;border-radius:3px;display:inline-block;flex:0 0 auto;box-shadow:inset 0 0 0 1px rgba(0,0,0,.1)}\n    .tip{display:none!important;position:fixed;pointer-events:none;background:rgba(255,255,252,.95);-webkit-backdrop-filter:blur(20px) saturate(150%);backdrop-filter:blur(20px) saturate(150%);color:#1D1D1F;border:1px solid rgba(0,0,0,.08);border-radius:6px;padding:6px 9px;font-size:11px;opacity:0;transition:opacity .12s ease;box-shadow:0 2px 8px rgba(0,0,0,.15);z-index:9;max-width:220px}\n    .infobar{display:none!important;position:absolute;left:0;right:0;bottom:0;background:rgba(246,246,247,.85);-webkit-backdrop-filter:blur(20px) saturate(180%);backdrop-filter:blur(20px) saturate(180%);border-top:1px solid rgba(0,0,0,.1);color:#1D1D1F;padding:10px 14px;font-size:13px;display:flex;justify-content:space-between;align-items:center;gap:10px;transform:translateY(100%);transition:transform .18s ease;z-index:8}\n    .infobar.show{transform:translateY(0)}\n    .infobar .x{opacity:.5;cursor:pointer;padding:2px 7px;border-radius:5px;transition:background .12s ease,opacity .12s ease}\n    .infobar .x:hover{opacity:.9;background:rgba(0,0,0,.06)}\n    .load{position:absolute;inset:0;background:rgba(236,236,236,.94);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:5;color:rgba(0,0,0,.5);text-align:center;padding:16px;cursor:default;font-size:13px}\n    .load.err{cursor:pointer;text-decoration:underline;color:#FF3B30}\n    .empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:rgba(0,0,0,.5);text-align:center;padding:24px;font-size:13px}\n    .hidden{display:none}\n  `,\n  render(){\n    return `<div class=\"wrap\">\n      <div class=\"stage\"><svg id=\"map\" viewBox=\"0 0 700 500\" preserveAspectRatio=\"xMidYMid meet\"></svg></div>\n      <div class=\"legend hidden\" id=\"legend\"></div>\n      <div class=\"tip\" id=\"tip\"></div>\n      <div class=\"infobar\" id=\"infobar\"><span id=\"infobarText\"></span><span class=\"x\" id=\"infobarClose\">✕</span></div>\n      <div class=\"load\" id=\"loader\">carregando malhas do Brasil…</div>\n      <div class=\"empty hidden\" id=\"empty\">Nenhuma localização declarada — informe country_list, region_list, state_list, city_list ou municipality_list.</div>\n    </div>`;\n  },\n  mounted(d2, root){\n\n    const $ = s => root.querySelector(s);\n    const NS = 'http://www.w3.org/2000/svg';\n    const norm = s => (s||'').normalize('NFKD').replace(/[̀-ͯ]/g,'').toUpperCase().replace(/[^A-Z0-9]+/g,' ').trim();\n    const esc = s => (s==null?'':String(s)).replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));\n    const toArr = v => Array.isArray(v) ? v : [];\n\n    function matchUf(idOrName){\n      if(!idOrName) return null;\n      const up = String(idOrName).trim().toUpperCase();\n      if (UF_CODE[up]) return up;\n      const n = norm(idOrName);\n      const hit = Object.keys(UF_NAMES).find(code => norm(UF_NAMES[code]) === n);\n      return hit || null;\n    }\n    function matchRegion(idOrName){\n      if(!idOrName) return null;\n      const up = String(idOrName).trim().toUpperCase();\n      if (REGION_UFS[up]) return up;\n      const n = norm(idOrName);\n      const hit = Object.keys(REGION_NAMES).find(code => norm(REGION_NAMES[code]) === n);\n      return hit || null;\n    }\n\n    async function geoUF(uf){\n      const code = UF_CODE[uf] || uf;\n      const r = await fetch('https://raw.githubusercontent.com/tbrugz/geodata-br/master/geojson/geojs-'+code+'-mun.json');\n      if (!r.ok) throw new Error('geo ' + uf);\n      return r.json();\n    }\n    function ptsOf(f){\n      const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates], s=new Set();\n      ps.forEach(poly=>poly.forEach(ring=>ring.forEach(p=>s.add(Math.round(p[0]*1000)/1000+','+Math.round(p[1]*1000)/1000))));\n      return s;\n    }\n    function buildIndex(features){\n      const recs = features.map(f => ({\n        name: norm(f.properties.name || f.properties.NOME || f.properties.nome || ''),\n        label: f.properties.name || f.properties.NOME || f.properties.nome || '',\n        uf: f.__uf, f, pts: ptsOf(f)\n      }));\n      const ptIdx = {};\n      recs.forEach((r,i) => r.pts.forEach(p => (ptIdx[p]=ptIdx[p]||[]).push(i)));\n      const nbrsOf = i => {\n        const c={};\n        recs[i].pts.forEach(p => ptIdx[p].forEach(j => { if (j!==i) c[j]=(c[j]||0)+1; }));\n        return Object.entries(c).filter(([,n])=>n>=2).map(([j])=>+j);\n      };\n      const byUfName = {};\n      recs.forEach((r,i) => { byUfName[r.uf]=byUfName[r.uf]||{}; byUfName[r.uf][r.name]=i; });\n      const anyName = {};\n      recs.forEach((r,i) => { (anyName[r.name]=anyName[r.name]||[]).push(i); });\n      return { recs, nbrsOf, byUfName, anyName };\n    }\n    function project(feats){\n      let mnx=1/0,mny=1/0,mxx=-1/0,mxy=-1/0;\n      const each = cb => feats.forEach(f=>{const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];ps.forEach(p=>p.forEach(r=>r.forEach(cb)));});\n      each(p=>{mnx=Math.min(mnx,p[0]);mxx=Math.max(mxx,p[0]);mny=Math.min(mny,p[1]);mxy=Math.max(mxy,p[1]);});\n      const mlat=(mny+mxy)/2, kx=Math.cos(mlat*Math.PI/180), W=700,H=500,pad=22;\n      const gw=(mxx-mnx)*kx, gh=(mxy-mny), sc=Math.min((W-2*pad)/gw,(H-2*pad)/gh);\n      const ox=(W-gw*sc)/2, oy=(H-gh*sc)/2;\n      const pr = p=>[ox+(p[0]-mnx)*kx*sc, oy+(mxy-p[1])*sc];\n      pr.scale = sc;\n      return pr;\n    }\n    function pathFor(f,pr){\n      const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n      return ps.map(poly=>poly.map(r=>'M'+r.map(p=>{const q=pr(p);return q[0].toFixed(1)+','+q[1].toFixed(1);}).join('L')+'Z').join('')).join('');\n    }\n    function cxy(f,pr){\n      let x=0,y=0,n=0; const g=f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n      ps.forEach(poly=>poly[0].forEach(p=>{const q=pr(p);x+=q[0];y+=q[1];n++;}));\n      return [x/n, y/n];\n    }\n    function ringsOf(f){\n      const g = f.geometry, ps = g.type==='MultiPolygon' ? g.coordinates : [g.coordinates];\n      const rings = [];\n      ps.forEach(poly => poly.forEach(ring => rings.push(ring)));\n      return rings;\n    }\n    const quant = p => [Math.round(p[0]*1000)/1000, Math.round(p[1]*1000)/1000];\n    const keyOf = p => p[0] + ',' + p[1];\n    // Dissolves the municipio polygons in `idxs` into their outer boundary (+ any real holes)\n    // by cancelling every edge shared by two included polygons — the border between two\n    // adjacent included municipios disappears, leaving only the union's true edge. Exact for\n    // this dataset because neighboring municipio files share literal boundary coordinates (the\n    // same tolerance ptsOf already relies on for adjacency).\n    function dissolveRings(idxs, recs){\n      if (idxs.length === 1) return ringsOf(recs[idxs[0]].f);\n      const edgeCount = new Map(), edges = [];\n      idxs.forEach(i => {\n        ringsOf(recs[i].f).forEach(ring => {\n          const q = ring.map(quant);\n          for (let k = 0; k < q.length - 1; k++){\n            const a = q[k], b = q[k+1], ka = keyOf(a), kb = keyOf(b);\n            if (ka === kb) continue;\n            const uk = ka < kb ? ka+'|'+kb : kb+'|'+ka;\n            edgeCount.set(uk, (edgeCount.get(uk)||0) + 1);\n            edges.push({ a, b, ka, kb, uk });\n          }\n        });\n      });\n      const kept = edges.filter(e => edgeCount.get(e.uk) === 1);\n      const byStart = new Map();\n      kept.forEach(e => {\n        if (!byStart.has(e.ka)) byStart.set(e.ka, []);\n        byStart.get(e.ka).push(e);\n      });\n      const used = new Set(), rings = [];\n      kept.forEach(seed => {\n        if (used.has(seed)) return;\n        const ring = [seed.a];\n        let cur = seed, guard = 0;\n        while (guard++ < 50000) {\n          used.add(cur);\n          ring.push(cur.b);\n          if (cur.kb === seed.ka) break;\n          const next = (byStart.get(cur.kb) || []).find(e => !used.has(e));\n          if (!next) break;\n          cur = next;\n        }\n        if (ring.length >= 4 && keyOf(ring[0]) === keyOf(ring[ring.length-1])) rings.push(ring);\n      });\n      return rings;\n    }\n    function pathFromRings(rings, pr){\n      return rings.map(ring => 'M'+ring.map(p=>{const q=pr(p);return q[0].toFixed(1)+','+q[1].toFixed(1);}).join('L')+'Z').join('');\n    }\n    function stripeClipRings(rings, pr, x0, x1){\n      let d = '';\n      rings.forEach(ring => {\n        let pts = ring.map(p => pr(p));\n        pts = clipHalfPlane(pts, (x) => x - x0);\n        if (pts.length) pts = clipHalfPlane(pts, (x) => x1 - x);\n        if (pts.length >= 3) d += 'M' + pts.map(q => q[0].toFixed(1) + ',' + q[1].toFixed(1)).join('L') + 'Z';\n      });\n      return d;\n    }\n    function mergeCentroid(idxs, pr, recs){\n      let x=0,y=0,n=0;\n      idxs.forEach(i=>{ const [cx,cy]=cxy(recs[i].f,pr); x+=cx; y+=cy; n++; });\n      return n ? [x/n, y/n] : [0,0];\n    }\n    function mergedBbox(idxs, pr, recs){\n      let minX=Infinity,minY=Infinity,maxX=-Infinity,maxY=-Infinity;\n      idxs.forEach(i => {\n        const g=recs[i].f.geometry, ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n        ps.forEach(poly=>poly.forEach(ring=>ring.forEach(p=>{\n          const q=pr(p);\n          minX=Math.min(minX,q[0]); maxX=Math.max(maxX,q[0]);\n          minY=Math.min(minY,q[1]); maxY=Math.max(maxY,q[1]);\n        })));\n      });\n      return [minX,minY,maxX,maxY];\n    }\n    // Sutherland-Hodgman clip against a straight half-plane (side(x,y)>=0 kept). A straight-line\n    // cut is exact for ANY simple polygon, convex or concave, so stacking bands never leaves a gap\n    // — unlike an angular wedge radiating from one apex, which can't \"see\" a concave shape's whole border.\n    function clipHalfPlane(ring, side){\n      const out = [];\n      const n = ring.length;\n      for (let i = 0; i < n; i++){\n        const curr = ring[i], prev = ring[(i - 1 + n) % n];\n        const sCurr = side(curr[0], curr[1]);\n        const sPrev = side(prev[0], prev[1]);\n        const currIn = sCurr >= -1e-9;\n        const prevIn = sPrev >= -1e-9;\n        if (currIn) {\n          if (!prevIn) out.push(intersectHalfPlane(prev, curr, side));\n          out.push(curr);\n        } else if (prevIn) {\n          out.push(intersectHalfPlane(prev, curr, side));\n        }\n      }\n      return out;\n    }\n    function intersectHalfPlane(p1, p2, side){\n      const s1 = side(p1[0], p1[1]), s2 = side(p2[0], p2[1]);\n      const t = s1 / (s1 - s2);\n      return [p1[0] + t * (p2[0] - p1[0]), p1[1] + t * (p2[1] - p1[1])];\n    }\n\n    function projRingsWorld(rings, pr){\n      return rings.map(ring => ring.map(p => pr(p)));\n    }\n    function stripeClipRingsPts(rings, pr, x0, x1){\n      const out = [];\n      rings.forEach(ring => {\n        let pts = ring.map(p => pr(p));\n        pts = clipHalfPlane(pts, (x) => x - x0);\n        if (pts.length) pts = clipHalfPlane(pts, (x) => x1 - x);\n        if (pts.length >= 3) out.push(pts);\n      });\n      return out;\n    }\n    function stringifyPtRings(ptRings, warp){\n      return ptRings.map(ring => 'M'+ring.map(p=>{const q=warp(p);return q[0].toFixed(1)+','+q[1].toFixed(1);}).join('L')+'Z').join('');\n    }\n    // Bends the already-projected flat map toward an orthographic-globe look: each point is\n    // pulled toward the camera-center radius via sin(theta) instead of growing linearly, so it\n    // compresses near the simulated limb — the classic \"looking at a sphere\" cue. `bend` softens\n    // as view.scale grows (zoomed in reads as close to the surface, ~flat), and grows as it\n    // shrinks (zoomed out reads as more of the globe's curvature). The camera center (cx,cy) is\n    // derived from the current pan/zoom, so the bend's origin follows wherever the user is looking.\n    function makeWarp(view, W, H){\n      const cx = (W / 2 - view.tx) / view.scale;\n      const cy = (H / 2 - view.ty) / view.scale;\n      const bend = Math.max(0.08, Math.min(0.92, 0.55 / Math.sqrt(view.scale)));\n      const maxR = Math.max(W, H) * 0.62;\n      return function(p){\n        const dx = p[0] - cx, dy = p[1] - cy;\n        const r = Math.hypot(dx, dy);\n        if (r < 1e-6) return [W / 2, H / 2];\n        const rn = Math.min(r / maxR, 1);\n        const theta = rn * (Math.PI / 2) * bend;\n        const k = Math.sin(theta) / rn;\n        const sx = W / 2 + dx * k * view.scale;\n        const sy = H / 2 + dy * k * view.scale;\n        const droop = (dx * dx) / (maxR * maxR) * bend * 14;\n        return [sx, sy + droop];\n      };\n    }\n\n    const loader = (on, t, isErr) => {\n      const l = $('#loader');\n      l.classList.toggle('hidden', !on);\n      l.classList.toggle('err', !!isErr);\n      if (t != null) l.textContent = t;\n    };\n\n    let IDX = null;\n    let S = { pr: null, view: { scale: 1, tx: 0, ty: 0 }, focus: [], ctx: [], paths: [], labels: [], globeOn: null };\n    let dragStart = null, dragMoved = false;\n    let pointers = new Map();\n    let pinchDist = null;\n\n    function svgPoint(evt){\n      const svg = $('#map');\n      const pt = svg.createSVGPoint();\n      pt.x = evt.clientX; pt.y = evt.clientY;\n      const ctm = svg.getScreenCTM();\n      if (!ctm) return { x: 0, y: 0 };\n      const p = pt.matrixTransform(ctm.inverse());\n      return { x: p.x, y: p.y };\n    }\n    function applyTransform(){\n      const g = $('#viewport');\n      if (g) g.setAttribute('transform', `translate(${S.view.tx} ${S.view.ty}) scale(${S.view.scale})`);\n    }\n    const VB_W = 700, VB_H = 500;\n    let globeRaf = null;\n    // Non-globe mode keeps the cheap path: a single <g transform> on the viewport, path `d`s\n    // untouched. Globe mode can't be expressed as one affine transform (the bend is nonlinear\n    // per-point), so it re-stringifies every path/label against the current camera each redraw —\n    // opt-in cost, gated entirely behind experimental_globe.\n    function redraw(){\n      const globeOn = !!d2.param('experimental_globe', false);\n      const modeChanged = globeOn !== S.globeOn;\n      S.globeOn = globeOn;\n      const stage = $('.stage');\n      if (stage) stage.classList.toggle('globe', globeOn);\n      if (globeOn) {\n        const g = $('#viewport');\n        if (g) g.removeAttribute('transform');\n        const warp = makeWarp(S.view, VB_W, VB_H);\n        S.paths.forEach(({ el, ptRings }) => el.setAttribute('d', stringifyPtRings(ptRings, warp)));\n        S.labels.forEach(({ el, pt }) => { const q = warp(pt); el.setAttribute('x', q[0]); el.setAttribute('y', q[1]); });\n      } else {\n        if (modeChanged) {\n          const flat = p => p;\n          S.paths.forEach(({ el, ptRings }) => el.setAttribute('d', stringifyPtRings(ptRings, flat)));\n          S.labels.forEach(({ el, pt }) => { el.setAttribute('x', pt[0]); el.setAttribute('y', pt[1]); });\n        }\n        applyTransform();\n      }\n    }\n    function scheduleRedraw(){\n      if (globeRaf) return;\n      globeRaf = requestAnimationFrame(() => { globeRaf = null; redraw(); });\n    }\n    function zoomAt(p, factor){\n      const next = Math.min(8, Math.max(0.15, S.view.scale * factor));\n      const k = next / S.view.scale;\n      S.view.tx = p.x - k * (p.x - S.view.tx);\n      S.view.ty = p.y - k * (p.y - S.view.ty);\n      S.view.scale = next;\n      scheduleRedraw();\n    }\n    d2.on('.stage', 'wheel', e => { e.preventDefault(); zoomAt(svgPoint(e), e.deltaY < 0 ? 1.15 : 1/1.15); });\n    d2.on('.stage', 'pointerdown', (e, el) => {\n      pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n      if (pointers.size === 1) {\n        const p = svgPoint(e);\n        dragStart = { x: p.x, y: p.y, tx: S.view.tx, ty: S.view.ty, pointerId: e.pointerId, el, sx: e.clientX, sy: e.clientY };\n        dragMoved = false;\n      } else {\n        dragStart = null;\n        pinchDist = null;\n      }\n    });\n    d2.on(window, 'pointermove', e => {\n      if (!pointers.has(e.pointerId)) return;\n      pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n      if (pointers.size === 2) {\n        const pts = [...pointers.values()];\n        const dist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);\n        if (pinchDist) {\n          const mid = { clientX: (pts[0].x + pts[1].x) / 2, clientY: (pts[0].y + pts[1].y) / 2 };\n          zoomAt(svgPoint(mid), dist / pinchDist);\n          dragMoved = true;\n        }\n        pinchDist = dist;\n        return;\n      }\n      if (!dragStart) return;\n      const p = svgPoint(e);\n      const dx = p.x - dragStart.x, dy = p.y - dragStart.y;\n      // Capture the pointer only once real drag movement is detected. Capturing eagerly on\n      // every pointerdown (the previous behavior) redirects the native `click` event's target\n      // to the capturing element (a browser click-synthesis quirk with setPointerCapture), so a\n      // plain tap on a color wedge never reached its own onclick. Deferring capture until the\n      // gesture crosses the drag threshold lets a no-movement click hit-test normally.\n      //\n      // The threshold is measured in SCREEN pixels (clientX/clientY), not viewBox units: a mouse\n      // cursor is sub-pixel precise, but a real finger tap naturally drifts several pixels, and\n      // the old 0.5-viewBox-unit threshold (~0.6 screen px, shrinking further when zoomed in)\n      // misread almost every touch tap as a drag — silently swallowing the color's onClick.\n      // Only widen the tolerance when the instance opts into touch_controls — that param is\n      // already the explicit signal this instance is being driven by touch, so gate on it\n      // instead of sniffing e.pointerType (unreliable on hybrid/stylus devices anyway).\n      if (!dragMoved) {\n        const sdx = e.clientX - dragStart.sx, sdy = e.clientY - dragStart.sy;\n        const touchMode = !!d2.param('touch_controls', false);\n        const threshold = touchMode ? 10 : 4;\n        if (Math.hypot(sdx, sdy) > threshold) {\n          dragMoved = true;\n          try { dragStart.el.setPointerCapture?.(dragStart.pointerId); } catch (_) {}\n        }\n      }\n      if (dragMoved) {\n        S.view.tx = dragStart.tx + dx; S.view.ty = dragStart.ty + dy;\n        scheduleRedraw();\n      }\n    });\n    d2.on(window, 'pointerup', e => { pointers.delete(e.pointerId); pinchDist = null; if (!pointers.size) dragStart = null; });\n    d2.on(window, 'pointercancel', e => { pointers.delete(e.pointerId); pinchDist = null; dragStart = null; });\n\n    function showInfo(desc, evt){\n      const touch = !!d2.param('touch_controls', false);\n      const sub = desc.color ? esc(desc.color.color_label || desc.color.color_id) : 'apenas contexto';\n      if (touch) {\n        $('#infobarText').textContent = `${desc.label}${desc.color ? ' · ' + sub : ''}`;\n        $('#infobar').classList.add('show');\n      } else {\n        const tip = $('#tip');\n        if (evt) { tip.style.left = (evt.clientX + 12) + 'px'; tip.style.top = (evt.clientY + 12) + 'px'; }\n        tip.innerHTML = `<b>${esc(desc.label)}</b>${desc.color ? '<br>' + sub : ''}`;\n        tip.style.opacity = 1;\n      }\n    }\n    function hideInfo(){ $('#tip').style.opacity = 0; }\n    $('#infobarClose').onclick = () => $('#infobar').classList.remove('show');\n\n    function addCountry(list, focus, usedUfs){\n      list.forEach((entry, ei) => {\n        if (!entry) return;\n        if (String(entry.location_id||'').trim().toUpperCase() !== 'BR') {\n          d2.log('multiescala: country_list só suporta BR por enquanto (' + entry.location_id + ')');\n          return;\n        }\n        const ufs = Object.keys(UF_CODE);\n        ufs.forEach(u => usedUfs.add(u));\n        const idxs = ufs.flatMap(u => IDX.byUfName[u] ? Object.values(IDX.byUfName[u]) : []);\n        if (!idxs.length) return;\n        focus.push({ key: `country-${ei}`, idxs, level: 'country', entry, label: entry.location_label || 'Brasil', colors: toArr(entry.location_colors) });\n      });\n    }\n    function addRegion(list, focus, usedUfs, usedRegions){\n      list.forEach((entry, ei) => {\n        if (!entry) return;\n        const rc = matchRegion(entry.location_id);\n        if (!rc) { d2.log('multiescala: region_list id não reconhecido (' + entry.location_id + ')'); return; }\n        usedRegions.add(rc);\n        REGION_UFS[rc].forEach(u => usedUfs.add(u));\n        const idxs = REGION_UFS[rc].flatMap(u => IDX.byUfName[u] ? Object.values(IDX.byUfName[u]) : []);\n        if (!idxs.length) return;\n        focus.push({ key: `region-${ei}`, idxs, level: 'region', entry, label: entry.location_label || REGION_NAMES[rc], colors: toArr(entry.location_colors) });\n      });\n    }\n    function addState(list, focus, usedUfs){\n      list.forEach((entry, ei) => {\n        if (!entry) return;\n        const uf = matchUf(entry.location_id);\n        if (!uf) { d2.log('multiescala: state_list id não reconhecido (' + entry.location_id + ')'); return; }\n        usedUfs.add(uf);\n        const idxs = IDX.byUfName[uf] ? Object.values(IDX.byUfName[uf]) : [];\n        if (!idxs.length) return;\n        focus.push({ key: `state-${ei}`, idxs, level: 'state', entry, label: entry.location_label || UF_NAMES[uf] || uf, colors: toArr(entry.location_colors) });\n      });\n    }\n    function addLeaf(list, level, focus, usedUfs){\n      list.forEach((entry, ei) => {\n        if (!entry) return;\n        const n = norm(entry.location_id);\n        const matches = IDX.anyName[n];\n        if (!matches || !matches.length) { d2.log('multiescala: ' + level + ' não encontrado (' + entry.location_id + ')'); return; }\n        const i = matches[0];\n        usedUfs.add(IDX.recs[i].uf);\n        focus.push({ key: `${level}-${ei}`, idxs: [i], level, entry, label: entry.location_label || IDX.recs[i].label, colors: toArr(entry.location_colors) });\n      });\n    }\n\n    function buildScene(){\n      const countryList = toArr(d2.param('country_list', []));\n      const regionList = toArr(d2.param('region_list', []));\n      const stateList = toArr(d2.param('state_list', []));\n      const cityList = toArr(d2.param('city_list', []));\n      const muniList = toArr(d2.param('municipality_list', []));\n      const wantNeighbors = !!d2.param('should_render_neighbors', false);\n\n      const focus = [];\n      const usedUfs = new Set();\n      const usedRegions = new Set();\n      const lowest = (cityList.length || muniList.length) ? 'leaf'\n        : stateList.length ? 'state'\n        : regionList.length ? 'region'\n        : countryList.length ? 'country' : null;\n\n      addCountry(countryList, focus, usedUfs);\n      addRegion(regionList, focus, usedUfs, usedRegions);\n      addState(stateList, focus, usedUfs);\n      addLeaf(cityList, 'city', focus, usedUfs);\n      addLeaf(muniList, 'municipality', focus, usedUfs);\n\n      const ctx = [];\n      const focusIdxSet = new Set(focus.flatMap(s => s.idxs));\n      if (wantNeighbors && lowest) {\n        if (lowest === 'leaf') {\n          const nbrIdx = new Set();\n          focus.filter(s => s.level === 'municipality' || s.level === 'city')\n            .forEach(s => IDX.nbrsOf(s.idxs[0]).forEach(j => { if (!focusIdxSet.has(j)) nbrIdx.add(j); }));\n          if (nbrIdx.size) ctx.push({ idxs: [...nbrIdx] });\n        } else if (lowest === 'state') {\n          const nbrUfs = new Set([...usedUfs].flatMap(u => UF_BORDERS[u] || []).filter(u => !usedUfs.has(u)));\n          nbrUfs.forEach(u => { if (IDX.byUfName[u]) ctx.push({ idxs: Object.values(IDX.byUfName[u]) }); });\n        } else if (lowest === 'region') {\n          const nbrRegions = new Set();\n          [...usedUfs].forEach(u => (UF_BORDERS[u] || []).forEach(b => {\n            const br = Object.keys(REGION_UFS).find(r => REGION_UFS[r].includes(b));\n            if (br && !usedRegions.has(br)) nbrRegions.add(br);\n          }));\n          nbrRegions.forEach(r => {\n            const idxs = REGION_UFS[r].flatMap(u => IDX.byUfName[u] ? Object.values(IDX.byUfName[u]) : []);\n            if (idxs.length) ctx.push({ idxs });\n          });\n        }\n      }\n\n      S.focus = focus; S.ctx = ctx;\n\n      if (!focus.length && !ctx.length) {\n        $('#empty').classList.remove('hidden');\n        $('#legend').classList.add('hidden');\n        $('#map').innerHTML = '';\n        return;\n      }\n      $('#empty').classList.add('hidden');\n\n      const zoomIdxs = focus.length ? focus.flatMap(s => s.idxs) : ctx.flatMap(s => s.idxs);\n      S.pr = project(zoomIdxs.map(i => IDX.recs[i].f));\n      S.view = { scale: 1, tx: 0, ty: 0 };\n      drawMap();\n    }\n\n    function drawMap(){\n      const svg = $('#map'); svg.innerHTML = '';\n      const viewport = document.createElementNS(NS, 'g');\n      viewport.setAttribute('id', 'viewport');\n      svg.appendChild(viewport);\n      S.paths = []; S.labels = []; S.globeOn = null;\n      const touch = !!d2.param('touch_controls', false);\n      const labelFontSize = Math.max(4, Math.min(14, 9 * ((S.pr && S.pr.scale || 1000) / 1000)));\n\n      S.ctx.forEach((c, i) => {\n        const p = document.createElementNS(NS, 'path');\n        const ptRings = projRingsWorld(dissolveRings(c.idxs, IDX.recs), S.pr);\n        p.setAttribute('data-key', 'ctx-' + i);\n        p.setAttribute('class', 'area');\n        p.setAttribute('fill', '#dcdfe8');\n        p.setAttribute('opacity', '.55');\n        viewport.appendChild(p);\n        S.paths.push({ el: p, ptRings });\n      });\n\n      const legendGroups = [];\n      S.focus.forEach(item => {\n        const rings = dissolveRings(item.idxs, IDX.recs);\n        const basePtRings = projRingsWorld(rings, S.pr);\n        const base = document.createElementNS(NS, 'path');\n        base.setAttribute('data-key', item.key + '-base');\n        base.setAttribute('class', 'area');\n        base.setAttribute('fill', item.colors.length ? '#eef0f5' : '#94a3b8');\n        base.onmousemove = e => showInfo({ label: item.label, color: null }, e);\n        base.onmouseleave = hideInfo;\n        viewport.appendChild(base);\n        S.paths.push({ el: base, ptRings: basePtRings });\n\n        if (item.colors.length) {\n          const [bx0, , bx1] = mergedBbox(item.idxs, S.pr, IDX.recs);\n          const n = item.colors.length;\n          const stripeW = (bx1 - bx0) / n;\n          item.colors.forEach((c, ci) => {\n            const wedgePtRings = stripeClipRingsPts(rings, S.pr, bx0 + ci * stripeW, bx0 + (ci + 1) * stripeW);\n            if (!wedgePtRings.length) return;\n            const wedge = document.createElementNS(NS, 'path');\n            wedge.setAttribute('data-key', item.key + '-w' + ci);\n            wedge.setAttribute('class', 'wedge' + (touch ? ' touchy' : ''));\n            wedge.setAttribute('fill', c.color || PALETTE[ci % PALETTE.length]);\n            wedge.onclick = (e) => {\n              if (dragMoved) { dragMoved = false; return; }\n              if (touch) showInfo({ label: item.label, color: c });\n              // The ONLY click entry point — a color with no `onClick` just isn't actionable.\n              // Augment the REAL click event (not a bare payload object) so APIs that need a\n              // genuine DOM event as their first arg (e.g. @__DialogsService, which walks\n              // $event.parentElement to find the modal mount point) still work, while\n              // `$event.color_id` etc. remain readable.\n              if (typeof c.onClick === 'function') {\n                c.onClick(Object.assign(e, {\n                  level: item.level,\n                  location_id: item.entry.location_id,\n                  location_label: item.entry.location_label,\n                  color_id: c.color_id,\n                  color_label: c.color_label,\n                  color: c.color,\n                }));\n              }\n            };\n            if (!touch) {\n              wedge.onmousemove = e => showInfo({ label: item.label, color: c }, e);\n              wedge.onmouseleave = hideInfo;\n            }\n            viewport.appendChild(wedge);\n            S.paths.push({ el: wedge, ptRings: wedgePtRings });\n          });\n        }\n\n        const [cx, cy] = item.idxs.length > 1 ? mergeCentroid(item.idxs, S.pr, IDX.recs) : cxy(IDX.recs[item.idxs[0]].f, S.pr);\n        const t = document.createElementNS(NS, 'text');\n        t.setAttribute('class', 'lbl');\n        t.style.fontSize = labelFontSize + 'px';\n        t.textContent = (item.label || '').slice(0, 16);\n        viewport.appendChild(t);\n        S.labels.push({ el: t, pt: [cx, cy] });\n\n        if (item.colors.length) legendGroups.push({ entry: item.entry, items: item.colors });\n      });\n\n      renderLegend(legendGroups);\n      redraw();\n    }\n    function renderLegend(groups){\n      const el = $('#legend');\n      if (!groups.length) { el.classList.add('hidden'); return; }\n      el.classList.remove('hidden');\n      el.innerHTML = groups.map(g => {\n        const rows = g.items.map(c => `<div class=\"row\"><b style=\"background:${esc(c.color)}\"></b>${esc(c.color_label || c.color_id)}</div>`).join('');\n        return `<div class=\"grp\"><div class=\"grpname\">${esc(g.entry.location_label || g.entry.location_id || '')}</div>${rows}</div>`;\n      }).join('');\n    }\n\n    async function load(){\n      loader(true, 'carregando malhas do Brasil (27 UFs)…', false);\n      try {\n        const ufs = Object.keys(UF_CODE);\n        const geos = await Promise.all(ufs.map(u => geoUF(u).catch(() => null)));\n        const allFeat = [];\n        ufs.forEach((u, i) => { const g = geos[i]; if (!g) return; g.features.forEach(f => { f.__uf = u; allFeat.push(f); }); });\n        if (!allFeat.length) throw new Error('nenhuma malha carregada');\n        IDX = buildIndex(allFeat);\n        buildScene();\n        loader(false);\n      } catch (e) {\n        loader(true, 'erro: ' + e.message + ' (clique p/ tentar de novo)', true);\n      }\n    }\n    $('#loader').onclick = () => { if (!$('#loader').classList.contains('err')) return; IDX = null; load(); };\n\n    load();\n  },\n};"}},"city_list":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"city_list"}}}},"country_list":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"country_list"}}}},"municipality_list":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"municipality_list"}}}},"region_list":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"region_list"}}}},"should_render_neighbors":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"should_render_neighbors"}}}},"state_list":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"state_list"}}}},"touch_controls":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"touch_controls"}}}},"experimental_globe":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"experimental_globe"}}}}}}}}}},{"_id":"6a73f636d5a555faccfd905c","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"NQBdVnJLb4YsikMQETP8X","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function roundRect(ctx, x, y, w, h, r) {\n  const rr = Math.min(r, w / 2, h / 2)\n  ctx.beginPath()\n  ctx.moveTo(x + rr, y)\n  ctx.arcTo(x + w, y, x + w, y + h, rr)\n  ctx.arcTo(x + w, y + h, x, y + h, rr)\n  ctx.arcTo(x, y + h, x, y, rr)\n  ctx.arcTo(x, y, x + w, y, rr)\n  ctx.closePath()\n}\n\nfunction makeCanvasTexture(THREE, draw, w, h, scale) {\n  const s = scale || 2\n  const cnv = document.createElement('canvas')\n  cnv.width = w * s\n  cnv.height = h * s\n  const ctx = cnv.getContext('2d')\n  ctx.scale(s, s)\n  draw(ctx, w, h)\n  const tex = new THREE.CanvasTexture(cnv)\n  tex.needsUpdate = true\n  if (THREE.SRGBColorSpace) tex.colorSpace = THREE.SRGBColorSpace\n  return tex\n}\n\nfunction makeLabelSprite(THREE, text) {\n  const tex = makeCanvasTexture(THREE, (ctx, w, h) => {\n    ctx.clearRect(0, 0, w, h)\n    ctx.font = '600 40px -apple-system, \"Segoe UI\", Arial, sans-serif'\n    ctx.fillStyle = 'rgba(38,38,42,0.88)'\n    ctx.textAlign = 'center'\n    ctx.textBaseline = 'middle'\n    ctx.fillText(text, w / 2, h / 2)\n  }, 512, 128)\n  const mat = new THREE.SpriteMaterial({ map: tex, transparent: true, depthWrite: false })\n  const spr = new THREE.Sprite(mat)\n  spr.scale.set(1.8, 0.45, 1)\n  return spr\n}\n\nfunction makeGlowSprite(THREE, colorInner, colorOuter, size) {\n  const tex = makeCanvasTexture(THREE, (ctx, w, h) => {\n    const g = ctx.createRadialGradient(w / 2, h / 2, 0, w / 2, h / 2, w / 2)\n    g.addColorStop(0, colorInner)\n    g.addColorStop(0.35, colorOuter)\n    g.addColorStop(1, 'rgba(255,255,255,0)')\n    ctx.fillStyle = g\n    ctx.fillRect(0, 0, w, h)\n  }, 512, 512, 1)\n  const mat = new THREE.SpriteMaterial({\n    map: tex, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending\n  })\n  const spr = new THREE.Sprite(mat)\n  spr.scale.set(size, size, 1)\n  return spr\n}\n\n// ---------- simple 2D value noise (no external lib) ----------\nfunction hash2(x, y) {\n  const s = Math.sin(x * 127.1 + y * 311.7) * 43758.5453123\n  return s - Math.floor(s)\n}\nfunction noise2D(x, y) {\n  const xi = Math.floor(x), yi = Math.floor(y)\n  const xf = x - xi, yf = y - yi\n  const u = xf * xf * (3 - 2 * xf)\n  const v = yf * yf * (3 - 2 * yf)\n  const a = hash2(xi, yi)\n  const b = hash2(xi + 1, yi)\n  const c = hash2(xi, yi + 1)\n  const d = hash2(xi + 1, yi + 1)\n  return (a * (1 - u) * (1 - v) + b * u * (1 - v) + c * (1 - u) * v + d * u * v) * 2 - 1\n}\nfunction terrainHeight(x, y, peak, radius) {\n  const dist = Math.sqrt(x * x + y * y)\n  const falloff = Math.max(0, 1 - dist / radius)\n  const dome = Math.pow(falloff, 1.6)\n  const n1 = noise2D(x * 1.8 + 4.1, y * 1.8 + 1.7)\n  const n2 = noise2D(x * 5.5 + 9.3, y * 5.5 + 2.2)\n  const rough = (n1 * 0.12 + n2 * 0.045) * falloff\n  return Math.max(0, dome * peak + rough * peak)\n}\nconst TERRAIN_STOPS = [\n  { t: 0.00, c: [0.80, 0.72, 0.55] },\n  { t: 0.20, c: [0.36, 0.55, 0.27] },\n  { t: 0.52, c: [0.53, 0.50, 0.46] },\n  { t: 0.80, c: [0.84, 0.83, 0.80] },\n  { t: 1.00, c: [1.00, 1.00, 1.00] }\n]\nfunction heightColor(t) {\n  for (let i = 0; i < TERRAIN_STOPS.length - 1; i++) {\n    const a = TERRAIN_STOPS[i], b = TERRAIN_STOPS[i + 1]\n    if (t >= a.t && t <= b.t) {\n      const f = (t - a.t) / (b.t - a.t || 1)\n      return [\n        a.c[0] + (b.c[0] - a.c[0]) * f,\n        a.c[1] + (b.c[1] - a.c[1]) * f,\n        a.c[2] + (b.c[2] - a.c[2]) * f\n      ]\n    }\n  }\n  return TERRAIN_STOPS[TERRAIN_STOPS.length - 1].c\n}\n\n// ---------- app-card canvas drawings (logical 480x600 units) ----------\nfunction drawCalendarCard(ctx, w, h) {\n  ctx.clearRect(0, 0, w, h)\n  roundRect(ctx, 6, 6, w - 12, h - 12, 28)\n  ctx.fillStyle = '#ffffff'\n  ctx.fill()\n  ctx.save()\n  roundRect(ctx, 6, 6, w - 12, h - 12, 28)\n  ctx.clip()\n\n  ctx.fillStyle = '#1a1a1e'\n  ctx.font = '700 30px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.textAlign = 'left'\n  ctx.fillText('Calendar', 34, 62)\n  ctx.font = '500 18px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.fillStyle = '#8a8a90'\n  ctx.fillText('April 2016', 34, 90)\n\n  ctx.strokeStyle = '#eceded'\n  ctx.lineWidth = 2\n  ctx.beginPath()\n  ctx.moveTo(24, 110)\n  ctx.lineTo(w - 24, 110)\n  ctx.stroke()\n\n  const days = ['S', 'M', 'T', 'W', 'T', 'F', 'S']\n  const gridX = 30, gridY = 140, cellW = (w - 60) / 7, cellH = 46\n  ctx.font = '600 15px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.fillStyle = '#b3b3b8'\n  ctx.textAlign = 'center'\n  days.forEach((d, i) => ctx.fillText(d, gridX + cellW * i + cellW / 2, gridY))\n\n  const firstDayCol = 5\n  const daysInMonth = 30\n  let col = firstDayCol, row = 0\n  ctx.font = '500 17px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  for (let day = 1; day <= daysInMonth; day++) {\n    const cx = gridX + cellW * col + cellW / 2\n    const cy = gridY + 40 + row * cellH\n    if (day === 6) {\n      ctx.beginPath()\n      ctx.arc(cx, cy, 17, 0, Math.PI * 2)\n      ctx.fillStyle = '#e0384a'\n      ctx.fill()\n      ctx.fillStyle = '#ffffff'\n    } else {\n      ctx.fillStyle = '#3a3a3e'\n    }\n    ctx.fillText(String(day), cx, cy + 5)\n    col++\n    if (col > 6) { col = 0; row++ }\n  }\n\n  const evY = gridY + 40 + (row + 1) * cellH + 20\n  ctx.textAlign = 'left'\n  ctx.font = '600 15px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.fillStyle = '#6a6a70'\n  ctx.fillText('TODAY', 34, evY)\n  const events = ['Team meeting', 'Lunch with team', 'Design review', 'Get groceries']\n  ctx.font = '500 16px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  events.forEach((e, i) => {\n    const y = evY + 34 + i * 34\n    ctx.beginPath()\n    ctx.arc(40, y - 5, 4, 0, Math.PI * 2)\n    ctx.fillStyle = '#e0384a'\n    ctx.fill()\n    ctx.fillStyle = '#2c2c30'\n    ctx.fillText(e, 56, y)\n  })\n\n  ctx.restore()\n}\n\nfunction drawWeatherCard(ctx, w, h) {\n  ctx.clearRect(0, 0, w, h)\n  roundRect(ctx, 6, 6, w - 12, h - 12, 28)\n  ctx.fillStyle = '#1f3fe0'\n  ctx.fill()\n  ctx.save()\n  roundRect(ctx, 6, 6, w - 12, h - 12, 28)\n  ctx.clip()\n  ctx.fillStyle = '#ffffff'\n  ctx.textAlign = 'center'\n  ctx.font = '300 150px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.fillText('46°', w / 2, h / 2 + 10)\n  ctx.font = '600 24px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.globalAlpha = 0.85\n  ctx.fillText('WEATHER', w / 2, h / 2 + 90)\n  ctx.globalAlpha = 1\n  ctx.restore()\n}\n\nfunction drawCalculatorCard(ctx, w, h) {\n  ctx.clearRect(0, 0, w, h)\n  roundRect(ctx, 6, 6, w - 12, h - 12, 28)\n  ctx.fillStyle = '#e0384a'\n  ctx.fill()\n  ctx.save()\n  roundRect(ctx, 6, 6, w - 12, h - 12, 28)\n  ctx.clip()\n\n  ctx.strokeStyle = 'rgba(255,255,255,0.85)'\n  ctx.lineWidth = 2\n  ctx.beginPath()\n  ctx.moveTo(30, 40)\n  ctx.lineTo(50, 40)\n  ctx.stroke()\n  ctx.strokeRect(70, 32, 16, 16)\n  ctx.beginPath()\n  ctx.moveTo(100, 32)\n  ctx.lineTo(116, 48)\n  ctx.moveTo(116, 32)\n  ctx.lineTo(100, 48)\n  ctx.stroke()\n\n  const dispX = 30, dispY = 64, dispW = w - 72, dispH = 90\n  roundRect(ctx, dispX, dispY, dispW, dispH, 14)\n  ctx.fillStyle = '#ffffff'\n  ctx.fill()\n  ctx.fillStyle = '#1a1a1e'\n  ctx.font = '600 52px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.textAlign = 'right'\n  ctx.fillText('0', dispX + dispW - 20, dispY + dispH - 26)\n\n  const keys = [\n    ['CE', 'C', '⌫', '÷'],\n    ['7', '8', '9', '×'],\n    ['4', '5', '6', '−'],\n    ['1', '2', '3', '+'],\n    ['0', '.', '=', '=']\n  ]\n  const padX = 30, padY = 180, padW = w - 60, gap = 10\n  const keyW = (padW - gap * 3) / 4\n  const keyH = 62\n  keys.forEach((rowArr, r) => {\n    rowArr.forEach((k, c) => {\n      if (r === 4 && c === 3) return\n      const kx = padX + c * (keyW + gap)\n      const ky = padY + r * (keyH + gap)\n      let kw = keyW\n      if (r === 4 && c === 2) kw = keyW * 2 + gap\n      roundRect(ctx, kx, ky, kw, keyH, 12)\n      const isOp = ['÷', '×', '−', '+', '='].includes(k)\n      ctx.fillStyle = isOp ? 'rgba(255,255,255,0.92)' : 'rgba(255,255,255,0.18)'\n      ctx.fill()\n      ctx.fillStyle = isOp ? '#e0384a' : '#ffffff'\n      ctx.font = '600 24px -apple-system, \"Segoe UI\", Arial, sans-serif'\n      ctx.textAlign = 'center'\n      ctx.fillText(k, kx + kw / 2, ky + keyH / 2 + 8)\n    })\n  })\n\n  ctx.restore()\n}\n\nfunction drawWorldClockCard(ctx, w, h) {\n  ctx.clearRect(0, 0, w, h)\n  roundRect(ctx, 6, 6, w - 12, h - 12, 28)\n  ctx.fillStyle = '#0d1420'\n  ctx.fill()\n  ctx.save()\n  roundRect(ctx, 6, 6, w - 12, h - 12, 28)\n  ctx.clip()\n\n  ctx.fillStyle = '#ffffff'\n  ctx.textAlign = 'left'\n  ctx.font = '600 22px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.fillText('London', 40, 90)\n  ctx.font = '300 64px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.fillText('14:32', 40, 170)\n\n  ctx.strokeStyle = 'rgba(255,255,255,0.12)'\n  ctx.lineWidth = 2\n  ctx.beginPath()\n  ctx.moveTo(40, 220)\n  ctx.lineTo(w - 40, 220)\n  ctx.stroke()\n\n  ctx.font = '600 22px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.fillStyle = '#ffffff'\n  ctx.fillText('Berlin', 40, 270)\n  ctx.font = '300 64px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.fillText('15:32', 40, 350)\n\n  ctx.font = '500 16px -apple-system, \"Segoe UI\", Arial, sans-serif'\n  ctx.fillStyle = 'rgba(255,255,255,0.55)'\n  ctx.fillText('1 hour ahead', 40, 390)\n\n  const cxp = w - 90, cyp = h - 90, r = 52\n  ctx.strokeStyle = 'rgba(255,255,255,0.4)'\n  ctx.lineWidth = 2\n  ctx.beginPath()\n  ctx.arc(cxp, cyp, r, 0, Math.PI * 2)\n  ctx.stroke()\n  for (let i = 0; i < 12; i++) {\n    const a = (i / 12) * Math.PI * 2\n    const x1 = cxp + Math.cos(a) * (r - 6), y1 = cyp + Math.sin(a) * (r - 6)\n    const x2 = cxp + Math.cos(a) * r, y2 = cyp + Math.sin(a) * r\n    ctx.beginPath()\n    ctx.moveTo(x1, y1)\n    ctx.lineTo(x2, y2)\n    ctx.stroke()\n  }\n  ctx.beginPath()\n  ctx.moveTo(cxp, cyp)\n  ctx.lineTo(cxp + Math.cos(-1.2) * r * 0.55, cyp + Math.sin(-1.2) * r * 0.55)\n  ctx.stroke()\n  ctx.beginPath()\n  ctx.moveTo(cxp, cyp)\n  ctx.lineTo(cxp + Math.cos(0.4) * r * 0.8, cyp + Math.sin(0.4) * r * 0.8)\n  ctx.stroke()\n\n  ctx.restore()\n}\n\n// ---------- floor widgets ----------\nfunction buildIslandWidget(THREE, x, z) {\n  const group = new THREE.Group()\n  group.position.set(x, 0, z)\n\n  const water = new THREE.Mesh(\n    new THREE.PlaneGeometry(3.3, 3.3),\n    new THREE.MeshPhysicalMaterial({ color: 0x2f8a95, roughness: 0.15, metalness: 0, transparent: true, opacity: 0.92 })\n  )\n  water.rotation.x = -Math.PI / 2\n  water.position.y = 0.02\n  water.receiveShadow = true\n  group.add(water)\n\n  const peak = 1.15\n  const radius = 1.05\n  const size = 2.1\n  const segs = 64\n  const geo = new THREE.PlaneGeometry(size, size, segs, segs)\n  const pos = geo.attributes.position\n  const colors = new Float32Array(pos.count * 3)\n  for (let i = 0; i < pos.count; i++) {\n    const vx = pos.getX(i)\n    const vy = pos.getY(i)\n    const hgt = terrainHeight(vx, vy, peak, radius)\n    pos.setZ(i, hgt)\n    const t = Math.max(0, Math.min(1, hgt / peak))\n    const c = heightColor(t)\n    colors[i * 3] = c[0]; colors[i * 3 + 1] = c[1]; colors[i * 3 + 2] = c[2]\n  }\n  geo.computeVertexNormals()\n  geo.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3))\n  const terrain = new THREE.Mesh(\n    geo,\n    new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 0.88, metalness: 0 })\n  )\n  terrain.rotation.x = -Math.PI / 2\n  terrain.position.y = 0.05\n  terrain.castShadow = true\n  terrain.receiveShadow = true\n  group.add(terrain)\n\n  const treeSpots = [[0.38, -0.22], [-0.32, 0.18], [0.06, 0.44]]\n  treeSpots.forEach(([tx, tz]) => {\n    const th = terrainHeight(tx, tz, peak, radius) + 0.05\n    const trunk = new THREE.Mesh(\n      new THREE.CylinderGeometry(0.015, 0.02, 0.09, 5),\n      new THREE.MeshStandardMaterial({ color: 0x5a4028, roughness: 0.9 })\n    )\n    trunk.position.set(tx, th + 0.045, tz)\n    trunk.castShadow = true\n    trunk.receiveShadow = true\n    const foliage = new THREE.Mesh(\n      new THREE.ConeGeometry(0.075, 0.16, 6),\n      new THREE.MeshStandardMaterial({ color: 0x3f6b3a, roughness: 0.85 })\n    )\n    foliage.position.set(tx, th + 0.09 + 0.08, tz)\n    foliage.castShadow = true\n    foliage.receiveShadow = true\n    group.add(trunk, foliage)\n  })\n\n  const label = makeLabelSprite(THREE, 'Research Project')\n  label.position.set(0, 0.1, 1.45)\n  group.add(label)\n\n  return group\n}\n\nfunction buildGemWidget(THREE, x, z) {\n  const group = new THREE.Group()\n  group.position.set(x, 0, z)\n\n  const pedestal = new THREE.Mesh(\n    new THREE.CylinderGeometry(0.88, 0.98, 0.16, 28),\n    new THREE.MeshStandardMaterial({ color: 0xeceae4, roughness: 0.72, metalness: 0 })\n  )\n  pedestal.position.y = 0.08\n  pedestal.castShadow = true\n  pedestal.receiveShadow = true\n  group.add(pedestal)\n\n  const radius = 0.62\n  let geo = new THREE.IcosahedronGeometry(radius, 0)\n  geo = geo.toNonIndexed()\n  const posAttr = geo.attributes.position\n  const faceCount = posAttr.count / 3\n  const palette = [\n    [0.60, 0.34, 0.86], [0.42, 0.30, 0.80], [0.72, 0.46, 0.92],\n    [0.48, 0.55, 0.93], [0.78, 0.55, 0.95], [0.55, 0.40, 0.88]\n  ]\n  const colors = new Float32Array(posAttr.count * 3)\n  for (let f = 0; f < faceCount; f++) {\n    const c = palette[f % palette.length]\n    for (let v = 0; v < 3; v++) {\n      const idx = (f * 3 + v) * 3\n      colors[idx] = c[0]; colors[idx + 1] = c[1]; colors[idx + 2] = c[2]\n    }\n  }\n  geo.setAttribute('color', new THREE.BufferAttribute(colors, 3))\n  geo.computeVertexNormals()\n  const gem = new THREE.Mesh(geo, new THREE.MeshPhysicalMaterial({\n    vertexColors: true, flatShading: true, roughness: 0.2,\n    clearcoat: 0.6, clearcoatRoughness: 0.15, metalness: 0.05\n  }))\n  gem.position.y = 0.16 + radius * 0.82\n  gem.rotation.y = 0.4\n  gem.castShadow = true\n  gem.receiveShadow = true\n  group.add(gem)\n  group.userData.gem = gem\n\n  const label = makeLabelSprite(THREE, 'Rewards')\n  label.position.set(0, 0.1, 1.15)\n  group.add(label)\n\n  return group\n}\n\nfunction buildPieWidget(THREE, x, z) {\n  const group = new THREE.Group()\n  group.position.set(x, 0, z)\n\n  const radius = 1.05\n  const slices = [\n    { deg: 120, height: 0.85, color: 0xf5f0e6 },\n    { deg: 90, height: 0.55, color: 0x6b7280 },\n    { deg: 90, height: 1.25, color: 0xe0384a },\n    { deg: 60, height: 0.35, color: 0x2b2b30 }\n  ]\n  let startAngle = -Math.PI / 2\n  slices.forEach(s => {\n    const angleRad = (s.deg * Math.PI) / 180\n    const endAngle = startAngle + angleRad\n    const shape = new THREE.Shape()\n    shape.moveTo(0, 0)\n    shape.absarc(0, 0, radius, startAngle, endAngle, false)\n    shape.lineTo(0, 0)\n    const geo = new THREE.ExtrudeGeometry(shape, { depth: s.height, bevelEnabled: false, curveSegments: 24 })\n    geo.rotateX(-Math.PI / 2)\n    geo.computeBoundingBox()\n    const minY = geo.boundingBox.min.y\n    geo.translate(0, -minY, 0)\n    geo.computeVertexNormals()\n    const mat = new THREE.MeshStandardMaterial({ color: s.color, roughness: 0.6, metalness: 0, flatShading: true })\n    const mesh = new THREE.Mesh(geo, mat)\n    mesh.castShadow = true\n    mesh.receiveShadow = true\n    group.add(mesh)\n    const edgeGeo = new THREE.EdgesGeometry(geo, 20)\n    const edgeMat = new THREE.LineBasicMaterial({ color: 0x1c1c1e, transparent: true, opacity: 0.35 })\n    group.add(new THREE.LineSegments(edgeGeo, edgeMat))\n    startAngle = endAngle\n  })\n\n  const label = makeLabelSprite(THREE, 'March 2016 Sales')\n  label.position.set(0, 0.12, 1.55)\n  group.add(label)\n\n  return group\n}\n\nfunction buildTaskCubesWidget(THREE, x, z) {\n  const group = new THREE.Group()\n  group.position.set(x, 0, z)\n\n  function cube(cx, cz, bodyColor, text) {\n    const size = 0.62\n    const bodyMat = new THREE.MeshStandardMaterial({ color: bodyColor, roughness: 0.68, metalness: 0 })\n    const tex = makeCanvasTexture(THREE, (ctx, w, h) => {\n      ctx.clearRect(0, 0, w, h)\n      ctx.fillStyle = '#ffffff'\n      ctx.textAlign = 'center'\n      ctx.textBaseline = 'middle'\n      ctx.font = '700 64px -apple-system, \"Segoe UI\", Arial, sans-serif'\n      ctx.fillText(text, w / 2, h / 2)\n    }, 256, 256)\n    const faceMat = new THREE.MeshStandardMaterial({ map: tex, transparent: true, roughness: 0.6, metalness: 0 })\n    const materials = [bodyMat, bodyMat, bodyMat, bodyMat, faceMat, bodyMat]\n    const mesh = new THREE.Mesh(new THREE.BoxGeometry(size, size, size), materials)\n    mesh.position.set(cx, size / 2, cz)\n    mesh.castShadow = true\n    mesh.receiveShadow = true\n    mesh.userData.faceTexture = tex\n    return mesh\n  }\n\n  const c1 = cube(-0.42, 0, 0x17181c, '2/24')\n  const c2 = cube(0.32, 0.06, 0xe0384a, '4/19')\n  const c3 = cube(1.15, -0.25, 0x17181c, '')\n  group.add(c1, c2, c3)\n\n  const homeLabel = makeLabelSprite(THREE, 'Home remodel')\n  homeLabel.scale.set(1.3, 0.32, 1)\n  homeLabel.position.set(1.15, 0.85, -0.25)\n  group.add(homeLabel)\n\n  const label = makeLabelSprite(THREE, 'Tasks')\n  label.position.set(0.3, 0.12, 1.1)\n  group.add(label)\n\n  return { group, textures: [c1.userData.faceTexture, c2.userData.faceTexture, c3.userData.faceTexture] }\n}\n\nfunction initScene(THREE, d2, root, canvas) {\n  const scene = new THREE.Scene()\n\n  const camera = new THREE.PerspectiveCamera(36, 1, 0.1, 100)\n  const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true })\n  renderer.setClearColor(0x000000, 0)\n  renderer.shadowMap.enabled = true\n  renderer.shadowMap.type = THREE.PCFSoftShadowMap\n  renderer.outputColorSpace = THREE.SRGBColorSpace\n  renderer.toneMapping = THREE.ACESFilmicToneMapping\n  renderer.toneMappingExposure = 1.1\n\n  scene.fog = new THREE.Fog(0xdfe1e3, 12, 30)\n\n  const hemi = new THREE.HemisphereLight(0xeef1f3, 0xb6b2a6, 0.55)\n  const key = new THREE.DirectionalLight(0xfff0d6, 2.4)\n  key.position.set(6.5, 12, 6)\n  key.castShadow = true\n  key.shadow.mapSize.set(2048, 2048)\n  key.shadow.bias = -0.0015\n  key.shadow.normalBias = 0.02\n  const rim = new THREE.DirectionalLight(0xdfe8ff, 0.35)\n  rim.position.set(-8, 6, -5)\n  scene.add(hemi, key, rim)\n\n  const floor = new THREE.Mesh(\n    new THREE.PlaneGeometry(48, 48),\n    new THREE.MeshStandardMaterial({ color: 0xd7d9dc, roughness: 0.92, metalness: 0 })\n  )\n  floor.rotation.x = -Math.PI / 2\n  floor.receiveShadow = true\n  scene.add(floor)\n\n  // sun + glow (self-lit, excepted from PBR shading per spec)\n  const sunGroup = new THREE.Group()\n  const sun = new THREE.Mesh(\n    new THREE.SphereGeometry(0.45, 24, 24),\n    new THREE.MeshBasicMaterial({ color: 0xfff3c4, toneMapped: false })\n  )\n  const glow = makeGlowSprite(THREE, 'rgba(255,247,214,0.9)', 'rgba(255,236,170,0.35)', 5.2)\n  sunGroup.add(glow, sun)\n  sunGroup.position.set(7, 10, -7)\n  scene.add(sunGroup)\n\n  const contentGroup = new THREE.Group()\n  scene.add(contentGroup)\n\n  const windowsGroup = new THREE.Group()\n  contentGroup.add(windowsGroup)\n  const windowDefs = [\n    { draw: drawCalendarCard, x: -3.3, y: 4.1, z: 1.0, rotY: 0.16 },\n    { draw: drawWeatherCard, x: -1.05, y: 4.7, z: -0.6, rotY: -0.06 },\n    { draw: drawCalculatorCard, x: 1.35, y: 4.35, z: 0.9, rotY: -0.2 },\n    { draw: drawWorldClockCard, x: 3.55, y: 3.75, z: -1.1, rotY: 0.11 }\n  ]\n  const windowTextures = []\n  const bevelMat = new THREE.MeshStandardMaterial({ color: 0xe4e3e6, roughness: 0.68, metalness: 0 })\n  windowDefs.forEach(def => {\n    const tex = makeCanvasTexture(THREE, def.draw, 480, 600, 2)\n    windowTextures.push(tex)\n    const frontMat = new THREE.MeshStandardMaterial({ map: tex, roughness: 0.55, metalness: 0 })\n    const depth = 0.22\n    const geo = new THREE.BoxGeometry(2.15, 2.6875, depth)\n    const mesh = new THREE.Mesh(geo, [bevelMat, bevelMat, bevelMat, bevelMat, frontMat, bevelMat])\n    mesh.position.set(def.x, def.y, def.z)\n    mesh.rotation.y = def.rotY\n    mesh.castShadow = true\n    mesh.receiveShadow = true\n    windowsGroup.add(mesh)\n  })\n\n  const widgetsGroup = new THREE.Group()\n  contentGroup.add(widgetsGroup)\n  const island = buildIslandWidget(THREE, -4.4, 4.0)\n  const gemWidget = buildGemWidget(THREE, -1.4, 4.7)\n  const pie = buildPieWidget(THREE, 1.5, 4.9)\n  const cubesResult = buildTaskCubesWidget(THREE, 4.4, 4.1)\n  widgetsGroup.add(island, gemWidget, pie, cubesResult.group)\n\n  // fit the key-light shadow frustum to the actual scene bounds\n  const bbox = new THREE.Box3().setFromObject(contentGroup)\n  const bcenter = bbox.getCenter(new THREE.Vector3())\n  const bsize = bbox.getSize(new THREE.Vector3())\n  const halfSpan = Math.max(bsize.x, bsize.z) / 2 + 1.5\n  key.target.position.copy(bcenter)\n  scene.add(key.target)\n  key.shadow.camera.near = 1\n  key.shadow.camera.far = halfSpan * 3 + 20\n  key.shadow.camera.left = -halfSpan\n  key.shadow.camera.right = halfSpan\n  key.shadow.camera.top = halfSpan\n  key.shadow.camera.bottom = -halfSpan\n  key.shadow.camera.updateProjectionMatrix()\n\n  function setAnisotropy() {\n    const maxAniso = renderer.capabilities.getMaxAnisotropy()\n    windowTextures.forEach(t => { t.anisotropy = maxAniso; t.needsUpdate = true })\n    cubesResult.textures.forEach(t => { if (t) { t.anisotropy = maxAniso; t.needsUpdate = true } })\n  }\n  setAnisotropy()\n\n  function resize() {\n    const w = canvas.clientWidth || 1\n    const h = canvas.clientHeight || 1\n    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2))\n    renderer.setSize(w, h, false)\n    camera.aspect = w / h\n    camera.updateProjectionMatrix()\n  }\n  resize()\n  const ro = new ResizeObserver(resize)\n  ro.observe(canvas)\n\n  const mouse = { x: 0, y: 0 }\n  const mouseEased = { x: 0, y: 0 }\n  d2.on(window, 'mousemove', (e) => {\n    const rect = canvas.getBoundingClientRect()\n    mouse.x = Math.max(-1, Math.min(1, ((e.clientX - rect.left) / rect.width) * 2 - 1))\n    mouse.y = Math.max(-1, Math.min(1, ((e.clientY - rect.top) / rect.height) * 2 - 1))\n  })\n\n  const center = new THREE.Vector3(0, 1.75, 1.1)\n  const R = 12.5\n  const H = 6.4\n  let theta = 0\n\n  const clockEls = {\n    time: root.querySelector('.overlay .time'),\n    day: root.querySelector('.overlay .day'),\n    date: root.querySelector('.overlay .date')\n  }\n  let lastMinute = -1\n  function tickClock() {\n    const now = new Date()\n    if (now.getMinutes() === lastMinute) return\n    lastMinute = now.getMinutes()\n    const h = now.getHours()\n    const hh = ((h + 11) % 12) + 1\n    const mm = String(now.getMinutes()).padStart(2, '0')\n    const ampm = h >= 12 ? 'PM' : 'AM'\n    if (clockEls.time) clockEls.time.textContent = hh + ':' + mm + ' ' + ampm\n    const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']\n    const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']\n    if (clockEls.day) clockEls.day.textContent = dayNames[now.getDay()]\n    if (clockEls.date) clockEls.date.textContent = monthNames[now.getMonth()] + ' ' + now.getDate()\n  }\n  tickClock()\n\n  d2.raf(() => {\n    theta += 0.00075\n    mouseEased.x += (mouse.x - mouseEased.x) * 0.04\n    mouseEased.y += (mouse.y - mouseEased.y) * 0.04\n\n    camera.position.x = center.x + Math.sin(theta) * R + mouseEased.x * 1.1\n    camera.position.z = center.z + Math.cos(theta) * R\n    camera.position.y = H + mouseEased.y * 0.6\n    camera.lookAt(center.x + mouseEased.x * 0.3, center.y, center.z)\n\n    if (gemWidget.userData.gem) gemWidget.userData.gem.rotation.y += 0.004\n\n    renderer.render(scene, camera)\n    tickClock()\n  })\n\n  d2.onUnmount(() => {\n    ro.disconnect()\n    renderer.dispose()\n  })\n}\n\nexport default {\n  css: `\n    .stage { position:absolute; inset:0; width:100%; height:100%; overflow:hidden;\n      background: linear-gradient(180deg, #eef0f2 0%, #e6e8eb 50%, #d7d9dc 100%); }\n    canvas.scene-canvas { position:absolute; inset:0; width:100%; height:100%; display:block; }\n    .overlay { position:absolute; top:28px; left:36px; pointer-events:none;\n      font-family: -apple-system, \"Segoe UI\", Roboto, Arial, sans-serif; color:#141414; }\n    .overlay .time { font-size:56px; font-weight:800; line-height:1; letter-spacing:-1px; }\n    .overlay .day { font-size:20px; font-weight:700; opacity:.72; margin-top:8px; }\n    .overlay .date { font-size:20px; font-weight:700; opacity:.72; }\n    .loading { position:absolute; inset:0; display:flex; align-items:center; justify-content:center;\n      font-family: -apple-system, sans-serif; color:#9a9a9e;\n      background: linear-gradient(180deg, #eef0f2 0%, #d7d9dc 100%); }\n  `,\n  render(d2) {\n    const THREE = d2.lib('three@0.160.0')\n    if (!THREE || d2.lib.state('three@0.160.0') !== 'ready') {\n      return `<div class=\"loading\">Loading scene…</div>`\n    }\n    return `<div class=\"stage\">\n      <canvas class=\"scene-canvas\" data-d2-static></canvas>\n      <div class=\"overlay\" data-d2-static>\n        <div class=\"time\">--:--</div>\n        <div class=\"day\">-</div>\n        <div class=\"date\">-</div>\n      </div>\n    </div>`\n  },\n  updated(d2, root) {\n    const THREE = d2.lib('three@0.160.0')\n    if (!THREE) return\n    const canvas = root.querySelector('canvas.scene-canvas')\n    if (!canvas || canvas.__inited) return\n    canvas.__inited = true\n    initScene(THREE, d2, root, canvas)\n  }\n}\n"}}}}}}}},{"_id":"6a73fbd2f08aa56d3e04cc5b","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"Mm2odErnOlW8b_N0I0TTd","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    .hic-frame  { position:relative; width:100%; height:100%; box-sizing:border-box;\n                  border-radius:16px; overflow:hidden; }\n    .hic-canvas { display:block; width:100%; height:100%; }\n    /* natural-size so it can float + reflect; not stretched to fill */\n    .hic-content{ display:inline-block; box-sizing:border-box; }\n    /* fallback path (API off) — just show the content */\n    .hic-fallback{ display:block; width:100%; height:100%; box-sizing:border-box; }\n  `,\n  render(d2) {\n    const html = d2.param('html', '<p>No content</p>')\n    const supported = typeof CanvasRenderingContext2D !== 'undefined'\n      && typeof CanvasRenderingContext2D.prototype.drawElementImage === 'function'\n    if (!supported) {\n      return `<div class=\"hic-fallback\">${html}</div>`\n    }\n    return `<div class=\"hic-frame\"><canvas layoutsubtree class=\"hic-canvas\"><div class=\"hic-content\">${html}</div></canvas></div>`\n  },\n  mounted(d2) { wireCanvas(d2) },\n  updated(d2) {\n    wireCanvas(d2)\n    const canvas = d2.$('.hic-canvas')\n    if (canvas && canvas.requestPaint) canvas.requestPaint()\n  },\n}\n\nfunction wireCanvas(d2) {\n  const canvas = d2.$('.hic-canvas')\n  if (!canvas || canvas._hicWired) return\n  canvas._hicWired = true\n\n  const ctx = canvas.getContext('2d')\n  const content = d2.$('.hic-content')\n  const hue = Number(d2.param('accent', 265))\n\n  let raf = 0, W = 1, H = 1, dpr = 1\n  const parts = Array.from({ length: 40 }, (_, i) => ({\n    x: (i * 97) % 920 / 920, y: ((i * 173) % 520) / 520,\n    r: 0.6 + (i % 5) * 0.7, s: 0.15 + (i % 7) * 0.05, ph: i * 0.6,\n  }))\n\n  const size = () => {\n    const r = canvas.getBoundingClientRect()\n    W = Math.max(1, r.width); H = Math.max(1, r.height)\n    dpr = Math.max(1, window.devicePixelRatio || 1)\n    canvas.width = Math.round(W * dpr)      // DPR-scaled backing store = crisp on retina\n    canvas.height = Math.round(H * dpr)\n  }\n\n  const aurora = (t) => {\n    const g = ctx.createLinearGradient(0, 0, W, H)\n    const s = Math.sin(t / 4000) * 40\n    g.addColorStop(0, `hsl(${hue} 70% 12%)`)\n    g.addColorStop(0.5, `hsl(${hue + s + 40} 60% 9%)`)\n    g.addColorStop(1, `hsl(${hue + 80} 65% 8%)`)\n    ctx.fillStyle = g; ctx.fillRect(0, 0, W, H)\n    for (let k = 0; k < 2; k++) {\n      const cx = W * (0.3 + 0.4 * k) + Math.sin(t / 2600 + k * 2) * 90\n      const cy = H * (0.35 + 0.3 * k) + Math.cos(t / 3100 + k) * 70\n      const rad = ctx.createRadialGradient(cx, cy, 0, cx, cy, 240)\n      rad.addColorStop(0, `hsl(${hue + k * 90} 85% 60% / .28)`)\n      rad.addColorStop(1, 'transparent')\n      ctx.fillStyle = rad; ctx.fillRect(0, 0, W, H)\n    }\n  }\n\n  const particles = (t) => {\n    ctx.fillStyle = `hsl(${hue} 90% 85% / .5)`\n    for (const p of parts) {\n      let py = (p.y - (t / 1000) * p.s * 0.05) % 1; if (py < 0) py += 1\n      const xx = p.x * W + Math.sin(t / 1500 + p.ph) * 10\n      ctx.globalAlpha = (0.4 + 0.6 * Math.abs(Math.sin(t / 900 + p.ph))) * 0.6\n      ctx.beginPath(); ctx.arc(xx, py * H, p.r, 0, Math.PI * 2); ctx.fill()\n    }\n    ctx.globalAlpha = 1\n  }\n\n  const frame = (t) => {\n    ctx.reset(); ctx.scale(dpr, dpr)\n    aurora(t); particles(t)\n\n    const cw = content.offsetWidth || W, ch = content.offsetHeight || H\n    const cx = (W - cw) / 2\n    const cy = (H - ch) / 2 + Math.sin(t / 1000) * 6\n\n    // reflection first (non-interactive copy)\n    ctx.save()\n    ctx.globalAlpha = 0.16\n    ctx.translate(0, cy * 2 + ch + 22); ctx.scale(1, -1)\n    try { ctx.drawElementImage(content, cx, -cy - ch) } catch (e) {}\n    ctx.restore()\n\n    // primary draw — feed the returned matrix back so clicks/focus line up\n    try {\n      const m = ctx.drawElementImage(content, cx, cy)\n      if (m && m.toString) content.style.transform = m.toString()\n    } catch (e) {}\n\n    raf = requestAnimationFrame(frame)\n  }\n\n  size()\n  const ro = new ResizeObserver(() => size())\n  ro.observe(canvas)\n  raf = requestAnimationFrame(frame)\n\n  d2.onUnmount(() => { cancelAnimationFrame(raf); ro.disconnect() })\n}"}},"html":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"html"}}}}}}}}}},{"_id":"6a73feb5d5a555faccfd9eb6","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"TeXEHCJDi5uGSHcKKr56N","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `.plain-html-content { display:block; width:100%; height:100%; box-sizing:border-box; }`,\n  render(d2) {\n    const html = d2.param('html', '<p>No content</p>')\n    return `<div class=\"plain-html-content\">${html}</div>`\n  },\n}\n"}},"html":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"html"}}}}}}}}}},{"_id":"6a76491f505962a50a29efb1","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"_EH47qLRPDw222FnDDcNM","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const MAPLIBRE_JS = { url: 'https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.js', global: 'maplibregl' };\nconst MAPLIBRE_CSS = { css: 'https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.css' };\nconst WORLD_SPAN_DEG = 4;\n\nfunction esc(s) {\n  return (s == null ? '' : String(s)).replace(/[&<>\"']/g, function (c) {\n    return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c];\n  });\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%;box-sizing:border-box}\n    *{box-sizing:border-box}\n    .app{position:relative;width:100%;height:100%;display:flex;flex-direction:column;\n      font:13px -apple-system,BlinkMacSystemFont,\"SF Pro Text\",Inter,sans-serif;\n      background:#111214;border-radius:14px;overflow:hidden;\n      box-shadow:0 10px 30px rgba(0,0,0,.28);color:#1d1d1f}\n\n    .topbar{display:flex;align-items:center;gap:12px;padding:10px 14px;\n      background:rgba(28,28,30,.92);-webkit-backdrop-filter:blur(20px) saturate(160%);\n      backdrop-filter:blur(20px) saturate(160%);border-bottom:1px solid rgba(255,255,255,.08);\n      z-index:20;position:relative}\n    .tabs{display:flex;gap:6px;flex-wrap:wrap}\n    .tab{appearance:none;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.06);\n      color:rgba(255,255,255,.75);padding:6px 12px;border-radius:8px;font-size:12px;font-weight:600;\n      cursor:pointer;transition:background .12s ease,color .12s ease}\n    .tab:hover{background:rgba(255,255,255,.12)}\n    .tab.active{background:#f5b642;color:#1d1d1f;border-color:transparent}\n\n    .search{position:relative;flex:1;max-width:280px}\n    .search input{width:100%;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);\n      color:#fff;border-radius:8px;padding:7px 10px;font-size:12px;outline:none}\n    .search input::placeholder{color:rgba(255,255,255,.4)}\n    .search input:focus{border-color:#f5b642}\n    .searchResults{position:absolute;top:calc(100% + 6px);left:0;right:0;background:rgba(28,28,30,.98);\n      border:1px solid rgba(255,255,255,.1);border-radius:10px;overflow:hidden;display:none;\n      box-shadow:0 12px 28px rgba(0,0,0,.4);max-height:260px;overflow-y:auto;z-index:30}\n    .searchResults.show{display:block}\n    .resultRow{display:flex;align-items:center;gap:8px;padding:8px 10px;color:rgba(255,255,255,.85);\n      cursor:pointer;font-size:12px;border-bottom:1px solid rgba(255,255,255,.06)}\n    .resultRow:last-child{border-bottom:none}\n    .resultRow:hover{background:rgba(255,255,255,.08)}\n    .resultRegion{margin-left:auto;color:rgba(255,255,255,.4);font-size:11px}\n\n    .fsBtn{appearance:none;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.06);\n      color:rgba(255,255,255,.8);width:32px;height:32px;border-radius:8px;cursor:pointer;font-size:15px;\n      display:flex;align-items:center;justify-content:center;flex:0 0 auto}\n    .fsBtn:hover{background:rgba(255,255,255,.14)}\n\n    .stage{position:relative;flex:1;min-height:0}\n    #mapEl{position:absolute;inset:0;background:#000;z-index:1;font-family:inherit}\n    #mapEl .maplibregl-ctrl-attrib,#mapEl .maplibregl-ctrl-logo{display:none}\n    #mapEl .maplibregl-popup-content{background:#fff;padding:10px 12px;border-radius:10px;\n      box-shadow:0 8px 20px rgba(0,0,0,.35)}\n    #mapEl .maplibregl-popup-tip{border-top-color:#fff}\n    #mapEl .maplibregl-popup-anchor-bottom .maplibregl-popup-tip{border-top-color:#fff}\n    #mapEl .maplibregl-popup-close-button{font-size:16px;color:#999;padding:2px 6px}\n\n    .sidebar{position:absolute;top:14px;right:14px;width:200px;max-height:calc(100% - 28px);\n      overflow-y:auto;background:#1c1c1e;border:1px solid rgba(255,255,255,.12);border-radius:12px;\n      padding:10px;z-index:15;box-shadow:0 8px 24px rgba(0,0,0,.45)}\n    .sidebarHeader{display:flex;align-items:center;justify-content:space-between;color:rgba(255,255,255,.55);\n      font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px}\n    .reset{appearance:none;border:none;background:none;color:#f5b642;font-size:11px;cursor:pointer;\n      font-weight:600}\n    .catList{display:flex;flex-direction:column;gap:6px}\n    .catRow{display:flex;align-items:center;gap:7px;color:rgba(255,255,255,.85);font-size:12px;\n      cursor:pointer;padding:3px 2px;border-radius:6px}\n    .catRow:hover{background:rgba(255,255,255,.06)}\n    .catRow input{accent-color:#f5b642}\n    .dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto;box-shadow:0 0 0 1px rgba(0,0,0,.3)}\n    .catName{flex:1}\n    .catCount{color:rgba(255,255,255,.35);font-size:11px}\n\n    .coords{position:absolute;left:12px;bottom:10px;background:rgba(0,0,0,.55);color:rgba(255,255,255,.75);\n      font-size:11px;padding:4px 8px;border-radius:6px;z-index:15;pointer-events:none;\n      font-family:ui-monospace,Menlo,monospace}\n\n    .boot{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;\n      background:#1c1c1e;color:rgba(255,255,255,.6);font:13px -apple-system,sans-serif;z-index:25;\n      text-align:center;padding:24px;line-height:1.5}\n    .boot.err{color:#ff8a80}\n    .boot code{background:rgba(255,255,255,.1);padding:2px 5px;border-radius:4px}\n\n    .pin{background:transparent;border:none}\n    .pinBadge{width:26px;height:26px;border-radius:50% 50% 50% 4px;transform:rotate(-45deg);\n      display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(0,0,0,.5),\n      inset 0 0 0 2px rgba(255,255,255,.5);font-size:12px;font-weight:800;color:#111;\n      font-family:-apple-system,sans-serif;overflow:hidden}\n    .pinBadge span{transform:rotate(45deg);display:block}\n    .pinBadge img{width:16px;height:16px;object-fit:contain;transform:rotate(45deg)}\n\n    .pop{min-width:200px;max-width:240px;font:12px -apple-system,sans-serif;color:#1d1d1f}\n    .popHead{display:flex;align-items:center;gap:8px;border-bottom:2px solid #ccc;padding-bottom:6px;margin-bottom:6px}\n    .popGlyph{width:24px;height:24px;border-radius:6px;display:flex;align-items:center;justify-content:center;\n      font-size:12px;font-weight:800;color:#111;flex:0 0 auto;overflow:hidden}\n    .popGlyph img{width:16px;height:16px;object-fit:contain}\n    .popTitle{font-weight:700;font-size:13px;line-height:1.2}\n    .popCat{color:#888;font-size:10px;text-transform:uppercase;letter-spacing:.04em}\n    .popDesc{color:#333;line-height:1.4}\n    .popCoords{margin-top:6px;color:#999;font-size:10px;font-family:ui-monospace,Menlo,monospace}\n  `,\n  render(d2) {\n    const regions = d2.param('regions', []) || [];\n    const gl = d2.lib(MAPLIBRE_JS);\n    d2.lib(MAPLIBRE_CSS);\n\n    let bootHtml = '';\n    if (!gl) {\n      const st = d2.lib.state(MAPLIBRE_JS);\n      bootHtml = st === 'error'\n        ? '<div class=\"boot err\">Failed to load MapLibre GL: ' + esc(d2.lib.error(MAPLIBRE_JS) || 'unknown error') + '</div>'\n        : '<div class=\"boot\">Loading map…</div>';\n    } else if (!regions.length) {\n      bootHtml = '<div class=\"boot\">No map data supplied.<br/>Pass <code>regions</code>, <code>categories</code> and <code>markers</code> params to this component instance.</div>';\n    }\n\n    const tabs = regions.map(function (r) {\n      return '<button class=\"tab\" data-region=\"' + esc(r.id) + '\">' + esc(r.name || r.id) + '</button>';\n    }).join('');\n\n    return (\n      '<div class=\"app\">' +\n      '<div class=\"topbar\">' +\n      '<div class=\"tabs\">' + tabs + '</div>' +\n      '<div class=\"search\"><input id=\"search\" type=\"text\" placeholder=\"Search the map…\" autocomplete=\"off\"/>' +\n      '<div id=\"searchResults\" class=\"searchResults\"></div></div>' +\n      '<button id=\"fsBtn\" class=\"fsBtn\" title=\"Toggle fullscreen\">&#10021;</button>' +\n      '</div>' +\n      '<div class=\"stage\">' +\n      '<div id=\"mapEl\" class=\"mapEl\" data-d2-static></div>' +\n      '<div class=\"sidebar\">' +\n      '<div class=\"sidebarHeader\">Categories<button id=\"resetFilters\" class=\"reset\">Reset</button></div>' +\n      '<div id=\"catList\" class=\"catList\"></div>' +\n      '</div>' +\n      '<div id=\"coords\" class=\"coords\">—, —</div>' +\n      bootHtml +\n      '</div>' +\n      '</div>'\n    );\n  },\n  mounted(d2, root) {\n    tryInit(d2, root);\n  },\n  updated(d2, root) {\n    tryInit(d2, root);\n  }\n};\n\n// A game map is flat pixel space, not real geography. MapLibre only projects real\n// mercator lng/lat, so every region is squeezed into a tiny patch of degrees centered\n// on the equator/prime meridian — small enough (WORLD_SPAN_DEG) that mercator distortion\n// is imperceptible, which makes the projection behave like a plain Cartesian CRS.\nfunction regionMetrics(region) {\n  const w = region.width || 1000;\n  const h = region.height || 700;\n  const scale = WORLD_SPAN_DEG / Math.max(w, h);\n  return { w: w, h: h, scale: scale };\n}\n\nfunction pxToLngLat(region, px, py) {\n  const m = regionMetrics(region);\n  return [(px - m.w / 2) * m.scale, (m.h / 2 - py) * m.scale];\n}\n\nfunction regionCorners(region) {\n  const m = regionMetrics(region);\n  return {\n    tl: pxToLngLat(region, 0, 0),\n    tr: pxToLngLat(region, m.w, 0),\n    br: pxToLngLat(region, m.w, m.h),\n    bl: pxToLngLat(region, 0, m.h)\n  };\n}\n\nfunction regionBoundsLngLat(region, pad) {\n  const m = regionMetrics(region);\n  const p = pad || 0;\n  const nw = pxToLngLat(region, -m.w * p, -m.h * p);\n  const se = pxToLngLat(region, m.w * (1 + p), m.h * (1 + p));\n  return { sw: [nw[0], se[1]], ne: [se[0], nw[1]] };\n}\n\nfunction tryInit(d2, root) {\n  const mapEl = root.querySelector('#mapEl');\n  if (!mapEl) return;\n  const gl = d2.lib(MAPLIBRE_JS);\n  if (!gl) return;\n\n  const regions = d2.param('regions', []) || [];\n  const categories = d2.param('categories', []) || [];\n  const markers = d2.param('markers', []) || [];\n  if (!regions.length) return;\n\n  const sig = JSON.stringify([regions, categories, markers]);\n  if (mapEl.getAttribute('data-sig') === sig) return;\n  mapEl.setAttribute('data-sig', sig);\n  if (mapEl._d2map) {\n    mapEl._d2map.remove();\n    mapEl._d2map = null;\n  }\n\n  const state = {\n    ready: false,\n    regionId: regions[0].id,\n    activeCats: new Set(categories.map(function (c) { return c.id; })),\n    catMarkers: {},\n    markerRefs: {},\n    regionBaseZoom: {}\n  };\n\n  const map = new gl.Map({\n    container: mapEl,\n    style: { version: 8, sources: {}, layers: [{ id: 'bg', type: 'background', paint: { 'background-color': '#000' } }] },\n    center: [0, 0],\n    zoom: 0,\n    attributionControl: false,\n    dragRotate: false,\n    pitchWithRotate: false,\n    touchPitch: false,\n    maxPitch: 0,\n    minZoom: 0,\n    maxZoom: 22\n  });\n  mapEl._d2map = map;\n\n  const catList = root.querySelector('#catList');\n  catList.innerHTML = categories.map(function (c) {\n    return (\n      '<label class=\"catRow\" data-cat=\"' + esc(c.id) + '\">' +\n      '<input type=\"checkbox\" checked/>' +\n      '<span class=\"dot\" style=\"background:' + esc(c.color || '#888') + '\"></span>' +\n      '<span class=\"catName\">' + esc(c.name || c.id) + '</span>' +\n      '<span class=\"catCount\" id=\"cnt-' + esc(c.id) + '\"></span>' +\n      '</label>'\n    );\n  }).join('');\n\n  function catFor(id) {\n    for (let i = 0; i < categories.length; i++) if (categories[i].id === id) return categories[i];\n    return { id: id, name: id, color: '#888' };\n  }\n\n  function badgeInner(cat) {\n    return cat.iconUrl\n      ? '<img src=\"' + esc(cat.iconUrl) + '\" alt=\"\"/>'\n      : '<span>' + esc((cat.glyph || (cat.name || cat.id || '?').charAt(0).toUpperCase())) + '</span>';\n  }\n\n  function buildPinEl(cat) {\n    const el = document.createElement('div');\n    el.className = 'pin';\n    el.innerHTML = '<div class=\"pinBadge\" style=\"background:' + esc(cat.color || '#888') + '\">' + badgeInner(cat) + '</div>';\n    return el;\n  }\n\n  function popupHtml(m, cat) {\n    return (\n      '<div class=\"pop\">' +\n      '<div class=\"popHead\">' +\n      '<span class=\"popGlyph\" style=\"background:' + esc(cat.color || '#888') + '\">' + badgeInner(cat) + '</span>' +\n      '<div><div class=\"popTitle\">' + esc(m.name) + '</div><div class=\"popCat\">' + esc(cat.name || cat.id) + '</div></div>' +\n      '</div>' +\n      (m.description ? '<div class=\"popDesc\">' + esc(m.description) + '</div>' : '') +\n      '<div class=\"popCoords\">x ' + Number(m.x).toFixed(1) + ' · y ' + Number(m.y).toFixed(1) + '</div>' +\n      '</div>'\n    );\n  }\n\n  function clearMarkers() {\n    Object.keys(state.markerRefs).forEach(function (id) { state.markerRefs[id].remove(); });\n    state.markerRefs = {};\n    state.catMarkers = {};\n  }\n\n  function addMarker(m, region) {\n    const cat = catFor(m.category);\n    const px = (Number(m.x) / 100) * (region.width || 1000);\n    const py = (Number(m.y) / 100) * (region.height || 700);\n    const ll = pxToLngLat(region, px, py);\n    const marker = new gl.Marker({ element: buildPinEl(cat), anchor: 'bottom' })\n      .setLngLat(ll)\n      .setPopup(new gl.Popup({ offset: 20, closeButton: true }).setHTML(popupHtml(m, cat)));\n    marker._added = false;\n    state.markerRefs[m.id] = marker;\n    if (!state.catMarkers[cat.id]) state.catMarkers[cat.id] = [];\n    state.catMarkers[cat.id].push(marker);\n    if (state.activeCats.has(cat.id)) {\n      marker.addTo(map);\n      marker._added = true;\n    }\n  }\n\n  function setCatVisible(catId, visible) {\n    const arr = state.catMarkers[catId] || [];\n    arr.forEach(function (mk) {\n      if (visible && !mk._added) { mk.addTo(map); mk._added = true; }\n      else if (!visible && mk._added) { mk.remove(); mk._added = false; }\n    });\n  }\n\n  function loadRegion(regionId) {\n    if (!state.ready) return;\n    const region = regions.filter(function (r) { return r.id === regionId; })[0] || regions[0];\n    state.regionId = region.id;\n\n    clearMarkers();\n    if (map.getLayer('region-img')) map.removeLayer('region-img');\n    if (map.getSource('region-img')) map.removeSource('region-img');\n\n    if (region.imageUrl) {\n      const corners = regionCorners(region);\n      map.addSource('region-img', {\n        type: 'image',\n        url: region.imageUrl,\n        coordinates: [corners.tl, corners.tr, corners.br, corners.bl]\n      });\n      map.addLayer({ id: 'region-img', type: 'raster', source: 'region-img', paint: { 'raster-resampling': 'nearest' } });\n    }\n\n    const padded = regionBoundsLngLat(region, 0.25);\n    map.setMaxBounds([padded.sw, padded.ne]);\n    const fit = regionBoundsLngLat(region, 0);\n    map.fitBounds([fit.sw, fit.ne], { animate: false, padding: 20 });\n    state.regionBaseZoom[region.id] = map.getZoom();\n\n    categories.forEach(function (cat) { state.catMarkers[cat.id] = []; });\n\n    const regionMarkers = markers.filter(function (m) { return m.region === region.id; });\n    regionMarkers.forEach(function (m) { addMarker(m, region); });\n\n    categories.forEach(function (cat) {\n      const cntEl = root.querySelector('#cnt-' + cat.id);\n      if (cntEl) cntEl.textContent = String(regionMarkers.filter(function (m) { return m.category === cat.id; }).length);\n    });\n\n    Array.prototype.forEach.call(root.querySelectorAll('.tab'), function (btn) {\n      btn.classList.toggle('active', btn.getAttribute('data-region') === region.id);\n    });\n  }\n\n  function renderSearchResults(q) {\n    const searchResultsEl = root.querySelector('#searchResults');\n    q = q.trim().toLowerCase();\n    if (!q) {\n      searchResultsEl.innerHTML = '';\n      searchResultsEl.classList.remove('show');\n      return;\n    }\n    const matches = markers.filter(function (m) {\n      return (m.name || '').toLowerCase().indexOf(q) !== -1;\n    }).slice(0, 8);\n    searchResultsEl.innerHTML = matches.map(function (m) {\n      const cat = catFor(m.category);\n      const region = regions.filter(function (r) { return r.id === m.region; })[0] || { name: m.region };\n      return (\n        '<div class=\"resultRow\" data-id=\"' + esc(m.id) + '\">' +\n        '<span class=\"dot\" style=\"background:' + esc(cat.color || '#888') + '\"></span>' +\n        esc(m.name) +\n        '<span class=\"resultRegion\">' + esc(region.name || region.id) + '</span>' +\n        '</div>'\n      );\n    }).join('');\n    searchResultsEl.classList.toggle('show', matches.length > 0);\n  }\n\n  function flyToMarker(id) {\n    const m = markers.filter(function (x) { return x.id === id; })[0];\n    if (!m) return;\n    if (m.region !== state.regionId) loadRegion(m.region);\n    const region = regions.filter(function (r) { return r.id === m.region; })[0];\n    if (!region) return;\n    const px = (Number(m.x) / 100) * (region.width || 1000);\n    const py = (Number(m.y) / 100) * (region.height || 700);\n    const ll = pxToLngLat(region, px, py);\n\n    if (!state.activeCats.has(m.category)) {\n      state.activeCats.add(m.category);\n      const cb = catList.querySelector('.catRow[data-cat=\"' + m.category + '\"] input');\n      if (cb) cb.checked = true;\n      setCatVisible(m.category, true);\n    }\n\n    const baseZoom = state.regionBaseZoom[state.regionId] || map.getZoom();\n    map.flyTo({ center: ll, zoom: Math.max(map.getZoom(), baseZoom + 2), essential: true });\n    const marker = state.markerRefs[m.id];\n    if (marker) {\n      const popup = marker.getPopup();\n      if (popup && !popup.isOpen()) marker.togglePopup();\n    }\n\n    const searchResultsEl = root.querySelector('#searchResults');\n    const searchInput = root.querySelector('#search');\n    searchResultsEl.classList.remove('show');\n    searchInput.value = '';\n  }\n\n  function toggleFullscreen() {\n    const appEl = root.querySelector('.app');\n    if (!document.fullscreenElement) {\n      const req = appEl.requestFullscreen && appEl.requestFullscreen();\n      if (req && req.catch) req.catch(function () { appEl.classList.add('fsFallback'); });\n    } else if (document.exitFullscreen) {\n      document.exitFullscreen();\n    }\n  }\n\n  d2.on(catList, 'change', function (e) {\n    const row = e.target.closest('.catRow');\n    if (!row) return;\n    const catId = row.getAttribute('data-cat');\n    if (e.target.checked) {\n      state.activeCats.add(catId);\n      setCatVisible(catId, true);\n    } else {\n      state.activeCats.delete(catId);\n      setCatVisible(catId, false);\n    }\n  });\n\n  d2.on(root, 'click', function (e) {\n    const tab = e.target.closest('.tab');\n    if (tab) {\n      loadRegion(tab.getAttribute('data-region'));\n      renderSearchResults('');\n      root.querySelector('#search').value = '';\n      return;\n    }\n    if (e.target.id === 'resetFilters') {\n      state.activeCats = new Set(categories.map(function (c) { return c.id; }));\n      Array.prototype.forEach.call(catList.querySelectorAll('input[type=checkbox]'), function (cb) { cb.checked = true; });\n      categories.forEach(function (cat) { setCatVisible(cat.id, true); });\n      return;\n    }\n    if (e.target.id === 'fsBtn') {\n      toggleFullscreen();\n      return;\n    }\n    const resultRow = e.target.closest('.resultRow');\n    if (resultRow) {\n      flyToMarker(resultRow.getAttribute('data-id'));\n      return;\n    }\n  });\n\n  d2.on(root.querySelector('#search'), 'input', function (e) {\n    renderSearchResults(e.target.value);\n  });\n\n  map.on('mousemove', function (e) {\n    const c = root.querySelector('#coords');\n    if (!c) return;\n    const region = regions.filter(function (r) { return r.id === state.regionId; })[0];\n    if (!region) return;\n    const m = regionMetrics(region);\n    const px = e.lngLat.lng / m.scale + m.w / 2;\n    const py = m.h / 2 - e.lngLat.lat / m.scale;\n    c.textContent = px.toFixed(0) + ', ' + py.toFixed(0);\n  });\n\n  map.on('load', function () {\n    state.ready = true;\n    loadRegion(state.regionId);\n    setTimeout(function () { map.resize(); }, 60);\n  });\n}\n"}},"regions":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"regions"}}}},"categories":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"categories"}}}},"markers":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"markers"}}}}}}}}}},{"_id":"6a7a7d1868641501c2dbc6b7","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"InspectorMacOS","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// Inspector · macOS — schema-driven inspector tree (Data2 code component)\nconst ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }\nconst esc = v => String(v == null ? '' : v).replace(/[&<>\"']/g, c => ESC[c])\n\nconst I = {\n  chev: '<svg viewBox=\"0 0 8 12\" class=\"ic\"><path d=\"M2.2 1.4 6.4 6l-4.2 4.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  search: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><circle cx=\"6\" cy=\"6\" r=\"4.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/><path d=\"M9.3 9.3 12.4 12.4\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>',\n  pin: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M8.6 1.6 12.4 5.4 10.6 6.2 9.6 9.4 4.6 4.4 7.8 3.4z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/><path d=\"M4.6 9.4 1.8 12.2\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  copy: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><rect x=\"2.2\" y=\"2.2\" width=\"7\" height=\"7\" rx=\"1.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><path d=\"M4.8 11.8h5a2 2 0 0 0 2-2v-5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  undo: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M3 6.2a4.4 4.4 0 1 1 .3 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/><path d=\"M2.4 2.6v3.6H6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  link: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M5.8 8.2a2.6 2.6 0 0 0 3.7 0l1.7-1.7a2.6 2.6 0 0 0-3.7-3.7l-.9.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/><path d=\"M8.2 5.8a2.6 2.6 0 0 0-3.7 0L2.8 7.5a2.6 2.6 0 0 0 3.7 3.7l.9-.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  plus: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M7 3v8M3 7h8\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>',\n  lock: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><rect x=\"3\" y=\"6.2\" width=\"8\" height=\"5.6\" rx=\"1.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><path d=\"M4.9 6.2V4.8a2.1 2.1 0 0 1 4.2 0v1.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  trash: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M3.2 4.2h7.6M5.6 4.2V3a.8.8 0 0 1 .8-.8h1.2a.8.8 0 0 1 .8.8v1.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/><path d=\"M4.3 4.2l.5 6.6a1 1 0 0 0 1 .9h2.4a1 1 0 0 0 1-.9l.5-6.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  dup: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><rect x=\"1.8\" y=\"1.8\" width=\"7\" height=\"7\" rx=\"1.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><rect x=\"5.2\" y=\"5.2\" width=\"7\" height=\"7\" rx=\"1.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  code: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M5 3.4 1.8 7 5 10.6M9 3.4 12.2 7 9 10.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  save: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M2.4 3.6a1.2 1.2 0 0 1 1.2-1.2h5.6l2.4 2.4v5.6a1.2 1.2 0 0 1-1.2 1.2H3.6a1.2 1.2 0 0 1-1.2-1.2z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/><path d=\"M4.8 2.4v3h4.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  cube: '<svg viewBox=\"0 0 16 16\" class=\"ic\"><path d=\"M8 1.8 14 5v6l-6 3.2L2 11V5z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/><path d=\"M2 5l6 3.2L14 5M8 8.2v6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.1\" opacity=\".55\"/></svg>'\n}\n\nconst TABS = [\n  { id: 'all', label: 'All' },\n  { id: 'style', label: 'Style' },\n  { id: 'content', label: 'Content' },\n  { id: 'logic', label: 'Logic' },\n  { id: 'used', label: 'Used' }\n]\n\nconst DEFAULT_SCHEMA = [\n  {\n    id: 'block', title: 'Block', tab: 'style', open: true, rows: [\n      {\n        kind: 'group', id: 'border', label: 'Border', open: true, rows: [\n          { kind: 'color', key: 'border.color', label: 'Color', value: '#0A84FF' },\n          { kind: 'number', key: 'border.width', label: 'Width', value: 1, unit: 'px' },\n          { kind: 'select', key: 'border.style', label: 'Style', value: 'Solid', options: ['None', 'Solid', 'Dashed', 'Dotted'] },\n          { kind: 'corners', key: 'border.radius', label: 'Radius', value: [10, 10, 10, 10] }\n        ]\n      },\n      { kind: 'toggle', key: 'childrenOverflow', label: 'Children overflow', value: false },\n      { kind: 'toggle', key: 'hide', label: 'Hide', value: false },\n      { kind: 'slider', key: 'opacity', label: 'Opacity', value: 100, min: 0, max: 100, unit: '%' }\n    ]\n  },\n  {\n    id: 'position', title: 'Position', tab: 'style', open: true, rows: [\n      { kind: 'segmented', key: 'position.mode', label: 'Mode', value: 'Free transform', options: ['Free transform', 'Fixed'] },\n      { kind: 'anchor', key: 'position.anchor', label: 'Anchors', value: { top: true, left: true, right: false, bottom: false } },\n      { kind: 'pair', label: 'Offset', items: [{ key: 'position.x', label: 'X', value: 1135, unit: 'px' }, { key: 'position.y', label: 'Y', value: 51, unit: 'px' }] }\n    ]\n  },\n  {\n    id: 'dimensions', title: 'Dimensions', tab: 'style', open: true, rows: [\n      { kind: 'pair', label: 'Size', items: [{ key: 'width', label: 'W', value: 160, unit: 'px' }, { key: 'height', label: 'H', value: 540, unit: 'px' }] },\n      { kind: 'toggle', key: 'growW', label: 'Fit children width', hint: 'Width adapts to children', value: false },\n      { kind: 'toggle', key: 'growH', label: 'Fit children height', hint: 'Height adapts to children', value: false }\n    ]\n  },\n  {\n    id: 'fill', title: 'Fill', tab: 'style', open: false, rows: [\n      { kind: 'color', key: 'fill.color', label: 'Color', value: '#1D1D1F' },\n      { kind: 'select', key: 'fill.blend', label: 'Blend', value: 'Normal', options: ['Normal', 'Multiply', 'Screen', 'Overlay'] }\n    ]\n  },\n  {\n    id: 'text', title: 'Text', tab: 'content', open: true, rows: [\n      { kind: 'text', key: 'text.value', label: 'Content', value: '', placeholder: 'Type text…' },\n      { kind: 'select', key: 'text.font', label: 'Font', value: 'SF Pro Text', options: ['SF Pro Text', 'Inter', 'Helvetica Neue', 'Menlo'] },\n      { kind: 'pair', label: 'Metrics', items: [{ key: 'text.size', label: 'S', value: 13, unit: 'px' }, { key: 'text.lh', label: 'LH', value: 18, unit: 'px' }] }\n    ]\n  },\n  {\n    id: 'logic', title: 'Interactions', tab: 'logic', open: true, rows: [\n      { kind: 'select', key: 'onClick', label: 'On click', value: 'None', options: ['None', 'Set value', 'Go to route', 'Run service'] },\n      { kind: 'toggle', key: 'disabled', label: 'Disabled', value: false },\n      { kind: 'note', text: 'Handlers run in the order they are listed.' }\n    ]\n  },\n  {\n    id: 'used', title: 'Used by', tab: 'used', open: true, rows: [\n      { kind: 'note', text: 'Referenced by 3 artboards and 1 user component.' }\n    ]\n  }\n]\n\n/* ---------- schema helpers ---------- */\nfunction leaves(rows, out) {\n  out = out || []\n  ;(rows || []).forEach(r => {\n    if (r.kind === 'group') leaves(r.rows, out)\n    else if (r.kind === 'pair') (r.items || []).forEach(it => out.push(it))\n    else if (r.key != null) out.push(r)\n  })\n  return out\n}\n\nfunction defaults(schema) {\n  const d = {}\n  ;(schema || []).forEach(s => leaves(s.rows).forEach(f => { d[f.key] = f.value }))\n  return d\n}\n\nfunction sameValue(a, b) {\n  if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => String(v) === String(b[i]))\n  if (a && b && typeof a === 'object' && typeof b === 'object') {\n    const ka = Object.keys(a), kb = Object.keys(b)\n    return ka.length === kb.length && ka.every(k => String(a[k]) === String(b[k]))\n  }\n  return String(a) === String(b)\n}\n\nfunction openMap(schema) {\n  const o = {}\n  ;(schema || []).forEach(s => {\n    o['s:' + s.id] = s.open !== false\n    ;(s.rows || []).forEach(r => { if (r.kind === 'group') o['g:' + s.id + ':' + r.id] = r.open !== false })\n  })\n  return o\n}\n\nfunction matches(row, q) {\n  if (!q) return true\n  const hay = [row.label, row.text, row.value, (row.items || []).map(i => i.label).join(' ')].join(' ').toLowerCase()\n  return hay.indexOf(q) > -1\n}\n\n/* ---------- field renderers ---------- */\nfunction dot(modified) {\n  return '<span class=\"dot' + (modified ? ' on' : '') + '\"></span>'\n}\n\nfunction numField(f, v, extra) {\n  return '<span class=\"num' + (extra || '') + '\">' +\n    '<input class=\"in num-in\" type=\"text\" inputmode=\"decimal\" spellcheck=\"false\" data-key=\"' + esc(f.key) + '\" data-kind=\"num\" value=\"' + esc(v) + '\">' +\n    (f.unit ? '<i class=\"unit\">' + esc(f.unit) + '</i>' : '') +\n    '</span>'\n}\n\nfunction renderRow(row, ctx) {\n  if (row.kind === 'pair') return renderPair(row, ctx)\n  const vals = ctx.values, defs = ctx.defs\n  const k = row.key\n  const v = k != null ? vals[k] : null\n  const mod = k != null && !sameValue(v, defs[k])\n\n  if (row.kind === 'note') return '<div class=\"row note\">' + esc(row.text) + '</div>'\n\n  if (row.kind === 'group') {\n    const id = 'g:' + ctx.sectionId + ':' + row.id\n    const isOpen = ctx.open[id] !== false\n    const kids = (row.rows || []).filter(r => matches(r, ctx.q))\n    if (!kids.length) return ''\n    const anyMod = leaves(kids).some(f => !sameValue(vals[f.key], defs[f.key]))\n    return '<div class=\"grp' + (isOpen ? ' open' : '') + '\">' +\n      '<button class=\"row grp-h\" data-toggle=\"' + esc(id) + '\">' +\n      dot(anyMod) + '<span class=\"chev\">' + I.chev + '</span>' +\n      '<span class=\"lbl\">' + esc(row.label) + '</span>' +\n      '<span class=\"grp-n\">' + kids.length + '</span>' +\n      '</button>' +\n      (isOpen ? '<div class=\"grp-b\">' + kids.map(r => renderRow(r, ctx)).join('') + '</div>' : '') +\n      '</div>'\n  }\n\n  let ctrl = ''\n  if (row.kind === 'toggle') {\n    ctrl = '<button class=\"sw' + (v ? ' on' : '') + '\" role=\"switch\" aria-checked=\"' + (v ? 'true' : 'false') + '\" data-key=\"' + esc(k) + '\" data-kind=\"toggle\"><i></i></button>'\n  } else if (row.kind === 'text') {\n    ctrl = '<input class=\"in txt-in\" type=\"text\" spellcheck=\"false\" data-key=\"' + esc(k) + '\" data-kind=\"text\" value=\"' + esc(v) + '\" placeholder=\"' + esc(row.placeholder || '') + '\">'\n  } else if (row.kind === 'number') {\n    ctrl = numField(row, v)\n  } else if (row.kind === 'select') {\n    ctrl = '<span class=\"sel\"><select class=\"in sel-in\" data-key=\"' + esc(k) + '\" data-kind=\"select\">' +\n      (row.options || []).map(o => '<option' + (String(o) === String(v) ? ' selected' : '') + '>' + esc(o) + '</option>').join('') +\n      '</select><span class=\"sel-c\">' + I.chev + '</span></span>'\n  } else if (row.kind === 'color') {\n    ctrl = '<span class=\"col\"><span class=\"sw-c\" style=\"background:' + esc(v) + '\"></span>' +\n      '<input class=\"in col-in\" type=\"text\" spellcheck=\"false\" data-key=\"' + esc(k) + '\" data-kind=\"text\" value=\"' + esc(v) + '\"></span>'\n  } else if (row.kind === 'slider') {\n    const pct = Math.max(0, Math.min(100, ((Number(v) - (row.min || 0)) / ((row.max || 100) - (row.min || 0))) * 100))\n    ctrl = '<span class=\"sld-wrap\">' +\n      '<span class=\"sld\" data-key=\"' + esc(k) + '\" data-kind=\"slider\" data-min=\"' + (row.min || 0) + '\" data-max=\"' + (row.max || 100) + '\">' +\n      '<i class=\"sld-t\" style=\"width:' + pct + '%\"></i><i class=\"sld-k\" style=\"left:' + pct + '%\"></i></span>' +\n      numField(row, v, ' sm') + '</span>'\n  } else if (row.kind === 'segmented') {\n    ctrl = '<span class=\"seg' + (row.wide ? ' wide' : '') + '\">' +\n      (row.options || []).map(o => '<button class=\"seg-o' + (String(o) === String(v) ? ' on' : '') + '\" data-key=\"' + esc(k) + '\" data-kind=\"seg\" data-val=\"' + esc(o) + '\">' + esc(o) + '</button>').join('') +\n      '</span>'\n  } else if (row.kind === 'corners') {\n    const arr = Array.isArray(v) ? v : [v, v, v, v]\n    const linked = arr.every(x => String(x) === String(arr[0]))\n    ctrl = '<span class=\"quad\">' +\n      arr.map((x, i) => '<span class=\"num qn\"><input class=\"in num-in\" type=\"text\" inputmode=\"decimal\" data-key=\"' + esc(k) + '\" data-kind=\"quad\" data-idx=\"' + i + '\" value=\"' + esc(x) + '\"></span>').join('') +\n      '<button class=\"mini' + (linked ? ' on' : '') + '\" data-key=\"' + esc(k) + '\" data-kind=\"link\" title=\"Link corners\">' + I.link + '</button>' +\n      '</span>'\n  } else if (row.kind === 'anchor') {\n    const a = v || {}\n    const cell = side => '<button class=\"a-' + side + (a[side] ? ' on' : '') + '\" data-key=\"' + esc(k) + '\" data-kind=\"anchor\" data-side=\"' + side + '\"></button>'\n    ctrl = '<span class=\"anc\">' +\n      '<span class=\"anc-g\">' +\n      '<i></i>' + cell('top') + '<i></i>' +\n      cell('left') + '<span class=\"a-c\"></span>' + cell('right') +\n      '<i></i>' + cell('bottom') + '<i></i>' +\n      '</span></span>'\n  }\n\n  const stacked = row.kind === 'anchor' || row.kind === 'corners'\n  return '<div class=\"row f' + (stacked ? ' stack' : '') + '\" data-row=\"' + esc(k || '') + '\">' +\n    dot(mod) +\n    (row.label ? '<span class=\"lbl\">' + esc(row.label) + (row.hint ? '<i class=\"hint\">' + esc(row.hint) + '</i>' : '') + '</span>' : '') +\n    '<span class=\"ctl\">' + ctrl + '</span>' +\n    (mod ? '<button class=\"mini rst\" data-kind=\"reset\" data-key=\"' + esc(k) + '\" title=\"Reset\">' + I.undo + '</button>' : '') +\n    '</div>'\n}\n\nfunction renderPair(row, ctx) {\n  const mod = (row.items || []).some(it => !sameValue(ctx.values[it.key], ctx.defs[it.key]))\n  return '<div class=\"row f pair\">' + dot(mod) +\n    '<span class=\"lbl\">' + esc(row.label) + '</span>' +\n    '<span class=\"ctl pair-c\">' +\n    (row.items || []).map(it => '<span class=\"num\"><i class=\"pfx\">' + esc(it.label) + '</i>' +\n      '<input class=\"in num-in\" type=\"text\" inputmode=\"decimal\" data-key=\"' + esc(it.key) + '\" data-kind=\"num\" value=\"' + esc(ctx.values[it.key]) + '\">' +\n      (it.unit ? '<i class=\"unit\">' + esc(it.unit) + '</i>' : '') + '</span>').join('') +\n    '</span></div>'\n}\n\nexport default {\n  css: `\n:host { display: block; width: 100%; height: 100% }\n:host, .ins { box-sizing: border-box }\n.ins *, .ins *::before, .ins *::after { box-sizing: border-box }\n.ins {\n  --accent: #0A84FF;\n  --bg: #2c2c2e; --bg2: #333336; --card: rgba(255,255,255,.055); --card-b: rgba(255,255,255,.09);\n  --sep: rgba(255,255,255,.075); --txt: rgba(255,255,255,.93); --txt2: rgba(235,235,245,.62);\n  --txt3: rgba(235,235,245,.35); --ctl: rgba(255,255,255,.10); --ctl2: rgba(255,255,255,.16);\n  --ctl-b: rgba(0,0,0,.32); --track: rgba(0,0,0,.28); --sh: 0 1px 2px rgba(0,0,0,.4);\n  --scroll: rgba(255,255,255,.22); --dot: rgba(235,235,245,.22);\n  width: 100%; height: 100%; display: flex; flex-direction: column; overflow: hidden;\n  border-radius: 11px; background: linear-gradient(180deg, var(--bg2), var(--bg) 120px);\n  color: var(--txt); font-family: -apple-system, BlinkMacSystemFont, \"SF Pro Text\", \"Helvetica Neue\", \"Segoe UI\", system-ui, sans-serif;\n  font-size: 12px; line-height: 1.35; -webkit-font-smoothing: antialiased;\n  box-shadow: 0 0 0 .5px rgba(255,255,255,.09), 0 12px 34px rgba(0,0,0,.34);\n}\n.ins.light {\n  --accent: #007AFF;\n  --bg: #ECECEE; --bg2: #F7F7F9; --card: #FFFFFF; --card-b: rgba(0,0,0,.075);\n  --sep: rgba(0,0,0,.07); --txt: #1D1D1F; --txt2: rgba(60,60,67,.62); --txt3: rgba(60,60,67,.34);\n  --ctl: #FFFFFF; --ctl2: rgba(0,0,0,.06); --ctl-b: rgba(0,0,0,.18); --track: rgba(120,120,128,.22);\n  --sh: 0 1px 1.5px rgba(0,0,0,.14); --scroll: rgba(0,0,0,.24); --dot: rgba(60,60,67,.2);\n  box-shadow: 0 0 0 .5px rgba(0,0,0,.1), 0 12px 34px rgba(0,0,0,.16);\n}\n.ic { width: 100%; height: 100%; display: block }\nbutton { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer }\n\n/* ---- title bar ---- */\n.tbar { display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 10px 0 12px; flex: none;\n  border-bottom: .5px solid var(--sep) }\n.tbar .t { font-size: 12.5px; font-weight: 590; letter-spacing: -.01em; flex: 1 }\n.mini { width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; color: var(--txt2);\n  transition: background .12s, color .12s }\n.mini > svg { width: 13px; height: 13px }\n.mini:hover { background: var(--ctl2); color: var(--txt) }\n.mini.on { color: var(--accent) }\n\n/* ---- subject chip ---- */\n.subj { display: flex; align-items: center; gap: 9px; margin: 10px 10px 8px; padding: 8px 9px;\n  background: var(--card); border: .5px solid var(--card-b); border-radius: 9px }\n.subj .tile { width: 26px; height: 26px; border-radius: 7px; flex: none; display: grid; place-items: center;\n  color: #fff; background: linear-gradient(160deg, #5aa9ff, var(--accent)); box-shadow: var(--sh) }\n.subj .tile > svg { width: 15px; height: 15px }\n.subj .m { min-width: 0; flex: 1; display: block }\n.subj .n { display: block; font-size: 12.5px; font-weight: 590; letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.subj .p { display: block; font-size: 10.5px; color: var(--txt2); font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n  white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n\n/* ---- identity ---- */\n.ident { margin: 0 10px 10px }\n.ident .ir { display: flex; align-items: center; gap: 8px; min-height: 26px }\n.ident .ik { width: 78px; flex: none; font-size: 11px; color: var(--txt2) }\n.ident .iv { flex: 1; min-width: 0; font-family: ui-monospace, \"SF Mono\", Menlo, monospace; font-size: 10.5px;\n  color: var(--txt2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n\n/* ---- tabs ---- */\n.tabs { display: flex; align-items: center; gap: 6px; margin: 0 10px 10px }\n.segbar { display: flex; flex: 1; gap: 2px; padding: 2px; background: var(--track); border-radius: 8px }\n.segbar button { flex: 1; height: 20px; border-radius: 6px; font-size: 11px; color: var(--txt2);\n  transition: background .14s, color .14s, box-shadow .14s }\n.segbar button:hover { color: var(--txt) }\n.segbar button.on { background: var(--ctl2); color: var(--txt); font-weight: 590; box-shadow: var(--sh) }\n.ins.light .segbar button.on { background: #fff }\n.srch { display: flex; align-items: center; gap: 5px; height: 24px; padding: 0 7px; border-radius: 7px;\n  background: var(--ctl); border: .5px solid var(--ctl-b); width: 0; opacity: 0; overflow: hidden;\n  transition: width .18s ease, opacity .14s }\n.srch.on { width: 108px; opacity: 1 }\n.srch > svg { width: 11px; height: 11px; color: var(--txt3); flex: none }\n.srch input { flex: 1; min-width: 0; background: none; border: 0; outline: none; color: var(--txt); font: inherit; font-size: 11px }\n\n/* ---- body ---- */\n.body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 0 10px 14px; scrollbar-width: thin }\n.body::-webkit-scrollbar { width: 9px }\n.body::-webkit-scrollbar-thumb { background: transparent; border-radius: 5px; border: 3px solid transparent; background-clip: padding-box }\n.body:hover::-webkit-scrollbar-thumb { background: var(--scroll); background-clip: padding-box }\n\n.sec { margin-bottom: 12px }\n.sec-h { display: flex; align-items: center; gap: 6px; width: 100%; height: 22px; padding: 0 2px;\n  color: var(--txt3); font-size: 10.5px; font-weight: 590; text-transform: uppercase; letter-spacing: .055em }\n.sec-h:hover { color: var(--txt2) }\n.sec-h .chev { width: 8px; height: 8px; color: var(--txt3); transition: transform .16s ease }\n.sec.open .sec-h .chev { transform: rotate(90deg) }\n.sec-h .t { flex: 1; text-align: left }\n.sec-h .n { font-variant-numeric: tabular-nums; opacity: .8; text-transform: none; letter-spacing: 0 }\n\n.card { background: var(--card); border: .5px solid var(--card-b); border-radius: 10px; overflow: hidden }\n.ins.light .card { box-shadow: 0 1px 2px rgba(0,0,0,.05) }\n.row { display: flex; align-items: center; gap: 7px; min-height: 30px; padding: 4px 9px 4px 6px; width: 100% }\n.row + .row, .grp + .row, .row + .grp, .grp + .grp { border-top: .5px solid var(--sep) }\n.row.stack { flex-wrap: wrap }\n.row.note { color: var(--txt2); font-size: 11px; line-height: 1.45; padding: 8px 10px }\n.dot { width: 5px; height: 5px; border-radius: 50%; flex: none; background: transparent;\n  box-shadow: inset 0 0 0 1px var(--dot) }\n.dot.on { background: var(--accent); box-shadow: 0 0 0 2.5px color-mix(in srgb, var(--accent) 22%, transparent) }\n.lbl { flex: 1; min-width: 0; font-size: 11.5px; color: var(--txt); display: flex; flex-direction: column; text-align: left }\n.lbl .hint { font-size: 10px; color: var(--txt3); font-style: normal; margin-top: 1px }\n.ctl { display: flex; align-items: center; justify-content: flex-end; gap: 5px; flex: none }\n.row.stack .ctl { flex: 1 0 100%; justify-content: stretch; margin-top: 4px }\n.rst { opacity: .75 }\n\n/* nested group */\n.grp-h { text-align: left; color: var(--txt) }\n.grp-h .chev { width: 8px; height: 8px; color: var(--txt3); transition: transform .16s ease; flex: none }\n.grp.open .grp-h .chev { transform: rotate(90deg) }\n.grp-h .lbl { font-weight: 590; font-size: 11.5px }\n.grp-n { font-size: 10px; color: var(--txt3); font-variant-numeric: tabular-nums }\n.grp-b { padding-left: 13px; background: rgba(255,255,255,.02) }\n.ins.light .grp-b { background: rgba(0,0,0,.018) }\n.grp-b > .row { border-top: .5px solid var(--sep) }\n\n/* controls */\n.in { background: var(--ctl); border: .5px solid var(--ctl-b); border-radius: 6px; color: var(--txt);\n  font: inherit; font-size: 11.5px; height: 22px; padding: 0 6px; outline: none; min-width: 0;\n  box-shadow: inset 0 .5px 0 rgba(255,255,255,.07); transition: border-color .12s, box-shadow .12s }\n.ins.light .in { box-shadow: 0 .5px 1px rgba(0,0,0,.06) }\n.in:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--ctl-b)) }\n.in:focus { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 26%, transparent) }\n.txt-in { width: 128px }\n.num { position: relative; display: inline-flex; align-items: center }\n.num .pfx { position: absolute; left: 6px; font-size: 10px; color: var(--txt3); font-style: normal; pointer-events: none }\n.num .unit { position: absolute; right: 6px; font-size: 10px; color: var(--txt3); font-style: normal; pointer-events: none }\n.num-in { width: 68px; text-align: right; padding-right: 18px; font-variant-numeric: tabular-nums }\n.num .pfx ~ .num-in { padding-left: 15px }\n.num.sm .num-in { width: 52px; padding-right: 18px; text-align: right }\n.num.qn { flex: 1 }\n.num.qn .num-in { width: 100%; padding: 0 4px; text-align: center }\n.pair-c { gap: 6px }\n.sel { position: relative; display: inline-flex; align-items: center }\n.sel-in { appearance: none; -webkit-appearance: none; width: 128px; padding-right: 20px; cursor: pointer }\n.sel-c { position: absolute; right: 6px; width: 7px; height: 7px; color: var(--txt3); transform: rotate(90deg); pointer-events: none }\n.col { display: inline-flex; align-items: center; gap: 6px }\n.sw-c { width: 20px; height: 20px; border-radius: 5px; box-shadow: inset 0 0 0 .5px rgba(0,0,0,.35), 0 0 0 .5px rgba(255,255,255,.14) }\n.col-in { width: 82px; font-family: ui-monospace, \"SF Mono\", Menlo, monospace; font-size: 10.5px; text-transform: uppercase }\n.sw { width: 30px; height: 18px; border-radius: 9px; background: var(--track); position: relative; flex: none;\n  transition: background .18s ease; box-shadow: inset 0 0 0 .5px rgba(0,0,0,.14) }\n.sw i { position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: #fff;\n  box-shadow: 0 1px 2px rgba(0,0,0,.35); transition: transform .18s cubic-bezier(.32,.72,0,1) }\n.ins.light .sw { box-shadow: inset 0 0 0 .5px rgba(0,0,0,.07) }\n.ins.light .sw i { box-shadow: 0 1px 2px rgba(0,0,0,.24), 0 0 0 .5px rgba(0,0,0,.07) }\n.sw.on { background: var(--accent) }\n.sw.on i { transform: translateX(12px) }\n.seg { display: inline-flex; gap: 2px; padding: 2px; background: var(--track); border-radius: 7px }\n.seg.wide { width: 100% }\n.seg-o { flex: 1; height: 20px; padding: 0 10px; border-radius: 5px; font-size: 11px; color: var(--txt2); white-space: nowrap }\n.seg-o.on { background: var(--ctl2); color: var(--txt); font-weight: 590; box-shadow: var(--sh) }\n.ins.light .seg-o.on { background: #fff }\n.quad { display: inline-flex; align-items: center; gap: 3px }\n.row.stack .quad { width: 100%; gap: 5px }\n.row.stack .ctl { gap: 6px }\n.sld-wrap { display: inline-flex; align-items: center; gap: 8px }\n.sld { position: relative; width: 84px; height: 4px; border-radius: 2px; background: var(--track); cursor: pointer }\n.sld-t { position: absolute; left: 0; top: 0; height: 100%; border-radius: 2px; background: var(--accent) }\n.sld-k { position: absolute; top: 50%; width: 13px; height: 13px; margin-left: -6.5px; border-radius: 50%;\n  background: #fff; transform: translateY(-50%); box-shadow: 0 1px 2.5px rgba(0,0,0,.4), 0 0 0 .5px rgba(0,0,0,.12) }\n.anc { display: flex; align-items: center; justify-content: center; width: 100% }\n.anc-g { display: grid; grid-template-columns: 22px 30px 22px; grid-template-rows: 18px 24px 18px; gap: 3px;\n  padding: 8px; background: var(--track); border-radius: 8px }\n.anc-g > i { display: block }\n.anc-g button { border-radius: 3px; background: var(--dot); transition: background .14s, box-shadow .14s }\n.anc-g button:hover { background: var(--txt3) }\n.anc-g button.on { background: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 24%, transparent) }\n.a-top, .a-bottom { justify-self: center; align-self: center; width: 22px; height: 3px }\n.a-left, .a-right { align-self: center; justify-self: center; width: 3px; height: 18px }\n.a-c { border-radius: 5px; background: var(--ctl2); box-shadow: inset 0 0 0 .5px var(--card-b) }\n\n/* ---- dock ---- */\n.dock { flex: none; display: flex; align-items: center; gap: 2px; height: 36px; padding: 0 8px;\n  border-top: .5px solid var(--sep); background: var(--bg2) }\n.dock .sp { flex: 1 }\n.dock .mini { width: 24px; height: 24px }\n.dock .mini > svg { width: 14px; height: 14px }\n.dock .mini.danger:hover { background: rgba(255,69,58,.16); color: #ff453a }\n.empty { padding: 26px 12px; text-align: center; color: var(--txt3); font-size: 11.5px }\n`,\n\n  render(d2) {\n    const schema = d2.param('schema') || DEFAULT_SCHEMA\n    const defs = defaults(schema)\n    const values = d2.state.values || defs\n    const open = d2.state.open || openMap(schema)\n    const tab = d2.state.tab || 'all'\n    const q = (d2.state.query || '').trim().toLowerCase()\n    const light = String(d2.param('appearance', 'dark')).toLowerCase() === 'light'\n    const accent = d2.param('accentHex', '')\n\n    const ctx = { values: values, defs: defs, open: open, q: q, sectionId: '' }\n\n    const secs = schema.filter(s => tab === 'all' || (s.tab || 'style') === tab).map(s => {\n      ctx.sectionId = s.id\n      const rows = (s.rows || []).filter(r => r.kind === 'group' || matches(r, q))\n      const html = rows.map(r => renderRow(r, ctx)).join('')\n      if (!html.trim()) return ''\n      const isOpen = open['s:' + s.id] !== false\n      return '<section class=\"sec' + (isOpen ? ' open' : '') + '\">' +\n        '<button class=\"sec-h\" data-toggle=\"s:' + esc(s.id) + '\"><span class=\"chev\">' + I.chev + '</span>' +\n        '<span class=\"t\">' + esc(s.title) + '</span><span class=\"n\">' + leaves(rows).length + '</span></button>' +\n        (isOpen ? '<div class=\"card\">' + html + '</div>' : '') +\n        '</section>'\n    }).join('')\n\n    return '<div class=\"ins' + (light ? ' light' : '') + '\"' + (accent ? ' style=\"--accent:' + esc(accent) + '\"' : '') + '>' +\n      '<div class=\"tbar\"><span class=\"t\">' + esc(d2.param('panelTitle', 'Inspector')) + '</span>' +\n      '<button class=\"mini' + (d2.state.search ? ' on' : '') + '\" data-kind=\"search-toggle\" title=\"Filter\">' + I.search + '</button>' +\n      '<button class=\"mini\" data-kind=\"noop\" title=\"Keep open\">' + I.pin + '</button></div>' +\n\n      '<div class=\"subj\"><span class=\"tile\">' + I.cube + '</span>' +\n      '<span class=\"m\"><span class=\"n\">' + esc(d2.param('subjectTitle', 'Block')) + '</span>' +\n      '<span class=\"p\">' + esc(d2.param('subjectType', 'data2-core-primitives@Block')) + '</span></span>' +\n      '<button class=\"mini\" data-kind=\"noop\" title=\"Copy type id\">' + I.copy + '</button></div>' +\n\n      '<div class=\"ident\">' +\n      '<div class=\"ir\"><span class=\"ik\">Component ID</span><span class=\"iv\">' + esc(d2.param('subjectId', '6a7a79932add8bd2c1a66f18')) + '</span>' +\n      '<button class=\"mini\" data-kind=\"noop\" title=\"Copy id\">' + I.copy + '</button></div>' +\n      '<div class=\"ir\"><span class=\"ik\">Reference</span><input class=\"in txt-in\" style=\"flex:1;width:auto\" data-key=\"__reference\" data-kind=\"text\" spellcheck=\"false\" value=\"' + esc(values.__reference == null ? d2.param('subjectReference', '') : values.__reference) + '\" placeholder=\"auto\"></div>' +\n      '<div class=\"ir\"><span class=\"ik\">Display name</span><input class=\"in txt-in\" style=\"flex:1;width:auto\" data-key=\"__displayName\" data-kind=\"text\" spellcheck=\"false\" value=\"' + esc(values.__displayName == null ? d2.param('subjectName', '') : values.__displayName) + '\" placeholder=\"Untitled\"></div>' +\n      '</div>' +\n\n      '<div class=\"tabs\"><span class=\"segbar\">' +\n      TABS.map(t => '<button class=\"' + (t.id === tab ? 'on' : '') + '\" data-kind=\"tab\" data-val=\"' + t.id + '\">' + t.label + '</button>').join('') +\n      '</span>' +\n      '<span class=\"srch' + (d2.state.search ? ' on' : '') + '\">' + I.search +\n      '<input type=\"text\" spellcheck=\"false\" placeholder=\"Filter\" data-kind=\"query\" value=\"' + esc(d2.state.query || '') + '\"></span>' +\n      '</div>' +\n\n      '<div class=\"body\">' + (secs.trim() ? secs : '<div class=\"empty\">No properties in this tab.</div>') + '</div>' +\n\n      '<div class=\"dock\">' +\n      '<button class=\"mini\" data-kind=\"noop\" title=\"Edit code\">' + I.code + '</button>' +\n      '<button class=\"mini\" data-kind=\"noop\" title=\"Save as preset\">' + I.save + '</button>' +\n      '<button class=\"mini\" data-kind=\"noop\" title=\"Duplicate\">' + I.dup + '</button>' +\n      '<span class=\"sp\"></span>' +\n      '<button class=\"mini\" data-kind=\"noop\" title=\"Lock\">' + I.lock + '</button>' +\n      '<button class=\"mini\" data-kind=\"reset-all\" title=\"Reset all\">' + I.undo + '</button>' +\n      '<button class=\"mini danger\" data-kind=\"noop\" title=\"Delete\">' + I.trash + '</button>' +\n      '</div></div>'\n  },\n\n  mounted(d2) {\n    const schemaOf = () => d2.param('schema') || DEFAULT_SCHEMA\n    const valsOf = () => d2.state.values || defaults(schemaOf())\n    const openOf = () => d2.state.open || openMap(schemaOf())\n    const setVal = (k, v) => {\n      const next = Object.assign({}, valsOf())\n      next[k] = v\n      d2.setState({ values: next })\n    }\n\n    d2.on('[data-toggle]', 'click', e => {\n      const el = e.target.closest('[data-toggle]')\n      if (!el) return\n      const id = el.getAttribute('data-toggle')\n      const next = Object.assign({}, openOf())\n      next[id] = next[id] === false\n      d2.setState({ open: next })\n    })\n\n    d2.on('[data-kind]', 'click', e => {\n      const el = e.target.closest('[data-kind]')\n      if (!el) return\n      const kind = el.getAttribute('data-kind')\n      const key = el.getAttribute('data-key')\n      if (kind === 'toggle') setVal(key, !valsOf()[key])\n      else if (kind === 'seg') setVal(key, el.getAttribute('data-val'))\n      else if (kind === 'tab') d2.setState({ tab: el.getAttribute('data-val') })\n      else if (kind === 'search-toggle') d2.setState({ search: !d2.state.search, query: '' })\n      else if (kind === 'anchor') {\n        const side = el.getAttribute('data-side')\n        const cur = Object.assign({}, valsOf()[key] || {})\n        cur[side] = !cur[side]\n        setVal(key, cur)\n      } else if (kind === 'link') {\n        const arr = valsOf()[key]\n        if (Array.isArray(arr)) setVal(key, [arr[0], arr[0], arr[0], arr[0]])\n      } else if (kind === 'reset') {\n        setVal(key, defaults(schemaOf())[key])\n      } else if (kind === 'reset-all') {\n        d2.setState({ values: defaults(schemaOf()) })\n      }\n    })\n\n    d2.on('[data-kind=\"slider\"]', 'pointerdown', e => {\n      const el = e.target.closest('[data-kind=\"slider\"]')\n      if (!el) return\n      const key = el.getAttribute('data-key')\n      const min = Number(el.getAttribute('data-min')) || 0\n      const max = Number(el.getAttribute('data-max')) || 100\n      const move = ev => {\n        const r = el.getBoundingClientRect()\n        const p = Math.max(0, Math.min(1, (ev.clientX - r.left) / (r.width || 1)))\n        setVal(key, Math.round(min + p * (max - min)))\n      }\n      move(e)\n      const up = () => { window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up) }\n      window.addEventListener('pointermove', move)\n      window.addEventListener('pointerup', up)\n    })\n\n    d2.on('input', 'input', e => {\n      const el = e.target\n      const kind = el.getAttribute && el.getAttribute('data-kind')\n      const key = el.getAttribute && el.getAttribute('data-key')\n      if (kind === 'query') { d2.setState({ query: el.value }); return }\n      if (!key) return\n      if (kind === 'num') {\n        const n = parseFloat(el.value)\n        setVal(key, isNaN(n) ? el.value : n)\n      } else if (kind === 'quad') {\n        const idx = Number(el.getAttribute('data-idx')) || 0\n        const arr = (valsOf()[key] || []).slice()\n        const n = parseFloat(el.value)\n        arr[idx] = isNaN(n) ? 0 : n\n        setVal(key, arr)\n      } else if (kind === 'text') {\n        setVal(key, el.value)\n      }\n    })\n\n    d2.on('select', 'change', e => {\n      const el = e.target\n      const key = el.getAttribute && el.getAttribute('data-key')\n      if (key) setVal(key, el.value)\n    })\n  }\n}\n"}},"panelTitle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"panelTitle"}}}},"subjectTitle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectTitle"}}}},"subjectType":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectType"}}}},"subjectId":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectId"}}}},"subjectReference":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectReference"}}}},"subjectName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectName"}}}},"schema":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"schema"}}}},"appearance":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"appearance"}}}},"accentHex":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accentHex"}}}}}}}}}},{"_id":"6a7a81283d96feddf93e4b27","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"InspectorPiecesMac","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// Inspector Pieces · macOS — props as modular, copy/cut/paste-able lego pieces\nconst ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }\nconst esc = v => String(v == null ? '' : v).replace(/[&<>\"']/g, c => ESC[c])\nconst clone = o => JSON.parse(JSON.stringify(o))\nconst FOCUSED = new WeakSet()\n\nconst I = {\n  chev: '<svg viewBox=\"0 0 8 12\" class=\"ic\"><path d=\"M2.2 1.4 6.4 6l-4.2 4.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  copy: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><rect x=\"2.2\" y=\"2.2\" width=\"7\" height=\"7\" rx=\"1.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><path d=\"M4.8 11.8h5a2 2 0 0 0 2-2v-5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  cut: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><circle cx=\"3.6\" cy=\"10.4\" r=\"1.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\"/><circle cx=\"10.4\" cy=\"10.4\" r=\"1.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\"/><path d=\"M4.6 8.8 10.2 1.8M9.4 8.8 3.8 1.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" stroke-linecap=\"round\"/></svg>',\n  paste: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M4.6 2.6H3.4a1.2 1.2 0 0 0-1.2 1.2v7.4a1.2 1.2 0 0 0 1.2 1.2h7.2a1.2 1.2 0 0 0 1.2-1.2V3.8a1.2 1.2 0 0 0-1.2-1.2H9.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><rect x=\"4.6\" y=\"1.4\" width=\"4.8\" height=\"2.6\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  dup: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><rect x=\"1.8\" y=\"1.8\" width=\"7\" height=\"7\" rx=\"1.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><rect x=\"5.2\" y=\"5.2\" width=\"7\" height=\"7\" rx=\"1.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  trash: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M3.2 4.2h7.6M5.6 4.2V3a.8.8 0 0 1 .8-.8h1.2a.8.8 0 0 1 .8.8v1.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/><path d=\"M4.3 4.2l.5 6.6a1 1 0 0 0 1 .9h2.4a1 1 0 0 0 1-.9l.5-6.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  undo: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M3 6.2a4.4 4.4 0 1 1 .3 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/><path d=\"M2.4 2.6v3.6H6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  link: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M5.8 8.2a2.6 2.6 0 0 0 3.7 0l1.7-1.7a2.6 2.6 0 0 0-3.7-3.7l-.9.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/><path d=\"M8.2 5.8a2.6 2.6 0 0 0-3.7 0L2.8 7.5a2.6 2.6 0 0 0 3.7 3.7l.9-.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  plus: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M7 3v8M3 7h8\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>',\n  search: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><circle cx=\"6\" cy=\"6\" r=\"4.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/><path d=\"M9.3 9.3 12.4 12.4\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>',\n  cube: '<svg viewBox=\"0 0 16 16\" class=\"ic\"><path d=\"M8 1.8 14 5v6l-6 3.2L2 11V5z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/><path d=\"M2 5l6 3.2L14 5M8 8.2v6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.1\" opacity=\".55\"/></svg>',\n  arw: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M6 1.8v8M2.8 6.6 6 9.8l3.2-3.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  arw2: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M6 1.6v8.8M3.4 4 6 1.4 8.6 4M3.4 8 6 10.6 8.6 8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  algn: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M1.4 1.2v9.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/><rect x=\"3.6\" y=\"3.4\" width=\"7\" height=\"5.2\" rx=\"1.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  check: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M2.4 6.4 4.8 8.8 9.6 3.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  x: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M3.2 3.2 8.8 8.8M8.8 3.2 3.2 8.8\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>',\n  dots: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><circle cx=\"6\" cy=\"2.5\" r=\"1.05\" fill=\"currentColor\"/><circle cx=\"6\" cy=\"6\" r=\"1.05\" fill=\"currentColor\"/><circle cx=\"6\" cy=\"9.5\" r=\"1.05\" fill=\"currentColor\"/></svg>'\n}\n\nconst TABS = [\n  { id: 'all', label: 'All' },\n  { id: 'style', label: 'Style' },\n  { id: 'layout', label: 'Layout' },\n  { id: 'content', label: 'Content' },\n  { id: 'logic', label: 'Logic' }\n]\n\nconst KIND_LABEL = { border: 'border', fill: 'fill', shadow: 'shadow', geometry: 'position', size: 'size', spacing: 'spacing', flags: 'flags', text: 'text' }\n\nconst DEFAULT_MODEL = [\n  {\n    id: 'root', title: 'Block', pieces: [\n      /* ---- style ---- */\n      { id: 'p1', kind: 'border', title: 'Border', cat: 'style',\n        v: { r: [10, 10, 10, 10], w: [1, 1, 1, 1], color: '#0078DC', style: 'Solid' },\n        def: { r: [0, 0, 0, 0], w: [0, 0, 0, 0], color: '#000000', style: 'None' } },\n      { id: 'p2', kind: 'fill', title: 'Fill', cat: 'style',\n        v: { color: '#1D1D1F', opacity: 100, blend: 'Normal' },\n        def: { color: '#000000', opacity: 100, blend: 'Normal' } },\n      { id: 'p3', kind: 'fields', title: 'Opacity', cat: 'style',\n        fields: [{ path: 'value', label: '%', type: 'num' }],\n        v: { value: 100 }, def: { value: 100 } },\n      { id: 'p4', kind: 'shadow', title: 'Shadow', cat: 'style', sub: 'List of shadows',\n        v: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' },\n        def: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' } },\n      { id: 'p5', kind: 'fields', title: 'Backdrop filter', cat: 'style', sub: 'List of bitmap filters',\n        fields: [{ path: 'blur', label: 'blur', type: 'num' }, { path: 'saturate', label: 'sat', type: 'num' }],\n        v: { blur: 0, saturate: 100 }, def: { blur: 0, saturate: 100 } },\n      { id: 'p21', kind: 'fields', title: 'Filter', cat: 'style', sub: 'List of bitmap filters',\n        fields: [{ path: 'blur', label: 'blur', type: 'num' }, { path: 'grayscale', label: 'gray', type: 'num' }],\n        v: { blur: 0, grayscale: 0 }, def: { blur: 0, grayscale: 0 } },\n      { id: 'p6', kind: 'fields', title: 'Transition', cat: 'style',\n        fields: [{ path: 'ms', label: 'ms', type: 'num' }, { path: 'ease', label: 'easing', type: 'enum', options: ['Linear', 'Ease', 'Ease in', 'Ease out', 'Spring'] }],\n        v: { ms: 0, ease: 'Ease' }, def: { ms: 0, ease: 'Ease' } },\n      /* ---- layout ---- */\n      { id: 'p7', kind: 'geometry', title: 'Position', cat: 'layout',\n        v: { anchor: { top: true, left: true, right: false, bottom: false }, x: 1135, y: 51, growH: false, growV: false },\n        def: { anchor: { top: false, left: false, right: false, bottom: false }, x: 0, y: 0, growH: false, growV: false } },\n      { id: 'p8', kind: 'size', title: 'Dimensions', cat: 'layout',\n        v: { w: 160, h: 540, link: false, growW: false, growH: false },\n        def: { w: 0, h: 0, link: false, growW: false, growH: false } },\n      { id: 'p9', kind: 'spacing', title: 'Padding', cat: 'layout',\n        v: { t: 12, r: 14, b: 12, l: 14 }, def: { t: 0, r: 0, b: 0, l: 0 } },\n      { id: 'p10', kind: 'fields', title: 'Constraints', cat: 'layout',\n        fields: [{ path: 'minW', label: 'min W', type: 'num' }, { path: 'maxW', label: 'max W', type: 'num' }, { path: 'minH', label: 'min H', type: 'num' }, { path: 'maxH', label: 'max H', type: 'num' }],\n        v: { minW: 0, maxW: 0, minH: 0, maxH: 0 }, def: { minW: 0, maxW: 0, minH: 0, maxH: 0 } },\n      { id: 'p11', kind: 'fields', title: 'Children mode', cat: 'layout',\n        fields: [{ path: 'mode', label: 'flow', type: 'enum', options: ['Absolute', 'Rows', 'Columns', 'Reversed rows', 'Reversed columns'] }],\n        v: { mode: 'Absolute' }, def: { mode: 'Absolute' } },\n      { id: 'p12', kind: 'fields', title: 'Scroll', cat: 'layout',\n        fields: [{ path: 'on', label: 'Scrollable', type: 'bool' }],\n        v: { on: false }, def: { on: false } },\n      /* ---- content ---- */\n      { id: 'p13', kind: 'fields', title: 'Text', cat: 'content',\n        fields: [{ path: 'value', label: 'content', type: 'str', ph: 'Type text…' }],\n        v: { value: '' }, def: { value: '' } },\n      { id: 'p14', kind: 'fields', title: 'Typography', cat: 'content',\n        fields: [{ path: 'font', label: 'font', type: 'enum', options: ['SF Pro Text', 'Inter', 'Helvetica Neue', 'Menlo'] }, { path: 'weight', label: 'weight', type: 'enum', options: ['Regular', 'Medium', 'Semibold', 'Bold'] }, { path: 'size', label: 'size', type: 'num' }, { path: 'lh', label: 'line', type: 'num' }],\n        v: { font: 'SF Pro Text', weight: 'Regular', size: 13, lh: 18 },\n        def: { font: 'SF Pro Text', weight: 'Regular', size: 13, lh: 18 } },\n      { id: 'p15', kind: 'fields', title: 'Text color', cat: 'content',\n        fields: [{ path: 'color', label: '', type: 'color' }],\n        v: { color: '#FFFFFF' }, def: { color: '#FFFFFF' } },\n      { id: 'p16', kind: 'fields', title: 'Icon', cat: 'content',\n        fields: [{ path: 'name', label: 'name', type: 'str', ph: 'fa fa-star' }],\n        v: { name: '' }, def: { name: '' } },\n      /* ---- logic ---- */\n      { id: 'p17', kind: 'fields', title: 'Hide', cat: 'logic',\n        fields: [{ path: 'on', label: 'Hidden', type: 'bool' }], v: { on: false }, def: { on: false } },\n      { id: 'p18', kind: 'fields', title: 'Disabled', cat: 'logic',\n        fields: [{ path: 'on', label: 'Disabled', type: 'bool' }], v: { on: false }, def: { on: false } },\n      { id: 'p19', kind: 'fields', title: 'Children overflow', cat: 'logic',\n        fields: [{ path: 'on', label: 'Visible outside bounds', type: 'bool' }], v: { on: false }, def: { on: false } },\n      { id: 'p20', kind: 'fields', title: 'On click', cat: 'logic',\n        fields: [{ path: 'action', label: 'action', type: 'enum', options: ['None', 'Set value', 'Go to route', 'Run service', 'Open link'] }],\n        v: { action: 'None' }, def: { action: 'None' } }\n    ]\n  }\n]\n\n/* addable layers — the `+` on a node appends another instance of one of these */\nconst TEMPLATES = {\n  border: { kind: 'border', title: 'Border', cat: 'style', v: { r: [0, 0, 0, 0], w: [0, 0, 0, 0], color: '#0078DC', style: 'None' }, def: { r: [0, 0, 0, 0], w: [0, 0, 0, 0], color: '#0078DC', style: 'None' } },\n  fill: { kind: 'fill', title: 'Fill', cat: 'style', v: { color: '#0078DC', opacity: 100, blend: 'Normal' }, def: { color: '#0078DC', opacity: 100, blend: 'Normal' } },\n  shadow: { kind: 'shadow', title: 'Shadow', cat: 'style', v: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' }, def: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' } },\n  padding: { kind: 'spacing', title: 'Padding', cat: 'layout', v: { t: 0, r: 0, b: 0, l: 0 }, def: { t: 0, r: 0, b: 0, l: 0 } },\n  position: { kind: 'geometry', title: 'Position', cat: 'layout', v: { anchor: { top: false, left: false, right: false, bottom: false }, x: 0, y: 0, growH: false, growV: false }, def: { anchor: { top: false, left: false, right: false, bottom: false }, x: 0, y: 0, growH: false, growV: false } },\n  dimensions: { kind: 'size', title: 'Dimensions', cat: 'layout', v: { w: 0, h: 0, link: false, growW: false, growH: false }, def: { w: 0, h: 0, link: false, growW: false, growH: false } },\n  transition: { kind: 'fields', title: 'Transition', cat: 'style', fields: [{ path: 'ms', label: 'ms', type: 'num' }, { path: 'ease', label: 'easing', type: 'enum', options: ['Linear', 'Ease', 'Ease in', 'Ease out', 'Spring'] }], v: { ms: 0, ease: 'Ease' }, def: { ms: 0, ease: 'Ease' } },\n  text: { kind: 'fields', title: 'Text', cat: 'content', fields: [{ path: 'value', label: 'content', type: 'str', ph: 'Type text…' }], v: { value: '' }, def: { value: '' } }\n}\n\n/* ---------- value path helpers ---------- */\nfunction getAt(v, path) {\n  const parts = String(path).split('.')\n  let cur = v\n  for (let i = 0; i < parts.length; i++) {\n    if (cur == null) return null\n    const k = parts[i]\n    cur = Array.isArray(cur) ? cur[Number(k)] : cur[k]\n  }\n  return cur\n}\nfunction setAt(v, path, val) {\n  const parts = String(path).split('.')\n  let cur = v\n  for (let i = 0; i < parts.length - 1; i++) {\n    const k = parts[i]\n    cur = Array.isArray(cur) ? cur[Number(k)] : cur[k]\n    if (cur == null) return\n  }\n  const last = parts[parts.length - 1]\n  if (Array.isArray(cur)) cur[Number(last)] = val\n  else cur[last] = val\n}\nfunction findPiece(model, id) {\n  for (let i = 0; i < model.length; i++) {\n    const ps = model[i].pieces || []\n    for (let j = 0; j < ps.length; j++) if (ps[j].id === id) return { tray: model[i], piece: ps[j], ti: i, pi: j }\n  }\n  return null\n}\nfunction nextId(model) {\n  let max = 0\n  model.forEach(t => (t.pieces || []).forEach(p => {\n    const n = parseInt(String(p.id).replace(/[^0-9]/g, ''), 10)\n    if (n > max) max = n\n  }))\n  return 'p' + (max + 1)\n}\n\n/* ---------- slot renderers ---------- */\nfunction slot(pid, path, type, value, opts) {\n  opts = opts || {}\n  const sel = opts.sel && opts.sel.type === 'slot' && opts.sel.piece === pid && opts.sel.path === path\n  const cls = 'slot' + (sel ? ' sel' : '') + (opts.cls ? ' ' + opts.cls : '')\n  let inner = ''\n  if (type === 'color') {\n    inner = '<span class=\"chip-c\" style=\"background:' + esc(value) + '\"></span>' +\n      '<input class=\"sin hex\" data-in=\"' + esc(pid) + '|' + esc(path) + '|text\" spellcheck=\"false\" value=\"' + esc(value) + '\">'\n  } else if (type === 'enum') {\n    inner = '<select class=\"sin\" data-in=\"' + esc(pid) + '|' + esc(path) + '|text\">' +\n      (opts.options || []).map(o => '<option' + (String(o) === String(value) ? ' selected' : '') + '>' + esc(o) + '</option>').join('') + '</select>'\n  } else if (type === 'text') {\n    inner = '<input class=\"sin\" data-in=\"' + esc(pid) + '|' + esc(path) + '|text\" spellcheck=\"false\" value=\"' + esc(value) + '\" placeholder=\"' + esc(opts.ph || '') + '\">'\n  } else {\n    inner = '<input class=\"sin num\" data-in=\"' + esc(pid) + '|' + esc(path) + '|num\" inputmode=\"decimal\" spellcheck=\"false\" value=\"' + esc(value) + '\">'\n  }\n  return '<span class=\"' + cls + '\" data-slot=\"' + esc(pid) + '|' + esc(path) + '|' + type + '\"' +\n    (opts.cap ? ' data-cap=\"' + esc(opts.cap) + '\"' : '') + '>' +\n    (opts.cap ? '<i class=\"cap\">' + esc(opts.cap) + '</i>' : '') + inner + '</span>'\n}\n\nfunction chip(pid, path, on, label) {\n  return '<button class=\"tchip' + (on ? ' on' : '') + '\" data-act=\"flag\" data-piece=\"' + esc(pid) + '\" data-path=\"' + esc(path) + '\">' +\n    '<i class=\"tbox\"></i>' + esc(label) + '</button>'\n}\n\n/* ---------- piece bodies ---------- */\nfunction bodyBorder(p, s) {\n  const v = p.v\n  const c = i => slot(p.id, 'r.' + i, 'num', v.r[i], { sel: s, cls: 'corner c' + i, cap: 'radius' })\n  const e = i => slot(p.id, 'w.' + i, 'num', v.w[i], { sel: s, cls: 'edge', cap: 'width' })\n  const selC = s && s.type === 'slot' && s.piece === p.id && s.path === 'color'\n  return '<div class=\"mtx brd\">' +\n    c(0) + e(0) + c(1) +\n    e(3) + '<span class=\"cempty\"></span>' + e(1) +\n    c(3) + e(2) + c(2) +\n    '<span class=\"cbig slot' + (selC ? ' sel' : '') + '\" data-slot=\"' + esc(p.id) + '|color|color\" style=\"background:' + esc(v.color) + '\">' +\n    '<input class=\"sin hex big\" data-in=\"' + esc(p.id) + '|color|text\" spellcheck=\"false\" value=\"' + esc(v.color) + '\"></span>' +\n    '</div>' +\n    '<div class=\"prow\">' + slot(p.id, 'style', 'enum', v.style, { sel: s, cls: 'wide', options: ['None', 'Solid', 'Dashed', 'Dotted'], cap: 'type' }) + '</div>'\n}\n\nfunction bodySpacing(p, s) {\n  const v = p.v\n  return '<div class=\"mtx pad\">' +\n    '<i></i>' + slot(p.id, 't', 'num', v.t, { sel: s, cap: 'top' }) + '<i></i>' +\n    slot(p.id, 'l', 'num', v.l, { sel: s, cap: 'left' }) + '<span class=\"padc\">content</span>' + slot(p.id, 'r', 'num', v.r, { sel: s, cap: 'right' }) +\n    '<i></i>' + slot(p.id, 'b', 'num', v.b, { sel: s, cap: 'bottom' }) + '<i></i>' +\n    '</div>'\n}\n\nfunction bodyGeometry(p, s) {\n  const v = p.v, a = v.anchor || {}\n  const cell = side => '<button class=\"a-' + side + (a[side] ? ' on' : '') + '\" data-act=\"anchor\" data-piece=\"' + esc(p.id) + '\" data-side=\"' + side + '\"></button>'\n  const grp = ks => '<span class=\"clus\">' + ks.map(k => '<button class=\"ib al-' + k + '\" data-act=\"noop\" title=\"Align\">' + I.algn + '</button>').join('') + '</span>'\n  return '<div class=\"geo\">' +\n    '<span class=\"anc-g\">' + '<i></i>' + cell('top') + '<i></i>' + cell('left') + '<span class=\"a-c\"></span>' + cell('right') + '<i></i>' + cell('bottom') + '<i></i>' + '</span>' +\n    '<span class=\"geo-r\">' +\n    chip(p.id, 'growH', v.growH, 'Grow H') + chip(p.id, 'growV', v.growV, 'Grow V') +\n    '</span></div>' +\n    '<div class=\"clusrow\">' + grp(['l', 'c', 'r']) + grp(['t', 'm', 'b']) + '</div>' +\n    '<div class=\"prow2\">' + slot(p.id, 'x', 'num', v.x, { sel: s, cap: 'X' }) + slot(p.id, 'y', 'num', v.y, { sel: s, cap: 'Y' }) + '</div>'\n}\n\nfunction bodySize(p, s) {\n  const v = p.v\n  const grp = ks => '<span class=\"clus\">' + ks.map(k => '<button class=\"ib d-' + k + '\" data-act=\"noop\" title=\"Distribute\">' + (k === 'w1' || k === 'h1' ? I.arw2 : I.arw) + '</button>').join('') + '</span>'\n  return '<div class=\"clusrow\">' + grp(['w0', 'w1', 'w2']) + grp(['h0', 'h1', 'h2']) + '</div>' +\n    '<div class=\"prow2\">' + slot(p.id, 'w', 'num', v.w, { sel: s, cap: 'W' }) +\n    '<button class=\"ib lk' + (v.link ? ' on' : '') + '\" data-act=\"flag\" data-piece=\"' + esc(p.id) + '\" data-path=\"link\">' + I.link + '</button>' +\n    slot(p.id, 'h', 'num', v.h, { sel: s, cap: 'H' }) + '</div>' +\n    '<div class=\"chips\">' + chip(p.id, 'growW', v.growW, 'Fit width') + chip(p.id, 'growH', v.growH, 'Fit height') + '</div>'\n}\n\nfunction bodyFill(p, s) {\n  const v = p.v\n  return '<div class=\"prow2\">' + slot(p.id, 'color', 'color', v.color, { sel: s, cls: 'grow' }) +\n    slot(p.id, 'opacity', 'num', v.opacity, { sel: s, cap: '%' }) + '</div>' +\n    '<div class=\"prow\">' + slot(p.id, 'blend', 'enum', v.blend, { sel: s, cls: 'wide', options: ['Normal', 'Multiply', 'Screen', 'Overlay'], cap: 'blend' }) + '</div>'\n}\n\nfunction bodyShadow(p, s) {\n  const v = p.v\n  return '<div class=\"shw\"><span class=\"shw-p\" style=\"box-shadow:' + Number(v.x) + 'px ' + Number(v.y) + 'px ' + Number(v.blur) + 'px ' + Number(v.spread) + 'px ' + esc(v.color) + '\"></span></div>' +\n    '<div class=\"prow4\">' + slot(p.id, 'x', 'num', v.x, { sel: s, cap: 'X' }) + slot(p.id, 'y', 'num', v.y, { sel: s, cap: 'Y' }) +\n    slot(p.id, 'blur', 'num', v.blur, { sel: s, cap: 'blur' }) + slot(p.id, 'spread', 'num', v.spread, { sel: s, cap: 'sprd' }) + '</div>' +\n    '<div class=\"prow\">' + slot(p.id, 'color', 'color', v.color, { sel: s, cls: 'wide' }) + '</div>'\n}\n\nfunction bodyFlags(p, s) {\n  return '<div class=\"chips\">' + (p.v.items || []).map((it, i) => chip(p.id, 'items.' + i + '.on', it.on, it.label)).join('') + '</div>'\n}\n\nfunction bodyText(p, s) {\n  const v = p.v\n  return '<div class=\"prow\">' + slot(p.id, 'value', 'text', v.value, { sel: s, cls: 'wide', ph: 'Type text…' }) + '</div>' +\n    '<div class=\"prow\">' + slot(p.id, 'font', 'enum', v.font, { sel: s, cls: 'wide', options: ['SF Pro Text', 'Inter', 'Helvetica Neue', 'Menlo'], cap: 'font' }) + '</div>' +\n    '<div class=\"prow2\">' + slot(p.id, 'size', 'num', v.size, { sel: s, cap: 'size' }) + slot(p.id, 'lh', 'num', v.lh, { sel: s, cap: 'line' }) + '</div>'\n}\n\nfunction bodyFields(p, s) {\n  const fs = p.fields || []\n  const bools = fs.filter(f => f.type === 'bool')\n  const rest = fs.filter(f => f.type !== 'bool')\n  let out = ''\n  if (rest.length) {\n    const one = rest.length === 1\n    out += '<div class=\"' + (one ? 'prow' : 'fgrid') + '\">' + rest.map(f => {\n      const type = f.type === 'enum' ? 'enum' : f.type === 'color' ? 'color' : f.type === 'str' ? 'text' : 'num'\n      return slot(p.id, f.path, type, getAt(p.v, f.path), { sel: s, cls: one ? 'wide' : '', options: f.options, cap: f.label, ph: f.ph })\n    }).join('') + '</div>'\n  }\n  if (bools.length) out += '<div class=\"chips\">' + bools.map(f => chip(p.id, f.path, !!getAt(p.v, f.path), f.label)).join('') + '</div>'\n  return out || '<div class=\"empty\">No editable values.</div>'\n}\n\nconst BODIES = { fields: bodyFields, border: bodyBorder, spacing: bodySpacing, geometry: bodyGeometry, size: bodySize, fill: bodyFill, shadow: bodyShadow, flags: bodyFlags, text: bodyText }\n\n/* ---------- category selector (stepper pill) ---------- */\nfunction catCount(model, id) {\n  let n = 0\n  model.forEach(t => (t.pieces || []).forEach(p => { if (id === 'all' || (p.cat || 'style') === id) n += 1 }))\n  return n\n}\n\nfunction renderAddMenu(t) {\n  return '<div class=\"menu add-m\">' + Object.keys(TEMPLATES).map(k =>\n    '<button class=\"cat\" data-act=\"add-pick\" data-tray=\"' + esc(t.id) + '\" data-kind=\"' + esc(k) + '\">' +\n    '<span class=\"ck\"></span><span class=\"cl2\">' + esc(TEMPLATES[k].title) + '</span>' +\n    '<span class=\"cc\">' + esc(k) + '</span></button>').join('') + '</div>'\n}\n\nfunction pieceMatches(p, q) {\n  return (p.title + ' ' + p.kind + ' ' + JSON.stringify(p.v)).toLowerCase().indexOf(q) > -1\n}\n\nfunction renderPill(model, tab, mode, q) {\n  if (mode === 'search') {\n    return '<div class=\"tabs\"><div class=\"pwrap\"><div class=\"pill act\">' +\n      '<span class=\"pglyph\">' + I.search + '</span>' +\n      '<input class=\"pq\" spellcheck=\"false\" placeholder=\"Search props…\" value=\"' + esc(q) + '\">' +\n      '<button class=\"parr nx\" data-act=\"cat-close\" title=\"Close search\">' + I.x + '</button>' +\n      '</div></div></div>'\n  }\n  let idx = TABS.map(t => t.id).indexOf(tab)\n  if (idx < 0) idx = 0\n  const open = mode === 'list'\n  return '<div class=\"tabs\"><div class=\"pwrap\">' +\n    '<div class=\"pill' + (open ? ' act' : '') + '\">' +\n    '<button class=\"parr\" data-act=\"cat-prev\" title=\"Previous category\">' + I.chev + '</button>' +\n    '<button class=\"plabel\" data-act=\"cat-open\" title=\"Click to pick · double-click to search\">' + esc(TABS[idx].label) + '</button>' +\n    '<button class=\"parr nx\" data-act=\"cat-next\" title=\"Next category\">' + I.chev + '</button>' +\n    '</div>' +\n    (open ? '<div class=\"cats\">' + TABS.map(t => '<button class=\"cat' + (t.id === tab ? ' on' : '') + '\" data-act=\"cat-pick\" data-val=\"' + t.id + '\">' +\n      '<span class=\"ck\">' + (t.id === tab ? I.check : '') + '</span>' +\n      '<span class=\"cl2\">' + esc(t.label) + '</span>' +\n      '<span class=\"cc\">' + catCount(model, t.id) + '</span></button>').join('') + '</div>' : '') +\n    '</div></div>'\n}\n\nfunction renderPieceMenu(p) {\n  const items = [['copy', 'Copy'], ['cut', 'Cut'], ['dup', 'Duplicate'], ['paste-into', 'Paste into'], ['reset-piece', 'Reset'], ['del', 'Remove']]\n  return '<div class=\"menu p-m\">' + items.map(it =>\n    '<button class=\"cat' + (it[0] === 'del' ? ' dngr' : '') + '\" data-act=\"' + it[0] + '\" data-piece=\"' + esc(p.id) + '\">' +\n    '<span class=\"cl2\">' + it[1] + '</span></button>').join('') + '</div>'\n}\n\nfunction fxBody(p) {\n  return '<div class=\"fxrow\"><span class=\"fxk\">f()</span>' +\n    '<input class=\"sin fxin\" data-in=\"' + esc(p.id) + '|__fx|text\" spellcheck=\"false\" value=\"' + esc(p.v.__fx) + '\" placeholder=\"Reference(&#39;brandColor&#39;)\"></div>'\n}\n\nfunction renderPiece(p, tray, st) {\n  const ref = p.def !== undefined ? p.def : (TEMPLATES[p.kind] ? TEMPLATES[p.kind].v : null)\n  const pop = !ref || JSON.stringify(ref) !== JSON.stringify(p.v)\n  const exp = st.pOpen && st.pOpen[p.id] !== undefined ? st.pOpen[p.id] : pop\n  const sel = st.sel && st.sel.type === 'piece' && st.sel.piece === p.id\n  const cut = st.cut === p.id\n  const fx = p.v && p.v.__fx != null\n  const fn = BODIES[p.kind] || bodyFlags\n  return '<div class=\"piece' + (pop ? ' pop' : '') + (exp ? ' exp' : '') + (p.sub ? ' has-sub' : '') + (sel ? ' sel' : '') + (cut ? ' cut' : '') + (st.drag === p.id ? ' drag' : '') + '\" data-piece=\"' + esc(p.id) + '\" data-tray=\"' + esc(tray.id) + '\">' +\n    '<div class=\"ph\" data-act=\"select\" data-piece=\"' + esc(p.id) + '\" data-grip=\"' + esc(p.id) + '\">' +\n    '<button class=\"pdot' + (exp ? ' on' : '') + '\" data-act=\"pexp\" data-piece=\"' + esc(p.id) + '\" title=\"Expand / collapse\"></button>' +\n    '<span class=\"ptxt\"><span class=\"pt\">' + esc(p.title) + '</span>' +\n    (p.sub ? '<span class=\"psub\">' + esc(p.sub) + '</span>' : '') + '</span>' +\n    (fx ? '<span class=\"fxbadge\">f()</span>' : '') +\n    '<span class=\"pspace\"></span>' +\n    '<span class=\"ptools\">' +\n    '<button class=\"ib tb\" data-act=\"noop\" title=\"About this property\">?</button>' +\n    '<button class=\"ib tb' + (fx ? ' on' : '') + '\" data-act=\"fx\" data-piece=\"' + esc(p.id) + '\" title=\"Bind to an expression\">f()</button>' +\n    '<button class=\"ib tb\" data-act=\"menu\" data-piece=\"' + esc(p.id) + '\" title=\"More\">' + I.dots + '</button>' +\n    '</span>' +\n    (st.menuFor === p.id ? renderPieceMenu(p) : '') +\n    '</div>' +\n    (exp ? '<div class=\"pb\">' + (fx ? fxBody(p) : fn(p, st.sel)) + '</div>' : '') +\n    '</div>'\n}\n\nexport default {\n  css: `\n:host { display: block; width: 100%; height: 100% }\n:host, .ins { box-sizing: border-box }\n.ins *, .ins *::before, .ins *::after { box-sizing: border-box }\n.ins {\n  --accent: #0A84FF;\n  --bg: #2c2c2e; --bg2: #333336; --card: rgba(255,255,255,.052); --card-b: rgba(255,255,255,.09);\n  --sep: rgba(255,255,255,.075); --txt: rgba(255,255,255,.93); --txt2: rgba(235,235,245,.62);\n  --txt3: rgba(235,235,245,.34); --ctl: rgba(255,255,255,.10); --ctl2: rgba(255,255,255,.16);\n  --ctl-b: rgba(0,0,0,.32); --track: rgba(0,0,0,.28); --sh: 0 1px 2px rgba(0,0,0,.4);\n  --scroll: rgba(255,255,255,.22); --dot: rgba(235,235,245,.22); --stud: rgba(255,255,255,.13);\n  width: 300px; max-width: 100%; height: 100%; margin-left: auto; display: flex; flex-direction: column; overflow: hidden;\n  transition: width .22s cubic-bezier(.32,.72,0,1); border-radius: 11px; background: linear-gradient(180deg, var(--bg2), var(--bg) 120px);\n  color: var(--txt); font-family: -apple-system, BlinkMacSystemFont, \"SF Pro Text\", \"Helvetica Neue\", \"Segoe UI\", system-ui, sans-serif;\n  font-size: 12px; line-height: 1.35; -webkit-font-smoothing: antialiased; user-select: none;\n  box-shadow: 0 0 0 .5px rgba(255,255,255,.09), 0 12px 34px rgba(0,0,0,.34);\n}\n.ins.light {\n  --accent: #007AFF;\n  --bg: #ECECEE; --bg2: #F7F7F9; --card: #FFFFFF; --card-b: rgba(0,0,0,.08);\n  --sep: rgba(0,0,0,.07); --txt: #1D1D1F; --txt2: rgba(60,60,67,.62); --txt3: rgba(60,60,67,.34);\n  --ctl: #FFFFFF; --ctl2: rgba(0,0,0,.06); --ctl-b: rgba(0,0,0,.18); --track: rgba(120,120,128,.20);\n  --sh: 0 1px 1.5px rgba(0,0,0,.14); --scroll: rgba(0,0,0,.24); --dot: rgba(60,60,67,.2); --stud: rgba(0,0,0,.09);\n  box-shadow: 0 0 0 .5px rgba(0,0,0,.1), 0 12px 34px rgba(0,0,0,.16);\n}\n.ic { width: 100%; height: 100%; display: block }\nbutton { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer }\n.ib { width: 19px; height: 19px; border-radius: 5px; display: grid; place-items: center; color: var(--txt2); flex: none;\n  transition: background .12s, color .12s }\n.ib > svg { width: 12px; height: 12px }\n.ib:hover { background: var(--ctl2); color: var(--txt) }\n.ib.on { color: #fff; background: var(--accent) }\n.ib.danger:hover { background: rgba(255,69,58,.18); color: #ff453a }\n\n/* header */\n.tbar { display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 8px 0 12px; flex: none; border-bottom: .5px solid var(--sep) }\n.tbar .t { font-size: 12.5px; font-weight: 590; letter-spacing: -.01em; flex: 1 }\n.subj { display: flex; align-items: center; gap: 9px; margin: 10px 10px 8px; padding: 8px 9px;\n  background: var(--card); border: .5px solid var(--card-b); border-radius: 9px }\n.subj .tile { width: 26px; height: 26px; border-radius: 7px; flex: none; display: grid; place-items: center;\n  color: #fff; background: linear-gradient(160deg, #5aa9ff, var(--accent)); box-shadow: var(--sh) }\n.subj .tile > svg { width: 15px; height: 15px }\n.subj .m { min-width: 0; flex: 1; display: block }\n.subj .n { display: block; font-size: 12.5px; font-weight: 590; letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.subj .p { display: block; font-size: 10.5px; color: var(--txt2); font-family: ui-monospace, \"SF Mono\", Menlo, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.ident { margin: 0 10px 10px }\n.ident .ir { display: flex; align-items: center; gap: 8px; min-height: 25px }\n.ident .ik { width: 76px; flex: none; font-size: 11px; color: var(--txt2) }\n.ident .iv { flex: 1; min-width: 0; font-family: ui-monospace, \"SF Mono\", Menlo, monospace; font-size: 10.5px; color: var(--txt2);\n  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; user-select: text }\n.ident input { flex: 1; width: auto; background: var(--ctl); border: .5px solid var(--ctl-b); border-radius: 6px;\n  color: var(--txt); font: inherit; font-size: 11.5px; height: 22px; padding: 0 6px; outline: none; user-select: text }\n.ident input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 26%, transparent) }\n.tabs { display: flex; align-items: center; gap: 6px; margin: 0 10px 10px }\n.segbar { display: flex; flex: 1; gap: 2px; padding: 2px; background: var(--track); border-radius: 8px }\n.segbar button { flex: 1; height: 20px; border-radius: 6px; font-size: 10.5px; color: var(--txt2); transition: background .14s, color .14s }\n.segbar button.on { background: var(--ctl2); color: var(--txt); font-weight: 590; box-shadow: var(--sh) }\n.ins.light .segbar button.on { background: #fff }\n\n/* category stepper pill */\n.tabs { position: relative; z-index: 20 }\n.pwrap { position: relative; flex: 1 }\n.pill { display: flex; align-items: center; height: 30px; padding: 0 3px; border-radius: 999px;\n  background: rgba(0,0,0,.16); border: 1px solid var(--card-b); transition: border-color .14s, box-shadow .14s }\n.ins.light .pill { background: rgba(0,0,0,.028) }\n.pill:hover { border-color: color-mix(in srgb, var(--accent) 38%, var(--card-b)) }\n.pill.act { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent) }\n.parr { width: 26px; height: 24px; border-radius: 999px; display: grid; place-items: center; color: var(--txt3); flex: none;\n  transition: color .12s, background .12s }\n.parr > svg { width: 7px; height: 11px }\n.parr:hover { color: var(--txt); background: var(--ctl2) }\n.parr:not(.nx) > svg { transform: rotate(180deg) }\n.plabel { flex: 1; min-width: 0; height: 24px; border-radius: 999px; font-size: 12.5px; color: var(--txt2);\n  text-transform: lowercase; letter-spacing: .01em; transition: color .12s, background .12s }\n.plabel:hover { color: var(--txt); background: var(--ctl2) }\n.pglyph { width: 24px; display: grid; place-items: center; color: var(--txt3); flex: none }\n.pglyph > svg { width: 12px; height: 12px }\n.pq { flex: 1; min-width: 0; height: 24px; background: none; border: 0; outline: none; color: var(--txt);\n  font: inherit; font-size: 12px; user-select: text }\n.cats, .menu { position: absolute; left: 0; right: 0; top: 35px; z-index: 30; padding: 4px; border-radius: 10px;\n  background: var(--bg2); border: .5px solid var(--card-b); box-shadow: 0 12px 28px rgba(0,0,0,.45) }\n.ins.light .cats, .ins.light .menu { background: #fff; box-shadow: 0 12px 28px rgba(0,0,0,.16) }\n.cat { display: flex; align-items: center; gap: 7px; width: 100%; height: 26px; padding: 0 7px; border-radius: 6px;\n  font-size: 11.5px; color: var(--txt); text-transform: lowercase }\n.cat:hover { background: var(--accent); color: #fff }\n.cat .ck { width: 11px; height: 11px; flex: none; color: var(--accent) }\n.cat:hover .ck { color: #fff }\n.cat .cl2 { flex: 1; text-align: left }\n.cat .cc { font-size: 10px; color: var(--txt3); font-variant-numeric: tabular-nums }\n.cat:hover .cc { color: rgba(255,255,255,.8) }\n\n/* body / trays */\n.ins.wide { width: 100% }\n.ins.wide .tray-b { display: block; columns: 2; column-gap: 10px }\n.ins.wide .tray-b .piece { break-inside: avoid; margin-bottom: 10px }\n.ins.wide .body { padding: 0 12px 14px }\n.body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 0 10px 14px; scrollbar-width: thin }\n.body::-webkit-scrollbar { width: 9px }\n.body::-webkit-scrollbar-thumb { background: transparent; border-radius: 5px; border: 3px solid transparent; background-clip: padding-box }\n.body:hover::-webkit-scrollbar-thumb { background: var(--scroll); background-clip: padding-box }\n.tray { margin-bottom: 14px }\n.node { position: relative; display: flex; align-items: center; gap: 3px; height: 40px; padding: 0 4px 0 0; margin-bottom: 2px }\n.node-h { flex: 1; min-width: 0; display: flex; align-items: center; gap: 9px; height: 32px; padding: 0 8px;\n  border-radius: 8px; text-align: left; transition: background .12s }\n.node-h:hover { background: var(--ctl2) }\n.node-h .chev { width: 9px; height: 12px; flex: none; color: var(--txt3); transition: transform .16s ease }\n.tray.open .node-h .chev { transform: rotate(90deg) }\n.nt { font-size: 16px; font-weight: 590; letter-spacing: -.02em; color: var(--txt) }\n.ntools { display: flex; gap: 2px; opacity: 0; transition: opacity .12s }\n.node:hover .ntools, .node:focus-within .ntools { opacity: 1 }\n.nn { font-size: 10px; color: var(--txt3); font-variant-numeric: tabular-nums; min-width: 12px; text-align: right }\n.add-m { top: 36px; left: auto; right: 2px; width: 170px }\n.tray-h { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 2px; color: var(--txt3);\n  font-size: 10px; font-weight: 590; text-transform: uppercase; letter-spacing: .06em }\n.tray-h .t { flex: 1; text-align: left }\n.tray-h .n { font-variant-numeric: tabular-nums }\n.tray-b { display: flex; flex-direction: column; gap: 8px; padding: 2px; border-radius: 12px; border: 1px dashed transparent }\n.tray.over .tray-b { border-color: color-mix(in srgb, var(--accent) 55%, transparent); background: color-mix(in srgb, var(--accent) 7%, transparent) }\n.drop { height: 3px; border-radius: 2px; background: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent) }\n\n/* prop piece */\n.piece { position: relative; background: transparent; border: .5px solid transparent; border-radius: 10px;\n  transition: background .14s, border-color .14s, box-shadow .14s, opacity .14s }\n.piece.pop { background: var(--card); border-color: var(--card-b) }\n.ins.light .piece.pop { box-shadow: 0 1px 2px rgba(0,0,0,.06) }\n.piece.sel { box-shadow: 0 0 0 2px var(--accent) }\n.piece.cut { opacity: .42; outline: 1.5px dashed var(--accent); outline-offset: 2px }\n.piece.drag { opacity: .55 }\n.ph { position: relative; display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 5px 0 9px }\n.piece.pop.exp .ph { border-bottom: .5px solid var(--sep) }\n.pdot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: transparent;\n  box-shadow: inset 0 0 0 1.2px var(--dot); transition: background .14s, box-shadow .14s }\n.pdot:hover { box-shadow: inset 0 0 0 1.2px var(--txt3) }\n.pdot.on { background: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent) }\n.ptxt { display: flex; flex-direction: column; gap: 1px; min-width: 0 }\n.pt { font-size: 13px; font-weight: 640; letter-spacing: -.01em; color: var(--txt) }\n.psub { font-size: 10px; color: var(--txt3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.piece.has-sub .ph { height: 44px }\n.pspace { flex: 1 }\n.fxbadge { font-size: 8.5px; font-weight: 700; font-family: ui-monospace, \"SF Mono\", Menlo, monospace; color: var(--accent);\n  border: .5px solid color-mix(in srgb, var(--accent) 50%, transparent); border-radius: 4px; padding: 1px 3px }\n.ptools { display: flex; gap: 3px; opacity: 0; transition: opacity .12s }\n.piece:hover .ptools, .piece.sel .ptools { opacity: 1 }\n.tb { width: 21px; height: 18px; border: .5px solid var(--card-b); border-radius: 5px; font-size: 9px; font-weight: 700;\n  font-family: ui-monospace, \"SF Mono\", Menlo, monospace; color: var(--txt3) }\n.tb > svg { width: 10px; height: 10px }\n.tb:hover { color: var(--txt); background: var(--ctl2); border-color: var(--ctl-b) }\n.tb.on { color: #fff; background: var(--accent); border-color: transparent }\n.p-m { top: 30px; left: auto; right: 4px; width: 150px }\n.cat.dngr { color: #ff453a }\n.cat.dngr:hover { background: #ff453a; color: #fff }\n.fxrow { display: flex; align-items: center; gap: 7px; height: 28px; padding: 0 7px; border-radius: 7px;\n  background: color-mix(in srgb, var(--accent) 12%, transparent);\n  border: .5px solid color-mix(in srgb, var(--accent) 40%, transparent) }\n.fxk { font-size: 9.5px; font-weight: 700; font-family: ui-monospace, \"SF Mono\", Menlo, monospace; color: var(--accent); flex: none }\n.fxin { font-family: ui-monospace, \"SF Mono\", Menlo, monospace; font-size: 10.5px }\n.pb { padding: 11px 9px 10px; display: flex; flex-direction: column; gap: 12px }\n\n/* slots */\n.slot { position: relative; display: flex; align-items: center; min-width: 0; height: 26px; padding: 0 5px;\n  background: var(--ctl); border: .5px solid var(--ctl-b); border-radius: 6px; cursor: pointer;\n  transition: box-shadow .12s, border-color .12s }\n.slot:hover { border-color: color-mix(in srgb, var(--accent) 50%, var(--ctl-b)) }\n.slot.sel { box-shadow: 0 0 0 2px var(--accent); border-color: transparent }\n.slot .cap { position: absolute; top: -9px; left: 3px; font-size: 8px; line-height: 9px; font-style: normal;\n  letter-spacing: .03em; color: var(--txt3); pointer-events: none; white-space: nowrap }\n.sin { flex: 1; min-width: 0; width: 100%; background: none; border: 0; outline: none; color: var(--txt);\n  font: inherit; font-size: 11px; user-select: text }\n.sin.num { text-align: center; font-variant-numeric: tabular-nums }\n.sin.hex { font-family: ui-monospace, \"SF Mono\", Menlo, monospace; font-size: 10px; text-transform: uppercase }\nselect.sin { appearance: none; -webkit-appearance: none; cursor: pointer; text-align: left }\n/* the native popup inherits the select's colour — without this it is white-on-white */\nselect.sin option { background-color: #2F2F31; color: #FFFFFF }\n.ins.light select.sin option { background-color: #FFFFFF; color: #1D1D1F }\n.chip-c { width: 15px; height: 15px; border-radius: 4px; flex: none; margin-right: 5px;\n  box-shadow: inset 0 0 0 .5px rgba(0,0,0,.4), 0 0 0 .5px rgba(255,255,255,.16) }\n\n/* geometric matrices */\n.mtx { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px 6px }\n.mtx > i { display: block }\n.mtx .mid { padding: 0 4px }\n.mtx.brd { grid-template-columns: 1fr 1fr 1fr 1.3fr; grid-template-rows: repeat(3, 26px) }\n.cempty { border-radius: 6px; background: var(--track) }\n.cbig { grid-column: 4; grid-row: 1 / span 3; height: auto; padding: 0; align-items: flex-end; justify-content: center;\n  border-radius: 8px; overflow: hidden; border: .5px solid rgba(0,0,0,.32);\n  box-shadow: inset 0 0 0 .5px rgba(255,255,255,.16) }\n.hex.big { flex: none; width: calc(100% - 8px); margin: 0 4px 4px; height: 19px; text-align: center; color: #fff;\n  background: rgba(0,0,0,.34); border-radius: 5px; font-size: 9.5px; letter-spacing: .02em }\n.mtx.pad .padc { display: grid; place-items: center; font-size: 9px; color: var(--txt3); background: var(--track);\n  border-radius: 6px; letter-spacing: .04em }\n.corner.c0 { border-top-left-radius: 10px } .corner.c1 { border-top-right-radius: 10px }\n.corner.c2 { border-bottom-right-radius: 10px } .corner.c3 { border-bottom-left-radius: 10px }\n.prow { display: flex }\n.prow2 { display: flex; align-items: center; gap: 6px }\n.prow4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px }\n.fgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 6px }\n.prow .wide, .slot.wide { flex: 1; width: 100% }\n.prow2 .slot { flex: 1 } .prow2 .grow { flex: 2 }\n.chips { display: flex; flex-wrap: wrap; gap: 6px }\n.tchip { display: inline-flex; align-items: center; gap: 5px; height: 22px; padding: 0 8px 0 6px; border-radius: 6px;\n  background: var(--ctl); border: .5px solid var(--ctl-b); font-size: 10.5px; color: var(--txt2); transition: all .12s }\n.tchip .tbox { width: 9px; height: 9px; border-radius: 3px; border: 1px solid var(--txt3); flex: none }\n.tchip.on { background: color-mix(in srgb, var(--accent) 18%, transparent); border-color: color-mix(in srgb, var(--accent) 60%, transparent); color: var(--txt) }\n.tchip.on .tbox { background: var(--accent); border-color: var(--accent) }\n.clusrow { display: flex; gap: 8px }\n.clus { flex: 1; display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px; padding: 3px;\n  background: var(--track); border-radius: 7px }\n.clus .ib { width: 100%; height: 21px; border-radius: 5px }\n.d-h0 svg, .al-t svg { transform: rotate(180deg) } .d-h1 svg { transform: none } .d-h2 svg { transform: none }\n.d-w0 svg { transform: rotate(90deg) } .d-w1 svg { transform: rotate(90deg) } .d-w2 svg { transform: rotate(-90deg) }\n.al-c svg { transform: rotate(90deg) } .al-r svg { transform: scaleX(-1) } .al-m svg { transform: rotate(90deg) scaleX(-1) }\n.al-b svg { transform: rotate(90deg) scaleY(-1) }\n.geo { display: flex; align-items: center; gap: 10px }\n.geo-r { display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 0 }\n.anc-g { display: grid; grid-template-columns: 20px 26px 20px; grid-template-rows: 16px 22px 16px; gap: 3px;\n  padding: 7px; background: var(--track); border-radius: 8px; flex: none }\n.anc-g > i { display: block }\n.anc-g button { border-radius: 3px; background: var(--dot); transition: background .14s, box-shadow .14s }\n.anc-g button:hover { background: var(--txt3) }\n.anc-g button.on { background: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 24%, transparent) }\n.a-top, .a-bottom { justify-self: center; align-self: center; width: 20px; height: 3px }\n.a-left, .a-right { align-self: center; justify-self: center; width: 3px; height: 16px }\n.a-c { border-radius: 5px; background: var(--ctl2); box-shadow: inset 0 0 0 .5px var(--card-b) }\n.shw { display: grid; place-items: center; height: 56px; background: var(--track); border-radius: 8px }\n.shw-p { width: 46px; height: 26px; border-radius: 6px; background: var(--ctl2) }\n\n/* clipboard bar + dock */\n.clip { flex: none; display: flex; align-items: center; gap: 7px; margin: 0 10px 8px; padding: 5px 6px 5px 8px;\n  border-radius: 9px; background: color-mix(in srgb, var(--accent) 15%, transparent);\n  border: .5px solid color-mix(in srgb, var(--accent) 45%, transparent) }\n.clip > svg { width: 13px; height: 13px; flex: none; color: var(--accent) }\n.clip .cl { font-size: 10.5px; color: var(--txt2); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }\n.clip .cn { color: var(--txt); font-weight: 590 }\n.clip .pb2 { display: inline-flex; align-items: center; gap: 4px; height: 21px; padding: 0 8px; border-radius: 6px;\n  background: var(--accent); color: #fff; font-size: 10.5px; font-weight: 590 }\n.clip .pb2 > svg { width: 11px; height: 11px }\n.dock { flex: none; display: flex; align-items: center; gap: 2px; height: 36px; padding: 0 8px;\n  border-top: .5px solid var(--sep); background: var(--bg2) }\n.dock .sp { flex: 1 }\n.dock .ib { width: 23px; height: 23px }\n.dock .ib > svg { width: 13px; height: 13px }\n.hintbar { font-size: 9.5px; color: var(--txt3); padding: 0 4px }\n.empty { padding: 26px 12px; text-align: center; color: var(--txt3); font-size: 11.5px }\n`,\n\n  render(d2) {\n    const model = d2.state.model || (d2.param('model') || DEFAULT_MODEL)\n    const tab = d2.state.tab || 'all'\n    const light = String(d2.param('appearance', 'dark')).toLowerCase() === 'light'\n    const accent = d2.param('accentHex', '')\n    const baseMap = {}\n    ;(d2.param('model') || DEFAULT_MODEL).forEach(t => (t.pieces || []).forEach(p => { baseMap[p.id] = p.v }))\n    const st = { sel: d2.state.sel, cut: d2.state.cut, drag: d2.state.drag, base: baseMap,\n      pOpen: d2.state.pOpen, menuFor: d2.state.menuFor }\n    const drop = d2.state.drop\n\n    const q = (d2.state.q || '').trim().toLowerCase()\n    const nodes = d2.state.nodes || {}\n    const trays = model.map(t => {\n      const nodeOpen = nodes[t.id] !== false\n      const pieces = (t.pieces || []).filter(p => q ? pieceMatches(p, q) : (tab === 'all' || (p.cat || 'style') === tab))\n      let inner = ''\n      pieces.forEach((p, i) => {\n        if (drop && drop.tray === t.id && drop.index === i) inner += '<div class=\"drop\"></div>'\n        inner += renderPiece(p, t, st)\n      })\n      if (drop && drop.tray === t.id && drop.index >= pieces.length) inner += '<div class=\"drop\"></div>'\n      return '<div class=\"tray' + (nodeOpen ? ' open' : '') + (drop && drop.tray === t.id ? ' over' : '') + '\" data-tray=\"' + esc(t.id) + '\">' +\n        '<div class=\"node\">' +\n        '<button class=\"node-h\" data-act=\"node\" data-tray=\"' + esc(t.id) + '\">' +\n        '<span class=\"chev\">' + I.chev + '</span><span class=\"nt\">' + esc(t.title) + '</span></button>' +\n        '<span class=\"ntools\">' +\n        '<button class=\"ib add\" data-act=\"add-open\" data-tray=\"' + esc(t.id) + '\" title=\"Add property\">' + I.plus + '</button>' +\n        '<button class=\"ib\" data-act=\"paste-tray\" data-tray=\"' + esc(t.id) + '\" title=\"Paste piece here\">' + I.paste + '</button>' +\n        '</span><span class=\"nn\">' + pieces.length + '</span>' +\n        (d2.state.addFor === t.id ? renderAddMenu(t) : '') +\n        '</div>' +\n        (nodeOpen ? '<div class=\"tray-b\">' + (inner || '<div class=\"empty\">' + (q ? 'No matches.' : 'No properties in this category.') + '</div>') + '</div>' : '') +\n        '</div>'\n    }).join('')\n\n    const clip = d2.state.clip\n    const clipBar = clip ? '<div class=\"clip\">' + I.paste +\n      '<span class=\"cl\">' + (clip.mode === 'piece'\n        ? '<span class=\"cn\">' + esc(clip.piece.title) + '</span> piece' + (d2.state.cut ? ' · cut' : ' · copied')\n        : '<span class=\"cn\">' + esc(clip.value) + '</span> ' + esc(clip.type) + ' value') + '</span>' +\n      '<button class=\"pb2\" data-act=\"paste\">' + I.paste + 'Paste</button>' +\n      '<button class=\"ib\" data-act=\"clip-clear\" title=\"Clear clipboard\">' + I.trash + '</button></div>' : ''\n\n    return '<div class=\"ins' + (light ? ' light' : '') + (d2.state.wide ? ' wide' : '') + '\"' + (accent ? ' style=\"--accent:' + esc(accent) + '\"' : '') + '>' +\n      '<div class=\"tbar\"><span class=\"t\">' + esc(d2.param('panelTitle', 'Inspector')) + '</span>' +\n      '<button class=\"ib\" data-act=\"noop\" title=\"Filter\">' + I.search + '</button>' +\n      '<button class=\"ib' + (d2.state.wide ? ' on' : '') + '\" data-act=\"expand\" title=\"' + (d2.state.wide ? 'Collapse inspector' : 'Expand inspector — second column') + '\">' + I.plus + '</button></div>' +\n\n      '<div class=\"subj\"><span class=\"tile\">' + I.cube + '</span>' +\n      '<span class=\"m\"><span class=\"n\">' + esc(d2.param('subjectTitle', 'Block')) + '</span>' +\n      '<span class=\"p\">' + esc(d2.param('subjectType', 'data2-core-primitives@Block')) + '</span></span>' +\n      '<button class=\"ib\" data-act=\"noop\" title=\"Copy all pieces\">' + I.copy + '</button></div>' +\n\n      '<div class=\"ident\">' +\n      '<div class=\"ir\"><span class=\"ik\">Component ID</span><span class=\"iv\">' + esc(d2.param('subjectId', '6a7a79932add8bd2c1a66f18')) + '</span></div>' +\n      '<div class=\"ir\"><span class=\"ik\">Reference</span><input spellcheck=\"false\" placeholder=\"auto\"></div>' +\n      '<div class=\"ir\"><span class=\"ik\">Display name</span><input spellcheck=\"false\" value=\"' + esc(d2.param('subjectName', '')) + '\" placeholder=\"Untitled\"></div>' +\n      '</div>' +\n\n      renderPill(model, tab, d2.state.catMode, d2.state.q || '') +\n\n      '<div class=\"body\">' + (trays.trim() ? trays : '<div class=\"empty\">' + (q ? 'No pieces match “' + esc(q) + '”.' : 'No pieces in this category.') + '</div>') + '</div>' +\n      clipBar +\n      '<div class=\"dock\">' +\n      '<button class=\"ib\" data-act=\"copy-sel\" title=\"Copy (⌘C)\">' + I.copy + '</button>' +\n      '<button class=\"ib\" data-act=\"cut-sel\" title=\"Cut (⌘X)\">' + I.cut + '</button>' +\n      '<button class=\"ib\" data-act=\"paste\" title=\"Paste (⌘V)\">' + I.paste + '</button>' +\n      '<span class=\"sp\"></span><span class=\"hintbar\">⌘C · ⌘X · ⌘V · ⌘D</span><span class=\"sp\"></span>' +\n      '<button class=\"ib\" data-act=\"reset-all\" title=\"Reset all\">' + I.undo + '</button>' +\n      '<button class=\"ib danger\" data-act=\"del-sel\" title=\"Remove selected\">' + I.trash + '</button>' +\n      '</div></div>'\n  },\n\n  mounted(d2) {\n    let clickTimer = null\n    d2.onUnmount(() => { if (clickTimer) clearTimeout(clickTimer) })\n    const base = () => d2.param('model') || DEFAULT_MODEL\n    const M = () => d2.state.model || base()\n    const commit = m => d2.setState({ model: m })\n\n    function copyFrom(sel, cut) {\n      if (!sel) return\n      const m = M()\n      if (sel.type === 'piece') {\n        const hit = findPiece(m, sel.piece)\n        if (!hit) return\n        d2.setState({ clip: { mode: 'piece', kind: hit.piece.kind, piece: clone(hit.piece) }, cut: cut ? sel.piece : null })\n      } else {\n        const hit = findPiece(m, sel.piece)\n        if (!hit) return\n        d2.setState({ clip: { mode: 'slot', type: sel.stype, value: getAt(hit.piece.v, sel.path) }, cut: null })\n      }\n    }\n\n    function paste(trayId) {\n      const clip = d2.state.clip\n      if (!clip) return\n      const m = clone(M())\n      const sel = d2.state.sel\n\n      if (clip.mode === 'slot') {\n        if (!sel || sel.type !== 'slot' || sel.stype !== clip.type) return\n        const hit = findPiece(m, sel.piece)\n        if (!hit) return\n        setAt(hit.piece.v, sel.path, clip.value)\n        commit(m)\n        return\n      }\n\n      // piece paste — onto a same-kind selection it overwrites values, otherwise it inserts a clone\n      if (!trayId && sel && sel.type === 'piece') {\n        const hit = findPiece(m, sel.piece)\n        if (hit && hit.piece.kind === clip.kind && hit.piece.id !== clip.piece.id) {\n          hit.piece.v = clone(clip.piece.v)\n          if (d2.state.cut) removeById(m, d2.state.cut)\n          commit(m)\n          d2.setState({ cut: null })\n          return\n        }\n      }\n      let target = trayId\n      if (!target && sel) { const h = findPiece(m, sel.piece); if (h) target = h.tray.id }\n      if (!target) target = m[0] && m[0].id\n      const tray = m.filter(t => t.id === target)[0]\n      if (!tray) return\n      const fresh = clone(clip.piece)\n      if (d2.state.cut) {\n        removeById(m, d2.state.cut)\n        fresh.id = clip.piece.id\n      } else {\n        fresh.id = nextId(m)\n        fresh.title = clip.piece.title\n      }\n      tray.pieces = tray.pieces || []\n      tray.pieces.push(fresh)\n      commit(m)\n      d2.setState({ cut: null, sel: { type: 'piece', piece: fresh.id } })\n    }\n\n    function removeById(m, id) {\n      m.forEach(t => { t.pieces = (t.pieces || []).filter(p => p.id !== id) })\n    }\n\n    function duplicate(id) {\n      const m = clone(M())\n      const hit = findPiece(m, id)\n      if (!hit) return\n      const fresh = clone(hit.piece)\n      fresh.id = nextId(m)\n      hit.tray.pieces.splice(hit.pi + 1, 0, fresh)\n      commit(m)\n      d2.setState({ sel: { type: 'piece', piece: fresh.id } })\n    }\n\n    d2.on('[data-slot]', 'pointerdown', e => {\n      const el = e.target.closest('[data-slot]')\n      if (!el) return\n      const parts = el.getAttribute('data-slot').split('|')\n      d2.setState({ sel: { type: 'slot', piece: parts[0], path: parts[1], stype: parts[2] } })\n    })\n\n    d2.on('[data-act]', 'click', e => {\n      const el = e.target.closest('[data-act]')\n      if (!el) return\n      const act = el.getAttribute('data-act')\n      const pid = el.getAttribute('data-piece')\n      if (act === 'node') {\n        const id = el.getAttribute('data-tray')\n        const nx = Object.assign({}, d2.state.nodes || {})\n        nx[id] = nx[id] === false\n        d2.setState({ nodes: nx })\n        return\n      }\n      if (act === 'add-open') {\n        const id = el.getAttribute('data-tray')\n        d2.setState({ addFor: d2.state.addFor === id ? null : id })\n        return\n      }\n      if (act === 'add-pick') {\n        const tpl = TEMPLATES[el.getAttribute('data-kind')]\n        if (!tpl) return\n        const m = clone(M())\n        const tray = m.filter(t => t.id === el.getAttribute('data-tray'))[0]\n        if (!tray) return\n        const fresh = clone(tpl)\n        fresh.id = nextId(m)\n        tray.pieces = tray.pieces || []\n        tray.pieces.push(fresh)\n        commit(m)\n        d2.setState({ addFor: null, sel: { type: 'piece', piece: fresh.id }, tab: 'all' })\n        return\n      }\n      if (act === 'cat-prev' || act === 'cat-next') {\n        const ids = TABS.map(t => t.id)\n        let i = ids.indexOf(d2.state.tab || 'all')\n        if (i < 0) i = 0\n        i = (i + (act === 'cat-next' ? 1 : -1) + ids.length) % ids.length\n        d2.setState({ tab: ids[i], catMode: null, q: '' })\n        return\n      }\n      if (act === 'cat-open') {\n        if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; return }\n        clickTimer = setTimeout(() => {\n          clickTimer = null\n          d2.setState({ catMode: d2.state.catMode === 'list' ? null : 'list' })\n        }, 210)\n        return\n      }\n      if (act === 'cat-pick') { d2.setState({ tab: el.getAttribute('data-val'), catMode: null, q: '' }); return }\n      if (act === 'cat-close') { d2.setState({ catMode: null, q: '' }); return }\n      if (act === 'expand') { d2.setState({ wide: !d2.state.wide }); return }\n      if (d2.state.menuFor && act !== 'menu') d2.setState({ menuFor: null })\n      if (act === 'select') {\n        const op = Object.assign({}, d2.state.pOpen || {})\n        op[pid] = true\n        d2.setState({ sel: { type: 'piece', piece: pid }, pOpen: op })\n        return\n      }\n      if (act === 'pexp') {\n        const hit0 = findPiece(M(), pid)\n        if (!hit0) return\n        const ref0 = hit0.piece.def !== undefined ? hit0.piece.def : (TEMPLATES[hit0.piece.kind] ? TEMPLATES[hit0.piece.kind].v : null)\n        const pop0 = !ref0 || JSON.stringify(ref0) !== JSON.stringify(hit0.piece.v)\n        const cur = d2.state.pOpen || {}\n        const isOpen = cur[pid] !== undefined ? cur[pid] : pop0\n        const nx = Object.assign({}, cur)\n        nx[pid] = !isOpen\n        d2.setState({ pOpen: nx })\n        return\n      }\n      if (act === 'menu') { d2.setState({ menuFor: d2.state.menuFor === pid ? null : pid }); return }\n      if (act === 'fx') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        if (hit.piece.v.__fx != null) delete hit.piece.v.__fx\n        else hit.piece.v.__fx = ''\n        commit(m)\n        return\n      }\n      if (act === 'paste-into') {\n        const cb = d2.state.clip\n        if (!cb || cb.mode !== 'piece') return\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit || hit.piece.kind !== cb.kind) return\n        hit.piece.v = clone(cb.piece.v)\n        commit(m)\n        return\n      }\n      if (act === 'reset-piece') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        if (hit.piece.def !== undefined) hit.piece.v = clone(hit.piece.def)\n        else {\n          let src = null\n          ;(d2.param('model') || DEFAULT_MODEL).forEach(t => (t.pieces || []).forEach(x => { if (x.id === pid) src = x }))\n          hit.piece.v = clone(src ? src.v : (TEMPLATES[hit.piece.kind] || { v: {} }).v)\n        }\n        commit(m)\n        return\n      }\n      if (act === 'copy') { d2.setState({ sel: { type: 'piece', piece: pid } }); copyFrom({ type: 'piece', piece: pid }, false); return }\n      if (act === 'cut') { d2.setState({ sel: { type: 'piece', piece: pid } }); copyFrom({ type: 'piece', piece: pid }, true); return }\n      if (act === 'dup') { duplicate(pid); return }\n      if (act === 'del') { const m = clone(M()); removeById(m, pid); commit(m); d2.setState({ sel: null }); return }\n      if (act === 'copy-sel') { copyFrom(d2.state.sel, false); return }\n      if (act === 'cut-sel') { copyFrom(d2.state.sel, true); return }\n      if (act === 'paste') { paste(null); return }\n      if (act === 'paste-tray') { paste(el.getAttribute('data-tray')); return }\n      if (act === 'clip-clear') { d2.setState({ clip: null, cut: null }); return }\n      if (act === 'del-sel') {\n        const sel = d2.state.sel\n        if (sel && sel.type === 'piece') { const m = clone(M()); removeById(m, sel.piece); commit(m); d2.setState({ sel: null }) }\n        return\n      }\n      if (act === 'reset-all') { d2.setState({ model: clone(base()), sel: null, clip: null, cut: null }); return }\n      if (act === 'flag') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        const path = el.getAttribute('data-path')\n        setAt(hit.piece.v, path, !getAt(hit.piece.v, path))\n        commit(m)\n        return\n      }\n      if (act === 'anchor') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        const side = el.getAttribute('data-side')\n        hit.piece.v.anchor[side] = !hit.piece.v.anchor[side]\n        commit(m)\n      }\n    })\n\n    d2.on('input', 'input', e => {\n      const el = e.target\n      const spec = el.getAttribute && el.getAttribute('data-in')\n      if (!spec) return\n      const parts = spec.split('|')\n      const m = clone(M())\n      const hit = findPiece(m, parts[0])\n      if (!hit) return\n      let val = el.value\n      if (parts[2] === 'num') { const n = parseFloat(val); val = isNaN(n) ? 0 : n }\n      setAt(hit.piece.v, parts[1], val)\n      commit(m)\n    })\n\n    d2.on('select', 'change', e => {\n      const el = e.target\n      const spec = el.getAttribute && el.getAttribute('data-in')\n      if (!spec) return\n      const parts = spec.split('|')\n      const m = clone(M())\n      const hit = findPiece(m, parts[0])\n      if (!hit) return\n      setAt(hit.piece.v, parts[1], el.value)\n      commit(m)\n    })\n\n    /* drag a piece by its grip — reorder within or across trays */\n    d2.on('[data-grip]', 'pointerdown', e => {\n      const g = e.target.closest('[data-grip]')\n      if (!g) return\n      if (e.target.closest('.ptools') || e.target.closest('.pdot') || e.target.closest('.menu')) return\n      const id = g.getAttribute('data-grip')\n      let started = false\n      const startY = e.clientY\n\n      const move = ev => {\n        if (!started && Math.abs(ev.clientY - startY) < 4) return\n        if (!started) { started = true; d2.setState({ drag: id, sel: { type: 'piece', piece: id } }) }\n        const rows = d2.$$('.piece').map(el => ({\n          tray: el.getAttribute('data-tray'),\n          id: el.getAttribute('data-piece'),\n          r: el.getBoundingClientRect()\n        }))\n        let best = null\n        const wide = !!d2.state.wide\n        rows.forEach(row => {\n          if (row.id === id) return\n          const mid = row.r.top + row.r.height / 2\n          const cx = row.r.left + row.r.width / 2\n          const dy = ev.clientY - mid\n          const d = wide ? Math.sqrt((ev.clientX - cx) * (ev.clientX - cx) + dy * dy) : Math.abs(dy)\n          if (!best || d < best.d) best = { d: d, row: row, after: dy > 0 }\n        })\n        if (!best) return\n        const m = M()\n        const tray = m.filter(t => t.id === best.row.tray)[0]\n        if (!tray) return\n        let idx = (tray.pieces || []).findIndex(p => p.id === best.row.id)\n        if (best.after) idx += 1\n        const cur = d2.state.drop\n        if (!cur || cur.tray !== tray.id || cur.index !== idx) d2.setState({ drop: { tray: tray.id, index: idx } })\n      }\n\n      const up = () => {\n        window.removeEventListener('pointermove', move)\n        window.removeEventListener('pointerup', up)\n        const drop = d2.state.drop\n        if (started && drop) {\n          const m = clone(M())\n          const hit = findPiece(m, id)\n          if (hit) {\n            const moved = clone(hit.piece)\n            hit.tray.pieces.splice(hit.pi, 1)\n            const tray = m.filter(t => t.id === drop.tray)[0]\n            if (tray) {\n              let idx = drop.index\n              if (hit.tray.id === tray.id && hit.pi < idx) idx -= 1\n              tray.pieces.splice(Math.max(0, Math.min(idx, tray.pieces.length)), 0, moved)\n              commit(m)\n            }\n          }\n        }\n        d2.setState({ drag: null, drop: null })\n      }\n      window.addEventListener('pointermove', move)\n      window.addEventListener('pointerup', up)\n    })\n\n    d2.on('[data-act=\"cat-open\"]', 'dblclick', () => {\n      if (clickTimer) { clearTimeout(clickTimer); clickTimer = null }\n      d2.setState({ catMode: 'search', q: '' })\n    })\n\n    d2.on('.pq', 'input', e => { d2.setState({ q: e.target.value }) })\n\n    d2.on('.pq', 'keydown', e => {\n      if (e.key === 'Escape') { e.stopPropagation(); d2.setState({ catMode: null, q: '' }) }\n    })\n\n    d2.on(window, 'pointerdown', e => {\n      if (!d2.state.catMode && !d2.state.addFor && !d2.state.menuFor) return\n      const path = e.composedPath ? e.composedPath() : []\n      let inPill = false, inAdd = false, inMenu = false\n      for (let i = 0; i < path.length; i++) {\n        const el = path[i]\n        if (!el || !el.classList) continue\n        if (el.classList.contains('p-m') || el.classList.contains('tb')) inMenu = true\n        else if (el.classList.contains('add') || el.classList.contains('add-m')) inAdd = true\n        else if (el.classList.contains('pill') || el.classList.contains('cats')) inPill = true\n      }\n      if (d2.state.menuFor && !inMenu) d2.setState({ menuFor: null })\n      if (d2.state.addFor && !inAdd) d2.setState({ addFor: null })\n      if (!d2.state.catMode || inPill) return\n      if (d2.state.catMode === 'search' && (d2.state.q || '').trim()) return\n      d2.setState({ catMode: null })\n    })\n\n    d2.on(window, 'keydown', e => {\n      const path = e.composedPath ? e.composedPath() : []\n      const t = path[0] || e.target\n      const tag = t && t.tagName\n      if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return\n      if (!(e.metaKey || e.ctrlKey)) {\n        if (e.key === 'Escape') d2.setState({ sel: null, cut: null, catMode: null, addFor: null })\n        return\n      }\n      const k = String(e.key).toLowerCase()\n      if (k === 'c') { e.preventDefault(); copyFrom(d2.state.sel, false) }\n      else if (k === 'x') { e.preventDefault(); copyFrom(d2.state.sel, true) }\n      else if (k === 'v') { e.preventDefault(); paste(null) }\n      else if (k === 'd') {\n        e.preventDefault()\n        const sel = d2.state.sel\n        if (sel && sel.type === 'piece') duplicate(sel.piece)\n      }\n    })\n  },\n\n  updated(d2) {\n    if (d2.state.catMode === 'search') {\n      if (FOCUSED.has(d2)) return\n      const el = d2.$('.pq')\n      if (el) { FOCUSED.add(d2); el.focus() }\n    } else FOCUSED.delete(d2)\n  }\n}\n"}},"panelTitle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"panelTitle"}}}},"subjectTitle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectTitle"}}}},"subjectType":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectType"}}}},"subjectId":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectId"}}}},"subjectName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectName"}}}},"model":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"model"}}}},"appearance":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"appearance"}}}},"accentHex":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accentHex"}}}}}}}}}},{"_id":"6a7a8e4e68641501c2dbde58","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"InspectorPiecesRams","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// Inspector Pieces · Rams — same inspector recipe, Braun/Vitsoe design language\nconst ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }\nconst esc = v => String(v == null ? '' : v).replace(/[&<>\"']/g, c => ESC[c])\nconst clone = o => JSON.parse(JSON.stringify(o))\nconst FOCUSED = new WeakSet()\n\nconst I = {\n  chev: '<svg viewBox=\"0 0 8 12\" class=\"ic\"><path d=\"M2.4 1.6 6.2 6l-3.8 4.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"square\"/></svg>',\n  search: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><circle cx=\"6\" cy=\"6\" r=\"4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/><path d=\"M9.2 9.2 12.6 12.6\" stroke=\"currentColor\" stroke-width=\"1.4\"/></svg>',\n  copy: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><rect x=\"2\" y=\"2\" width=\"7\" height=\"7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><path d=\"M5 12h7V5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  cut: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><circle cx=\"3.6\" cy=\"10.4\" r=\"1.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\"/><circle cx=\"10.4\" cy=\"10.4\" r=\"1.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\"/><path d=\"M4.6 8.9 10.2 1.8M9.4 8.9 3.8 1.8\" stroke=\"currentColor\" stroke-width=\"1.2\"/></svg>',\n  paste: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><rect x=\"2.4\" y=\"2.6\" width=\"9.2\" height=\"9.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><rect x=\"4.8\" y=\"1.2\" width=\"4.4\" height=\"2.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  dup: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><rect x=\"1.6\" y=\"1.6\" width=\"7\" height=\"7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><rect x=\"5.4\" y=\"5.4\" width=\"7\" height=\"7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  trash: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M3 4h8M5.6 4V2.2h2.8V4M4.2 4l.5 7.8h4.6L9.8 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  undo: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M3 6.2a4.4 4.4 0 1 1 .3 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><path d=\"M2.4 2.6v3.6H6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  link: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M5.6 8.4h2.8M4.6 4.4H2.8v5.2h1.8M9.4 4.4h1.8v5.2H9.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  plus: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M7 2.6v8.8M2.6 7h8.8\" stroke=\"currentColor\" stroke-width=\"1.4\"/></svg>',\n  x: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M3 3l6 6M9 3l-6 6\" stroke=\"currentColor\" stroke-width=\"1.4\"/></svg>',\n  dots: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><rect x=\"5.1\" y=\"1.6\" width=\"1.8\" height=\"1.8\" fill=\"currentColor\"/><rect x=\"5.1\" y=\"5.1\" width=\"1.8\" height=\"1.8\" fill=\"currentColor\"/><rect x=\"5.1\" y=\"8.6\" width=\"1.8\" height=\"1.8\" fill=\"currentColor\"/></svg>',\n  cube: '<svg viewBox=\"0 0 16 16\" class=\"ic\"><path d=\"M8 1.6 14.2 5v6L8 14.4 1.8 11V5z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\"/><path d=\"M1.8 5 8 8.4 14.2 5M8 8.4v6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1\"/></svg>',\n  arw: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M6 1.8v8.4M2.8 7 6 10.2 9.2 7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  arw2: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M6 1.4v9.2M3.4 4 6 1.4 8.6 4M3.4 8 6 10.6 8.6 8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\"/></svg>',\n  algn: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M1.4 1.4v9.2\" stroke=\"currentColor\" stroke-width=\"1.4\"/><rect x=\"3.8\" y=\"3.6\" width=\"6.8\" height=\"4.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\"/></svg>'\n}\n\nconst TABS = [\n  { id: 'all', label: 'All' },\n  { id: 'style', label: 'Style' },\n  { id: 'layout', label: 'Layout' },\n  { id: 'content', label: 'Content' },\n  { id: 'logic', label: 'Logic' }\n]\n\nconst DEFAULT_MODEL = [\n  {\n    id: 'root', title: 'Block', pieces: [\n      { id: 'p1', kind: 'border', title: 'Border', cat: 'style',\n        v: { r: [10, 10, 10, 10], w: [1, 1, 1, 1], color: '#F25C19', style: 'Solid' },\n        def: { r: [0, 0, 0, 0], w: [0, 0, 0, 0], color: '#000000', style: 'None' } },\n      { id: 'p2', kind: 'fill', title: 'Fill', cat: 'style',\n        v: { color: '#1C1C1A', opacity: 100, blend: 'Normal' },\n        def: { color: '#000000', opacity: 100, blend: 'Normal' } },\n      { id: 'p3', kind: 'fields', title: 'Opacity', cat: 'style',\n        fields: [{ path: 'value', label: 'percent', type: 'num' }],\n        v: { value: 100 }, def: { value: 100 } },\n      { id: 'p4', kind: 'shadow', title: 'Shadow', cat: 'style', sub: 'List of shadows',\n        v: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' },\n        def: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' } },\n      { id: 'p5', kind: 'fields', title: 'Backdrop filter', cat: 'style', sub: 'List of bitmap filters',\n        fields: [{ path: 'blur', label: 'blur', type: 'num' }, { path: 'saturate', label: 'sat', type: 'num' }],\n        v: { blur: 0, saturate: 100 }, def: { blur: 0, saturate: 100 } },\n      { id: 'p21', kind: 'fields', title: 'Filter', cat: 'style', sub: 'List of bitmap filters',\n        fields: [{ path: 'blur', label: 'blur', type: 'num' }, { path: 'grayscale', label: 'gray', type: 'num' }],\n        v: { blur: 0, grayscale: 0 }, def: { blur: 0, grayscale: 0 } },\n      { id: 'p6', kind: 'fields', title: 'Transition', cat: 'style',\n        fields: [{ path: 'ms', label: 'ms', type: 'num' }, { path: 'ease', label: 'easing', type: 'enum', options: ['Linear', 'Ease', 'Ease in', 'Ease out', 'Spring'] }],\n        v: { ms: 0, ease: 'Ease' }, def: { ms: 0, ease: 'Ease' } },\n      { id: 'p7', kind: 'geometry', title: 'Position', cat: 'layout',\n        v: { anchor: { top: true, left: true, right: false, bottom: false }, x: 1135, y: 51, growH: false, growV: false },\n        def: { anchor: { top: false, left: false, right: false, bottom: false }, x: 0, y: 0, growH: false, growV: false } },\n      { id: 'p8', kind: 'size', title: 'Dimensions', cat: 'layout',\n        v: { w: 160, h: 540, link: false, growW: false, growH: false },\n        def: { w: 0, h: 0, link: false, growW: false, growH: false } },\n      { id: 'p9', kind: 'spacing', title: 'Padding', cat: 'layout',\n        v: { t: 12, r: 14, b: 12, l: 14 }, def: { t: 0, r: 0, b: 0, l: 0 } },\n      { id: 'p10', kind: 'fields', title: 'Constraints', cat: 'layout',\n        fields: [{ path: 'minW', label: 'min w', type: 'num' }, { path: 'maxW', label: 'max w', type: 'num' }, { path: 'minH', label: 'min h', type: 'num' }, { path: 'maxH', label: 'max h', type: 'num' }],\n        v: { minW: 0, maxW: 0, minH: 0, maxH: 0 }, def: { minW: 0, maxW: 0, minH: 0, maxH: 0 } },\n      { id: 'p11', kind: 'fields', title: 'Children mode', cat: 'layout',\n        fields: [{ path: 'mode', label: 'flow', type: 'enum', options: ['Absolute', 'Rows', 'Columns', 'Reversed rows', 'Reversed columns'] }],\n        v: { mode: 'Absolute' }, def: { mode: 'Absolute' } },\n      { id: 'p12', kind: 'fields', title: 'Scroll', cat: 'layout',\n        fields: [{ path: 'on', label: 'Scrollable', type: 'bool' }], v: { on: false }, def: { on: false } },\n      { id: 'p13', kind: 'fields', title: 'Text', cat: 'content',\n        fields: [{ path: 'value', label: 'content', type: 'str', ph: 'Type text' }],\n        v: { value: '' }, def: { value: '' } },\n      { id: 'p14', kind: 'fields', title: 'Typography', cat: 'content',\n        fields: [{ path: 'font', label: 'font', type: 'enum', options: ['Helvetica Neue', 'Akzidenz', 'Inter', 'Menlo'] }, { path: 'weight', label: 'weight', type: 'enum', options: ['Regular', 'Medium', 'Bold'] }, { path: 'size', label: 'size', type: 'num' }, { path: 'lh', label: 'line', type: 'num' }],\n        v: { font: 'Helvetica Neue', weight: 'Regular', size: 13, lh: 18 },\n        def: { font: 'Helvetica Neue', weight: 'Regular', size: 13, lh: 18 } },\n      { id: 'p15', kind: 'fields', title: 'Text color', cat: 'content',\n        fields: [{ path: 'color', label: '', type: 'color' }], v: { color: '#1C1C1A' }, def: { color: '#1C1C1A' } },\n      { id: 'p16', kind: 'fields', title: 'Icon', cat: 'content',\n        fields: [{ path: 'name', label: 'name', type: 'str', ph: 'fa fa-star' }], v: { name: '' }, def: { name: '' } },\n      { id: 'p17', kind: 'fields', title: 'Hide', cat: 'logic',\n        fields: [{ path: 'on', label: 'Hidden', type: 'bool' }], v: { on: false }, def: { on: false } },\n      { id: 'p18', kind: 'fields', title: 'Disabled', cat: 'logic',\n        fields: [{ path: 'on', label: 'Disabled', type: 'bool' }], v: { on: false }, def: { on: false } },\n      { id: 'p19', kind: 'fields', title: 'Children overflow', cat: 'logic',\n        fields: [{ path: 'on', label: 'Visible outside bounds', type: 'bool' }], v: { on: false }, def: { on: false } },\n      { id: 'p20', kind: 'fields', title: 'On click', cat: 'logic',\n        fields: [{ path: 'action', label: 'action', type: 'enum', options: ['None', 'Set value', 'Go to route', 'Run service', 'Open link'] }],\n        v: { action: 'None' }, def: { action: 'None' } }\n    ]\n  }\n]\n\nconst TEMPLATES = {\n  border: { kind: 'border', title: 'Border', cat: 'style', v: { r: [0, 0, 0, 0], w: [0, 0, 0, 0], color: '#F25C19', style: 'None' }, def: { r: [0, 0, 0, 0], w: [0, 0, 0, 0], color: '#F25C19', style: 'None' } },\n  fill: { kind: 'fill', title: 'Fill', cat: 'style', v: { color: '#F25C19', opacity: 100, blend: 'Normal' }, def: { color: '#F25C19', opacity: 100, blend: 'Normal' } },\n  shadow: { kind: 'shadow', title: 'Shadow', cat: 'style', v: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' }, def: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' } },\n  padding: { kind: 'spacing', title: 'Padding', cat: 'layout', v: { t: 0, r: 0, b: 0, l: 0 }, def: { t: 0, r: 0, b: 0, l: 0 } },\n  position: { kind: 'geometry', title: 'Position', cat: 'layout', v: { anchor: { top: false, left: false, right: false, bottom: false }, x: 0, y: 0, growH: false, growV: false }, def: { anchor: { top: false, left: false, right: false, bottom: false }, x: 0, y: 0, growH: false, growV: false } },\n  dimensions: { kind: 'size', title: 'Dimensions', cat: 'layout', v: { w: 0, h: 0, link: false, growW: false, growH: false }, def: { w: 0, h: 0, link: false, growW: false, growH: false } },\n  transition: { kind: 'fields', title: 'Transition', cat: 'style', fields: [{ path: 'ms', label: 'ms', type: 'num' }, { path: 'ease', label: 'easing', type: 'enum', options: ['Linear', 'Ease', 'Ease in', 'Ease out', 'Spring'] }], v: { ms: 0, ease: 'Ease' }, def: { ms: 0, ease: 'Ease' } },\n  text: { kind: 'fields', title: 'Text', cat: 'content', fields: [{ path: 'value', label: 'content', type: 'str', ph: 'Type text' }], v: { value: '' }, def: { value: '' } }\n}\n\n/* ---------- value path helpers ---------- */\nfunction getAt(v, path) {\n  const parts = String(path).split('.')\n  let cur = v\n  for (let i = 0; i < parts.length; i++) {\n    if (cur == null) return null\n    const k = parts[i]\n    cur = Array.isArray(cur) ? cur[Number(k)] : cur[k]\n  }\n  return cur\n}\nfunction setAt(v, path, val) {\n  const parts = String(path).split('.')\n  let cur = v\n  for (let i = 0; i < parts.length - 1; i++) {\n    const k = parts[i]\n    cur = Array.isArray(cur) ? cur[Number(k)] : cur[k]\n    if (cur == null) return\n  }\n  const last = parts[parts.length - 1]\n  if (Array.isArray(cur)) cur[Number(last)] = val\n  else cur[last] = val\n}\nfunction findPiece(model, id) {\n  for (let i = 0; i < model.length; i++) {\n    const ps = model[i].pieces || []\n    for (let j = 0; j < ps.length; j++) if (ps[j].id === id) return { tray: model[i], piece: ps[j], ti: i, pi: j }\n  }\n  return null\n}\nfunction nextId(model) {\n  let max = 0\n  model.forEach(t => (t.pieces || []).forEach(p => {\n    const n = parseInt(String(p.id).replace(/[^0-9]/g, ''), 10)\n    if (n > max) max = n\n  }))\n  return 'p' + (max + 1)\n}\n\n/* ---------- controls ---------- */\nfunction slot(pid, path, type, value, opts) {\n  opts = opts || {}\n  const sel = opts.sel && opts.sel.type === 'slot' && opts.sel.piece === pid && opts.sel.path === path\n  const cls = 'slot' + (sel ? ' sel' : '') + (opts.cls ? ' ' + opts.cls : '')\n  let inner = ''\n  if (type === 'color') {\n    inner = '<span class=\"chip-c\" style=\"background:' + esc(value) + '\"></span>' +\n      '<input class=\"sin hex\" data-in=\"' + esc(pid) + '|' + esc(path) + '|text\" spellcheck=\"false\" value=\"' + esc(value) + '\">'\n  } else if (type === 'enum') {\n    inner = '<select class=\"sin\" data-in=\"' + esc(pid) + '|' + esc(path) + '|text\">' +\n      (opts.options || []).map(o => '<option' + (String(o) === String(value) ? ' selected' : '') + '>' + esc(o) + '</option>').join('') + '</select>'\n  } else if (type === 'text') {\n    inner = '<input class=\"sin\" data-in=\"' + esc(pid) + '|' + esc(path) + '|text\" spellcheck=\"false\" value=\"' + esc(value) + '\" placeholder=\"' + esc(opts.ph || '') + '\">'\n  } else {\n    inner = '<input class=\"sin num\" data-in=\"' + esc(pid) + '|' + esc(path) + '|num\" inputmode=\"decimal\" spellcheck=\"false\" value=\"' + esc(value) + '\">'\n  }\n  return '<span class=\"' + cls + '\" data-slot=\"' + esc(pid) + '|' + esc(path) + '|' + type + '\">' +\n    (opts.cap ? '<i class=\"cap\">' + esc(opts.cap) + '</i>' : '') + inner + '</span>'\n}\n\nfunction rocker(pid, path, on, label) {\n  return '<span class=\"rockrow\"><button class=\"rock' + (on ? ' on' : '') + '\" data-act=\"flag\" data-piece=\"' + esc(pid) + '\" data-path=\"' + esc(path) + '\"><i></i><i></i></button>' +\n    (label ? '<span class=\"rocklbl\">' + esc(label) + '</span>' : '') + '</span>'\n}\n\n/* ---------- piece bodies ---------- */\nfunction bodyBorder(p, s) {\n  const v = p.v\n  const c = i => slot(p.id, 'r.' + i, 'num', v.r[i], { sel: s, cls: 'corner', cap: 'radius' })\n  const e = i => slot(p.id, 'w.' + i, 'num', v.w[i], { sel: s, cap: 'width' })\n  const selC = s && s.type === 'slot' && s.piece === p.id && s.path === 'color'\n  return '<div class=\"mtx brd\">' +\n    c(0) + e(0) + c(1) +\n    e(3) + '<span class=\"cempty\"></span>' + e(1) +\n    c(3) + e(2) + c(2) +\n    '<span class=\"cbig slot' + (selC ? ' sel' : '') + '\" data-slot=\"' + esc(p.id) + '|color|color\" style=\"background:' + esc(v.color) + '\">' +\n    '<input class=\"sin hex big\" data-in=\"' + esc(p.id) + '|color|text\" spellcheck=\"false\" value=\"' + esc(v.color) + '\"></span>' +\n    '</div>' +\n    '<div class=\"prow\">' + slot(p.id, 'style', 'enum', v.style, { sel: s, cls: 'wide', options: ['None', 'Solid', 'Dashed', 'Dotted'], cap: 'type' }) + '</div>'\n}\n\nfunction bodySpacing(p, s) {\n  const v = p.v\n  return '<div class=\"mtx pad\">' +\n    '<i></i>' + slot(p.id, 't', 'num', v.t, { sel: s, cap: 'top' }) + '<i></i>' +\n    slot(p.id, 'l', 'num', v.l, { sel: s, cap: 'left' }) + '<span class=\"padc\">content</span>' + slot(p.id, 'r', 'num', v.r, { sel: s, cap: 'right' }) +\n    '<i></i>' + slot(p.id, 'b', 'num', v.b, { sel: s, cap: 'bottom' }) + '<i></i>' +\n    '</div>'\n}\n\nfunction bodyGeometry(p, s) {\n  const v = p.v, a = v.anchor || {}\n  const cell = side => '<button class=\"a-' + side + (a[side] ? ' on' : '') + '\" data-act=\"anchor\" data-piece=\"' + esc(p.id) + '\" data-side=\"' + side + '\"></button>'\n  const grp = ks => '<span class=\"grp\">' + ks.map(k => '<button class=\"ib al-' + k + '\" data-act=\"noop\" title=\"Align\">' + I.algn + '</button>').join('') + '</span>'\n  return '<div class=\"geo\">' +\n    '<span class=\"anc-g\">' + '<i></i>' + cell('top') + '<i></i>' + cell('left') + '<span class=\"a-c\"></span>' + cell('right') + '<i></i>' + cell('bottom') + '<i></i>' + '</span>' +\n    '<span class=\"geo-r\">' + rocker(p.id, 'growH', v.growH, 'Grow H') + rocker(p.id, 'growV', v.growV, 'Grow V') + '</span></div>' +\n    '<div class=\"clusrow\">' + grp(['l', 'c', 'r']) + grp(['t', 'm', 'b']) + '</div>' +\n    '<div class=\"prow2\">' + slot(p.id, 'x', 'num', v.x, { sel: s, cap: 'X' }) + slot(p.id, 'y', 'num', v.y, { sel: s, cap: 'Y' }) + '</div>'\n}\n\nfunction bodySize(p, s) {\n  const v = p.v\n  const grp = ks => '<span class=\"grp\">' + ks.map(k => '<button class=\"ib d-' + k + '\" data-act=\"noop\" title=\"Distribute\">' + (k === 'w1' || k === 'h1' ? I.arw2 : I.arw) + '</button>').join('') + '</span>'\n  return '<div class=\"clusrow\">' + grp(['w0', 'w1', 'w2']) + grp(['h0', 'h1', 'h2']) + '</div>' +\n    '<div class=\"prow2\">' + slot(p.id, 'w', 'num', v.w, { sel: s, cap: 'W' }) +\n    '<button class=\"ib lk' + (v.link ? ' on' : '') + '\" data-act=\"flag\" data-piece=\"' + esc(p.id) + '\" data-path=\"link\">' + I.link + '</button>' +\n    slot(p.id, 'h', 'num', v.h, { sel: s, cap: 'H' }) + '</div>' +\n    '<div class=\"chips\">' + rocker(p.id, 'growW', v.growW, 'Fit width') + rocker(p.id, 'growH', v.growH, 'Fit height') + '</div>'\n}\n\nfunction bodyFill(p, s) {\n  const v = p.v\n  return '<div class=\"prow2\">' + slot(p.id, 'color', 'color', v.color, { sel: s, cls: 'grow' }) +\n    slot(p.id, 'opacity', 'num', v.opacity, { sel: s, cap: 'percent' }) + '</div>' +\n    '<div class=\"prow\">' + slot(p.id, 'blend', 'enum', v.blend, { sel: s, cls: 'wide', options: ['Normal', 'Multiply', 'Screen', 'Overlay'], cap: 'blend' }) + '</div>'\n}\n\nfunction bodyShadow(p, s) {\n  const v = p.v\n  return '<div class=\"shw\"><span class=\"shw-p\" style=\"box-shadow:' + Number(v.x) + 'px ' + Number(v.y) + 'px ' + Number(v.blur) + 'px ' + Number(v.spread) + 'px ' + esc(v.color) + '\"></span></div>' +\n    '<div class=\"prow4\">' + slot(p.id, 'x', 'num', v.x, { sel: s, cap: 'X' }) + slot(p.id, 'y', 'num', v.y, { sel: s, cap: 'Y' }) +\n    slot(p.id, 'blur', 'num', v.blur, { sel: s, cap: 'blur' }) + slot(p.id, 'spread', 'num', v.spread, { sel: s, cap: 'sprd' }) + '</div>' +\n    '<div class=\"prow\">' + slot(p.id, 'color', 'color', v.color, { sel: s, cls: 'wide' }) + '</div>'\n}\n\nfunction bodyFields(p, s) {\n  const fs = p.fields || []\n  const bools = fs.filter(f => f.type === 'bool')\n  const rest = fs.filter(f => f.type !== 'bool')\n  let out = ''\n  if (rest.length) {\n    const one = rest.length === 1\n    out += '<div class=\"' + (one ? 'prow' : 'fgrid') + '\">' + rest.map(f => {\n      const type = f.type === 'enum' ? 'enum' : f.type === 'color' ? 'color' : f.type === 'str' ? 'text' : 'num'\n      return slot(p.id, f.path, type, getAt(p.v, f.path), { sel: s, cls: one ? 'wide' : '', options: f.options, cap: f.label, ph: f.ph })\n    }).join('') + '</div>'\n  }\n  if (bools.length) out += '<div class=\"chips\">' + bools.map(f => rocker(p.id, f.path, !!getAt(p.v, f.path), f.label)).join('') + '</div>'\n  return out || '<div class=\"empty\">No editable values.</div>'\n}\n\nconst BODIES = { fields: bodyFields, border: bodyBorder, spacing: bodySpacing, geometry: bodyGeometry, size: bodySize, fill: bodyFill, shadow: bodyShadow }\n\n/* ---------- category stepper ---------- */\nfunction catCount(model, id) {\n  let n = 0\n  model.forEach(t => (t.pieces || []).forEach(p => { if (id === 'all' || (p.cat || 'style') === id) n += 1 }))\n  return n\n}\n\nfunction pieceMatches(p, q) {\n  return (p.title + ' ' + p.kind + ' ' + JSON.stringify(p.v)).toLowerCase().indexOf(q) > -1\n}\n\nfunction renderAddMenu(t) {\n  return '<div class=\"menu add-m\">' + Object.keys(TEMPLATES).map(k =>\n    '<button class=\"cat\" data-act=\"add-pick\" data-tray=\"' + esc(t.id) + '\" data-kind=\"' + esc(k) + '\">' +\n    '<span class=\"cl2\">' + esc(TEMPLATES[k].title) + '</span><span class=\"cc\">' + esc(k) + '</span></button>').join('') + '</div>'\n}\n\nfunction renderPill(model, tab, mode, q) {\n  if (mode === 'search') {\n    return '<div class=\"tabs\"><div class=\"pwrap\"><div class=\"pill act\">' +\n      '<span class=\"pglyph\">' + I.search + '</span>' +\n      '<input class=\"pq\" spellcheck=\"false\" placeholder=\"Search\" value=\"' + esc(q) + '\">' +\n      '<button class=\"parr\" data-act=\"cat-close\" title=\"Close search\">' + I.x + '</button>' +\n      '</div></div></div>'\n  }\n  let idx = TABS.map(t => t.id).indexOf(tab)\n  if (idx < 0) idx = 0\n  const open = mode === 'list'\n  return '<div class=\"tabs\"><div class=\"pwrap\">' +\n    '<div class=\"pill' + (open ? ' act' : '') + '\">' +\n    '<button class=\"parr pv\" data-act=\"cat-prev\" title=\"Previous\">' + I.chev + '</button>' +\n    '<button class=\"plabel\" data-act=\"cat-open\" title=\"Click to pick, double-click to search\">' + esc(TABS[idx].label) + '</button>' +\n    '<button class=\"parr\" data-act=\"cat-next\" title=\"Next\">' + I.chev + '</button>' +\n    '</div>' +\n    (open ? '<div class=\"menu cats\">' + TABS.map(t => '<button class=\"cat' + (t.id === tab ? ' on' : '') + '\" data-act=\"cat-pick\" data-val=\"' + t.id + '\">' +\n      '<span class=\"cl2\">' + esc(t.label) + '</span><span class=\"cc\">' + catCount(model, t.id) + '</span></button>').join('') + '</div>' : '') +\n    '</div></div>'\n}\n\n/* ---------- piece ---------- */\nfunction renderPieceMenu(p) {\n  const items = [['copy', 'Copy'], ['cut', 'Cut'], ['dup', 'Duplicate'], ['paste-into', 'Paste into'], ['reset-piece', 'Reset'], ['del', 'Remove']]\n  return '<div class=\"menu p-m\">' + items.map(it =>\n    '<button class=\"cat' + (it[0] === 'del' ? ' dngr' : '') + '\" data-act=\"' + it[0] + '\" data-piece=\"' + esc(p.id) + '\">' +\n    '<span class=\"cl2\">' + it[1] + '</span></button>').join('') + '</div>'\n}\n\nfunction fxBody(p) {\n  return '<div class=\"fxrow\"><span class=\"fxk\">f()</span>' +\n    '<input class=\"sin fxin\" data-in=\"' + esc(p.id) + '|__fx|text\" spellcheck=\"false\" value=\"' + esc(p.v.__fx) + '\" placeholder=\"Reference(&#39;brandColor&#39;)\"></div>'\n}\n\nfunction renderPiece(p, tray, st) {\n  const ref = p.def !== undefined ? p.def : (TEMPLATES[p.kind] ? TEMPLATES[p.kind].v : null)\n  const pop = !ref || JSON.stringify(ref) !== JSON.stringify(p.v)\n  const exp = st.pOpen && st.pOpen[p.id] !== undefined ? st.pOpen[p.id] : pop\n  const sel = st.sel && st.sel.type === 'piece' && st.sel.piece === p.id\n  const cut = st.cut === p.id\n  const fx = p.v && p.v.__fx != null\n  const fn = BODIES[p.kind] || bodyFields\n  return '<div class=\"piece' + (pop ? ' pop' : '') + (exp ? ' exp' : '') + (p.sub ? ' has-sub' : '') + (sel ? ' sel' : '') + (cut ? ' cut' : '') + (st.drag === p.id ? ' drag' : '') + '\" data-piece=\"' + esc(p.id) + '\" data-tray=\"' + esc(tray.id) + '\">' +\n    '<div class=\"ph\" data-act=\"select\" data-piece=\"' + esc(p.id) + '\" data-grip=\"' + esc(p.id) + '\">' +\n    '<button class=\"pdot' + (exp ? ' on' : '') + '\" data-act=\"pexp\" data-piece=\"' + esc(p.id) + '\" title=\"Expand / collapse\"></button>' +\n    '<span class=\"ptxt\"><span class=\"pt\">' + esc(p.title) + '</span>' +\n    (p.sub ? '<span class=\"psub\">' + esc(p.sub) + '</span>' : '') + '</span>' +\n    (fx ? '<span class=\"fxbadge\">f()</span>' : '') +\n    '<span class=\"pspace\"></span>' +\n    '<span class=\"ptools\">' +\n    '<button class=\"ib tb\" data-act=\"noop\" title=\"About this property\">?</button>' +\n    '<button class=\"ib tb' + (fx ? ' on' : '') + '\" data-act=\"fx\" data-piece=\"' + esc(p.id) + '\" title=\"Bind to an expression\">f()</button>' +\n    '<button class=\"ib tb\" data-act=\"menu\" data-piece=\"' + esc(p.id) + '\" title=\"More\">' + I.dots + '</button>' +\n    '</span>' +\n    (st.menuFor === p.id ? renderPieceMenu(p) : '') +\n    '</div>' +\n    (exp ? '<div class=\"pb\">' + (fx ? fxBody(p) : fn(p, st.sel)) + '</div>' : '') +\n    '</div>'\n}\n\nexport default {\n  css: `\n:host { display: block; width: 100%; height: 100% }\n:host, .ins { box-sizing: border-box }\n.ins *, .ins *::before, .ins *::after { box-sizing: border-box }\n.ins {\n  --acc: #F25C19;\n  --paper: #E5E3DE; --surf: #F1F0EC; --ink: #1C1C1A; --ink2: #6B6A66; --ink3: #9C9A94;\n  --rule: #C6C3BC; --ctl: #FCFBF9; --track: #D6D3CC; --grid: rgba(0,0,0,.10);\n  width: 300px; max-width: 100%; height: 100%; margin-left: auto; display: flex; flex-direction: column;\n  overflow: hidden; background: var(--paper); color: var(--ink); border: 1px solid var(--rule);\n  font-family: \"Helvetica Neue\", Helvetica, Arial, Inter, system-ui, sans-serif;\n  font-size: 11px; line-height: 1.35; -webkit-font-smoothing: antialiased; user-select: none;\n  transition: width .22s cubic-bezier(.4,0,.2,1);\n}\n.ins.dark {\n  --paper: #262523; --surf: #2F2E2B; --ink: #EDEBE6; --ink2: #A29F98; --ink3: #75726C;\n  --rule: #403E3B; --ctl: #383734; --track: #1D1C1B; --grid: rgba(255,255,255,.11);\n}\n.ic { width: 100%; height: 100%; display: block }\nbutton { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; border-radius: 0 }\ninput, select { border-radius: 0 }\n\n/* title bar */\n.tbar { display: flex; align-items: center; gap: 10px; height: 34px; padding: 0 6px 0 12px; flex: none;\n  border-bottom: 1px solid var(--rule) }\n.tbar .t { font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .18em; flex: none }\n.grille { flex: 1; height: 14px; background-image: radial-gradient(circle, var(--grid) 1.1px, transparent 1.2px);\n  background-size: 5px 5px; background-position: 0 1px }\n.ib { width: 20px; height: 20px; display: grid; place-items: center; color: var(--ink2); flex: none;\n  transition: background .1s, color .1s }\n.ib > svg { width: 12px; height: 12px }\n.ib:hover { background: var(--ink); color: var(--paper) }\n.ib.on { background: var(--acc); color: #fff }\n\n/* subject */\n.subj { display: flex; align-items: center; gap: 10px; padding: 12px; border-bottom: 1px solid var(--rule) }\n.subj .tile { width: 26px; height: 26px; flex: none; display: grid; place-items: center; background: var(--ink); color: var(--paper) }\n.subj .tile > svg { width: 15px; height: 15px }\n.subj .m { min-width: 0; flex: 1; display: block }\n.subj .n { display: block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .1em;\n  white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.subj .p { display: block; font-size: 9px; color: var(--ink2); font-family: ui-monospace, Menlo, monospace;\n  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px }\n\n/* identity */\n.ident { padding: 10px 12px; border-bottom: 1px solid var(--rule); display: flex; flex-direction: column; gap: 6px }\n.ir { display: flex; align-items: center; gap: 10px; min-height: 22px }\n.ik { width: 92px; flex: none; font-size: 8px; text-transform: uppercase; letter-spacing: .12em; color: var(--ink2);\n  white-space: nowrap }\n.iv { flex: 1; min-width: 0; font-family: ui-monospace, Menlo, monospace; font-size: 10px; color: var(--ink2);\n  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; user-select: text }\n.ident input { flex: 1; min-width: 0; height: 22px; background: var(--ctl); border: 1px solid var(--rule);\n  color: var(--ink); font: inherit; font-size: 11px; padding: 0 6px; outline: none; user-select: text }\n.ident input:focus { border-color: var(--acc) }\n\n/* category stepper */\n.tabs { padding: 10px 12px; border-bottom: 1px solid var(--rule); position: relative; z-index: 20 }\n.pwrap { position: relative }\n.pill { display: flex; align-items: center; height: 26px; background: var(--ctl); border: 1px solid var(--rule) }\n.pill.act { border-color: var(--acc) }\n.parr { width: 26px; height: 24px; display: grid; place-items: center; color: var(--ink2); flex: none }\n.parr > svg { width: 7px; height: 10px }\n.parr:hover { background: var(--ink); color: var(--paper) }\n.parr.pv > svg { transform: rotate(180deg) }\n.plabel { flex: 1; min-width: 0; height: 24px; font-size: 9.5px; font-weight: 700; text-transform: uppercase;\n  letter-spacing: .18em; color: var(--ink) }\n.plabel:hover { background: var(--track) }\n.pglyph { width: 24px; display: grid; place-items: center; color: var(--ink2); flex: none }\n.pglyph > svg { width: 11px; height: 11px }\n.pq { flex: 1; min-width: 0; height: 24px; background: none; border: 0; outline: none; color: var(--ink);\n  font: inherit; font-size: 11px; user-select: text }\n.menu { position: absolute; left: 0; right: 0; top: 27px; z-index: 30; background: var(--surf); border: 1px solid var(--ink) }\n.cat { display: flex; align-items: center; gap: 8px; width: 100%; height: 26px; padding: 0 8px;\n  font-size: 9.5px; text-transform: uppercase; letter-spacing: .1em; color: var(--ink); border-bottom: 1px solid var(--rule) }\n.cat:last-child { border-bottom: 0 }\n.cat:hover { background: var(--acc); color: #fff }\n.cat.on { font-weight: 700 }\n.cat.on .cl2::before { content: \"\\\\2022  \" }\n.cat .cl2 { flex: 1; text-align: left }\n.cat .cc { font-size: 8.5px; color: var(--ink3); letter-spacing: .06em }\n.cat:hover .cc { color: rgba(255,255,255,.8) }\n.cat.dngr { color: #C0341A }\n.cat.dngr:hover { background: #C0341A; color: #fff }\n\n/* body */\n.body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; scrollbar-width: thin }\n.body::-webkit-scrollbar { width: 10px }\n.body::-webkit-scrollbar-thumb { background: var(--track); border: 4px solid var(--paper) }\n.node { position: relative; display: flex; align-items: center; gap: 4px; height: 38px; padding: 0 8px 0 12px;\n  border-bottom: 1px solid var(--rule) }\n.node-h { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; height: 100%; text-align: left }\n.node-h .chev { width: 8px; height: 11px; flex: none; color: var(--ink2); transition: transform .15s }\n.tray.open .node-h .chev { transform: rotate(90deg) }\n.nt { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .16em }\n.ntools { display: flex; gap: 2px; opacity: 0; transition: opacity .1s }\n.node:hover .ntools { opacity: 1 }\n.nn { font-size: 9px; color: var(--ink3); font-variant-numeric: tabular-nums; min-width: 14px; text-align: right }\n.add-m { top: 34px; left: auto; right: 8px; width: 168px }\n.tray-b { padding: 12px; display: flex; flex-direction: column; gap: 10px }\n.tray.over .tray-b { background: color-mix(in srgb, var(--acc) 8%, transparent) }\n.drop { height: 2px; background: var(--acc) }\n\n/* piece */\n.piece { position: relative; background: transparent; border: 1px solid transparent }\n.piece.pop { background: var(--surf); border-color: var(--rule) }\n.piece.sel { border-color: var(--acc); box-shadow: inset 0 0 0 1px var(--acc) }\n.piece.cut { opacity: .4; border-style: dashed; border-color: var(--acc) }\n.piece.drag { opacity: .5 }\n.ph { position: relative; display: flex; align-items: center; gap: 9px; height: 30px; padding: 0 6px 0 9px }\n.piece.pop.exp .ph { border-bottom: 1px solid var(--rule) }\n.piece.has-sub .ph { height: 42px }\n.pdot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: transparent; border: 1.5px solid var(--ink3) }\n.pdot:hover { border-color: var(--ink) }\n.pdot.on { background: var(--acc); border-color: var(--acc) }\n.ptxt { display: flex; flex-direction: column; gap: 2px; min-width: 0 }\n.pt { font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .12em; color: var(--ink) }\n.psub { font-size: 8px; text-transform: uppercase; letter-spacing: .1em; color: var(--ink3);\n  white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.pspace { flex: 1 }\n.fxbadge { font-size: 8px; font-weight: 700; font-family: ui-monospace, Menlo, monospace; color: #fff;\n  background: var(--acc); padding: 2px 3px }\n.ptools { display: flex; gap: 3px; opacity: 0; transition: opacity .1s }\n.piece:hover .ptools, .piece.sel .ptools { opacity: 1 }\n.tb { width: 20px; height: 17px; border: 1px solid var(--rule); font-size: 8.5px; font-weight: 700;\n  font-family: ui-monospace, Menlo, monospace; color: var(--ink2); background: var(--ctl) }\n.tb > svg { width: 9px; height: 9px }\n.tb:hover { background: var(--ink); color: var(--paper); border-color: var(--ink) }\n.tb.on { background: var(--acc); color: #fff; border-color: var(--acc) }\n.p-m { top: 28px; left: auto; right: 6px; width: 148px }\n.pb { padding: 15px 9px 11px; display: flex; flex-direction: column; gap: 16px }\n\n/* slots */\n.slot { position: relative; display: flex; align-items: center; min-width: 0; height: 24px; padding: 0 5px;\n  background: var(--ctl); border: 1px solid var(--rule); cursor: pointer }\n.slot:hover { border-color: var(--ink2) }\n.slot.sel { border-color: var(--acc); box-shadow: inset 0 0 0 1px var(--acc) }\n.slot .cap { position: absolute; top: -12px; left: 0; font-size: 7.5px; font-style: normal; text-transform: uppercase;\n  letter-spacing: .14em; color: var(--ink3); pointer-events: none; white-space: nowrap }\n.sin { flex: 1; min-width: 0; width: 100%; background: none; border: 0; outline: none; color: var(--ink);\n  font: inherit; font-size: 10.5px; user-select: text }\n.sin.num { text-align: right; font-variant-numeric: tabular-nums; letter-spacing: .02em }\n.sin.hex { font-family: ui-monospace, Menlo, monospace; font-size: 9.5px; text-transform: uppercase; letter-spacing: .04em }\nselect.sin { appearance: none; -webkit-appearance: none; cursor: pointer; text-transform: uppercase;\n  font-size: 9px; letter-spacing: .1em }\n/* the native popup inherits the select's colour — without this it is white-on-white */\nselect.sin option { background-color: #F2F1ED; color: #1C1C1A; text-transform: none; letter-spacing: 0 }\n.ins.dark select.sin option { background-color: #2F2E2B; color: #EDEBE6 }\n.chip-c { width: 14px; height: 14px; flex: none; margin-right: 6px; border: 1px solid var(--rule) }\n\n/* matrices */\n.mtx { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px 6px }\n.mtx > i { display: block }\n.mtx.brd { grid-template-columns: 1fr 1fr 1fr 1.25fr; grid-template-rows: repeat(3, 24px) }\n.cempty { border: 1px solid var(--rule); background: var(--track) }\n.cbig { grid-column: 4; grid-row: 1 / span 3; height: auto; padding: 0; align-items: flex-end }\n.hex.big { width: 100%; height: 18px; text-align: center; color: var(--paper); background: var(--ink); padding: 0 2px }\n.mtx.pad .padc { display: grid; place-items: center; font-size: 7.5px; text-transform: uppercase; letter-spacing: .14em;\n  color: var(--ink3); border: 1px solid var(--rule); background: var(--track) }\n.prow { display: flex }\n.prow2 { display: flex; align-items: center; gap: 6px }\n.prow4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px }\n.fgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 6px }\n.prow .wide, .slot.wide { flex: 1; width: 100% }\n.prow2 .slot { flex: 1 } .prow2 .grow { flex: 2 }\n.chips { display: flex; flex-direction: column; gap: 8px }\n\n/* rocker switch */\n.rockrow { display: flex; align-items: center; gap: 8px }\n.rock { display: inline-flex; width: 32px; height: 16px; flex: none; border: 1px solid var(--rule); background: var(--ctl) }\n.rock i { flex: 1; transition: background .12s }\n.rock i:first-child { background: var(--ink3) }\n.rock.on { border-color: var(--acc) }\n.rock.on i:first-child { background: transparent }\n.rock.on i:last-child { background: var(--acc) }\n.rocklbl { font-size: 8.5px; text-transform: uppercase; letter-spacing: .12em; color: var(--ink2) }\n.rock.on ~ .rocklbl { color: var(--ink) }\n\n/* clusters */\n.d-w0 svg, .d-w1 svg { transform: rotate(90deg) }\n.d-w2 svg { transform: rotate(-90deg) }\n.d-h0 svg { transform: rotate(180deg) }\n.al-c svg { transform: rotate(90deg) }\n.al-r svg { transform: scaleX(-1) }\n.al-t svg { transform: rotate(180deg) }\n.al-m svg { transform: rotate(90deg) scaleX(-1) }\n.al-b svg { transform: rotate(90deg) scaleY(-1) }\n.clusrow { display: flex; gap: 10px }\n.grp { flex: 1; display: flex; border: 1px solid var(--rule); background: var(--ctl) }\n.grp .ib { flex: 1; width: auto; height: 21px; border-right: 1px solid var(--rule) }\n.grp .ib:last-child { border-right: 0 }\n.lk { width: 22px; height: 22px; border: 1px solid var(--rule); background: var(--ctl) }\n\n/* anchor pad */\n.geo { display: flex; align-items: center; gap: 12px }\n.geo-r { display: flex; flex-direction: column; gap: 9px; flex: 1; min-width: 0 }\n.anc-g { display: grid; grid-template-columns: 18px 24px 18px; grid-template-rows: 14px 20px 14px; gap: 4px;\n  padding: 7px; border: 1px solid var(--rule); background: var(--ctl); flex: none }\n.anc-g > i { display: block }\n.anc-g button { background: var(--track) }\n.anc-g button:hover { background: var(--ink2) }\n.anc-g button.on { background: var(--acc) }\n.a-top, .a-bottom { justify-self: center; align-self: center; width: 18px; height: 2px }\n.a-left, .a-right { align-self: center; justify-self: center; width: 2px; height: 14px }\n.a-c { border: 1px solid var(--ink3); background: transparent }\n.shw { display: grid; place-items: center; height: 54px; border: 1px solid var(--rule); background: var(--track) }\n.shw-p { width: 44px; height: 24px; background: var(--ctl); border: 1px solid var(--rule) }\n.fxrow { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 7px; border: 1px solid var(--acc);\n  background: color-mix(in srgb, var(--acc) 10%, transparent) }\n.fxk { font-size: 9px; font-weight: 700; font-family: ui-monospace, Menlo, monospace; color: var(--acc); flex: none }\n.fxin { font-family: ui-monospace, Menlo, monospace; font-size: 10px }\n.empty { padding: 18px 10px; text-align: center; color: var(--ink3); font-size: 9px; text-transform: uppercase; letter-spacing: .14em }\n\n/* wide */\n.ins.wide { width: 100% }\n.ins.wide .tray-b { display: block; columns: 2; column-gap: 12px }\n.ins.wide .tray-b .piece { break-inside: avoid; margin-bottom: 12px }\n\n/* clipboard + dock */\n.clip { flex: none; display: flex; align-items: center; gap: 8px; padding: 6px 8px 6px 10px;\n  background: var(--acc); color: #fff; border-top: 1px solid var(--rule) }\n.clip > svg { width: 12px; height: 12px; flex: none }\n.clip .cl { flex: 1; min-width: 0; font-size: 8.5px; text-transform: uppercase; letter-spacing: .12em;\n  white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.clip .cn { font-weight: 700 }\n.clip .pb2 { height: 20px; padding: 0 9px; background: #fff; color: var(--acc); font-size: 8.5px; font-weight: 700;\n  text-transform: uppercase; letter-spacing: .12em }\n.clip .ib:hover { background: #fff; color: var(--acc) }\n.dock { flex: none; display: flex; align-items: center; height: 34px; padding: 0 6px;\n  border-top: 1px solid var(--rule); background: var(--paper) }\n.dock .sp { flex: 1 }\n.dock .ib { width: 24px; height: 22px }\n.hintbar { font-size: 8px; color: var(--ink3); text-transform: uppercase; letter-spacing: .14em; padding: 0 6px }\n`,\n\n  render(d2) {\n    const model = d2.state.model || (d2.param('model') || DEFAULT_MODEL)\n    const tab = d2.state.tab || 'all'\n    const dark = String(d2.param('appearance', 'light')).toLowerCase() === 'dark'\n    const accent = d2.param('accentHex', '')\n    const st = { sel: d2.state.sel, cut: d2.state.cut, drag: d2.state.drag, pOpen: d2.state.pOpen, menuFor: d2.state.menuFor }\n    const drop = d2.state.drop\n    const q = (d2.state.q || '').trim().toLowerCase()\n    const nodes = d2.state.nodes || {}\n\n    const trays = model.map(t => {\n      const nodeOpen = nodes[t.id] !== false\n      const pieces = (t.pieces || []).filter(p => q ? pieceMatches(p, q) : (tab === 'all' || (p.cat || 'style') === tab))\n      let inner = ''\n      pieces.forEach((p, i) => {\n        if (drop && drop.tray === t.id && drop.index === i) inner += '<div class=\"drop\"></div>'\n        inner += renderPiece(p, t, st)\n      })\n      if (drop && drop.tray === t.id && drop.index >= pieces.length) inner += '<div class=\"drop\"></div>'\n      return '<div class=\"tray' + (nodeOpen ? ' open' : '') + (drop && drop.tray === t.id ? ' over' : '') + '\" data-tray=\"' + esc(t.id) + '\">' +\n        '<div class=\"node\">' +\n        '<button class=\"node-h\" data-act=\"node\" data-tray=\"' + esc(t.id) + '\">' +\n        '<span class=\"chev\">' + I.chev + '</span><span class=\"nt\">' + esc(t.title) + '</span></button>' +\n        '<span class=\"ntools\">' +\n        '<button class=\"ib add\" data-act=\"add-open\" data-tray=\"' + esc(t.id) + '\" title=\"Add property\">' + I.plus + '</button>' +\n        '<button class=\"ib\" data-act=\"paste-tray\" data-tray=\"' + esc(t.id) + '\" title=\"Paste here\">' + I.paste + '</button>' +\n        '</span><span class=\"nn\">' + pieces.length + '</span>' +\n        (d2.state.addFor === t.id ? renderAddMenu(t) : '') +\n        '</div>' +\n        (nodeOpen ? '<div class=\"tray-b\">' + (inner || '<div class=\"empty\">' + (q ? 'No matches' : 'No properties') + '</div>') + '</div>' : '') +\n        '</div>'\n    }).join('')\n\n    const clip = d2.state.clip\n    const clipBar = clip ? '<div class=\"clip\">' + I.paste +\n      '<span class=\"cl\">' + (clip.mode === 'piece'\n        ? '<span class=\"cn\">' + esc(clip.piece.title) + '</span> ' + (d2.state.cut ? 'cut' : 'copied')\n        : '<span class=\"cn\">' + esc(clip.value) + '</span> ' + esc(clip.type)) + '</span>' +\n      '<button class=\"pb2\" data-act=\"paste\">Paste</button>' +\n      '<button class=\"ib\" data-act=\"clip-clear\" title=\"Clear\">' + I.trash + '</button></div>' : ''\n\n    return '<div class=\"ins' + (dark ? ' dark' : '') + (d2.state.wide ? ' wide' : '') + '\"' + (accent ? ' style=\"--acc:' + esc(accent) + '\"' : '') + '>' +\n      '<div class=\"tbar\"><span class=\"t\">' + esc(d2.param('panelTitle', 'Inspector')) + '</span><span class=\"grille\"></span>' +\n      '<button class=\"ib\" data-act=\"noop\" title=\"Filter\">' + I.search + '</button>' +\n      '<button class=\"ib' + (d2.state.wide ? ' on' : '') + '\" data-act=\"expand\" title=\"Expand\">' + I.plus + '</button></div>' +\n\n      '<div class=\"subj\"><span class=\"tile\">' + I.cube + '</span>' +\n      '<span class=\"m\"><span class=\"n\">' + esc(d2.param('subjectTitle', 'Block')) + '</span>' +\n      '<span class=\"p\">' + esc(d2.param('subjectType', 'data2-core-primitives@Block')) + '</span></span>' +\n      '<button class=\"ib\" data-act=\"noop\" title=\"Copy type\">' + I.copy + '</button></div>' +\n\n      '<div class=\"ident\">' +\n      '<div class=\"ir\"><span class=\"ik\">Component ID</span><span class=\"iv\">' + esc(d2.param('subjectId', '6a7a79932add8bd2c1a66f18')) + '</span></div>' +\n      '<div class=\"ir\"><span class=\"ik\">Reference</span><input spellcheck=\"false\" placeholder=\"auto\"></div>' +\n      '<div class=\"ir\"><span class=\"ik\">Display name</span><input spellcheck=\"false\" value=\"' + esc(d2.param('subjectName', '')) + '\" placeholder=\"Untitled\"></div>' +\n      '</div>' +\n\n      renderPill(model, tab, d2.state.catMode, d2.state.q || '') +\n      '<div class=\"body\">' + (trays.trim() ? trays : '<div class=\"empty\">No properties</div>') + '</div>' +\n      clipBar +\n      '<div class=\"dock\">' +\n      '<button class=\"ib\" data-act=\"copy-sel\" title=\"Copy\">' + I.copy + '</button>' +\n      '<button class=\"ib\" data-act=\"cut-sel\" title=\"Cut\">' + I.cut + '</button>' +\n      '<button class=\"ib\" data-act=\"paste\" title=\"Paste\">' + I.paste + '</button>' +\n      '<span class=\"sp\"></span><span class=\"hintbar\">C X V D</span><span class=\"sp\"></span>' +\n      '<button class=\"ib\" data-act=\"reset-all\" title=\"Reset all\">' + I.undo + '</button>' +\n      '<button class=\"ib\" data-act=\"del-sel\" title=\"Remove\">' + I.trash + '</button>' +\n      '</div></div>'\n  },\n\n  mounted(d2) {\n    let clickTimer = null\n    d2.onUnmount(() => { if (clickTimer) clearTimeout(clickTimer) })\n    const base = () => d2.param('model') || DEFAULT_MODEL\n    const M = () => d2.state.model || base()\n    const commit = m => d2.setState({ model: m })\n\n    function removeById(m, id) { m.forEach(t => { t.pieces = (t.pieces || []).filter(p => p.id !== id) }) }\n\n    function copyFrom(sel, cut) {\n      if (!sel) return\n      const m = M()\n      const hit = findPiece(m, sel.piece)\n      if (!hit) return\n      if (sel.type === 'piece') d2.setState({ clip: { mode: 'piece', kind: hit.piece.kind, piece: clone(hit.piece) }, cut: cut ? sel.piece : null })\n      else d2.setState({ clip: { mode: 'slot', type: sel.stype, value: getAt(hit.piece.v, sel.path) }, cut: null })\n    }\n\n    function paste(trayId) {\n      const clip = d2.state.clip\n      if (!clip) return\n      const m = clone(M())\n      const sel = d2.state.sel\n      if (clip.mode === 'slot') {\n        if (!sel || sel.type !== 'slot' || sel.stype !== clip.type) return\n        const hit = findPiece(m, sel.piece)\n        if (!hit) return\n        setAt(hit.piece.v, sel.path, clip.value)\n        commit(m)\n        return\n      }\n      if (!trayId && sel && sel.type === 'piece') {\n        const hit = findPiece(m, sel.piece)\n        if (hit && hit.piece.kind === clip.kind && hit.piece.id !== clip.piece.id) {\n          hit.piece.v = clone(clip.piece.v)\n          if (d2.state.cut) removeById(m, d2.state.cut)\n          commit(m)\n          d2.setState({ cut: null })\n          return\n        }\n      }\n      let target = trayId\n      if (!target && sel) { const h = findPiece(m, sel.piece); if (h) target = h.tray.id }\n      if (!target) target = m[0] && m[0].id\n      const tray = m.filter(t => t.id === target)[0]\n      if (!tray) return\n      const fresh = clone(clip.piece)\n      if (d2.state.cut) { removeById(m, d2.state.cut); fresh.id = clip.piece.id }\n      else fresh.id = nextId(m)\n      tray.pieces = tray.pieces || []\n      tray.pieces.push(fresh)\n      commit(m)\n      d2.setState({ cut: null, sel: { type: 'piece', piece: fresh.id } })\n    }\n\n    function duplicate(id) {\n      const m = clone(M())\n      const hit = findPiece(m, id)\n      if (!hit) return\n      const fresh = clone(hit.piece)\n      fresh.id = nextId(m)\n      hit.tray.pieces.splice(hit.pi + 1, 0, fresh)\n      commit(m)\n      d2.setState({ sel: { type: 'piece', piece: fresh.id } })\n    }\n\n    d2.on('[data-slot]', 'pointerdown', e => {\n      const el = e.target.closest('[data-slot]')\n      if (!el) return\n      const parts = el.getAttribute('data-slot').split('|')\n      d2.setState({ sel: { type: 'slot', piece: parts[0], path: parts[1], stype: parts[2] } })\n    })\n\n    d2.on('[data-act]', 'click', e => {\n      const el = e.target.closest('[data-act]')\n      if (!el) return\n      const act = el.getAttribute('data-act')\n      const pid = el.getAttribute('data-piece')\n      if (act === 'expand') { d2.setState({ wide: !d2.state.wide }); return }\n      if (d2.state.menuFor && act !== 'menu') d2.setState({ menuFor: null })\n      if (act === 'select') {\n        const op = Object.assign({}, d2.state.pOpen || {})\n        op[pid] = true\n        d2.setState({ sel: { type: 'piece', piece: pid }, pOpen: op })\n        return\n      }\n      if (act === 'pexp') {\n        const hit0 = findPiece(M(), pid)\n        if (!hit0) return\n        const ref0 = hit0.piece.def !== undefined ? hit0.piece.def : (TEMPLATES[hit0.piece.kind] ? TEMPLATES[hit0.piece.kind].v : null)\n        const pop0 = !ref0 || JSON.stringify(ref0) !== JSON.stringify(hit0.piece.v)\n        const cur = d2.state.pOpen || {}\n        const isOpen = cur[pid] !== undefined ? cur[pid] : pop0\n        const nx = Object.assign({}, cur)\n        nx[pid] = !isOpen\n        d2.setState({ pOpen: nx })\n        return\n      }\n      if (act === 'menu') { d2.setState({ menuFor: d2.state.menuFor === pid ? null : pid }); return }\n      if (act === 'fx') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        if (hit.piece.v.__fx != null) delete hit.piece.v.__fx\n        else hit.piece.v.__fx = ''\n        commit(m)\n        return\n      }\n      if (act === 'paste-into') {\n        const cb = d2.state.clip\n        if (!cb || cb.mode !== 'piece') return\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit || hit.piece.kind !== cb.kind) return\n        hit.piece.v = clone(cb.piece.v)\n        commit(m)\n        return\n      }\n      if (act === 'reset-piece') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        if (hit.piece.def !== undefined) hit.piece.v = clone(hit.piece.def)\n        commit(m)\n        return\n      }\n      if (act === 'node') {\n        const id = el.getAttribute('data-tray')\n        const nx = Object.assign({}, d2.state.nodes || {})\n        nx[id] = nx[id] === false\n        d2.setState({ nodes: nx })\n        return\n      }\n      if (act === 'add-open') {\n        const id = el.getAttribute('data-tray')\n        d2.setState({ addFor: d2.state.addFor === id ? null : id })\n        return\n      }\n      if (act === 'add-pick') {\n        const tpl = TEMPLATES[el.getAttribute('data-kind')]\n        if (!tpl) return\n        const m = clone(M())\n        const tray = m.filter(t => t.id === el.getAttribute('data-tray'))[0]\n        if (!tray) return\n        const fresh = clone(tpl)\n        fresh.id = nextId(m)\n        tray.pieces = tray.pieces || []\n        tray.pieces.push(fresh)\n        commit(m)\n        d2.setState({ addFor: null, sel: { type: 'piece', piece: fresh.id }, tab: 'all' })\n        return\n      }\n      if (act === 'cat-prev' || act === 'cat-next') {\n        const ids = TABS.map(t => t.id)\n        let i = ids.indexOf(d2.state.tab || 'all')\n        if (i < 0) i = 0\n        i = (i + (act === 'cat-next' ? 1 : -1) + ids.length) % ids.length\n        d2.setState({ tab: ids[i], catMode: null, q: '' })\n        return\n      }\n      if (act === 'cat-open') {\n        if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; return }\n        clickTimer = setTimeout(() => {\n          clickTimer = null\n          d2.setState({ catMode: d2.state.catMode === 'list' ? null : 'list' })\n        }, 210)\n        return\n      }\n      if (act === 'cat-pick') { d2.setState({ tab: el.getAttribute('data-val'), catMode: null, q: '' }); return }\n      if (act === 'cat-close') { d2.setState({ catMode: null, q: '' }); return }\n      if (act === 'copy') { d2.setState({ sel: { type: 'piece', piece: pid } }); copyFrom({ type: 'piece', piece: pid }, false); return }\n      if (act === 'cut') { d2.setState({ sel: { type: 'piece', piece: pid } }); copyFrom({ type: 'piece', piece: pid }, true); return }\n      if (act === 'dup') { duplicate(pid); return }\n      if (act === 'del') { const m = clone(M()); removeById(m, pid); commit(m); d2.setState({ sel: null }); return }\n      if (act === 'copy-sel') { copyFrom(d2.state.sel, false); return }\n      if (act === 'cut-sel') { copyFrom(d2.state.sel, true); return }\n      if (act === 'paste') { paste(null); return }\n      if (act === 'paste-tray') { paste(el.getAttribute('data-tray')); return }\n      if (act === 'clip-clear') { d2.setState({ clip: null, cut: null }); return }\n      if (act === 'del-sel') {\n        const sel = d2.state.sel\n        if (sel && sel.type === 'piece') { const m = clone(M()); removeById(m, sel.piece); commit(m); d2.setState({ sel: null }) }\n        return\n      }\n      if (act === 'reset-all') { d2.setState({ model: clone(base()), sel: null, clip: null, cut: null }); return }\n      if (act === 'flag') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        const path = el.getAttribute('data-path')\n        setAt(hit.piece.v, path, !getAt(hit.piece.v, path))\n        commit(m)\n        return\n      }\n      if (act === 'anchor') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        const side = el.getAttribute('data-side')\n        hit.piece.v.anchor[side] = !hit.piece.v.anchor[side]\n        commit(m)\n      }\n    })\n\n    d2.on('input', 'input', e => {\n      const el = e.target\n      const spec = el.getAttribute && el.getAttribute('data-in')\n      if (!spec) {\n        if (el.classList && el.classList.contains('pq')) d2.setState({ q: el.value })\n        return\n      }\n      const parts = spec.split('|')\n      const m = clone(M())\n      const hit = findPiece(m, parts[0])\n      if (!hit) return\n      let val = el.value\n      if (parts[2] === 'num') { const n = parseFloat(val); val = isNaN(n) ? 0 : n }\n      setAt(hit.piece.v, parts[1], val)\n      commit(m)\n    })\n\n    d2.on('select', 'change', e => {\n      const el = e.target\n      const spec = el.getAttribute && el.getAttribute('data-in')\n      if (!spec) return\n      const parts = spec.split('|')\n      const m = clone(M())\n      const hit = findPiece(m, parts[0])\n      if (!hit) return\n      setAt(hit.piece.v, parts[1], el.value)\n      commit(m)\n    })\n\n    d2.on('[data-act=\"cat-open\"]', 'dblclick', () => {\n      if (clickTimer) { clearTimeout(clickTimer); clickTimer = null }\n      d2.setState({ catMode: 'search', q: '' })\n    })\n\n    d2.on('.pq', 'keydown', e => {\n      if (e.key === 'Escape') { e.stopPropagation(); d2.setState({ catMode: null, q: '' }) }\n    })\n\n    d2.on('[data-grip]', 'pointerdown', e => {\n      const g = e.target.closest('[data-grip]')\n      if (!g) return\n      if (e.target.closest('.ptools') || e.target.closest('.pdot') || e.target.closest('.menu')) return\n      const id = g.getAttribute('data-grip')\n      let started = false\n      const startY = e.clientY\n\n      const move = ev => {\n        if (!started && Math.abs(ev.clientY - startY) < 4) return\n        if (!started) { started = true; d2.setState({ drag: id, sel: { type: 'piece', piece: id } }) }\n        const rows = d2.$$('.piece').map(el => ({\n          tray: el.getAttribute('data-tray'),\n          id: el.getAttribute('data-piece'),\n          r: el.getBoundingClientRect()\n        }))\n        let best = null\n        const wide = !!d2.state.wide\n        rows.forEach(row => {\n          if (row.id === id) return\n          const mid = row.r.top + row.r.height / 2\n          const cx = row.r.left + row.r.width / 2\n          const dy = ev.clientY - mid\n          const d = wide ? Math.sqrt((ev.clientX - cx) * (ev.clientX - cx) + dy * dy) : Math.abs(dy)\n          if (!best || d < best.d) best = { d: d, row: row, after: dy > 0 }\n        })\n        if (!best) return\n        const m = M()\n        const tray = m.filter(t => t.id === best.row.tray)[0]\n        if (!tray) return\n        let idx = (tray.pieces || []).findIndex(p => p.id === best.row.id)\n        if (best.after) idx += 1\n        const cur = d2.state.drop\n        if (!cur || cur.tray !== tray.id || cur.index !== idx) d2.setState({ drop: { tray: tray.id, index: idx } })\n      }\n\n      const up = () => {\n        window.removeEventListener('pointermove', move)\n        window.removeEventListener('pointerup', up)\n        const drop = d2.state.drop\n        if (started && drop) {\n          const m = clone(M())\n          const hit = findPiece(m, id)\n          if (hit) {\n            const moved = clone(hit.piece)\n            hit.tray.pieces.splice(hit.pi, 1)\n            const tray = m.filter(t => t.id === drop.tray)[0]\n            if (tray) {\n              let idx = drop.index\n              if (hit.tray.id === tray.id && hit.pi < idx) idx -= 1\n              tray.pieces.splice(Math.max(0, Math.min(idx, tray.pieces.length)), 0, moved)\n              commit(m)\n            }\n          }\n        }\n        d2.setState({ drag: null, drop: null })\n      }\n      window.addEventListener('pointermove', move)\n      window.addEventListener('pointerup', up)\n    })\n\n    d2.on(window, 'pointerdown', e => {\n      if (!d2.state.catMode && !d2.state.addFor && !d2.state.menuFor) return\n      const path = e.composedPath ? e.composedPath() : []\n      let inPill = false, inAdd = false, inMenu = false\n      for (let i = 0; i < path.length; i++) {\n        const el = path[i]\n        if (!el || !el.classList) continue\n        if (el.classList.contains('p-m') || el.classList.contains('tb')) inMenu = true\n        else if (el.classList.contains('add') || el.classList.contains('add-m')) inAdd = true\n        else if (el.classList.contains('pill') || el.classList.contains('cats')) inPill = true\n      }\n      if (d2.state.menuFor && !inMenu) d2.setState({ menuFor: null })\n      if (d2.state.addFor && !inAdd) d2.setState({ addFor: null })\n      if (!d2.state.catMode || inPill) return\n      if (d2.state.catMode === 'search' && (d2.state.q || '').trim()) return\n      d2.setState({ catMode: null })\n    })\n\n    d2.on(window, 'keydown', e => {\n      const path = e.composedPath ? e.composedPath() : []\n      const t = path[0] || e.target\n      const tag = t && t.tagName\n      if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return\n      if (!(e.metaKey || e.ctrlKey)) {\n        if (e.key === 'Escape') d2.setState({ sel: null, cut: null, catMode: null, addFor: null, menuFor: null })\n        return\n      }\n      const k = String(e.key).toLowerCase()\n      if (k === 'c') { e.preventDefault(); copyFrom(d2.state.sel, false) }\n      else if (k === 'x') { e.preventDefault(); copyFrom(d2.state.sel, true) }\n      else if (k === 'v') { e.preventDefault(); paste(null) }\n      else if (k === 'd') {\n        e.preventDefault()\n        const sel = d2.state.sel\n        if (sel && sel.type === 'piece') duplicate(sel.piece)\n      }\n    })\n  },\n\n  updated(d2) {\n    if (d2.state.catMode === 'search') {\n      if (FOCUSED.has(d2)) return\n      const el = d2.$('.pq')\n      if (el) { FOCUSED.add(d2); el.focus() }\n    } else FOCUSED.delete(d2)\n  }\n}\n"}},"panelTitle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"panelTitle"}}}},"subjectTitle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectTitle"}}}},"subjectType":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectType"}}}},"subjectId":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectId"}}}},"subjectName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectName"}}}},"model":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"model"}}}},"appearance":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"appearance"}}}},"accentHex":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accentHex"}}}}}}}}}},{"_id":"6a7a9bb2a0a162269e7fe809","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"InspectorPiecesGT7","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// Inspector Pieces · GT7 — same inspector recipe, Gran Turismo 7 UI scheme\nconst ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }\nconst esc = v => String(v == null ? '' : v).replace(/[&<>\"']/g, c => ESC[c])\nconst clone = o => JSON.parse(JSON.stringify(o))\nconst FOCUSED = new WeakSet()\n\nconst I = {\n  chev: '<svg viewBox=\"0 0 8 12\" class=\"ic\"><path d=\"M2.4 1.6 6.2 6l-3.8 4.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  chev2: '<svg viewBox=\"0 0 14 12\" class=\"ic\"><path d=\"M2 1.8 6 6l-4 4.2M7.4 1.8 11.4 6l-4 4.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  caret: '<svg viewBox=\"0 0 12 8\" class=\"ic\"><path d=\"M1.6 2.2 6 6.4l4.4-4.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  tri: '<svg viewBox=\"0 0 10 7\" class=\"ic\"><path d=\"M0.6 1.2h8.8L5 6.2z\" fill=\"currentColor\"/></svg>',\n  shapes: '<svg viewBox=\"0 0 20 20\" class=\"ic\"><path d=\"M10 1.6 14.6 8.6H5.4z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linejoin=\"round\"/><rect x=\"1.4\" y=\"10.8\" width=\"7.6\" height=\"7.6\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/><circle cx=\"14.8\" cy=\"14.6\" r=\"3.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/></svg>',\n  search: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><circle cx=\"6.2\" cy=\"6.2\" r=\"4.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/><path d=\"M9.4 9.4 12.4 12.4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/></svg>',\n  copy: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><rect x=\"2.2\" y=\"2.2\" width=\"7\" height=\"7\" rx=\"1.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><path d=\"M4.8 11.8h5a2 2 0 0 0 2-2v-5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  cut: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><circle cx=\"3.6\" cy=\"10.4\" r=\"1.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\"/><circle cx=\"10.4\" cy=\"10.4\" r=\"1.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\"/><path d=\"M4.6 8.9 10.2 1.8M9.4 8.9 3.8 1.8\" stroke=\"currentColor\" stroke-width=\"1.2\" stroke-linecap=\"round\"/></svg>',\n  paste: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M4.6 2.6H3.4a1.2 1.2 0 0 0-1.2 1.2v7.4a1.2 1.2 0 0 0 1.2 1.2h7.2a1.2 1.2 0 0 0 1.2-1.2V3.8a1.2 1.2 0 0 0-1.2-1.2H9.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><rect x=\"4.6\" y=\"1.4\" width=\"4.8\" height=\"2.6\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  dup: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><rect x=\"1.8\" y=\"1.8\" width=\"7\" height=\"7\" rx=\"1.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><rect x=\"5.2\" y=\"5.2\" width=\"7\" height=\"7\" rx=\"1.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  trash: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M3.2 4.2h7.6M5.6 4.2V3a.8.8 0 0 1 .8-.8h1.2a.8.8 0 0 1 .8.8v1.2M4.3 4.2l.5 6.6a1 1 0 0 0 1 .9h2.4a1 1 0 0 0 1-.9l.5-6.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  undo: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M3 6.2a4.4 4.4 0 1 1 .3 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/><path d=\"M2.4 2.6v3.6H6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  link: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M5.8 8.2a2.6 2.6 0 0 0 3.7 0l1.7-1.7a2.6 2.6 0 0 0-3.7-3.7l-.9.9M8.2 5.8a2.6 2.6 0 0 0-3.7 0L2.8 7.5a2.6 2.6 0 0 0 3.7 3.7l.9-.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  plus: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M7 2.8v8.4M2.8 7h8.4\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>',\n  x: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M3.2 3.2 8.8 8.8M8.8 3.2 3.2 8.8\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>',\n  dots: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><circle cx=\"6\" cy=\"2.5\" r=\"1.05\" fill=\"currentColor\"/><circle cx=\"6\" cy=\"6\" r=\"1.05\" fill=\"currentColor\"/><circle cx=\"6\" cy=\"9.5\" r=\"1.05\" fill=\"currentColor\"/></svg>',\n  cube: '<svg viewBox=\"0 0 16 16\" class=\"ic\"><path d=\"M8 1.8 14 5v6l-6 3.2L2 11V5z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/><path d=\"M2 5l6 3.2L14 5M8 8.2v6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.1\" opacity=\".5\"/></svg>',\n  arw: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M6 1.8v8.4M2.8 7 6 10.2 9.2 7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  arw2: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M6 1.4v9.2M3.4 4 6 1.4 8.6 4M3.4 8 6 10.6 8.6 8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  algn: '<svg viewBox=\"0 0 12 12\" class=\"ic\"><path d=\"M1.4 1.4v9.2\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/><rect x=\"3.8\" y=\"3.6\" width=\"6.8\" height=\"4.8\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\"/></svg>'\n}\n\n/* row icons — one per property, GT7 puts a glyph on every settings row */\nconst PI = {\n  border: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><rect x=\"2.6\" y=\"2.6\" width=\"12.8\" height=\"12.8\" rx=\"2.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/></svg>',\n  fill: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><rect x=\"2.6\" y=\"2.6\" width=\"12.8\" height=\"12.8\" rx=\"2.4\" fill=\"currentColor\" opacity=\".85\"/></svg>',\n  shadow: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><rect x=\"2.2\" y=\"2.2\" width=\"10\" height=\"10\" rx=\"2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/><path d=\"M6.4 15.6h7.2a2 2 0 0 0 2-2V6.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" opacity=\".55\"/></svg>',\n  opacity: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><circle cx=\"9\" cy=\"9\" r=\"6.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/><path d=\"M9 2.6a6.4 6.4 0 0 1 0 12.8z\" fill=\"currentColor\" opacity=\".8\"/></svg>',\n  filter: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M2.4 5h13.2M2.4 9h13.2M2.4 13h13.2\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/><circle cx=\"6\" cy=\"5\" r=\"1.7\" fill=\"currentColor\"/><circle cx=\"12\" cy=\"9\" r=\"1.7\" fill=\"currentColor\"/><circle cx=\"7.6\" cy=\"13\" r=\"1.7\" fill=\"currentColor\"/></svg>',\n  transition: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M2 13.4C5.4 13.4 6.6 4.6 10 4.6c2.4 0 3.4 4.4 6 4.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/></svg>',\n  position: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M9 1.6v14.8M1.6 9h14.8\" stroke=\"currentColor\" stroke-width=\"1.2\" opacity=\".55\"/><rect x=\"6\" y=\"6\" width=\"6\" height=\"6\" rx=\"1.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/></svg>',\n  size: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M2.4 2.4h5M2.4 2.4v5M15.6 15.6h-5M15.6 15.6v-5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/><path d=\"M2.4 2.4 15.6 15.6\" stroke=\"currentColor\" stroke-width=\"1.2\" opacity=\".5\"/></svg>',\n  spacing: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><rect x=\"1.8\" y=\"1.8\" width=\"14.4\" height=\"14.4\" rx=\"2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" opacity=\".55\"/><rect x=\"5.4\" y=\"5.4\" width=\"7.2\" height=\"7.2\" rx=\"1.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/></svg>',\n  constraints: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M5.4 2.6H2.6v12.8h2.8M12.6 2.6h2.8v12.8h-2.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/></svg>',\n  flow: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><rect x=\"2.4\" y=\"2.6\" width=\"13.2\" height=\"3.4\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><rect x=\"2.4\" y=\"7.4\" width=\"13.2\" height=\"3.4\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><rect x=\"2.4\" y=\"12.2\" width=\"13.2\" height=\"3.2\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/></svg>',\n  scroll: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><rect x=\"4.4\" y=\"1.8\" width=\"9.2\" height=\"14.4\" rx=\"2.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><path d=\"M9 5.4v7.2M6.8 7.6 9 5.4l2.2 2.2M6.8 10.4 9 12.6l2.2-2.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" stroke-linecap=\"round\"/></svg>',\n  text: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M3.4 4.2h11.2M9 4.2v10.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>',\n  typo: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M2.2 14 6.4 3.6 10.6 14M3.6 10.8h5.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M13 14V8.6a2 2 0 1 1 3 1.7V14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  tcolor: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M4 11.4 8 3.4l4 8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M5.4 8.8h5.2\" stroke=\"currentColor\" stroke-width=\"1.3\"/><rect x=\"3\" y=\"13.6\" width=\"12\" height=\"2.4\" rx=\"1.2\" fill=\"currentColor\"/></svg>',\n  icon: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M9 2.2l2 4.4 4.8.5-3.6 3.2 1 4.7L9 12.6l-4.2 2.4 1-4.7L2.2 7.1 7 6.6z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/></svg>',\n  hide: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M1.8 9S4.6 4 9 4s7.2 5 7.2 5-2.8 5-7.2 5S1.8 9 1.8 9z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><circle cx=\"9\" cy=\"9\" r=\"2.1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><path d=\"M3 15 15 3\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  disabled: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><circle cx=\"9\" cy=\"9\" r=\"6.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/><path d=\"M4.4 13.6 13.6 4.4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/></svg>',\n  overflow: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><rect x=\"2.4\" y=\"6\" width=\"9.6\" height=\"9.6\" rx=\"1.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/><path d=\"M8.4 2.6h7v7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-dasharray=\"2 2\"/></svg>',\n  click: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M4.6 2.4 7 14.2l2.4-3.6 4.4-.6z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linejoin=\"round\"/></svg>',\n  rotate: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M14.8 7A6.2 6.2 0 1 0 15.2 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/><path d=\"M11.2 2.8h4.2V7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  transform: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M6.4 3h8.4l-3.2 12H3.2z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/></svg>',\n  light: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><circle cx=\"9\" cy=\"9\" r=\"3.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/><path d=\"M9 1.6v2M9 14.4v2M1.6 9h2M14.4 9h2M3.9 3.9l1.4 1.4M12.7 12.7l1.4 1.4M14.1 3.9l-1.4 1.4M5.3 12.7l-1.4 1.4\" stroke=\"currentColor\" stroke-width=\"1.2\" stroke-linecap=\"round\"/></svg>',\n  textsize: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M1.8 13.4 5.4 4.6 9 13.4M3 10.8h4.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M14 4.6v9M11.9 6.7 14 4.6l2.1 2.1M11.9 11.5 14 13.6l2.1-2.1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" stroke-linecap=\"round\"/></svg>',\n  format: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M2.6 4.2h12.8M2.6 7.8h8.8M2.6 11.4h12.8M2.6 15h6.6\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  decoration: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M4.6 2.8v6.2a4.4 4.4 0 0 0 8.8 0V2.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/><path d=\"M3.6 15.2h10.8\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\"/></svg>',\n  tooltip: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M2.6 4.4a1.8 1.8 0 0 1 1.8-1.8h9.2a1.8 1.8 0 0 1 1.8 1.8v5.8a1.8 1.8 0 0 1-1.8 1.8H9.6l-3.2 3.2v-3.2H4.4a1.8 1.8 0 0 1-1.8-1.8z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/></svg>',\n  states: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M9 1.8 16 5.4 9 9 2 5.4z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/><path d=\"M2 9.4 9 13l7-3.6M2 12.8 9 16.4l7-3.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" opacity=\".55\"/></svg>',\n  event: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M10.4 1.8 4.2 10.4h4l-.6 5.8 6.2-8.6h-4z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/></svg>',\n  index: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M6.4 2.6 4.8 15.4M11.6 2.6 10 15.4M2.6 6.6h12.8M2 11.4h12.8\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  a11y: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><circle cx=\"9\" cy=\"9\" r=\"7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><circle cx=\"9\" cy=\"5.4\" r=\"1.1\" fill=\"currentColor\"/><path d=\"M5.6 7.8h6.8M9 7.8v3.2M9 11l-1.7 3.2M9 11l1.7 3.2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" stroke-linecap=\"round\"/></svg>',\n  mouse: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><path d=\"M5 2.6 7.4 13.6l2.2-3.4 4-.6z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/><path d=\"M2.8 15.2 15.2 2.8\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\"/></svg>',\n  modal: '<svg viewBox=\"0 0 18 18\" class=\"ic\"><rect x=\"1.8\" y=\"3.6\" width=\"14.4\" height=\"10.8\" rx=\"1.8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" opacity=\".5\"/><rect x=\"5\" y=\"6.4\" width=\"8\" height=\"6.4\" rx=\"1.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"/></svg>'\n}\n\n/* category glyphs — grid for All, then one per facet */\nconst CI = {\n  all: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><g fill=\"currentColor\"><rect x=\"1.5\" y=\"1.5\" width=\"3\" height=\"3\" rx=\".6\"/><rect x=\"5.5\" y=\"1.5\" width=\"3\" height=\"3\" rx=\".6\"/><rect x=\"9.5\" y=\"1.5\" width=\"3\" height=\"3\" rx=\".6\"/><rect x=\"1.5\" y=\"5.5\" width=\"3\" height=\"3\" rx=\".6\"/><rect x=\"5.5\" y=\"5.5\" width=\"3\" height=\"3\" rx=\".6\"/><rect x=\"9.5\" y=\"5.5\" width=\"3\" height=\"3\" rx=\".6\"/><rect x=\"1.5\" y=\"9.5\" width=\"3\" height=\"3\" rx=\".6\"/><rect x=\"5.5\" y=\"9.5\" width=\"3\" height=\"3\" rx=\".6\"/><rect x=\"9.5\" y=\"9.5\" width=\"3\" height=\"3\" rx=\".6\"/></g></svg>',\n  style: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M7 1.5s4.3 4.5 4.3 7.1A4.3 4.3 0 0 1 2.7 8.6C2.7 6 7 1.5 7 1.5z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/></svg>',\n  layout: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><rect x=\"1.6\" y=\"2.2\" width=\"10.8\" height=\"9.6\" rx=\"1.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/><path d=\"M5.5 2.2v9.6\" stroke=\"currentColor\" stroke-width=\"1.2\"/></svg>',\n  content: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M2.4 11.8 7 2.5l4.6 9.3M4.2 8.9h5.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>',\n  logic: '<svg viewBox=\"0 0 14 14\" class=\"ic\"><path d=\"M8.3 1.4 3.3 8.1h3.3l-.5 4.5 5-6.7H7.9z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linejoin=\"round\"/></svg>'\n}\n\nconst TABS = [\n  { id: 'all', label: 'All' },\n  { id: 'style', label: 'Style' },\n  { id: 'layout', label: 'Layout' },\n  { id: 'content', label: 'Content' },\n  { id: 'logic', label: 'Logic' }\n]\n\nconst ACTIONS = ['None', 'Set value', 'Go to route', 'Run service', 'Open link', 'Emit event']\nconst CURSORS = ['[none]', 'Default', 'Pointer', 'Text', 'Move', 'Grab', 'Not allowed', 'Crosshair']\nconst ROLES = ['[none]', 'Button', 'Link', 'Heading', 'Image', 'List', 'Navigation', 'Dialog']\nconst FLOWS = ['[none]', 'Rows', 'Columns', 'Reversed rows', 'Reversed columns']\n\nfunction ev(id, title) {\n  return { id: id, kind: 'fields', title: title, cat: 'logic', ic: 'event',\n    fields: [{ path: 'action', label: 'Action', type: 'enum', options: ACTIONS }],\n    v: { action: 'None' }, def: { action: 'None' } }\n}\nfunction lst(id, title, sub, ic, cat) {\n  return { id: id, kind: 'list', title: title, sub: sub, cat: cat, ic: ic, v: { items: [] }, def: { items: [] } }\n}\n/* simple types render their control in the row itself — no bullet, nothing to open */\nfunction bl(id, title, cat, ic, label) {\n  return { id: id, kind: 'fields', title: title, cat: cat, ic: ic, inline: true,\n    fields: [{ path: 'on', label: label || title, type: 'bool' }], v: { on: false }, def: { on: false } }\n}\nfunction nm(id, title, cat, ic, label, unit, dflt) {\n  const d = dflt == null ? 0 : dflt\n  return { id: id, kind: 'fields', title: title, cat: cat, ic: ic, inline: true,\n    fields: [{ path: 'value', label: label, type: 'num', unit: unit }], v: { value: d }, def: { value: d } }\n}\nfunction sr(id, title, cat, ic, ph) {\n  return { id: id, kind: 'fields', title: title, cat: cat, ic: ic, inline: true,\n    fields: [{ path: 'value', label: 'Value', type: 'str', ph: ph }], v: { value: '' }, def: { value: '' } }\n}\nfunction en(id, title, cat, ic, label, options) {\n  return { id: id, kind: 'fields', title: title, cat: cat, ic: ic, inline: true,\n    fields: [{ path: 'value', label: label, type: 'enum', options: options }],\n    v: { value: options[0] }, def: { value: options[0] } }\n}\n/* opt out — this prop opens a body instead */\nfunction bul(p) { delete p.inline; return p }\n\n/* the full declared surface of data2-core-primitives@Block, in inspector order */\nconst DEFAULT_MODEL = [\n  {\n    id: 'root', title: 'Block', pieces: [\n      { id: 'p1', kind: 'border', title: 'Border', cat: 'style', ic: 'border',\n        v: { r: [14, 14, 14, 14], w: [1, 1, 1, 1], color: '#E8502A', style: 'Solid' },\n        def: { r: [0, 0, 0, 0], w: [0, 0, 0, 0], color: '#000000', style: 'None' } },\n      bl('p19', 'Children Overflow', 'logic', 'overflow', 'Visible outside bounds'),\n      bl('p17', 'Hide', 'logic', 'hide', 'Hidden'),\n      { id: 'p3', kind: 'fields', title: 'Opacity', cat: 'style', ic: 'opacity',\n        fields: [{ path: 'value', label: 'Degree', type: 'slider', min: 0, max: 100, unit: '%' }],\n        v: { value: 100 }, def: { value: 100 } },\n      { id: 'p7', kind: 'geometry', title: 'Position', cat: 'layout', ic: 'position',\n        v: { anchor: { top: true, left: true, right: false, bottom: false }, x: 1135, y: 51, growH: false, growV: false },\n        def: { anchor: { top: false, left: false, right: false, bottom: false }, x: 0, y: 0, growH: false, growV: false } },\n      { id: 'p8', kind: 'size', title: 'Dimensions', cat: 'layout', ic: 'size',\n        v: { w: 160, h: 540, link: false, growW: false, growH: false },\n        def: { w: 0, h: 0, link: false, growW: false, growH: false } },\n      { id: 'p9', kind: 'spacing', title: 'Padding', cat: 'layout', ic: 'spacing',\n        v: { t: 12, r: 14, b: 12, l: 14 }, def: { t: 0, r: 0, b: 0, l: 0 } },\n      { id: 'p10', kind: 'fields', title: 'Constraints', cat: 'layout', ic: 'constraints',\n        fields: [{ path: 'minW', label: 'Min width', type: 'num', unit: 'px' }, { path: 'maxW', label: 'Max width', type: 'num', unit: 'px' }, { path: 'minH', label: 'Min height', type: 'num', unit: 'px' }, { path: 'maxH', label: 'Max height', type: 'num', unit: 'px' }],\n        v: { minW: 0, maxW: 0, minH: 0, maxH: 0 }, def: { minW: 0, maxW: 0, minH: 0, maxH: 0 } },\n      { id: 'p2', kind: 'fill', title: 'Fill', cat: 'style', ic: 'fill',\n        v: { color: '#1A1A1C', opacity: 100, blend: 'Normal' },\n        def: { color: '#000000', opacity: 100, blend: 'Normal' } },\n      lst('p5', 'Backdrop Filter', 'List of BitmapFilters', 'filter', 'style'),\n      lst('p21', 'Filter', 'List of BitmapFilters', 'filter', 'style'),\n      { id: 'p4', kind: 'shadow', title: 'Shadow', cat: 'style', ic: 'shadow',\n        v: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' },\n        def: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' } },\n      bul(bl('p12', 'Scroll', 'layout', 'scroll', 'Scrollable')),\n      nm('p22', 'Rotate', 'style', 'rotate', 'Degrees', 'deg', 0),\n      lst('p23', 'Transitions', 'List of Transitions', 'transition', 'style'),\n      lst('p24', 'Transform', 'List of Transforms', 'transform', 'style'),\n      lst('p25', 'Light Interaction', 'Light Interaction', 'light', 'style'),\n      sr('p13', 'Text', 'content', 'text', 'Type text'),\n      nm('p26', 'Text Size', 'content', 'textsize', 'Size', 'px', 13),\n      bul(en('p14', 'Font', 'content', 'typo', 'Family', ['[none]', 'Inter', 'Helvetica Neue', 'Roboto', 'Menlo'])),\n      { id: 'p15', kind: 'fields', title: 'Text Color', cat: 'content', ic: 'tcolor',\n        fields: [{ path: 'color', label: 'Color', type: 'color' }], v: { color: '#FFFFFF' }, def: { color: '#FFFFFF' } },\n      { id: 'p27', kind: 'fields', title: 'Text Format', cat: 'content', ic: 'format',\n        fields: [{ path: 'weight', label: 'Weight', type: 'enum', options: ['Regular', 'Medium', 'Bold'] }, { path: 'align', label: 'Align', type: 'enum', options: ['Left', 'Center', 'Right', 'Justify'] }],\n        v: { weight: 'Regular', align: 'Left' }, def: { weight: 'Regular', align: 'Left' } },\n      lst('p28', 'Text Decoration', 'TextDecoration', 'decoration', 'content'),\n      sr('p29', 'Tooltip', 'content', 'tooltip', 'Tooltip text'),\n      en('p11', 'Children Mode', 'layout', 'flow', 'Flow', FLOWS),\n      lst('p30', 'State Definitions', 'List of StateDefinitions', 'states', 'logic'),\n      ev('p31', 'On Touch Start'),\n      ev('p32', 'On Touch End'),\n      en('p33', 'Cursor', 'logic', 'click', 'Cursor', CURSORS),\n      ev('p20', 'On Click'),\n      ev('p34', 'On Drag Enter'),\n      ev('p35', 'On Drag Leave'),\n      ev('p36', 'On Drop'),\n      ev('p37', 'On Click Outside'),\n      ev('p38', 'On Mouse Down'),\n      ev('p39', 'On Mouse Up'),\n      ev('p40', 'On Mouse Enter'),\n      ev('p41', 'On Mouse Leave'),\n      ev('p42', 'On Visible On Screen'),\n      ev('p43', 'On Invisible On Screen'),\n      nm('p44', 'Index', 'layout', 'index', 'Order', '', 0),\n      nm('p45', 'Tab Index', 'logic', 'index', 'Tab order', '', 0),\n      bl('p46', 'Accessibility Hide', 'logic', 'a11y', 'Hidden from AT'),\n      en('p47', 'Accessibility Role', 'logic', 'a11y', 'Role', ROLES),\n      nm('p48', 'Accessibility Level', 'logic', 'a11y', 'Level', '', 0),\n      bl('p51', 'Accessibility Modal', 'logic', 'modal', 'Modal'),\n      sr('p52', 'Accessibility Label', 'logic', 'a11y', 'Label for assistive tech'),\n      bl('p18', 'Disabled', 'logic', 'disabled', 'Disabled'),\n      bl('p49', 'Ignore Mouse Events', 'logic', 'mouse', 'Pointer events off'),\n      bl('p50', 'Prevent User Selection', 'logic', 'mouse', 'Not selectable')\n    ]\n  }\n]\n\nconst TEMPLATES = {\n  border: { kind: 'border', title: 'Border', cat: 'style', ic: 'border', v: { r: [0, 0, 0, 0], w: [0, 0, 0, 0], color: '#E8502A', style: 'None' }, def: { r: [0, 0, 0, 0], w: [0, 0, 0, 0], color: '#E8502A', style: 'None' } },\n  fill: { kind: 'fill', title: 'Fill', cat: 'style', ic: 'fill', v: { color: '#E8502A', opacity: 100, blend: 'Normal' }, def: { color: '#E8502A', opacity: 100, blend: 'Normal' } },\n  shadow: { kind: 'shadow', title: 'Shadow', cat: 'style', ic: 'shadow', v: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' }, def: { x: 0, y: 0, blur: 0, spread: 0, color: '#000000' } },\n  padding: { kind: 'spacing', title: 'Padding', cat: 'layout', ic: 'spacing', v: { t: 0, r: 0, b: 0, l: 0 }, def: { t: 0, r: 0, b: 0, l: 0 } },\n  position: { kind: 'geometry', title: 'Position', cat: 'layout', ic: 'position', v: { anchor: { top: false, left: false, right: false, bottom: false }, x: 0, y: 0, growH: false, growV: false }, def: { anchor: { top: false, left: false, right: false, bottom: false }, x: 0, y: 0, growH: false, growV: false } },\n  dimensions: { kind: 'size', title: 'Dimensions', cat: 'layout', ic: 'size', v: { w: 0, h: 0, link: false, growW: false, growH: false }, def: { w: 0, h: 0, link: false, growW: false, growH: false } },\n  transitions: lst('tpl-tr', 'Transitions', 'List of Transitions', 'transition', 'style'),\n  transform: lst('tpl-tf', 'Transform', 'List of Transforms', 'transform', 'style'),\n  text: sr('tpl-tx', 'Text', 'content', 'text', 'Type text'),\n  handler: ev('tpl-ev', 'On Click')\n}\n\n/* ---------- value path helpers ---------- */\nfunction getAt(v, path) {\n  const parts = String(path).split('.')\n  let cur = v\n  for (let i = 0; i < parts.length; i++) {\n    if (cur == null) return null\n    const k = parts[i]\n    cur = Array.isArray(cur) ? cur[Number(k)] : cur[k]\n  }\n  return cur\n}\nfunction setAt(v, path, val) {\n  const parts = String(path).split('.')\n  let cur = v\n  for (let i = 0; i < parts.length - 1; i++) {\n    const k = parts[i]\n    cur = Array.isArray(cur) ? cur[Number(k)] : cur[k]\n    if (cur == null) return\n  }\n  const last = parts[parts.length - 1]\n  if (Array.isArray(cur)) cur[Number(last)] = val\n  else cur[last] = val\n}\nfunction findPiece(model, id) {\n  for (let i = 0; i < model.length; i++) {\n    const ps = model[i].pieces || []\n    for (let j = 0; j < ps.length; j++) if (ps[j].id === id) return { tray: model[i], piece: ps[j], ti: i, pi: j }\n  }\n  return null\n}\nfunction nextId(model) {\n  let max = 0\n  model.forEach(t => (t.pieces || []).forEach(p => {\n    const n = parseInt(String(p.id).replace(/[^0-9]/g, ''), 10)\n    if (n > max) max = n\n  }))\n  return 'p' + (max + 1)\n}\n\n/* ---------- controls ---------- */\nfunction slot(pid, path, type, value, opts) {\n  opts = opts || {}\n  const sel = opts.sel && opts.sel.type === 'slot' && opts.sel.piece === pid && opts.sel.path === path\n  const cls = 'slot' + (sel ? ' sel' : '') + (opts.cls ? ' ' + opts.cls : '')\n  let inner = ''\n  if (type === 'color') {\n    inner = '<span class=\"chip-c\" style=\"background:' + esc(value) + '\"></span>' +\n      '<input class=\"sin hex\" data-in=\"' + esc(pid) + '|' + esc(path) + '|text\" spellcheck=\"false\" value=\"' + esc(value) + '\">'\n  } else if (type === 'enum') {\n    inner = '<select class=\"sin\" data-in=\"' + esc(pid) + '|' + esc(path) + '|text\">' +\n      (opts.options || []).map(o => '<option' + (String(o) === String(value) ? ' selected' : '') + '>' + esc(o) + '</option>').join('') + '</select>' +\n      '<span class=\"selc\">' + I.tri + '</span>'\n  } else if (type === 'text') {\n    inner = '<input class=\"sin\" data-in=\"' + esc(pid) + '|' + esc(path) + '|text\" spellcheck=\"false\" value=\"' + (opts.blank ? '' : esc(value)) + '\" placeholder=\"' + esc(opts.blank ? '·' : (opts.ph || '')) + '\">'\n  } else {\n    inner = '<input class=\"sin num\" data-in=\"' + esc(pid) + '|' + esc(path) + '|num\" inputmode=\"decimal\" spellcheck=\"false\" value=\"' + (opts.blank ? '' : esc(value)) + '\" placeholder=\"' + (opts.blank ? '·' : '') + '\">' +\n      (opts.unit ? '<i class=\"unit\">' + esc(opts.unit) + '</i>' : '')\n  }\n  return '<span class=\"' + cls + '\" data-slot=\"' + esc(pid) + '|' + esc(path) + '|' + type + '\">' +\n    (opts.cap ? '<i class=\"cap\">' + esc(opts.cap) + '</i>' : '') + inner + '</span>'\n}\n\n/* radius templates — quads are [TL, TR, BR, BL] */\nconst RTPL = [\n  { n: 'Square', r: [0, 0, 0, 0] },\n  { n: 'Soft', r: [4, 4, 4, 4] },\n  { n: 'Round', r: [14, 14, 14, 14] },\n  { n: 'Pill', r: [99, 99, 99, 99] },\n  { n: 'Arch', r: [16, 16, 0, 0] },\n  { n: 'Base', r: [0, 0, 16, 16] },\n  { n: 'Leaf', r: [16, 0, 16, 0] },\n  { n: 'Wing', r: [0, 16, 0, 16] },\n  { n: 'Drop', r: [16, 16, 16, 0] },\n  { n: 'Blade', r: [16, 0, 0, 0] }\n]\nfunction radCss(q, cap) {\n  return q.map(x => Math.min(Math.max(Number(x) || 0, 0), cap) + 'px').join(' ')\n}\nfunction tplName(q) {\n  const k = (q || []).join(',')\n  for (let i = 0; i < RTPL.length; i++) if (RTPL[i].r.join(',') === k) return RTPL[i].n\n  return 'Custom'\n}\n\nfunction optsOnly(pid, path, on) {\n  return '<button class=\"tgl' + (on ? ' on' : '') + '\" role=\"switch\" aria-checked=\"' + (on ? 'true' : 'false') + '\"' +\n    ' data-act=\"flag\" data-piece=\"' + esc(pid) + '\" data-path=\"' + esc(path) + '\"><i></i></button>'\n}\n\nfunction inlineCtl(p, f, s, blank) {\n  const v = getAt(p.v, f.path)\n  if (f.type === 'bool') return optsOnly(p.id, f.path, !!v)\n  if (f.type === 'color') return slot(p.id, f.path, 'color', v, { sel: s })\n  if (f.type === 'slider') return sldRow(p, f, v)\n  const t = f.type === 'enum' ? 'enum' : f.type === 'str' ? 'text' : 'num'\n  return slot(p.id, f.path, t, v, { sel: s, cls: 'wide', options: f.options, ph: f.ph, unit: f.unit, blank: blank })\n}\n\n/* GT7 slider: 1px rail, small rectangular handle, value right-aligned */\nfunction sldRow(p, f, v) {\n  const min = f.min == null ? 0 : f.min\n  const max = f.max == null ? 100 : f.max\n  const pct = Math.max(0, Math.min(100, ((Number(v) - min) / ((max - min) || 1)) * 100))\n  return '<span class=\"sldrow\">' +\n    '<span class=\"sld\" data-sld=\"' + esc(p.id) + '|' + esc(f.path) + '|' + min + '|' + max + '\">' +\n    '<i class=\"sld-r\"></i><i class=\"sld-f\" style=\"width:' + pct + '%\"></i><i class=\"sld-h\" style=\"left:' + pct + '%\"></i></span>' +\n    '<span class=\"sld-v\">' + esc(v) + (f.unit ? '<i>' + esc(f.unit) + '</i>' : '') + '</span></span>'\n}\n\n\nfunction optbool(pid, path, on, label) {\n  return '<div class=\"brow\">' + (label ? '<span class=\"blbl\">' + esc(label) + '</span>' : '') +\n    optsOnly(pid, path, on) + '</div>'\n}\n\nfunction divider(label) {\n  return '<div class=\"dvd\"><i></i><span>' + esc(label) + '</span><i></i></div>'\n}\n\n/* ---------- piece bodies ---------- */\nfunction bodyBorder(p, s, blank, st) {\n  const v = p.v\n  const c = i => slot(p.id, 'r.' + i, 'num', v.r[i], { sel: s, cap: 'Radius' })\n  const e = i => slot(p.id, 'w.' + i, 'num', v.w[i], { sel: s, cap: 'Width' })\n  const selC = s && s.type === 'slot' && s.piece === p.id && s.path === 'color'\n  const open = st && st.tplFor === p.id\n  const cur = (v.r || []).join(',')\n  return '<div class=\"mtx brd\">' +\n    c(0) + e(0) + c(1) +\n    e(3) +\n    '<button class=\"ctpl' + (open ? ' on' : '') + '\" data-act=\"tpl-open\" data-piece=\"' + esc(p.id) + '\" title=\"Radius template\">' +\n    '<span class=\"ctpl-i\">' + I.shapes + '</span></button>' +\n    e(1) +\n    c(3) + e(2) + c(2) +\n    '<span class=\"cbig slot' + (selC ? ' sel' : '') + '\" data-slot=\"' + esc(p.id) + '|color|color\" style=\"background:' + esc(v.color) + '\">' +\n    '<input class=\"sin hex big\" data-in=\"' + esc(p.id) + '|color|text\" spellcheck=\"false\" value=\"' + esc(v.color) + '\"></span>' +\n    (open ? '<div class=\"tplm\">' +\n      '<div class=\"tplh\"><span>Radius</span><em>' + esc(tplName(v.r)) + '</em></div>' +\n      '<div class=\"tplg\">' + RTPL.map(t =>\n        '<button class=\"tplb' + (t.r.join(',') === cur ? ' on' : '') + '\" data-act=\"tpl-pick\" data-piece=\"' + esc(p.id) + '\" data-r=\"' + t.r.join(',') + '\" title=\"' + esc(t.n) + '\">' +\n        '<i style=\"border-radius:' + radCss(t.r, 10) + '\"></i><em>' + esc(t.n) + '</em></button>').join('') +\n      '</div></div>' : '') +\n    '</div>' +\n    '<div class=\"prow\">' + slot(p.id, 'style', 'enum', v.style, { sel: s, cls: 'wide', options: ['None', 'Solid', 'Dashed', 'Dotted'], cap: 'Type' }) + '</div>'\n}\n\nfunction bodySpacing(p, s) {\n  const v = p.v\n  return '<div class=\"mtx pad\">' +\n    '<i></i>' + slot(p.id, 't', 'num', v.t, { sel: s, cap: 'Top' }) + '<i></i>' +\n    slot(p.id, 'l', 'num', v.l, { sel: s, cap: 'Left' }) + '<span class=\"padc\">Content</span>' + slot(p.id, 'r', 'num', v.r, { sel: s, cap: 'Right' }) +\n    '<i></i>' + slot(p.id, 'b', 'num', v.b, { sel: s, cap: 'Bottom' }) + '<i></i>' +\n    '</div>'\n}\n\nfunction bodyGeometry(p, s) {\n  const v = p.v, a = v.anchor || {}\n  const cell = side => '<button class=\"a-' + side + (a[side] ? ' on' : '') + '\" data-act=\"anchor\" data-piece=\"' + esc(p.id) + '\" data-side=\"' + side + '\"></button>'\n  const grp = ks => '<span class=\"grp\">' + ks.map(k => '<button class=\"ib al-' + k + '\" data-act=\"noop\" title=\"Align\">' + I.algn + '</button>').join('') + '</span>'\n  return '<div class=\"geo\">' +\n    '<span class=\"anc-g\">' + '<i></i>' + cell('top') + '<i></i>' + cell('left') + '<span class=\"a-c\"></span>' + cell('right') + '<i></i>' + cell('bottom') + '<i></i>' + '</span>' +\n    '<span class=\"geo-r\">' + optbool(p.id, 'growH', v.growH, 'Grow H') + optbool(p.id, 'growV', v.growV, 'Grow V') + '</span></div>' +\n    '<div class=\"clusrow\">' + grp(['l', 'c', 'r']) + grp(['t', 'm', 'b']) + '</div>' +\n    '<div class=\"prow2\">' + slot(p.id, 'x', 'num', v.x, { sel: s, cap: 'X', unit: 'px' }) + slot(p.id, 'y', 'num', v.y, { sel: s, cap: 'Y', unit: 'px' }) + '</div>'\n}\n\nfunction bodySize(p, s) {\n  const v = p.v\n  const grp = ks => '<span class=\"grp\">' + ks.map(k => '<button class=\"ib d-' + k + '\" data-act=\"noop\" title=\"Distribute\">' + (k === 'w1' || k === 'h1' ? I.arw2 : I.arw) + '</button>').join('') + '</span>'\n  return '<div class=\"clusrow\">' + grp(['w0', 'w1', 'w2']) + grp(['h0', 'h1', 'h2']) + '</div>' +\n    '<div class=\"prow2\">' + slot(p.id, 'w', 'num', v.w, { sel: s, cap: 'Width', unit: 'px' }) +\n    '<button class=\"ib lk' + (v.link ? ' on' : '') + '\" data-act=\"flag\" data-piece=\"' + esc(p.id) + '\" data-path=\"link\">' + I.link + '</button>' +\n    slot(p.id, 'h', 'num', v.h, { sel: s, cap: 'Height', unit: 'px' }) + '</div>' +\n    optbool(p.id, 'growW', v.growW, 'Fit width') + optbool(p.id, 'growH', v.growH, 'Fit height')\n}\n\nfunction bodyFill(p, s) {\n  const v = p.v\n  return '<div class=\"prow2\">' + slot(p.id, 'color', 'color', v.color, { sel: s, cls: 'grow', cap: 'Color' }) +\n    slot(p.id, 'opacity', 'num', v.opacity, { sel: s, cap: 'Opacity', unit: '%' }) + '</div>' +\n    '<div class=\"prow\">' + slot(p.id, 'blend', 'enum', v.blend, { sel: s, cls: 'wide', options: ['Normal', 'Multiply', 'Screen', 'Overlay'], cap: 'Blend' }) + '</div>'\n}\n\nfunction bodyShadow(p, s) {\n  const v = p.v\n  return '<div class=\"shw\"><span class=\"shw-p\" style=\"box-shadow:' + Number(v.x) + 'px ' + Number(v.y) + 'px ' + Number(v.blur) + 'px ' + Number(v.spread) + 'px ' + esc(v.color) + '\"></span></div>' +\n    '<div class=\"prow4\">' + slot(p.id, 'x', 'num', v.x, { sel: s, cap: 'X' }) + slot(p.id, 'y', 'num', v.y, { sel: s, cap: 'Y' }) +\n    slot(p.id, 'blur', 'num', v.blur, { sel: s, cap: 'Blur' }) + slot(p.id, 'spread', 'num', v.spread, { sel: s, cap: 'Spread' }) + '</div>' +\n    '<div class=\"prow\">' + slot(p.id, 'color', 'color', v.color, { sel: s, cls: 'wide', cap: 'Color' }) + '</div>'\n}\n\nfunction bodyFields(p, s, blank) {\n  const fs = p.fields || []\n  const bools = fs.filter(f => f.type === 'bool')\n  const slides = fs.filter(f => f.type === 'slider')\n  const rest = fs.filter(f => f.type !== 'bool' && f.type !== 'slider')\n  let out = ''\n  slides.forEach(f => {\n    out += '<div class=\"brow\"><span class=\"blbl\">' + esc(f.label) + '</span>' + sldRow(p, f, getAt(p.v, f.path)) + '</div>'\n  })\n  if (rest.length) {\n    const one = rest.length === 1\n    out += '<div class=\"' + (one ? 'prow' : 'fgrid') + '\">' + rest.map(f => {\n      const type = f.type === 'enum' ? 'enum' : f.type === 'color' ? 'color' : f.type === 'str' ? 'text' : 'num'\n      return slot(p.id, f.path, type, getAt(p.v, f.path), { sel: s, cls: one ? 'wide' : '', options: f.options, cap: f.label, ph: f.ph, unit: f.unit, blank: blank })\n    }).join('') + '</div>'\n  }\n  bools.forEach(f => { out += optbool(p.id, f.path, !!getAt(p.v, f.path), f.label) })\n  return out || '<div class=\"empty\">No editable values</div>'\n}\n\nfunction bodyList(p, s) {\n  const items = p.v.items || []\n  const rows = items.map((it, i) =>\n    '<div class=\"lrow\"><span class=\"ln\">' + (i + 1) + '</span>' +\n    '<span class=\"lt\">' + esc(it.name || (p.title + ' ' + (i + 1))) + '</span>' +\n    '<button class=\"ib tb\" data-act=\"list-del\" data-piece=\"' + esc(p.id) + '\" data-idx=\"' + i + '\" title=\"Remove\">' + I.x + '</button></div>').join('')\n  return (rows || '<div class=\"empty\">Empty list</div>') +\n    '<button class=\"addrow\" data-act=\"list-add\" data-piece=\"' + esc(p.id) + '\">' + I.plus + '<span>Add</span></button>'\n}\n\nconst BODIES = { list: bodyList, fields: bodyFields, border: bodyBorder, spacing: bodySpacing, geometry: bodyGeometry, size: bodySize, fill: bodyFill, shadow: bodyShadow }\n\n/* ---------- category stepper ---------- */\nfunction catCount(model, id) {\n  let n = 0\n  model.forEach(t => (t.pieces || []).forEach(p => { if (id === 'all' || (p.cat || 'style') === id) n += 1 }))\n  return n\n}\nfunction pieceMatches(p, q) {\n  return (p.title + ' ' + p.kind + ' ' + JSON.stringify(p.v)).toLowerCase().indexOf(q) > -1\n}\nfunction renderAddMenu(t) {\n  return '<div class=\"menu add-m\">' + Object.keys(TEMPLATES).map(k =>\n    '<button class=\"cat\" data-act=\"add-pick\" data-tray=\"' + esc(t.id) + '\" data-kind=\"' + esc(k) + '\">' +\n    '<span class=\"ci\">' + (PI[TEMPLATES[k].ic] || PI.filter) + '</span>' +\n    '<span class=\"cl2\">' + esc(TEMPLATES[k].title) + '</span></button>').join('') + '</div>'\n}\nfunction renderPill(model, tab, mode, q, anim) {\n  if (mode === 'search') {\n    return '<div class=\"tabs\"><div class=\"pwrap\"><div class=\"pill act\">' +\n      '<span class=\"pglyph\">' + I.search + '</span>' +\n      '<input class=\"pq\" spellcheck=\"false\" placeholder=\"Search properties\" value=\"' + esc(q) + '\">' +\n      '<button class=\"parr\" data-act=\"cat-close\" title=\"Close search\">' + I.x + '</button>' +\n      '</div></div></div>'\n  }\n  let idx = TABS.map(t => t.id).indexOf(tab)\n  if (idx < 0) idx = 0\n  const open = mode === 'list'\n  return '<div class=\"tabs\"><div class=\"pwrap\">' +\n    '<div class=\"pill' + (open ? ' act' : '') + '\">' +\n    '<button class=\"parr pv\" data-act=\"cat-prev\" title=\"Previous\">' + I.chev + '</button>' +\n    '<button class=\"plabel ' + ((anim || 0) % 2 ? 'la' : 'lb') + '\" data-act=\"cat-open\" title=\"Click to pick, double-click to search\">' +\n    '<span class=\"pli\">' + (CI[TABS[idx].id] || '') + '</span>' + esc(TABS[idx].label) + '</button>' +\n    '<button class=\"parr\" data-act=\"cat-next\" title=\"Next\">' + I.chev + '</button>' +\n    '</div>' +\n    (open ? '<div class=\"menu cats\">' + TABS.map(t => '<button class=\"cat' + (t.id === tab ? ' on' : '') + '\" data-act=\"cat-pick\" data-val=\"' + t.id + '\">' +\n      '<span class=\"ci\">' + (CI[t.id] || '') + '</span>' +\n      '<span class=\"cl2\">' + esc(t.label) + '</span><span class=\"cc\">' + catCount(model, t.id) + '</span></button>').join('') + '</div>' : '') +\n    '</div></div>'\n}\n\n/* ---------- piece ---------- */\nfunction renderPieceMenu(p) {\n  const items = [['copy', 'Copy'], ['cut', 'Cut'], ['dup', 'Duplicate'], ['paste-into', 'Paste into'], ['reset-piece', 'Reset'], ['del', 'Remove']]\n  return '<div class=\"menu p-m\">' + items.map(it =>\n    '<button class=\"cat' + (it[0] === 'del' ? ' dngr' : '') + '\" data-act=\"' + it[0] + '\" data-piece=\"' + esc(p.id) + '\">' +\n    '<span class=\"cl2\">' + it[1] + '</span></button>').join('') + '</div>'\n}\nfunction fxBody(p) {\n  return '<div class=\"fxrow\"><span class=\"fxk\">f()</span>' +\n    '<input class=\"sin fxin\" data-in=\"' + esc(p.id) + '|__fx|text\" spellcheck=\"false\" value=\"' + esc(p.v.__fx) + '\" placeholder=\"Reference(&#39;brandColor&#39;)\"></div>'\n}\n\nfunction renderPiece(p, tray, st, i) {\n  const ref = p.def !== undefined ? p.def : (TEMPLATES[p.kind] ? TEMPLATES[p.kind].v : null)\n  const pop = !ref || JSON.stringify(ref) !== JSON.stringify(p.v)\n  const exp = st.pOpen && st.pOpen[p.id] !== undefined ? st.pOpen[p.id] : pop\n  const sel = st.sel && st.sel.type === 'piece' && st.sel.piece === p.id\n  const cut = st.cut === p.id\n  const fx = p.v && p.v.__fx != null\n  const fn = BODIES[p.kind] || bodyFields\n  const inl = p.inline && p.fields && p.fields.length === 1 ? p.fields[0] : null\n\n  if (inl) {\n    const onRight = inl.type === 'bool' || inl.type === 'color'\n    return '<div class=\"piece inl' + (pop ? ' pop' : '') + (sel ? ' sel' : '') + (cut ? ' cut' : '') + (st.drag === p.id ? ' drag' : '') + '\" data-piece=\"' + esc(p.id) + '\" data-tray=\"' + esc(tray.id) + '\" style=\"--i:' + (i || 0) + '\">' +\n      '<div class=\"ph\" data-act=\"select\" data-piece=\"' + esc(p.id) + '\" data-grip=\"' + esc(p.id) + '\">' +\n      '<span class=\"pic\">' + (PI[p.ic] || PI.filter) + '</span>' +\n      '<span class=\"ptxt\"><span class=\"pt\">' + esc(p.title) + '</span></span>' +\n      (fx ? '<span class=\"fxbadge\">f()</span>' : '') +\n      (onRight && !fx ? '<span class=\"pinl\">' + inlineCtl(p, inl, st.sel, !pop) + '</span>' : '') +\n      '<span class=\"ptools\">' +\n      '<button class=\"ib tb\" data-act=\"noop\" title=\"About this property\">?</button>' +\n      '<button class=\"ib tb' + (fx ? ' on' : '') + '\" data-act=\"fx\" data-piece=\"' + esc(p.id) + '\" title=\"Bind to an expression\">f()</button>' +\n      '<button class=\"ib tb\" data-act=\"menu\" data-piece=\"' + esc(p.id) + '\" title=\"More\">' + I.dots + '</button>' +\n      '</span>' +\n      (st.menuFor === p.id ? renderPieceMenu(p) : '') +\n      '</div>' +\n      (fx ? '<div class=\"pinrow\">' + fxBody(p) + '</div>'\n        : (onRight ? '' : '<div class=\"pinrow\">' + inlineCtl(p, inl, st.sel, !pop) + '</div>')) +\n      '</div>'\n  }\n\n  return '<div class=\"piece' + (pop ? ' pop' : '') + (exp ? ' exp' : '') + (sel ? ' sel' : '') + (cut ? ' cut' : '') + (st.drag === p.id ? ' drag' : '') + '\" data-piece=\"' + esc(p.id) + '\" data-tray=\"' + esc(tray.id) + '\" style=\"--i:' + (i || 0) + '\">' +\n    '<div class=\"ph\" data-act=\"select\" data-piece=\"' + esc(p.id) + '\" data-grip=\"' + esc(p.id) + '\">' +\n    '<button class=\"pdot' + (exp ? ' on' : '') + '\" data-act=\"pexp\" data-piece=\"' + esc(p.id) + '\" title=\"Expand / collapse\"></button>' +\n    '<span class=\"pic\">' + (PI[p.ic] || PI.filter) + '</span>' +\n    '<span class=\"ptxt\"><span class=\"pt\">' + esc(p.title) + '</span>' +\n    (p.sub ? '<span class=\"psub\">' + esc(p.sub) + '</span>' : '') + '</span>' +\n    (fx ? '<span class=\"fxbadge\">f()</span>' : '') +\n    '<span class=\"ptools\">' +\n    '<button class=\"ib tb\" data-act=\"noop\" title=\"About this property\">?</button>' +\n    '<button class=\"ib tb' + (fx ? ' on' : '') + '\" data-act=\"fx\" data-piece=\"' + esc(p.id) + '\" title=\"Bind to an expression\">f()</button>' +\n    '<button class=\"ib tb\" data-act=\"menu\" data-piece=\"' + esc(p.id) + '\" title=\"More\">' + I.dots + '</button>' +\n    '</span>' +\n    (st.menuFor === p.id ? renderPieceMenu(p) : '') +\n    '</div>' +\n    '<div class=\"pbwrap\"' + (exp ? '' : ' data-collapsed=\"1\"') + '><div class=\"pbi\"><div class=\"pb\">' +\n    (fx ? fxBody(p) : fn(p, st.sel, !pop, st)) + '</div></div></div>' +\n    '</div>'\n}\n\nexport default {\n  css: `\n:host { display: block; width: 100%; height: 100% }\n:host, .ins { box-sizing: border-box }\n.ins *, .ins *::before, .ins *::after { box-sizing: border-box }\n.ins {\n  --acc: #E8502A;\n  --bg: #0C0C0D; --bg2: #141416; --card: rgba(255,255,255,.045); --edge: rgba(255,255,255,.09);\n  --rule: rgba(255,255,255,.085); --txt: #FFFFFF; --txt2: rgba(255,255,255,.58); --txt3: rgba(255,255,255,.34);\n  --ctl: rgba(255,255,255,.06); --ctl2: rgba(255,255,255,.12); --focus: rgba(255,255,255,.6);\n  --well: rgba(0,0,0,.45); --ok: #3ED77A;\n  --strack: rgba(0,0,0,.55); --sbar: #6B6B70; --sbar2: #87878D; --tglo: rgba(255,255,255,.17);\n  width: 300px; max-width: 100%; height: 100%; margin-left: auto; display: flex; flex-direction: column;\n  overflow: hidden; border-radius: 12px; background: var(--bg); color: var(--txt);\n  border: 1px solid var(--edge);\n  font-family: Inter, \"Helvetica Neue\", Helvetica, Arial, system-ui, sans-serif;\n  font-size: 12px; line-height: 1.35; -webkit-font-smoothing: antialiased; user-select: none;\n  transition: width .24s cubic-bezier(.4,0,.2,1);\n}\n.ins.light {\n  --acc: #D8431F;\n  --bg: #DCDCDE; --bg2: #EDEDEF; --card: rgba(255,255,255,.72); --edge: rgba(0,0,0,.13);\n  --rule: rgba(0,0,0,.10); --txt: #17171A; --txt2: rgba(0,0,0,.56); --txt3: rgba(0,0,0,.34);\n  --ctl: rgba(255,255,255,.8); --ctl2: rgba(0,0,0,.07); --focus: rgba(0,0,0,.5);\n  --well: rgba(255,255,255,.92); --ok: #1E9E52;\n  --strack: rgba(0,0,0,.13); --sbar: #9A9AA0; --sbar2: #7E7E85; --tglo: rgba(0,0,0,.19);\n}\n.ic { width: 100%; height: 100%; display: block }\nbutton { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer }\n.ib { width: 24px; height: 24px; border-radius: 999px; display: grid; place-items: center; color: var(--txt2);\n  flex: none; border: 1px solid transparent; transition: background .12s, color .12s, border-color .12s }\n.ib > svg { width: 13px; height: 13px }\n.ib:hover { background: var(--ctl2); color: var(--txt); border-color: var(--edge) }\n.ib.on { background: var(--acc); color: #fff; border-color: transparent }\n\n/* title bar */\n.tbar { display: flex; align-items: center; gap: 4px; height: 40px; padding: 0 8px 0 14px; flex: none;\n  border-bottom: 1px solid var(--rule) }\n.tbar .t { font-size: 13px; font-weight: 600; letter-spacing: .01em; flex: 1 }\n\n/* subject — GT7 car card */\n.subj { display: flex; align-items: center; gap: 10px; margin: 10px; padding: 10px; border-radius: 10px;\n  background: var(--bg2); border: 1px solid var(--rule) }\n.subj .tile { width: 30px; height: 30px; border-radius: 8px; flex: none; display: grid; place-items: center;\n  background: var(--ctl); color: var(--txt) }\n.subj .tile > svg { width: 17px; height: 17px }\n.subj .m { min-width: 0; flex: 1; display: block }\n.subj .n { display: block; font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.subj .p { display: block; font-size: 10px; color: var(--txt2); font-family: ui-monospace, Menlo, monospace;\n  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px }\n.pp { flex: none; height: 22px; padding: 0 9px; border-radius: 999px; background: var(--txt); color: var(--bg);\n  font-size: 9.5px; font-weight: 700; letter-spacing: .04em; display: grid; place-items: center;\n  font-variant-numeric: tabular-nums; white-space: nowrap }\n\n/* identity — GT7 spec rows */\n.ident { padding: 0 14px 4px }\n.ir { display: flex; align-items: center; gap: 10px; min-height: 30px; border-bottom: 1px solid var(--rule) }\n.ir:last-child { border-bottom: 0 }\n.ik { flex: 1; font-size: 11.5px; color: var(--txt2) }\n.iv { font-family: ui-monospace, Menlo, monospace; font-size: 10.5px; color: var(--txt);\n  max-width: 60%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; user-select: text }\n.ident input { width: 148px; height: 24px; background: var(--ctl); border: 1px solid var(--rule); border-radius: 6px;\n  color: var(--txt); font: inherit; font-size: 11.5px; padding: 0 7px; outline: none; text-align: right; user-select: text }\n.ident input:focus { border-color: var(--focus) }\n\n/* category stepper */\n.tabs { padding: 8px 10px 10px; position: relative; z-index: 20 }\n.pwrap { position: relative }\n.pill { display: flex; align-items: center; height: 32px; border-radius: 999px; background: var(--ctl);\n  border: 1px solid var(--rule); transition: border-color .14s, background .14s }\n.pill:hover { border-color: var(--edge) }\n.pill.act { border-color: var(--focus); background: var(--ctl2) }\n.parr { width: 30px; height: 30px; border-radius: 999px; display: grid; place-items: center; color: var(--txt2); flex: none }\n.parr > svg { width: 7px; height: 11px }\n.parr:hover { color: var(--txt); background: var(--ctl2) }\n.parr.pv > svg { transform: rotate(180deg) }\n.plabel { flex: 1; min-width: 0; height: 30px; border-radius: 999px; font-size: 12.5px; font-weight: 500;\n  color: var(--txt); text-transform: lowercase; display: flex; align-items: center; justify-content: center; gap: 8px }\n.pli { width: 13px; height: 13px; flex: none; color: var(--txt2); transition: color .14s var(--ez) }\n.plabel:hover .pli { color: var(--txt) }\n.cat.on .ci { color: var(--acc) }\n.cat:hover .ci { color: #fff }\n.plabel:hover { background: var(--ctl2) }\n.pglyph { width: 28px; display: grid; place-items: center; color: var(--txt3); flex: none }\n.pglyph > svg { width: 12px; height: 12px }\n.pq { flex: 1; min-width: 0; height: 30px; background: none; border: 0; outline: none; color: var(--txt);\n  font: inherit; font-size: 12px; user-select: text }\n.menu { position: absolute; left: 0; right: 0; top: 36px; z-index: 30; padding: 4px; border-radius: 10px;\n  background: var(--bg2); border: 1px solid var(--edge); box-shadow: 0 14px 34px rgba(0,0,0,.5) }\n.ins.light .menu { box-shadow: 0 14px 34px rgba(0,0,0,.18) }\n.cat { display: flex; align-items: center; gap: 8px; width: 100%; height: 30px; padding: 0 8px; border-radius: 7px;\n  font-size: 12px; color: var(--txt); border: 1px solid transparent }\n.cat:hover { background: var(--ctl2); border-color: var(--focus) }\n.cat.on { color: var(--acc); font-weight: 600 }\n.cat .ci { width: 15px; height: 15px; flex: none; color: var(--txt2) }\n.cat .cl2 { flex: 1; text-align: left }\n.cat .cc { font-size: 10.5px; color: var(--txt3); font-variant-numeric: tabular-nums }\n.cat.dngr { color: #FF5A4E }\n\n/* body */\n.body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 0 0 14px }\n.body::-webkit-scrollbar { width: 9px; background: var(--strack) }\n.body::-webkit-scrollbar-track { background: var(--strack) }\n.body::-webkit-scrollbar-thumb { background: var(--sbar); border-radius: 0; border: 0 }\n.body::-webkit-scrollbar-thumb:hover { background: var(--sbar2) }\n.body::-webkit-scrollbar-corner { background: var(--strack) }\n\n/* node — GT7 centred vermilion section header */\n.node { position: relative; display: flex; align-items: center; justify-content: center; height: 38px; padding: 0 12px }\n.node-h { display: flex; align-items: center; gap: 8px; height: 100%; padding: 0 6px }\n.node-h .chev { width: 11px; height: 8px; color: var(--acc); transition: transform .16s }\n.tray:not(.open) .node-h .chev { transform: rotate(-90deg) }\n.nt { font-size: 11px; font-weight: 700; color: var(--acc); letter-spacing: .06em }\n.nrule { position: absolute; top: 50%; height: 1px; background: var(--rule); width: 26% }\n.nrule.l { left: 12px } .nrule.r { right: 12px }\n.ntools { position: absolute; right: 10px; display: flex; gap: 2px; opacity: 0; transition: opacity .12s }\n.node:hover .ntools { opacity: 1 }\n.nn { position: absolute; right: 12px; font-size: 10px; color: var(--txt3); font-variant-numeric: tabular-nums }\n.node:hover .nn { opacity: 0 }\n.add-m { top: 34px; left: auto; right: 8px; width: 176px }\n.tray-b { padding: 0 10px }\n.tray.over { background: rgba(232,80,42,.07) }\n.drop { height: 2px; background: var(--acc); border-radius: 1px; margin: 3px 0 }\n\n/* piece — GT7 settings row */\n.piece { position: relative; border-radius: 9px; border: 1px solid transparent; background: transparent;\n  transition: background .14s, border-color .14s }\n.piece + .piece { margin-top: 4px }\n.piece.pop { background: var(--card) }\n.piece.sel { border-color: var(--focus); background: var(--ctl2) }\n.piece.cut { opacity: .4; border-style: dashed; border-color: var(--acc) }\n.piece.drag { opacity: .5 }\n.ph { position: relative; display: flex; align-items: center; gap: 9px; height: 40px; padding: 0 10px 0 9px }\n.piece.has-sub .ph { height: 48px }\n.piece.pop.exp .ph { border-bottom: 1px solid var(--rule) }\n.pdot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: transparent;\n  box-shadow: inset 0 0 0 1.4px var(--txt3); transition: background .14s, box-shadow .14s }\n.pdot:hover { box-shadow: inset 0 0 0 1.4px var(--txt2) }\n.pdot.on { background: var(--acc); box-shadow: 0 0 0 3px rgba(232,80,42,.24) }\n.pic { width: 17px; height: 17px; flex: none; color: var(--txt2) }\n.piece.exp .pic, .piece.sel .pic { color: var(--txt) }\n.ptxt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px }\n.pt { font-size: 12.5px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.psub { font-size: 10px; color: var(--txt3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.fxbadge { flex: none; font-size: 8.5px; font-weight: 700; font-family: ui-monospace, Menlo, monospace;\n  color: #fff; background: var(--acc); border-radius: 4px; padding: 2px 4px }\n.ptools { position: absolute; right: 8px; display: flex; gap: 3px; opacity: 0; transition: opacity .12s }\n.piece:hover .ptools, .piece.sel .ptools { opacity: 1 }\n.tb { width: 24px; height: 21px; border-radius: 6px; border: 1px solid var(--rule); font-size: 9px; font-weight: 700;\n  font-family: ui-monospace, Menlo, monospace; color: var(--txt2); background: var(--bg2) }\n.tb > svg { width: 11px; height: 11px }\n.tb:hover { color: var(--txt); border-color: var(--focus) }\n.tb.on { background: var(--acc); color: #fff; border-color: transparent }\n.p-m { top: 36px; left: auto; right: 8px; width: 154px }\n.pb { padding: 18px 11px 16px; display: flex; flex-direction: column; gap: 20px }\n\n/* inline props — control lives in the row, no bullet */\n.piece.inl .ph { height: 34px; padding-left: 11px }\n.pinl { display: flex; align-items: center; gap: 6px; flex: none }\n/* on an inline prop the control stays live — the tools open up beside it, never over it */\n.piece.inl .ptools { position: static; max-width: 0; overflow: hidden; opacity: 0;\n  transition: max-width .22s var(--ez), opacity .16s var(--ez) }\n.piece.inl:hover .ptools, .piece.inl.sel .ptools { max-width: 92px; opacity: 1 }\n.pinl .slot { height: 26px }\n.pinl .col-in, .pinl .hex { width: 74px }\n.pinrow { padding: 0 11px 12px; display: flex }\n.pinrow > .slot { flex: 1; width: 100% }\n.pinrow > .fxrow { flex: 1 }\n\n/* slots */\n.slot { position: relative; display: flex; align-items: center; min-width: 0; height: 32px; padding: 0 8px;\n  background: var(--well); border: 1px solid transparent; border-radius: 6px; cursor: pointer;\n  transition: border-color .12s, background .12s }\n.slot:hover { border-color: var(--edge) }\n.slot:focus-within { border-color: var(--focus) }\n.slot.sel { border-color: var(--focus) }\n.sin::placeholder { color: var(--txt3); opacity: 1 }\n.sin.num::placeholder { font-size: 15px; line-height: 1 }\n.slot .cap { position: absolute; top: -13px; left: 1px; font-size: 9.5px; font-style: normal; color: var(--txt3);\n  pointer-events: none; white-space: nowrap }\n.sin { flex: 1; min-width: 0; width: 100%; background: none; border: 0; outline: none; color: var(--txt);\n  font: inherit; font-size: 12px; user-select: text }\n.sin.num { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600 }\n.sin.hex { font-family: ui-monospace, Menlo, monospace; font-size: 10.5px; text-transform: uppercase }\n.unit { font-style: normal; font-size: 9.5px; color: var(--txt3); margin-left: 4px; flex: none }\nselect.sin { appearance: none; -webkit-appearance: none; cursor: pointer; text-align: right; padding-right: 2px }\n/* the native popup inherits the select's colour — without this it is white-on-white */\nselect.sin option { background-color: #17171A; color: #F2F2F2 }\n.ins.light select.sin option { background-color: #FFFFFF; color: #17171A }\n.selc { width: 9px; height: 6px; color: var(--txt2); flex: none; margin-left: 6px }\n.sldrow { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0 }\n.sld { position: relative; flex: 1; min-width: 0; height: 22px; cursor: pointer; display: flex; align-items: center }\n.sld-r { position: absolute; left: 0; right: 0; height: 1px; background: var(--txt3) }\n.sld-f { position: absolute; left: 0; height: 1px; background: var(--txt) }\n.sld-h { position: absolute; top: 50%; width: 5px; height: 15px; margin-left: -2.5px; border-radius: 1px;\n  background: var(--txt); transform: translateY(-50%); transition: background .12s }\n.sld:hover .sld-h { background: var(--acc) }\n.sld-v { flex: none; min-width: 42px; text-align: right; font-size: 12px; font-weight: 600;\n  font-variant-numeric: tabular-nums }\n.sld-v > i { font-style: normal; font-size: 9.5px; color: var(--txt3); margin-left: 2px }\n\n.chip-c { width: 15px; height: 15px; border-radius: 4px; flex: none; margin-right: 7px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.35) }\n\n/* matrices */\n.mtx { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 21px 6px }\n.mtx > i { display: block }\n.mtx.brd { grid-template-columns: 1fr 1fr 1fr 1.25fr; grid-template-rows: repeat(3, 32px) }\n.mtx.brd { position: relative }\n.ctpl { display: grid; place-items: center; border-radius: 6px; background: var(--well);\n  border: 1px solid transparent; transition: border-color .14s var(--ez) }\n.ctpl:hover { border-color: var(--edge) }\n.ctpl.on { border-color: var(--acc) }\n.ctpl-i { display: block; width: 18px; height: 18px; color: var(--txt2); transition: color .14s var(--ez) }\n.ctpl:hover .ctpl-i { color: var(--txt) }\n.ctpl.on .ctpl-i { color: var(--acc) }\n.tplm { position: absolute; left: 0; right: 0; top: 0; z-index: 40; padding: 8px;\n  display: flex; flex-direction: column; gap: 7px; border-radius: 9px;\n  background: var(--bg2); border: 1px solid var(--edge); box-shadow: 0 16px 36px rgba(0,0,0,.6);\n  animation: menuIn .18s var(--ez) both }\n.ins.light .tplm { box-shadow: 0 16px 36px rgba(0,0,0,.2) }\n.tplh { display: flex; align-items: center; justify-content: space-between; padding: 0 2px;\n  font-size: 8.5px; letter-spacing: .15em; text-transform: uppercase; color: var(--txt3) }\n.tplh em { font-style: normal; font-weight: 700; letter-spacing: .1em; color: var(--acc) }\n.tplg { display: grid; grid-template-columns: repeat(5, 1fr); gap: 7px 5px }\n.tplb { display: grid; place-items: center; gap: 5px; padding: 4px 0 3px; border-radius: 6px;\n  border: 1px solid transparent; transition: background .12s var(--ez), border-color .12s var(--ez) }\n.tplb > i { display: block; width: 30px; height: 21px; border: 1.4px solid var(--txt2);\n  transition: border-color .14s var(--ez), border-radius .2s var(--ez) }\n.tplb > em { font-style: normal; font-size: 7.5px; letter-spacing: .08em; text-transform: uppercase;\n  color: var(--txt3); transition: color .12s var(--ez) }\n.tplb:hover { background: var(--ctl2) }\n.tplb:hover > i { border-color: var(--txt) }\n.tplb:hover > em { color: var(--txt) }\n.tplb.on > i { border-color: var(--acc) }\n.tplb.on > em { color: var(--acc); font-weight: 700 }\n.cbig { grid-column: 4; grid-row: 1 / span 3; height: auto; padding: 0; align-items: flex-end; border-radius: 8px; overflow: hidden }\n.hex.big { width: 100%; height: 21px; text-align: center; color: #fff; background: rgba(0,0,0,.42); font-size: 10px }\n.mtx.pad .padc { display: grid; place-items: center; font-size: 10px; color: var(--txt3); border-radius: 6px;\n  background: rgba(255,255,255,.04); border: 1px solid var(--rule) }\n.ins.light .mtx.pad .padc { background: rgba(0,0,0,.05) }\n.prow { display: flex }\n.prow2 { display: flex; align-items: center; gap: 6px }\n.prow4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px }\n.fgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 21px 6px }\n.prow .wide, .slot.wide { flex: 1; width: 100% }\n.prow2 .slot { flex: 1 } .prow2 .grow { flex: 2 }\n\n/* toggle */\n.brow { display: flex; align-items: center; gap: 12px; min-height: 34px }\n.blbl { flex: 1; min-width: 0; font-size: 12px; color: var(--txt2); white-space: nowrap;\n  overflow: hidden; text-overflow: ellipsis }\n.tgl { position: relative; width: 36px; height: 20px; border-radius: 999px; flex: none;\n  background: var(--tglo); transition: background .2s var(--ez) }\n.tgl > i { position: absolute; top: 3px; left: 3px; width: 14px; height: 14px; border-radius: 50%;\n  background: #E7E7EA; box-shadow: 0 1px 2px rgba(0,0,0,.45);\n  transition: transform .22s var(--ez), background .2s var(--ez) }\n.tgl:hover { background: color-mix(in srgb, var(--txt) 28%, var(--tglo)) }\n.tgl.on { background: var(--acc) }\n.tgl.on > i { transform: translateX(16px); background: #fff }\n\n/* clusters + anchors */\n.d-w0 svg, .d-w1 svg { transform: rotate(90deg) }\n.d-w2 svg { transform: rotate(-90deg) }\n.d-h0 svg { transform: rotate(180deg) }\n.al-c svg { transform: rotate(90deg) }\n.al-r svg { transform: scaleX(-1) }\n.al-t svg { transform: rotate(180deg) }\n.al-m svg { transform: rotate(90deg) scaleX(-1) }\n.al-b svg { transform: rotate(90deg) scaleY(-1) }\n.clusrow { display: flex; gap: 8px }\n.grp { flex: 1; display: flex; padding: 2px; gap: 2px; background: var(--ctl); border: 1px solid var(--rule); border-radius: 8px }\n.grp .ib { flex: 1; width: auto; height: 26px; border-radius: 6px }\n.lk { width: 26px; height: 26px; border: 1px solid var(--rule); background: var(--ctl); border-radius: 7px }\n.geo { display: flex; align-items: center; gap: 12px }\n.geo-r { display: flex; flex-direction: column; gap: 9px; flex: 1; min-width: 0 }\n.anc-g { display: grid; grid-template-columns: 20px 26px 20px; grid-template-rows: 16px 26px 16px; gap: 5px;\n  padding: 8px; background: var(--ctl); border: 1px solid var(--rule); border-radius: 9px; flex: none }\n.anc-g > i { display: block }\n.anc-g button { border-radius: 2px; background: var(--txt3) }\n.anc-g button:hover { background: var(--txt2) }\n.anc-g button.on { background: var(--acc) }\n.a-top, .a-bottom { justify-self: center; align-self: center; width: 20px; height: 3px }\n.a-left, .a-right { align-self: center; justify-self: center; width: 3px; height: 15px }\n.a-c { border-radius: 4px; border: 1px solid var(--txt3) }\n.shw { display: grid; place-items: center; height: 66px; background: var(--ctl); border: 1px solid var(--rule); border-radius: 9px }\n.shw-p { width: 46px; height: 26px; border-radius: 6px; background: var(--ctl2) }\n.fxrow { display: flex; align-items: center; gap: 8px; height: 30px; padding: 0 9px; border-radius: 8px;\n  background: rgba(232,80,42,.13); border: 1px solid rgba(232,80,42,.5) }\n.fxk { font-size: 10px; font-weight: 700; font-family: ui-monospace, Menlo, monospace; color: var(--acc); flex: none }\n.fxin { font-family: ui-monospace, Menlo, monospace; font-size: 11px }\n.dvd { display: flex; align-items: center; gap: 9px; color: var(--acc); font-size: 10.5px; font-weight: 700 }\n.dvd i { flex: 1; height: 1px; background: var(--rule) }\n.lrow { display: flex; align-items: center; gap: 9px; height: 32px; padding: 0 6px 0 9px; border-radius: 7px;\n  background: var(--ctl); border: 1px solid var(--rule) }\n.ln { width: 12px; font-size: 10px; color: var(--txt3); font-variant-numeric: tabular-nums }\n.lt { flex: 1; min-width: 0; font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.addrow { display: flex; align-items: center; justify-content: center; gap: 6px; height: 32px; border-radius: 7px;\n  border: 1px dashed var(--rule); color: var(--txt2); font-size: 11.5px; font-weight: 500;\n  transition: border-color .16s, color .16s, background .16s }\n.addrow > svg { width: 11px; height: 11px }\n.addrow:hover { border-color: var(--focus); color: var(--txt); background: var(--ctl) }\n.empty { padding: 16px 12px; text-align: center; color: var(--txt3); font-size: 11px }\n\n/* wide */\n.ins.wide { width: 100% }\n.ins.wide .tray-b { columns: 2; column-gap: 12px; padding: 0 12px }\n.ins.wide .tray-b .piece { break-inside: avoid; margin: 0 0 4px }\n\n/* clipboard + dock */\n.clip { flex: none; display: flex; align-items: center; gap: 9px; margin: 0 10px 8px; padding: 8px 8px 8px 11px;\n  border-radius: 9px; background: rgba(232,80,42,.15); border: 1px solid rgba(232,80,42,.45) }\n.clip > svg { width: 13px; height: 13px; flex: none; color: var(--acc) }\n.clip .cl { flex: 1; min-width: 0; font-size: 11px; color: var(--txt2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis }\n.clip .cn { color: var(--txt); font-weight: 600 }\n.clip .pb2 { height: 24px; padding: 0 11px; border-radius: 7px; background: var(--acc); color: #fff;\n  font-size: 11px; font-weight: 600 }\n.dock { flex: none; display: flex; align-items: center; gap: 2px; height: 42px; padding: 0 8px;\n  border-top: 1px solid var(--rule) }\n.dock .sp { flex: 1 }\n.hintbar { font-size: 9.5px; color: var(--txt3); letter-spacing: .1em }\n\n/* ---------------- motion ---------------- */\n:host { --ez: cubic-bezier(.22,.86,.16,1) }\n\n/* prop open/close: real height animation, both directions */\n.pbwrap { display: grid; grid-template-rows: 1fr; opacity: 1; visibility: visible;\n  transition: grid-template-rows .32s var(--ez), opacity .24s var(--ez), visibility 0s }\n.pbwrap > .pbi { overflow: hidden; min-height: 0; padding: 0 }\n.pbwrap[data-collapsed] { grid-template-rows: 0fr; opacity: 0; pointer-events: none; visibility: hidden;\n  transition: grid-template-rows .28s var(--ez), opacity .18s var(--ez), visibility 0s .28s }\n\n/* direction-aware category change + staggered rows */\n@keyframes slideA { from { opacity: 0; transform: translateX(var(--dx, 22px)) } to { opacity: 1; transform: none } }\n@keyframes slideB { from { opacity: 0; transform: translateX(var(--dx, 22px)) } to { opacity: 1; transform: none } }\n.tray-b.sa { animation: slideA .34s var(--ez) both }\n.tray-b.sb { animation: slideB .34s var(--ez) both }\n@keyframes popRow { from { opacity: 0; transform: translateY(7px) } to { opacity: 1; transform: none } }\n.tray-b.sa .piece, .tray-b.sb .piece { animation: popRow .4s var(--ez) both;\n  animation-delay: calc(var(--i, 0) * 26ms) }\n@keyframes labA { from { opacity: 0; transform: translateX(calc(var(--dx, 22px) * .5)) } to { opacity: 1; transform: none } }\n@keyframes labB { from { opacity: 0; transform: translateX(calc(var(--dx, 22px) * .5)) } to { opacity: 1; transform: none } }\n.plabel.la { animation: labA .3s var(--ez) both }\n.plabel.lb { animation: labB .3s var(--ez) both }\n\n/* menus + clipboard */\n@keyframes menuIn { from { opacity: 0; transform: translateY(-7px) scale(.97) } to { opacity: 1; transform: none } }\n.menu { transform-origin: top center; animation: menuIn .2s var(--ez) both }\n@keyframes rise { from { opacity: 0; transform: translateY(12px) } to { opacity: 1; transform: none } }\n.clip { animation: rise .3s var(--ez) both }\n\n/* expandable dock buttons — icon grows into a label on hover */\n.dock .ib.xp { display: flex; align-items: center; width: auto; min-width: 26px; padding: 0 5px; height: 26px }\n.ib.xp .g { width: 15px; height: 15px; display: grid; place-items: center; flex: none }\n.ib.xp .g > svg { width: 13px; height: 13px }\n.ib.xp .lb { max-width: 0; opacity: 0; overflow: hidden; white-space: nowrap; font-size: 11px; font-weight: 600;\n  transition: max-width .32s var(--ez), opacity .2s var(--ez), margin .32s var(--ez) }\n.ib.xp:hover .lb { max-width: 68px; opacity: 1; margin-left: 6px; margin-right: 2px }\n.ib.xp.dg:hover { background: rgba(255,90,78,.16); color: #FF5A4E; border-color: rgba(255,90,78,.4) }\n\n/* micro-interactions */\n.pdot { transition: background .2s var(--ez), box-shadow .24s var(--ez), transform .24s var(--ez) }\n.pdot.on { transform: scale(1.12) }\n.piece:not(.pop):hover { background: rgba(255,255,255,.03) }\n.ins.light .piece:not(.pop):hover { background: rgba(255,255,255,.45) }\n.pic { transition: color .18s var(--ez), transform .28s var(--ez) }\n.piece.exp .pic { transform: scale(1.06) }\n.opt, .slot, .ib, .tb, .anc-g button { transition-timing-function: var(--ez) }\n.nt, .node-h .chev { transition: opacity .2s var(--ez) }\n\n@media (prefers-reduced-motion: reduce) {\n  .ins *, .ins *::before, .ins *::after {\n    animation-duration: .01ms !important; animation-delay: 0s !important; transition-duration: .01ms !important }\n}\n`,\n\n  render(d2) {\n    const model = d2.state.model || (d2.param('model') || DEFAULT_MODEL)\n    const tab = d2.state.tab || 'all'\n    const light = String(d2.param('appearance', 'dark')).toLowerCase() === 'light'\n    const accent = d2.param('accentHex', '')\n    const st = { sel: d2.state.sel, cut: d2.state.cut, drag: d2.state.drag, pOpen: d2.state.pOpen,\n      menuFor: d2.state.menuFor, tplFor: d2.state.tplFor }\n    const drop = d2.state.drop\n    const q = (d2.state.q || '').trim().toLowerCase()\n    const nodes = d2.state.nodes || {}\n    const anim = d2.state.anim || 0\n    let total = 0\n    model.forEach(t => { total += (t.pieces || []).length })\n\n    const trays = model.map(t => {\n      const nodeOpen = nodes[t.id] !== false\n      const pieces = (t.pieces || []).filter(p => q ? pieceMatches(p, q) : (tab === 'all' || (p.cat || 'style') === tab))\n      let inner = ''\n      pieces.forEach((p, i) => {\n        if (drop && drop.tray === t.id && drop.index === i) inner += '<div class=\"drop\"></div>'\n        inner += renderPiece(p, t, st, i)\n      })\n      if (drop && drop.tray === t.id && drop.index >= pieces.length) inner += '<div class=\"drop\"></div>'\n      return '<div class=\"tray' + (nodeOpen ? ' open' : '') + (drop && drop.tray === t.id ? ' over' : '') + '\" data-tray=\"' + esc(t.id) + '\">' +\n        '<div class=\"node\"><span class=\"nrule l\"></span>' +\n        '<button class=\"node-h\" data-act=\"node\" data-tray=\"' + esc(t.id) + '\">' +\n        '<span class=\"nt\">' + esc(t.title) + '</span><span class=\"chev\">' + I.tri + '</span></button>' +\n        '<span class=\"nrule r\"></span>' +\n        '<span class=\"nn\">' + pieces.length + '</span>' +\n        '<span class=\"ntools\">' +\n        '<button class=\"ib add\" data-act=\"add-open\" data-tray=\"' + esc(t.id) + '\" title=\"Add property\">' + I.plus + '</button>' +\n        '<button class=\"ib\" data-act=\"paste-tray\" data-tray=\"' + esc(t.id) + '\" title=\"Paste here\">' + I.paste + '</button>' +\n        '</span>' +\n        (d2.state.addFor === t.id ? renderAddMenu(t) : '') +\n        '</div>' +\n        (nodeOpen ? '<div class=\"tray-b ' + (anim % 2 ? 'sa' : 'sb') + '\">' + (inner || '<div class=\"empty\">' + (q ? 'No matches' : 'No properties in this category') + '</div>') + '</div>' : '') +\n        '</div>'\n    }).join('')\n\n    const clip = d2.state.clip\n    const clipBar = clip ? '<div class=\"clip\">' + I.paste +\n      '<span class=\"cl\">' + (clip.mode === 'piece'\n        ? '<span class=\"cn\">' + esc(clip.piece.title) + '</span> ' + (d2.state.cut ? 'cut' : 'copied')\n        : '<span class=\"cn\">' + esc(clip.value) + '</span> ' + esc(clip.type)) + '</span>' +\n      '<button class=\"pb2\" data-act=\"paste\">Paste</button>' +\n      '<button class=\"ib\" data-act=\"clip-clear\" title=\"Clear\">' + I.trash + '</button></div>' : ''\n\n    return '<div class=\"ins' + (light ? ' light' : '') + (d2.state.wide ? ' wide' : '') + '\" style=\"--dx:' + ((d2.state.dir || 1) > 0 ? 22 : -22) + 'px' + (accent ? ';--acc:' + esc(accent) : '') + '\">' +\n      '<div class=\"tbar\"><span class=\"t\">' + esc(d2.param('panelTitle', 'Inspector')) + '</span>' +\n      '<button class=\"ib\" data-act=\"noop\" title=\"Filter\">' + I.search + '</button>' +\n      '<button class=\"ib' + (d2.state.wide ? ' on' : '') + '\" data-act=\"expand\" title=\"Expand\">' + I.plus + '</button></div>' +\n\n      '<div class=\"body\">' +\n\n      '<div class=\"subj\"><span class=\"tile\">' + I.cube + '</span>' +\n      '<span class=\"m\"><span class=\"n\">' + esc(d2.param('subjectTitle', 'Block')) + '</span>' +\n      '<span class=\"p\">' + esc(d2.param('subjectType', 'data2-core-primitives@Block')) + '</span></span>' +\n      '<span class=\"pp\">' + total + ' PROPS</span></div>' +\n\n      '<div class=\"ident\">' +\n      '<div class=\"ir\"><span class=\"ik\">Component ID</span><span class=\"iv\">' + esc(d2.param('subjectId', '6a7a79932add8bd2c1a66f18')) + '</span></div>' +\n      '<div class=\"ir\"><span class=\"ik\">Reference</span><input spellcheck=\"false\" placeholder=\"auto\"></div>' +\n      '<div class=\"ir\"><span class=\"ik\">Display name</span><input spellcheck=\"false\" value=\"' + esc(d2.param('subjectName', '')) + '\" placeholder=\"Untitled\"></div>' +\n      '</div>' +\n\n      renderPill(model, tab, d2.state.catMode, d2.state.q || '', anim) +\n      (trays.trim() ? trays : '<div class=\"empty\">No properties</div>') +\n      '</div>' +\n      clipBar +\n      '<div class=\"dock\">' +\n      '<button class=\"ib xp\" data-act=\"copy-sel\" title=\"Copy\"><span class=\"g\">' + I.copy + '</span><span class=\"lb\">Copy</span></button>' +\n      '<button class=\"ib xp\" data-act=\"cut-sel\" title=\"Cut\"><span class=\"g\">' + I.cut + '</span><span class=\"lb\">Cut</span></button>' +\n      '<button class=\"ib xp\" data-act=\"paste\" title=\"Paste\"><span class=\"g\">' + I.paste + '</span><span class=\"lb\">Paste</span></button>' +\n      '<span class=\"sp\"></span>' +\n      '<button class=\"ib xp\" data-act=\"reset-all\" title=\"Reset all\"><span class=\"g\">' + I.undo + '</span><span class=\"lb\">Reset</span></button>' +\n      '<button class=\"ib xp dg\" data-act=\"del-sel\" title=\"Remove\"><span class=\"g\">' + I.trash + '</span><span class=\"lb\">Remove</span></button>' +\n      '</div></div>'\n  },\n\n  mounted(d2) {\n    let clickTimer = null\n    d2.onUnmount(() => { if (clickTimer) clearTimeout(clickTimer) })\n    const base = () => d2.param('model') || DEFAULT_MODEL\n    const M = () => d2.state.model || base()\n    const commit = m => d2.setState({ model: m })\n    function removeById(m, id) { m.forEach(t => { t.pieces = (t.pieces || []).filter(p => p.id !== id) }) }\n\n    function copyFrom(sel, cut) {\n      if (!sel) return\n      const hit = findPiece(M(), sel.piece)\n      if (!hit) return\n      if (sel.type === 'piece') d2.setState({ clip: { mode: 'piece', kind: hit.piece.kind, piece: clone(hit.piece) }, cut: cut ? sel.piece : null })\n      else d2.setState({ clip: { mode: 'slot', type: sel.stype, value: getAt(hit.piece.v, sel.path) }, cut: null })\n    }\n\n    function paste(trayId) {\n      const clip = d2.state.clip\n      if (!clip) return\n      const m = clone(M())\n      const sel = d2.state.sel\n      if (clip.mode === 'slot') {\n        if (!sel || sel.type !== 'slot' || sel.stype !== clip.type) return\n        const hit = findPiece(m, sel.piece)\n        if (!hit) return\n        setAt(hit.piece.v, sel.path, clip.value)\n        commit(m)\n        return\n      }\n      if (!trayId && sel && sel.type === 'piece') {\n        const hit = findPiece(m, sel.piece)\n        if (hit && hit.piece.kind === clip.kind && hit.piece.id !== clip.piece.id) {\n          hit.piece.v = clone(clip.piece.v)\n          if (d2.state.cut) removeById(m, d2.state.cut)\n          commit(m)\n          d2.setState({ cut: null })\n          return\n        }\n      }\n      let target = trayId\n      if (!target && sel) { const h = findPiece(m, sel.piece); if (h) target = h.tray.id }\n      if (!target) target = m[0] && m[0].id\n      const tray = m.filter(t => t.id === target)[0]\n      if (!tray) return\n      const fresh = clone(clip.piece)\n      if (d2.state.cut) { removeById(m, d2.state.cut); fresh.id = clip.piece.id }\n      else fresh.id = nextId(m)\n      tray.pieces = tray.pieces || []\n      tray.pieces.push(fresh)\n      commit(m)\n      d2.setState({ cut: null, sel: { type: 'piece', piece: fresh.id } })\n    }\n\n    function duplicate(id) {\n      const m = clone(M())\n      const hit = findPiece(m, id)\n      if (!hit) return\n      const fresh = clone(hit.piece)\n      fresh.id = nextId(m)\n      hit.tray.pieces.splice(hit.pi + 1, 0, fresh)\n      commit(m)\n      d2.setState({ sel: { type: 'piece', piece: fresh.id } })\n    }\n\n    d2.on('[data-sld]', 'pointerdown', e => {\n      const el = e.target.closest('[data-sld]')\n      if (!el) return\n      const parts = el.getAttribute('data-sld').split('|')\n      const min = Number(parts[2]) || 0\n      const max = Number(parts[3]) || 100\n      const move = ev => {\n        const r = el.getBoundingClientRect()\n        const t = Math.max(0, Math.min(1, (ev.clientX - r.left) / (r.width || 1)))\n        const m = clone(M())\n        const hit = findPiece(m, parts[0])\n        if (!hit) return\n        setAt(hit.piece.v, parts[1], Math.round(min + t * (max - min)))\n        commit(m)\n      }\n      move(e)\n      const up = () => { window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up) }\n      window.addEventListener('pointermove', move)\n      window.addEventListener('pointerup', up)\n    })\n\n    d2.on('[data-slot]', 'pointerdown', e => {\n      const el = e.target.closest('[data-slot]')\n      if (!el) return\n      const parts = el.getAttribute('data-slot').split('|')\n      d2.setState({ sel: { type: 'slot', piece: parts[0], path: parts[1], stype: parts[2] } })\n    })\n\n    d2.on('[data-act]', 'click', e => {\n      const el = e.target.closest('[data-act]')\n      if (!el) return\n      const act = el.getAttribute('data-act')\n      const pid = el.getAttribute('data-piece')\n      if (act === 'expand') { d2.setState({ wide: !d2.state.wide }); return }\n      if (d2.state.menuFor && act !== 'menu') d2.setState({ menuFor: null })\n      if (act === 'select') {\n        const op = Object.assign({}, d2.state.pOpen || {})\n        op[pid] = true\n        d2.setState({ sel: { type: 'piece', piece: pid }, pOpen: op })\n        return\n      }\n      if (act === 'pexp') {\n        const hit0 = findPiece(M(), pid)\n        if (!hit0) return\n        const ref0 = hit0.piece.def !== undefined ? hit0.piece.def : (TEMPLATES[hit0.piece.kind] ? TEMPLATES[hit0.piece.kind].v : null)\n        const pop0 = !ref0 || JSON.stringify(ref0) !== JSON.stringify(hit0.piece.v)\n        const cur = d2.state.pOpen || {}\n        const isOpen = cur[pid] !== undefined ? cur[pid] : pop0\n        const nx = Object.assign({}, cur)\n        nx[pid] = !isOpen\n        d2.setState({ pOpen: nx })\n        return\n      }\n      if (act === 'menu') { d2.setState({ menuFor: d2.state.menuFor === pid ? null : pid }); return }\n      if (act === 'fx') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        if (hit.piece.v.__fx != null) delete hit.piece.v.__fx\n        else hit.piece.v.__fx = ''\n        commit(m)\n        return\n      }\n      if (act === 'tpl-open') { d2.setState({ tplFor: d2.state.tplFor === pid ? null : pid }); return }\n      if (act === 'tpl-pick') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        hit.piece.v.r = String(el.getAttribute('data-r')).split(',').map(Number)\n        commit(m)\n        d2.setState({ tplFor: null })\n        return\n      }\n      if (act === 'list-add') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        const cur = hit.piece.v.items || []\n        hit.piece.v.items = cur.concat([{ name: hit.piece.title + ' ' + (cur.length + 1) }])\n        commit(m)\n        return\n      }\n      if (act === 'list-del') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        const ix = Number(el.getAttribute('data-idx'))\n        hit.piece.v.items = (hit.piece.v.items || []).filter((x, i) => i !== ix)\n        commit(m)\n        return\n      }\n      if (act === 'setbool') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        setAt(hit.piece.v, el.getAttribute('data-path'), el.getAttribute('data-val') === '1')\n        commit(m)\n        return\n      }\n      if (act === 'paste-into') {\n        const cb = d2.state.clip\n        if (!cb || cb.mode !== 'piece') return\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit || hit.piece.kind !== cb.kind) return\n        hit.piece.v = clone(cb.piece.v)\n        commit(m)\n        return\n      }\n      if (act === 'reset-piece') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        if (hit.piece.def !== undefined) hit.piece.v = clone(hit.piece.def)\n        commit(m)\n        return\n      }\n      if (act === 'node') {\n        const id = el.getAttribute('data-tray')\n        const nx = Object.assign({}, d2.state.nodes || {})\n        nx[id] = nx[id] === false\n        d2.setState({ nodes: nx })\n        return\n      }\n      if (act === 'add-open') {\n        const id = el.getAttribute('data-tray')\n        d2.setState({ addFor: d2.state.addFor === id ? null : id })\n        return\n      }\n      if (act === 'add-pick') {\n        const tpl = TEMPLATES[el.getAttribute('data-kind')]\n        if (!tpl) return\n        const m = clone(M())\n        const tray = m.filter(t => t.id === el.getAttribute('data-tray'))[0]\n        if (!tray) return\n        const fresh = clone(tpl)\n        fresh.id = nextId(m)\n        tray.pieces = tray.pieces || []\n        tray.pieces.push(fresh)\n        commit(m)\n        d2.setState({ addFor: null, sel: { type: 'piece', piece: fresh.id }, tab: 'all' })\n        return\n      }\n      if (act === 'cat-prev' || act === 'cat-next') {\n        const ids = TABS.map(t => t.id)\n        let i = ids.indexOf(d2.state.tab || 'all')\n        if (i < 0) i = 0\n        i = (i + (act === 'cat-next' ? 1 : -1) + ids.length) % ids.length\n        d2.setState({ tab: ids[i], catMode: null, q: '', dir: act === 'cat-next' ? 1 : -1, anim: (d2.state.anim || 0) + 1 })\n        return\n      }\n      if (act === 'cat-open') {\n        if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; return }\n        clickTimer = setTimeout(() => {\n          clickTimer = null\n          d2.setState({ catMode: d2.state.catMode === 'list' ? null : 'list' })\n        }, 210)\n        return\n      }\n      if (act === 'cat-pick') {\n        const ids = TABS.map(t => t.id)\n        const nv = el.getAttribute('data-val')\n        const fwd = ids.indexOf(nv) >= ids.indexOf(d2.state.tab || 'all')\n        d2.setState({ tab: nv, catMode: null, q: '', dir: fwd ? 1 : -1, anim: (d2.state.anim || 0) + 1 })\n        return\n      }\n      if (act === 'cat-close') { d2.setState({ catMode: null, q: '' }); return }\n      if (act === 'copy') { d2.setState({ sel: { type: 'piece', piece: pid } }); copyFrom({ type: 'piece', piece: pid }, false); return }\n      if (act === 'cut') { d2.setState({ sel: { type: 'piece', piece: pid } }); copyFrom({ type: 'piece', piece: pid }, true); return }\n      if (act === 'dup') { duplicate(pid); return }\n      if (act === 'del') { const m = clone(M()); removeById(m, pid); commit(m); d2.setState({ sel: null }); return }\n      if (act === 'copy-sel') { copyFrom(d2.state.sel, false); return }\n      if (act === 'cut-sel') { copyFrom(d2.state.sel, true); return }\n      if (act === 'paste') { paste(null); return }\n      if (act === 'paste-tray') { paste(el.getAttribute('data-tray')); return }\n      if (act === 'clip-clear') { d2.setState({ clip: null, cut: null }); return }\n      if (act === 'del-sel') {\n        const sel = d2.state.sel\n        if (sel && sel.type === 'piece') { const m = clone(M()); removeById(m, sel.piece); commit(m); d2.setState({ sel: null }) }\n        return\n      }\n      if (act === 'reset-all') { d2.setState({ model: clone(base()), sel: null, clip: null, cut: null }); return }\n      if (act === 'flag') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        const path = el.getAttribute('data-path')\n        setAt(hit.piece.v, path, !getAt(hit.piece.v, path))\n        commit(m)\n        return\n      }\n      if (act === 'anchor') {\n        const m = clone(M())\n        const hit = findPiece(m, pid)\n        if (!hit) return\n        hit.piece.v.anchor[el.getAttribute('data-side')] = !hit.piece.v.anchor[el.getAttribute('data-side')]\n        commit(m)\n      }\n    })\n\n    d2.on('input', 'input', e => {\n      const el = e.target\n      const spec = el.getAttribute && el.getAttribute('data-in')\n      if (!spec) {\n        if (el.classList && el.classList.contains('pq')) d2.setState({ q: el.value })\n        return\n      }\n      const parts = spec.split('|')\n      const m = clone(M())\n      const hit = findPiece(m, parts[0])\n      if (!hit) return\n      let val = el.value\n      if (parts[2] === 'num') { const n = parseFloat(val); val = isNaN(n) ? 0 : n }\n      setAt(hit.piece.v, parts[1], val)\n      commit(m)\n    })\n\n    d2.on('select', 'change', e => {\n      const el = e.target\n      const spec = el.getAttribute && el.getAttribute('data-in')\n      if (!spec) return\n      const parts = spec.split('|')\n      const m = clone(M())\n      const hit = findPiece(m, parts[0])\n      if (!hit) return\n      setAt(hit.piece.v, parts[1], el.value)\n      commit(m)\n    })\n\n    d2.on('[data-act=\"cat-open\"]', 'dblclick', () => {\n      if (clickTimer) { clearTimeout(clickTimer); clickTimer = null }\n      d2.setState({ catMode: 'search', q: '' })\n    })\n\n    d2.on('.pq', 'keydown', e => {\n      if (e.key === 'Escape') { e.stopPropagation(); d2.setState({ catMode: null, q: '' }) }\n    })\n\n    d2.on('[data-grip]', 'pointerdown', e => {\n      const g = e.target.closest('[data-grip]')\n      if (!g) return\n      if (e.target.closest('.ptools') || e.target.closest('.pdot') || e.target.closest('.menu')) return\n      const id = g.getAttribute('data-grip')\n      let started = false\n      const startY = e.clientY\n      const move = ev => {\n        if (!started && Math.abs(ev.clientY - startY) < 4) return\n        if (!started) { started = true; d2.setState({ drag: id, sel: { type: 'piece', piece: id } }) }\n        const rows = d2.$$('.piece').map(el => ({\n          tray: el.getAttribute('data-tray'), id: el.getAttribute('data-piece'), r: el.getBoundingClientRect()\n        }))\n        let best = null\n        const wide = !!d2.state.wide\n        rows.forEach(row => {\n          if (row.id === id) return\n          const mid = row.r.top + row.r.height / 2\n          const cx = row.r.left + row.r.width / 2\n          const dy = ev.clientY - mid\n          const d = wide ? Math.sqrt((ev.clientX - cx) * (ev.clientX - cx) + dy * dy) : Math.abs(dy)\n          if (!best || d < best.d) best = { d: d, row: row, after: dy > 0 }\n        })\n        if (!best) return\n        const m = M()\n        const tray = m.filter(t => t.id === best.row.tray)[0]\n        if (!tray) return\n        let idx = (tray.pieces || []).findIndex(p => p.id === best.row.id)\n        if (best.after) idx += 1\n        const cur = d2.state.drop\n        if (!cur || cur.tray !== tray.id || cur.index !== idx) d2.setState({ drop: { tray: tray.id, index: idx } })\n      }\n      const up = () => {\n        window.removeEventListener('pointermove', move)\n        window.removeEventListener('pointerup', up)\n        const drop = d2.state.drop\n        if (started && drop) {\n          const m = clone(M())\n          const hit = findPiece(m, id)\n          if (hit) {\n            const moved = clone(hit.piece)\n            hit.tray.pieces.splice(hit.pi, 1)\n            const tray = m.filter(t => t.id === drop.tray)[0]\n            if (tray) {\n              let idx = drop.index\n              if (hit.tray.id === tray.id && hit.pi < idx) idx -= 1\n              tray.pieces.splice(Math.max(0, Math.min(idx, tray.pieces.length)), 0, moved)\n              commit(m)\n            }\n          }\n        }\n        d2.setState({ drag: null, drop: null })\n      }\n      window.addEventListener('pointermove', move)\n      window.addEventListener('pointerup', up)\n    })\n\n    d2.on(window, 'pointerdown', e => {\n      if (!d2.state.catMode && !d2.state.addFor && !d2.state.menuFor && !d2.state.tplFor) return\n      if (d2.state.tplFor) d2.setState({ tplFor: null })\n      const path = e.composedPath ? e.composedPath() : []\n      let inPill = false, inAdd = false, inMenu = false\n      for (let i = 0; i < path.length; i++) {\n        const el = path[i]\n        if (!el || !el.classList) continue\n        if (el.classList.contains('tplm') || el.classList.contains('ctpl')) return\n        if (el.classList.contains('p-m') || el.classList.contains('tb')) inMenu = true\n        else if (el.classList.contains('add') || el.classList.contains('add-m')) inAdd = true\n        else if (el.classList.contains('pill') || el.classList.contains('cats')) inPill = true\n      }\n      if (d2.state.menuFor && !inMenu) d2.setState({ menuFor: null })\n      if (d2.state.addFor && !inAdd) d2.setState({ addFor: null })\n      if (!d2.state.catMode || inPill) return\n      if (d2.state.catMode === 'search' && (d2.state.q || '').trim()) return\n      d2.setState({ catMode: null })\n    })\n\n    d2.on(window, 'keydown', e => {\n      const path = e.composedPath ? e.composedPath() : []\n      const t = path[0] || e.target\n      const tag = t && t.tagName\n      if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return\n      if (!(e.metaKey || e.ctrlKey)) {\n        if (e.key === 'Escape') d2.setState({ sel: null, cut: null, catMode: null, addFor: null, menuFor: null, tplFor: null })\n        return\n      }\n      const k = String(e.key).toLowerCase()\n      if (k === 'c') { e.preventDefault(); copyFrom(d2.state.sel, false) }\n      else if (k === 'x') { e.preventDefault(); copyFrom(d2.state.sel, true) }\n      else if (k === 'v') { e.preventDefault(); paste(null) }\n      else if (k === 'd') {\n        e.preventDefault()\n        const sel = d2.state.sel\n        if (sel && sel.type === 'piece') duplicate(sel.piece)\n      }\n    })\n  },\n\n  updated(d2) {\n    if (d2.state.catMode === 'search') {\n      if (FOCUSED.has(d2)) return\n      const el = d2.$('.pq')\n      if (el) { FOCUSED.add(d2); el.focus() }\n    } else FOCUSED.delete(d2)\n  }\n}\n"}},"panelTitle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"panelTitle"}}}},"subjectTitle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectTitle"}}}},"subjectType":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectType"}}}},"subjectId":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectId"}}}},"subjectName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"subjectName"}}}},"model":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"model"}}}},"appearance":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"appearance"}}}},"accentHex":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accentHex"}}}}}}}}}},{"_id":"6a876fee180c011e24d4159b","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"component":"smoothScrollIntoView","package":"lucca-office","definition":{"component":"data2-code-components@CodeFn","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default (params, d2) => {\n  if (typeof document === 'undefined') return false\n  document.title = 'INVOKED ' + (params && params.id)\n\n  const {\n    id,\n    alignment = 'start',\n    behavior = 'smooth',\n    duration = 450,\n    offset = 0,\n  } = params || {}\n\n  const el = document.getElementById(id)\n  if (!el) return false\n\n  if (behavior !== 'smooth') {\n    el.scrollIntoView({ block: alignment, behavior: 'auto' })\n    return true\n  }\n\n  const findScrollParent = (node) => {\n    let p = node.parentElement\n    while (p) {\n      const style = getComputedStyle(p)\n      const canScrollY = /(auto|scroll)/.test(style.overflowY) && p.scrollHeight > p.clientHeight\n      if (canScrollY) return p\n      p = p.parentElement\n    }\n    return document.scrollingElement || document.documentElement\n  }\n\n  const container = findScrollParent(el)\n  const usesWindow = container === document.scrollingElement || container === document.documentElement\n  const containerRect = usesWindow\n    ? { top: 0, bottom: window.innerHeight, height: window.innerHeight }\n    : container.getBoundingClientRect()\n  const elRect = el.getBoundingClientRect()\n  const currentScroll = usesWindow ? window.scrollY : container.scrollTop\n\n  const topWithin = elRect.top - containerRect.top + currentScroll\n  const bottomWithin = elRect.bottom - containerRect.bottom + currentScroll\n\n  let targetTop\n  if (alignment === 'end') {\n    targetTop = bottomWithin\n  } else if (alignment === 'center') {\n    targetTop = topWithin - (containerRect.height - elRect.height) / 2\n  } else if (alignment === 'nearest') {\n    const alreadyVisible = elRect.top >= containerRect.top && elRect.bottom <= containerRect.bottom\n    targetTop = alreadyVisible ? currentScroll : (Math.abs(topWithin - currentScroll) < Math.abs(bottomWithin - currentScroll) ? topWithin : bottomWithin)\n  } else {\n    targetTop = topWithin\n  }\n  targetTop += offset\n\n  const maxScroll = usesWindow\n    ? document.documentElement.scrollHeight - window.innerHeight\n    : container.scrollHeight - container.clientHeight\n  targetTop = Math.max(0, Math.min(targetTop, Math.max(0, maxScroll)))\n\n  const startTop = currentScroll\n  const delta = targetTop - startTop\n  if (Math.abs(delta) < 1) return Promise.resolve(true)\n\n  const easeInOutCubic = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2)\n\n  const setScroll = (v) => {\n    if (usesWindow) window.scrollTo(0, v)\n    else container.scrollTop = v\n  }\n\n  return new Promise((resolve) => {\n    let startTime = null\n    const step = (now) => {\n      if (startTime === null) startTime = now\n      const elapsed = now - startTime\n      const p = Math.min(elapsed / duration, 1)\n      setScroll(startTop + delta * easeInOutCubic(p))\n      if (p < 1) requestAnimationFrame(step)\n      else resolve(true)\n    }\n    requestAnimationFrame(step)\n  })\n}"}},"id":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"id"}}}},"alignment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"alignment"}}}},"behavior":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"behavior"}}}},"duration":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"duration"}}}},"offset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"offset"}}}}}}}},{"_id":"6a8775e4ef426ca40f64192b","component":"data2-core-types@Component","system":"6a11143bae205cd35b1d5de0","params":{"extends":"data2-core-primitives@Block","component":"smoothScrollDemo","package":"lucca-office","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    .demo { height: 100%; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }\n    .scroller { height: 100%; overflow: auto; background: #f5f5f7; }\n    .header { position: sticky; top: 0; z-index: 2; height: 80px; background: #1e1b2e; color: #fff;\n      display: flex; align-items: center; justify-content: center; font-size: 26px; }\n    .nav { display: flex; gap: 10px; padding: 16px; }\n    .nav button { flex: 1; height: 50px; border: 0; border-radius: 10px; color: #fff; font-size: 15px;\n      cursor: pointer; font-family: inherit; }\n    .section { margin: 0 16px 16px 16px; height: 500px; border-radius: 16px; display: flex;\n      align-items: center; justify-content: center; color: #fff; font-size: 32px; font-weight: 600; }\n    .back { display: block; margin: 0 16px 16px 16px; height: 40px; width: 160px; border: 0;\n      border-radius: 10px; background: #1e1b2e; color: #fff; cursor: pointer; font-size: 13px; font-family: inherit; }\n  `,\n  render() {\n    return `<div class=\"demo\"><div class=\"scroller\" id=\"scroller\">\n      <div class=\"header\" id=\"top-anchor\">Smooth Scroll Demo</div>\n      <div class=\"nav\">\n        <button class=\"go\" data-target=\"section-1\" style=\"background:#7c4dff\">Go to Section 1</button>\n        <button class=\"go\" data-target=\"section-2\" style=\"background:#16a34a\">Go to Section 2</button>\n        <button class=\"go\" data-target=\"section-3\" style=\"background:#f97316\">Go to Section 3</button>\n      </div>\n      <div class=\"section\" id=\"section-1\" style=\"background:#7c4dff\">Section 1</div>\n      <button class=\"back\" data-target=\"top-anchor\">&uarr; Back to top</button>\n      <div class=\"section\" id=\"section-2\" style=\"background:#16a34a\">Section 2</div>\n      <button class=\"back\" data-target=\"top-anchor\">&uarr; Back to top</button>\n      <div class=\"section\" id=\"section-3\" style=\"background:#f97316\">Section 3</div>\n      <button class=\"back\" data-target=\"top-anchor\">&uarr; Back to top</button>\n    </div></div>`\n  },\n  mounted(d2) {\n    const smoothScrollIntoView = ({ id, alignment = 'start', duration = 600, offset = 0 }) => {\n      const container = d2.$('#scroller')\n      const el = d2.$('#' + id)\n      if (!container || !el) return\n      const containerRect = container.getBoundingClientRect()\n      const elRect = el.getBoundingClientRect()\n      const currentScroll = container.scrollTop\n      const topWithin = elRect.top - containerRect.top + currentScroll\n      const bottomWithin = elRect.bottom - containerRect.bottom + currentScroll\n      let targetTop\n      if (alignment === 'end') targetTop = bottomWithin\n      else if (alignment === 'center') targetTop = topWithin - (containerRect.height - elRect.height) / 2\n      else targetTop = topWithin\n      targetTop += offset\n      const maxScroll = container.scrollHeight - container.clientHeight\n      targetTop = Math.max(0, Math.min(targetTop, Math.max(0, maxScroll)))\n      const startTop = currentScroll\n      const delta = targetTop - startTop\n      if (Math.abs(delta) < 1) return\n      const easeInOutCubic = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2)\n      let startTime = null\n      const step = (now) => {\n        if (startTime === null) startTime = now\n        const p = Math.min((now - startTime) / duration, 1)\n        container.scrollTop = startTop + delta * easeInOutCubic(p)\n        if (p < 1) requestAnimationFrame(step)\n      }\n      requestAnimationFrame(step)\n    }\n    d2.on('.go', 'click', (e, el) => smoothScrollIntoView({ id: el.dataset.target, alignment: 'start' }))\n    d2.on('.back', 'click', (e, el) => smoothScrollIntoView({ id: el.dataset.target, alignment: 'start' }))\n  },\n}"}}}}}}}},{"_id":"5ecd05ded68ce41360f74b43","component":"data2-core-types@Component","params":{"extendable":{"placeholder":"children"},"component":"Block","package":"data2-core-primitives"}},{"_id":"60019e844369e2af95919c94","component":"data2-core-types@Component","params":{"definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"html@iframe","params":{"frameBorder":"0","src":{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-types@Reference","params":{"reference":"builder"}}],"then":"about:blank","else":{"component":"data2-core-types@Reference","params":{"reference":"url"}}}},"allow":"autoplay","allowFullScreen":{"component":"data2-core-types@Reference","params":{"reference":"allowFullScreen"}},"loading":"lazy","style":{"position":"absolute","width":"100%","height":"100%","border":"none","padding":"none","overflow":"auto"}}}}},"component":"IframeBlock","package":"data2-blocks","extends":"data2-core-primitives@Block"}}]}},{"reference":"ExternalComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"_id":"5f060d9b1ca96def42f3c40a","displayName":"Builder AuthStrategy","component":"data2-core@AuthenticationStrategy","system":"5e3b796d6228227ca637d0f4","params":{"credentials":[{"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"status"}}],"verifier":{"component":"data2-core-types@DataSetReference","params":{"reference":"UsersDataSet"}},"sessionShared":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},"email":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},"resetPassword":{"resetEmail":{"artboard":{"params":{"preventEvaluation":true,"reference":"nR6Q9d7qdaPtZhJ28Q8Dy"},"component":"data2-core-types@ArtboardReference"}},"resetSuccessEmail":{"artboard":{"params":{"preventEvaluation":true,"reference":"yPCJBTKo8q1Mw0B_3Kpwl"},"component":"data2-core-types@ArtboardReference"}}},"session":[{"component":"data2-core-types@DataSetColumnReference","params":{"column":"name"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"status"}}],"sessionMetadata":{"params":{"actions":[{"params":{"compare":{"params":{"subject":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},{"params":{"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"active"}},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user.status"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is active user"}]},"component":"data2-core-logical-operators@And","displayName":"is active and has _id"}},"component":"data2-core-logical-operators@Not"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check $user"},{"params":{"action":{"params":{"pattern":{"component":"data2-core-primitives@String","params":{"content":"@data2.app"}},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user.email"}}}}},"component":"data2-core-functions@Includes","displayName":"isData2User"},"resultName":{"component":"data2-core-primitives@String","params":{"content":"is_data2_user"}}},"component":"data2-core-flow@Step"},{"params":{"action":{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.d2_app"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"management"}}]},"component":"data2-core-functions@Includes","displayName":"isData2App Exception"},"resultName":{"component":"data2-core-primitives@String","params":{"content":"is_data2_app_exception"}}},"component":"data2-core-flow@Step"},{"params":{"resultName":{"component":"data2-core-primitives@String","params":{"content":"has_access"}},"action":{"params":{"compare":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_user"}}}},{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_app_exception"}}}}]},"component":"data2-core-logical-operators@Or"},"else":{"params":{"actions":[{"params":{"actions":[{"params":{"compare":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}},"component":"data2-core-logical-operators@Not"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check $metadata.system"},{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":"filter","args":[{"_id":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}}]},"component":"data2-core-types@Interaction"},{"params":{"compare":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records.length"}}}}},"component":"data2-core-logical-operators@Not","displayName":"systems not found"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check system found"},{"params":{"actions":[{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":"filter","args":[{"user":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},"organization":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"cursor.current.organization._id"}}}}}]},"component":"data2-core-types@Interaction"},{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":"filter","args":[{"user":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},"system":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}}]},"component":"data2-core-types@Interaction"}]},"component":"data2-core-flow@Parallel"}]},"component":"data2-core-flow@Series","displayName":"common setup"},{"params":{"then":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"else":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"compare":{"params":{"components":{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"mapper":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"system"}}],"body":{"params":{"expression":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.visibility"}}}},"defaultCase":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"public"}},"value":{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"owner"}},{"component":"data2-core-primitives@String","params":{"content":"billing-owner"}},{"component":"data2-core-primitives@String","params":{"content":"admin"}},{"component":"data2-core-primitives@String","params":{"content":"member"}}]},"component":"data2-core-functions@Includes","displayName":"has role with access"}]},"component":"data2-core-logical-operators@And","displayName":"has org member role with access"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is org member"},{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"collaborator"}}},"component":"data2-core-comparison-operators@Equal","displayName":"is collaborator"}]},"component":"data2-core-logical-operators@And","displayName":"is org member and collaborator"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is collaborator"},{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"access"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"access.system._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"has system access"}]},"component":"data2-core-logical-operators@And","displayName":"is collaborator and has system access"}]},"component":"data2-core-logical-operators@Or"}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"private"}},"value":{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"owner"}},{"component":"data2-core-primitives@String","params":{"content":"admin"}}]},"component":"data2-core-functions@Includes","displayName":"is owner/admin"}]},"component":"data2-core-logical-operators@And","displayName":"has org member role with access"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is owner/admin"},{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is org member"},{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"access"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"access.system._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"has system access"}]},"component":"data2-core-logical-operators@And","displayName":"is member and has system access"}]},"component":"data2-core-logical-operators@Or","displayName":"is owner/admin or has system access"}}}]},"component":"data2-core-flow@Switch"}}}},"component":"data2-core-functions@ArrayMap"}},"component":"data2-core-logical-operators@And","displayName":"has org member for all systems in request"}},"component":"data2-core-flow@If"}]},"component":"data2-core-flow@Series"},"then":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}},"component":"data2-core-flow@If"}},"component":"data2-core-flow@Step"},{"params":{"then":{"system":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}},"else":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"},"compare":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"has_access"}}}}},"component":"data2-core-flow@If"}]},"component":"data2-core-flow@Series"},"sessionExpiresIn":{"component":"data2-core-primitives@Number","params":{"content":604800}},"sessionRenewal":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"identityProviders":[{"provider":{"component":"data2-core-primitives@String","params":{"content":"GOOGLE"}},"clientId":{"component":"data2-core-primitives@String","params":{"content":"55470861603-22pvfc1t49lp8cjnbfk71s5ct9hp1q1v.apps.googleusercontent.com"}}}]},"reference":"Data2BuilderAuthentication","updatedAt":"2026-08-06T19:31:23.557Z"},{"_id":"5f060ee51ca96def42f3fbd1","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"UsersTableDataSource"}},"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":1}}}},"reference":"UsersDataSet","updatedAt":"2026-03-04T21:16:40.291Z"},{"_id":"68f938cf5fb002e33752a43d","displayName":"InternalApplicationsAuthenticationStrategy","component":"data2-core@AuthenticationStrategy","system":"5e3b796d6228227ca637d0f4","params":{"resetPassword":{"resetEmail":{"artboard":{"params":{"preventEvaluation":true},"component":"data2-core-types@ArtboardReference"}},"resetSuccessEmail":{"artboard":{"params":{"preventEvaluation":true},"component":"data2-core-types@ArtboardReference"}}},"verifier":{"disabled":false,"component":"data2-core-types@DataSetReference","params":{"reference":"InternalApplicationsVerifierDataSet"}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"secret"}},"credentials":[{"component":"data2-core-types@FieldReference","params":{"key":"application"}},{"component":"data2-core-types@FieldReference","params":{"key":"secret"}},{"component":"data2-core-types@FieldReference","params":{"key":"status"}}],"session":[{"component":"data2-core-types@FieldReference","params":{"key":"application"}},{"component":"data2-core-types@FieldReference","params":{"key":"secret"}},{"component":"data2-core-types@FieldReference","params":{"key":"status"}}]},"reference":"InternalApplicationsAuthenticationStrategy","updatedAt":"2025-10-22T20:08:54.442Z","createdAt":"2025-10-22T20:04:31.198Z"},{"component":"data2-core@DataSet","reference":"SessionsDataSet","params":{"requiresConfirmation":false,"pk":"key","datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"data2sessionsdatasource"}}}},{"reference":"UsersTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@ServerSessionsDataSource","reference":"data2sessionsdatasource","params":{}},{"_id":"5f1600b70528b92601890f75","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ServiceComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ServiceComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:20.208Z","createdAt":"2020-07-20T20:38:15.436Z"},{"_id":"5f1602190528b92601890f7d","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":40000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentInstancesTableDataSource"}},"sort":{"_id":1},"pk":"_id","autoEdit":true,"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:07.089Z","createdAt":"2020-07-20T20:44:09.350Z"},{"_id":"5f16017f0528b92601890f7a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"FileComponentInstancesDataSet","updatedAt":"2023-08-03T16:08:18.193Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"5f1601990528b92601890f7b","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ApplicationsTableDataSource"}},"pk":"_id","autoEdit":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"params":{"column":"_id"},"component":"data2-core-types@DataSetColumnReference"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"ApplicationsDataSet","updatedAt":"2022-12-13T21:51:13.995Z","createdAt":"2020-07-20T20:42:01.657Z"},{"_id":"5f16172a0528b92601890f85","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":10000}}},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"DataComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"DataComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:12.831Z","createdAt":"2020-07-20T22:14:02.097Z"},{"_id":"60ddc1bbda3861ca65407c5a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"pagination":{"size":1},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"}},"reference":"OneFileComponentInstancesDataSet","updatedAt":"2022-02-09T19:44:38.036Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"63ee3d38b8c9838c62fb05fb","displayName":"DriveFileComponentInstancesDataSet","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core@Drive"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"DriveFileComponentInstancesDataSet","updatedAt":"2025-12-04T21:58:32.367Z","createdAt":"2023-02-16T14:27:07.797Z"},{"reference":"ServiceComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a6ccff72e237fe6dd6fdff3","component":"data2-rush@HTTPListener","reference":"secDataFilesProxy","system":"6a11143bae205cd35b1d5de0","params":{}}]}},{"reference":"ComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a66a1bc39c8071d2a80313c","component":"data2-core-primitives@Artboard","system":"6a11143bae205cd35b1d5de0","displayName":"Cooltive Home","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Cooltive Home"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":474,"y":-3086,"exactX":474.44,"exactY":-3085.66}},"componentClass":{"disabled":true,"component":"data2-core-types@ComponentClass","params":{}},"alternativeArtboards":{"params":{"mobile":{"disabled":false,"component":"data2-core-types@ArtboardReference","params":{"reference":"_jwuLrGluc6V6THFWaIFh"}}},"component":"data2-core-types@ResponsiveArtboards"},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Poppins"}}},"reference":"b8pjqmYpTCWFyfEsSghFL"},{"_id":"1e263266f9ccad5554cf001d","parent":"6a66a1bc39c8071d2a80313c","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"Page","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":3940}},"childrenMode":"rows","fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#f7f2e6"}}}}]}},"scroll":{"component":"data2-core-types@Scroll","params":{"y":true}}},"reference":"VKX2unppGUG9AUl1Hdlax"},{"_id":"6a66a416b768b226f05afd72","component":"data2-core-primitives@Artboard","system":"6a11143bae205cd35b1d5de0","displayName":"Cooltive Mobile","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Cooltive Mobile"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":390,"height":844}},"position":{"component":"data2-core-types@Position","params":{"x":-107,"y":-2126,"exactX":-106.68,"exactY":-2125.8}},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Poppins"}}},"reference":"_jwuLrGluc6V6THFWaIFh"},{"_id":"3b0bd92cd62ad24b9e8bebdf","parent":"1e263266f9ccad5554cf001d","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"Header","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"left":true,"exactX":false,"exactY":false,"top":true,"right":true,"growH":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":88}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#f7f2e6"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"width":{"top":"0","right":"0","bottom":"1","left":"0"},"color":"rgba(0,0,0,0.06)"}}},"index":0,"reference":"tZ79APS6mGDWlbHhqdbmp"},{"_id":"a7db8abdae117ac9a04c02e7","parent":"1e263266f9ccad5554cf001d","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"Hero","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":760}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"image":"https://cooltive-me-demo.vercel.app/_next/image?url=%2Fimages%2Finterior-1.jpg&w=3840&q=75","size":"cover","positionX":"center","positionY":"center","repeat":"no-repeat"}}]}}},"index":1,"reference":"t1_Zr2JAWp5NmGViPwcg7"},{"_id":"f54ed8ba9108f5fe21adba48","parent":"1e263266f9ccad5554cf001d","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"About","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":700}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#f7f2e6"}}}}]}}},"index":2,"reference":"4aJq3wAE-WRIO2eI3U2DO"},{"_id":"8a19664cfee54b053ffa36ba","parent":"1e263266f9ccad5554cf001d","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"Mood","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":560}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"image":"https://cooltive-me-demo.vercel.app/_next/image?url=%2Fimages%2Fgreenery-mood.jpg&w=3840&q=75","size":"cover","positionX":"center","positionY":"center","repeat":"no-repeat"}}]}}},"index":3,"reference":"QHy5CZ5RFwB2EpKAMC1Ek"},{"_id":"0c89355317b749f1ae0974af","parent":"1e263266f9ccad5554cf001d","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"Menu","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":1150}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#f7f2e6"}}}}]}}},"index":4,"reference":"nwMOLaDVssm8XCubog21U"},{"_id":"a57c6aa3e24f4aae465cf3a2","parent":"1e263266f9ccad5554cf001d","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocationSection","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":560}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#2b2620"}}}}]}}},"index":5,"reference":"9evCpWtyQ3rqCtr1QyfAi"},{"_id":"de01aef8e71ac283a0179a5c","parent":"1e263266f9ccad5554cf001d","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"FooterBar","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":110}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#241f1a"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"width":{"top":"1","right":"0","bottom":"0","left":"0"},"color":"rgba(255,255,255,0.08)"}}},"index":6,"reference":"9cIZxIGXXBAF3uAdDz8UM"},{"_id":"1b06a5f9a8e9cf2239ced5a7","parent":"6a66a416b768b226f05afd72","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"Page","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":390,"height":4174}},"childrenMode":"rows","fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#f7f2e6"}}}}]}},"scroll":{"component":"data2-core-types@Scroll","params":{"y":true}}},"reference":"xMbyavFApTa7BZ2iLJu9S"},{"_id":"35be1bba0199e57d4075d593","parent":"3b0bd92cd62ad24b9e8bebdf","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LogoMark","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":24,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":40,"height":40}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#eaf1de"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"component":"data2-core-types@FileComponentUrlReference","params":{"reference":"dey1FpgK57lLJF5zA24EJ"}},"size":"contain","positionX":"center","positionY":"center"}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"40","rightTop":"40","rightBottom":"40","leftBottom":"40"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"#4a7c3f"}}},"index":0,"reference":"bglDI4O9my6mizHn_XxIY"},{"_id":"665bdce6ffa55d61078c07f1","parent":"3b0bd92cd62ad24b9e8bebdf","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LogoName","params":{"position":{"component":"data2-core-types@Position","params":{"x":116,"y":22,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":200,"height":26,"growWidthWithContent":true}},"text":"cooltive","textSize":22,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-display"}},"prop":"textFormat"}},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Cormorant Garamond"}}},"index":1,"reference":"f2r3hv8cRlPhjyK2HtFSq"},{"_id":"2b880a8b1d8cdb49d4f30267","parent":"3b0bd92cd62ad24b9e8bebdf","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LogoSub","params":{"position":{"component":"data2-core-types@Position","params":{"x":116,"y":50,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":200,"height":14}},"text":"CAFÉ & PLANTAS","textSize":10,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#6b8a5a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-label"}},"prop":"textFormat"}},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Cormorant Garamond"}}},"index":2,"reference":"Elpkd3KAHe2laGXBZ_Bcl"},{"_id":"9858b196960833c495a11ede","parent":"3b0bd92cd62ad24b9e8bebdf","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"NavSobre","params":{"position":{"component":"data2-core-types@Position","params":{"x":760,"y":34,"freeTransform":true,"left":false,"right":true,"top":true,"bottom":false,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":60,"height":20}},"text":"Sobre","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#2a2a2a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":3,"reference":"pvETvkecxmp7yHtgqIXZP"},{"_id":"d2f978a7b6f349c2afc896c8","parent":"3b0bd92cd62ad24b9e8bebdf","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"NavCardapio","params":{"position":{"component":"data2-core-types@Position","params":{"x":850,"y":34,"freeTransform":true,"left":false,"right":true,"top":true,"bottom":false,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":80,"height":20}},"text":"Cardápio","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#2a2a2a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":4,"reference":"CSULfhfDHGnHyRvNEd3LY"},{"_id":"627a662258e7e371b806a902","parent":"3b0bd92cd62ad24b9e8bebdf","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"NavLocalizacao","params":{"position":{"component":"data2-core-types@Position","params":{"x":960,"y":34,"freeTransform":true,"left":false,"right":true,"top":true,"bottom":false,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":100,"height":20}},"text":"Localização","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#2a2a2a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":5,"reference":"Ooi9s6DCDM1sn5iPiBPxf"},{"_id":"7892f2e69b1cc560966e6e31","parent":"3b0bd92cd62ad24b9e8bebdf","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"CtaComoChegar","params":{"position":{"component":"data2-core-types@Position","params":{"x":1226,"y":22,"freeTransform":true,"left":false,"right":true,"top":true,"bottom":false,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":150,"height":44}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#4a7c3f"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"text":"Como chegar","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":6,"reference":"rpdBXhsVecOdwFUrRUuUN"},{"_id":"a88c72e76dc83cae7bb0cfc5","parent":"a7db8abdae117ac9a04c02e7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"HeroOverlay","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":760}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(20,15,10,0.5)"}}}}]}}},"index":0,"reference":"KSU3mTy899TCuwD5yf9PO"},{"_id":"adb50dab166f3dcbc4b308f1","parent":"a7db8abdae117ac9a04c02e7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"HeroBadge","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":76,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":220,"height":30,"growWidthWithContent":true}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.12)"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"20","rightTop":"20","rightBottom":"20","leftBottom":"20"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(255,255,255,0.35)"}},"text":"SOUSAS · CAMPINAS","textSize":11,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":1,"reference":"h9lg2fgRrbhUIWhlpYaqq"},{"_id":"84002e4998c15f48b232c182","parent":"a7db8abdae117ac9a04c02e7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"HeroTitle","params":{"position":{"component":"data2-core-types@Position","params":{"x":60,"y":130,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":520,"height":90}},"text":"Cooltive","textSize":68,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-display"}},"prop":"textFormat"}},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Cormorant Garamond"}}},"index":2,"reference":"Zz26vFWvhWjABPe6Ns3QP"},{"_id":"12b234a059c98bb5de0c371d","parent":"a7db8abdae117ac9a04c02e7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"HeroScript","params":{"position":{"component":"data2-core-types@Position","params":{"x":66,"y":216,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":320,"height":40}},"text":"café & plantas","textSize":28,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#9fce7f"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-script"}},"prop":"textFormat"}},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Cormorant Garamond"}}},"index":3,"reference":"d10VmeKuSYP0xEoGARRlH"},{"_id":"3d1d4f6f8f562079a851a956","parent":"a7db8abdae117ac9a04c02e7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"HeroParagraph","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":270,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":440,"height":80}},"text":"Café de especialidade, comidinhas de verdade e um cantinho verde pra respirar em Sousas.","textSize":16,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.88)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"lineHeight":1.5}}},"index":4,"reference":"BhkLahbeqE0YKf94pfIAt"},{"_id":"e83b1c256e0c149b8deb954c","parent":"a7db8abdae117ac9a04c02e7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"BtnVerCardapio","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":370,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":150,"height":48}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#6fae52"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"text":"Ver cardápio","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":5,"reference":"b1DdfLeQQ12Ha76bAvldw"},{"_id":"50e609a36e545e6d6c6055e0","parent":"a7db8abdae117ac9a04c02e7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"BtnComoChegar","params":{"position":{"component":"data2-core-types@Position","params":{"x":226,"y":370,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":150,"height":48}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(255,255,255,0.7)"}},"text":"Como chegar","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":6,"reference":"DsnQw8FgG9rAyBu2Jltzl"},{"_id":"f23f02fc39ef700c91cb5427","parent":"a7db8abdae117ac9a04c02e7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"HeroFrame","params":{"position":{"component":"data2-core-types@Position","params":{"x":980,"y":140,"freeTransform":true,"left":false,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":340,"height":460}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"image":"https://cooltive-me-demo.vercel.app/_next/image?url=%2Fimages%2Finterior-1.jpg&w=3840&q=75","size":"cover","positionX":"center","positionY":"center","repeat":"no-repeat"}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"4","rightTop":"4","rightBottom":"4","leftBottom":"4"},"width":{"left":"10","top":"10","right":"10","bottom":"10"},"color":"#d8c9a8"}}},"index":7,"reference":"IDrDpS3lyOEx2IXG6b2Y0"},{"_id":"159e899a8211c4355672e3a4","parent":"a7db8abdae117ac9a04c02e7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"ScrollHint","params":{"position":{"component":"data2-core-types@Position","params":{"x":620,"y":716,"freeTransform":true,"left":false,"right":false,"top":false,"bottom":true,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":200,"height":16}},"text":"ROLE A PÁGINA","textSize":10,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.6)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center"}}},"index":8,"reference":"JlYa1O6ErC54QNo2WT8tx"},{"_id":"8c24baf2d983c36d924987f9","parent":"f54ed8ba9108f5fe21adba48","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"AboutImage","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":80,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":440,"height":520}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"image":"https://cooltive-me-demo.vercel.app/_next/image?url=%2Fimages%2Finterior-1.jpg&w=3840&q=75","size":"cover","positionX":"center","positionY":"center","repeat":"no-repeat"}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"12","rightTop":"12","rightBottom":"12","leftBottom":"12"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}}},"index":0,"reference":"GBUg-7iMKKQ-fSmJP-IHv"},{"_id":"7201b5413cc86c7769118aad","parent":"f54ed8ba9108f5fe21adba48","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"AboutLabel","params":{"position":{"component":"data2-core-types@Position","params":{"x":560,"y":88,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":300,"height":30}},"text":"nosso cantinho","textSize":20,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#5f9146"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-script"}},"prop":"textFormat"}}},"index":1,"reference":"tMbYOwA3nQjQRkEXcDoer"},{"_id":"025a0b22f40951a949b157c5","parent":"f54ed8ba9108f5fe21adba48","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"AboutHeading","params":{"position":{"component":"data2-core-types@Position","params":{"x":560,"y":120,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":580,"height":100}},"text":"Café e plantas, no mesmo endereço","textSize":38,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true}}},"index":2,"reference":"vwFgP1mCaUGdpK75AuwYX"},{"_id":"14df2cc3396626245cd3f74a","parent":"f54ed8ba9108f5fe21adba48","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"AboutPara1","params":{"position":{"component":"data2-core-types@Position","params":{"x":560,"y":240,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":130}},"text":"O Cooltive nasceu em Sousas, Campinas, misturando duas paixões: café de verdade e plantas por todo canto. Cada mesa fica perto de uma folhagem diferente, a luz entra pelas janelas e o cardápio é pensado pra quem quer desacelerar.","textSize":16,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3a3a3a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"lineHeight":1.6}}},"index":3,"reference":"9OCygK9l_jmnzFuZ3l6wv"},{"_id":"afe6c949899ae78098cdd07c","parent":"f54ed8ba9108f5fe21adba48","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"AboutPara2","params":{"position":{"component":"data2-core-types@Position","params":{"x":560,"y":384,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":70}},"text":"Grãos selecionados, comidinhas feitas na casa e uma seleção de plantas à venda pra você levar um pedacinho do Cooltive pra casa.","textSize":16,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3a3a3a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"lineHeight":1.6}}},"index":4,"reference":"d2zFB6kCsIymaSVNaOcmg"},{"_id":"90e09b7e792e7be9b2435d1a","parent":"f54ed8ba9108f5fe21adba48","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"StatV60","params":{"position":{"component":"data2-core-types@Position","params":{"x":560,"y":490,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":180,"height":60}},"text":"V60","textSize":26,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3f6b34"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-display"}},"prop":"textFormat"}}},"index":5,"reference":"axrdxHy0Krch3950kYazR"},{"_id":"bebe4d003c9d241ccb75072a","parent":"f54ed8ba9108f5fe21adba48","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"StatV60Caption","params":{"position":{"component":"data2-core-types@Position","params":{"x":560,"y":530,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":200,"height":20}},"text":"& PRENSA FRANCESA","textSize":11,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#6a6a6a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-label"}},"prop":"textFormat"}}},"index":6,"reference":"gUwkvIV6H1bbytPRsPom_"},{"_id":"710eebf53672d649a82057f6","parent":"f54ed8ba9108f5fe21adba48","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"Stat100","params":{"position":{"component":"data2-core-types@Position","params":{"x":780,"y":490,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":180,"height":60}},"text":"100%","textSize":26,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3f6b34"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-display"}},"prop":"textFormat"}}},"index":7,"reference":"YWk_TCvVIxEepdBqXvxFY"},{"_id":"cfea6cd6c415672677fa3a9d","parent":"f54ed8ba9108f5fe21adba48","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"Stat100Caption","params":{"position":{"component":"data2-core-types@Position","params":{"x":780,"y":530,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":200,"height":20}},"text":"FEITO NA CASA","textSize":11,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#6a6a6a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-label"}},"prop":"textFormat"}}},"index":8,"reference":"AUWtjELgwPuOVvVFe7joW"},{"_id":"ee0fbff3fbda76689217427d","parent":"f54ed8ba9108f5fe21adba48","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"AboutBadge","params":{"position":{"component":"data2-core-types@Position","params":{"x":204,"y":480,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":300,"height":120}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#3f6b34"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"8","rightTop":"8","rightBottom":"8","leftBottom":"8"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}}},"index":9,"reference":"0LaNKV4men9Dz9xBmrDhe"},{"_id":"d0a528898188ca88b2bb7904","parent":"8a19664cfee54b053ffa36ba","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MoodOverlay","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":560}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(35,58,30,0.62)"}}}}]}}},"index":0,"reference":"_YApSEmwoJoSOwKz5AQel"},{"_id":"6955b573946dbbeeb17e0c60","parent":"8a19664cfee54b053ffa36ba","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MoodQuote","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":96,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":50}},"text":"um respiro entre as plantas","textSize":34,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center"}}},"index":1,"reference":"4LsAjJZyzMBDUGuKSBbNR"},{"_id":"3626c8bd9209a13538ef8515","parent":"8a19664cfee54b053ffa36ba","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MoodLabelLeft","params":{"position":{"component":"data2-core-types@Position","params":{"x":300,"y":330,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":260,"height":20}},"text":"CULTIVADO COM CALMA","textSize":11,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#c9dcb8"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-label"}},"prop":"textFormat"}}},"index":2,"reference":"PagJJ1gat1B_j3P3iXI1A"},{"_id":"323daa6d86582dd1ba34adc8","parent":"8a19664cfee54b053ffa36ba","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MoodTextLeft","params":{"position":{"component":"data2-core-types@Position","params":{"x":300,"y":356,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":320,"height":60}},"text":"Cada xícara é feita na hora, sem pressa.","textSize":20,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true}}},"index":3,"reference":"W-9w0txJvan0PKpsv1LKg"},{"_id":"cfc9c823d6b889211bbaa1d8","parent":"8a19664cfee54b053ffa36ba","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MoodIconCircle","params":{"position":{"component":"data2-core-types@Position","params":{"x":678,"y":316,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":84,"height":84}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#eaf1de"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"42","rightTop":"42","rightBottom":"42","leftBottom":"42"},"width":{"left":"4","top":"4","right":"4","bottom":"4"},"color":"rgba(255,255,255,0.5)"}}},"index":4,"reference":"6ZJOzfXRI95juQArrDgWf"},{"_id":"4defddde36401eda481f6e40","parent":"8a19664cfee54b053ffa36ba","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MoodIcon","params":{"position":{"component":"data2-core-types@Position","params":{"x":678,"y":316,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":84,"height":84}},"icon":{"component":"data2-core-web@Icon","params":{"icon":"fa6-solid fa6-seedling","iconSet":"fontawesome6"}},"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3f6b34"}},"textSize":30,"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":5,"reference":"ECowScTKWDKS5VlVbn4YS"},{"_id":"e9b98b33454fd32b8ac9bec9","parent":"8a19664cfee54b053ffa36ba","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MoodLabelRight","params":{"position":{"component":"data2-core-types@Position","params":{"x":860,"y":330,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":260,"height":20}},"text":"CULTIVADO COM PLANTAS","textSize":11,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#c9dcb8"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-label"}},"prop":"textFormat"}}},"index":6,"reference":"cy2IDaKPtLWbzoDzJcCT3"},{"_id":"3a3d72b8af820c6b95e20de6","parent":"8a19664cfee54b053ffa36ba","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MoodTextRight","params":{"position":{"component":"data2-core-types@Position","params":{"x":860,"y":356,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":340,"height":60}},"text":"E cada planta é cuidada por gente que gosta do que faz.","textSize":20,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true}}},"index":7,"reference":"5XfnjNXBBbmNzi8aSOP9r"},{"_id":"e6afe6f5e4047f045f2ee6b2","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MenuScript","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":56,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":30}},"text":"é tudo pão, mas não só","textSize":20,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#5f9146"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center"}}},"index":0,"reference":"rC61KPknyxdcuXsFMaLq_"},{"_id":"e5e8aa056f05892d3c2ece14","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MenuHeading","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":88,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":60}},"text":"Cardápio","textSize":44,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center"}}},"index":1,"reference":"Q4sOmc1gObeXG_RlXX2jJ"},{"_id":"0cabdebb7a0b33a37d0f4c77","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"TabComidinhas","params":{"position":{"component":"data2-core-types@Position","params":{"x":520,"y":176,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":150,"height":44}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#4a7c3f"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"text":"Comidinhas","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle","fontWeight":600}}},"index":2,"reference":"bqRjYdjS9-hJmnD8Btouv"},{"_id":"0462b7259ba4fd72dfec95c4","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"TabDoces","params":{"position":{"component":"data2-core-types@Position","params":{"x":678,"y":176,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":110,"height":44}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(0,0,0,0.15)"}},"text":"Doces","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#2a2a2a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":3,"reference":"3f9d3yo6nVfQdCd3uluvp"},{"_id":"d41c0ab3888286c1ecbf999a","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"TabBebidas","params":{"position":{"component":"data2-core-types@Position","params":{"x":796,"y":176,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":120,"height":44}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(0,0,0,0.15)"}},"text":"Bebidas","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#2a2a2a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":4,"reference":"akR31Ysw4OoeElEJgS1Zs"},{"_id":"6672bd22019c844c2c093f18","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MenuCardLeft","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":260,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":640,"height":760}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#dfe6c9"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"16","rightTop":"16","rightBottom":"16","leftBottom":"16"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}}},"index":5,"reference":"75XQWzV5mM2WxJzCnHKfa"},{"_id":"4cba8b7b558b3b4be02175f5","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MenuCardRight","params":{"position":{"component":"data2-core-types@Position","params":{"x":736,"y":260,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":640,"height":760}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#dfe6c9"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"16","rightTop":"16","rightBottom":"16","leftBottom":"16"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}}},"index":6,"reference":"PaUq3HtIOoO8Gae8LyZPk"},{"_id":"b6d45baa793f3533f6f97b90","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LegendSG","params":{"position":{"component":"data2-core-types@Position","params":{"x":340,"y":1058,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":140,"height":24}},"text":"SG sem glúten","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#4a4a4a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":7,"reference":"FDSCfiS_aqajuC-wmuPoL"},{"_id":"74c27d26ed8f13993a520318","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LegendSL","params":{"position":{"component":"data2-core-types@Position","params":{"x":500,"y":1058,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":140,"height":24}},"text":"SL sem lactose","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#4a4a4a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":8,"reference":"siXtHOpLmUO8DirQfWxf5"},{"_id":"fead3317eb90e7e338cf0610","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LegendSA","params":{"position":{"component":"data2-core-types@Position","params":{"x":660,"y":1058,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":140,"height":24}},"text":"SA sem açúcar","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#4a4a4a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":9,"reference":"dNraCa8XTaSkdxHiFqs5s"},{"_id":"8c1dd7252543e87460fe4d20","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LegendV","params":{"position":{"component":"data2-core-types@Position","params":{"x":820,"y":1058,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":110,"height":24}},"text":"V vegano","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#4a4a4a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":10,"reference":"CqVsad2pS2eUyFUh3YVGV"},{"_id":"73dfc02719fe621687d051ac","parent":"0c89355317b749f1ae0974af","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LegendStar","params":{"position":{"component":"data2-core-types@Position","params":{"x":930,"y":1058,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":150,"height":24}},"text":"★ mais pedidos","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#4a4a4a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":11,"reference":"dhpeW66sIao-213DzlFCC"},{"_id":"a671357398d7568963b06bfc","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocLabel","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":72,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":300,"height":30}},"text":"venha nos visitar","textSize":20,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#8fc36a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-script"}},"prop":"textFormat"}}},"index":0,"reference":"23dfS4hj9OXL_lz3zDJYg"},{"_id":"fd5ac43ded79db08364f1260","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocHeading","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":104,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":100}},"text":"Estamos em Sousas, Campinas","textSize":38,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f7f2e6"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true}}},"index":1,"reference":"XxANhk0-VM0QzTYb_sfHu"},{"_id":"b6a9fc1da832f28e4504f0f1","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocAddress","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":232,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":460,"height":50}},"text":"Rua Pedro Antônio Pierro, 112 — Sousas, Campinas/SP · CEP 13105-504","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(247,242,230,0.75)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"lineHeight":1.5}}},"index":2,"reference":"VfuireplNfUylNgJH-72h"},{"_id":"d647e1928f7ca15b1e19ba44","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocMapsLink","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":288,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":220,"height":22}},"text":"Abrir no Google Maps","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#8fc36a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"fontWeight":600}}},"index":3,"reference":"bDWSEc0CjmCOjMGycXuOF"},{"_id":"8db00564247d0a0954399e5d","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocHoursLabel","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":348,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":200,"height":20}},"text":"HORÁRIOS","textSize":12,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#8fc36a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-label"}},"prop":"textFormat"}}},"index":4,"reference":"CkWLF025sLqJMvDpEQC4w"},{"_id":"8b9ab4fd5b62fd851afa52c9","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocHours1","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":380,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":460,"height":22}},"text":"Terça a sexta","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":5,"reference":"A50lxPhOCn_-OFjsvvBip"},{"_id":"c6d89f07de0c427358ba28fd","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocHours1v","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":380,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":460,"height":22}},"text":"9h às 18h","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(247,242,230,0.7)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"right"}}},"index":6,"reference":"iVrDzVgKT0coX3nZ_bxvb"},{"_id":"a90deca4dbbff93ff6001633","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocHours2","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":410,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":460,"height":22}},"text":"Sábado","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":7,"reference":"NwcvetdqWKL5TgBKkpBZW"},{"_id":"fdde4b756f4dc96ba9f7921e","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocHours2v","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":410,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":460,"height":22}},"text":"9h às 16h","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(247,242,230,0.7)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"right"}}},"index":8,"reference":"L-QMRkwfb_62CKdC6aYKO"},{"_id":"6313ae6f892b829728670be0","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocHours3","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":440,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":460,"height":22}},"text":"Domingo","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":9,"reference":"YUin3EvnDe_d48dOCseqP"},{"_id":"8b676f4ccad52e8c6cae4575","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocHours3v","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":440,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":460,"height":22}},"text":"9h às 12h","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(247,242,230,0.7)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"right"}}},"index":10,"reference":"vTD6-81baLQtqT-95sXPO"},{"_id":"9da3250d621262e502c425a5","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocHours4","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":470,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":460,"height":22}},"text":"Segunda","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":11,"reference":"z71qoVf1Zid_RByq2HX6M"},{"_id":"fe45ed57200129825a968c35","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"LocHours4v","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":470,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":460,"height":22}},"text":"fechado","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(247,242,230,0.5)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"right"}}},"index":12,"reference":"Rm3zBV5oOZBnLGVES3kgl"},{"_id":"4be7ed0d679e99761e2d6088","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"BtnInstagram","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":512,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":130,"height":42}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(247,242,230,0.4)"}},"text":"Instagram","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":13,"reference":"gFntu7CfcJzTcE-CQNVLj"},{"_id":"3fb1abdd2b77c0ad067d45ed","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"BtnWhatsapp","params":{"position":{"component":"data2-core-types@Position","params":{"x":210,"y":512,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":130,"height":42}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(247,242,230,0.4)"}},"text":"WhatsApp","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":14,"reference":"nI0DHNEV0kWcliTQ6QMTL"},{"_id":"79df592bc90b7779b87a0bea","parent":"a57c6aa3e24f4aae465cf3a2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MapCard","params":{"position":{"component":"data2-core-types@Position","params":{"x":776,"y":84,"freeTransform":true,"left":false,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":600,"height":420}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#cfe0c4"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"12","rightTop":"12","rightBottom":"12","leftBottom":"12"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}}},"index":15,"reference":"mAl9f09lyGJAaeCH-4jeM"},{"_id":"b22f10f26ff41b6f16d1d0f5","parent":"de01aef8e71ac283a0179a5c","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"FooterLogo","params":{"position":{"component":"data2-core-types@Position","params":{"x":64,"y":44,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":320,"height":24}},"text":"cooltive café & plantas","textSize":16,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-display"}},"prop":"textFormat"}}},"index":0,"reference":"ODkj-Z5pPUC0wSPNCIBoN"},{"_id":"846d750bb9a9655c19da7c0b","parent":"de01aef8e71ac283a0179a5c","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"FooterCopy","params":{"position":{"component":"data2-core-types@Position","params":{"x":916,"y":46,"freeTransform":true,"left":false,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":460,"height":20}},"text":"© 2026 · Cooltive Café & Plantas · Sousas, Campinas/SP","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(240,236,224,0.6)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"right"}}},"index":1,"reference":"yFQVRpSYc1YaY2Um3f1iy"},{"_id":"126de4b29f8e8059d06c0a92","parent":"1b06a5f9a8e9cf2239ced5a7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MHeader","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":390,"height":64}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#f7f2e6"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"width":{"top":"0","right":"0","bottom":"1","left":"0"},"color":"rgba(0,0,0,0.06)"}}},"index":0,"reference":"-Cf35C48-e50rBZJTTvDR"},{"_id":"c51ec4dc2ec427d1e512bd19","parent":"1b06a5f9a8e9cf2239ced5a7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MHero","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":390,"height":520}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"image":"https://cooltive-me-demo.vercel.app/_next/image?url=%2Fimages%2Finterior-1.jpg&w=3840&q=75","size":"cover","positionX":"center","positionY":"center","repeat":"no-repeat"}}]}}},"index":1,"reference":"I40t4Q3ZBHvbyltLsw73n"},{"_id":"5b753f2bffa8555778ebef11","parent":"1b06a5f9a8e9cf2239ced5a7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MAbout","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":390,"height":820}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#f7f2e6"}}}}]}}},"index":2,"reference":"XQkEpfBr91_BQyO_gJhde"},{"_id":"49fb244101033d3dc229ee23","parent":"1b06a5f9a8e9cf2239ced5a7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMood","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":390,"height":620}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"image":"https://cooltive-me-demo.vercel.app/_next/image?url=%2Fimages%2Fgreenery-mood.jpg&w=3840&q=75","size":"cover","positionX":"center","positionY":"center","repeat":"no-repeat"}}]}}},"index":3,"reference":"0I0X21U1rY8P_BOfzTQ15"},{"_id":"eb681d89f2a363f22000ebbc","parent":"1b06a5f9a8e9cf2239ced5a7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMenu","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":390,"height":1220}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#f7f2e6"}}}}]}}},"index":4,"reference":"ssYbAQnARLCPLMJl-vYHy"},{"_id":"c2f4c800df4c81353b21f607","parent":"1b06a5f9a8e9cf2239ced5a7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocation","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":390,"height":800}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#2b2620"}}}}]}}},"index":5,"reference":"YR9owJEnqdkfM5LyOt0yW"},{"_id":"fa6c99133d94eb465191d4bd","parent":"1b06a5f9a8e9cf2239ced5a7","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MFooterBar","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false,"growH":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":390,"height":130}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#241f1a"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"width":{"top":"1","right":"0","bottom":"0","left":"0"},"color":"rgba(255,255,255,0.08)"}}},"index":6,"reference":"Ou6HO1rd6R8GwnMj-BZGA"},{"_id":"420ade05bc2f4e70bd564d0d","parent":"ee0fbff3fbda76689217427d","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"AboutBadgeNum","params":{"position":{"component":"data2-core-types@Position","params":{"x":20,"y":18,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":260,"height":36}},"text":"+50","textSize":30,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-display"}},"prop":"textFormat"}}},"index":0,"reference":"iBA_UH5nax3IZ9QVzHivc"},{"_id":"8748229b37927856165fec1c","parent":"ee0fbff3fbda76689217427d","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"AboutBadgeCaption","params":{"position":{"component":"data2-core-types@Position","params":{"x":20,"y":58,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":260,"height":48}},"text":"ESPÉCIES DE PLANTAS PELA CASA","textSize":11,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#e7efdc"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"letterSpacing":1,"lineHeight":1.4}}},"index":1,"reference":"ANBvoEaZlgsazoJJPLrti"},{"_id":"a80823d80e5b94373625b6a8","parent":"6672bd22019c844c2c093f18","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"CardLabelLeft","params":{"position":{"component":"data2-core-types@Position","params":{"x":40,"y":32,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":400,"height":28}},"text":"comidinhas","textSize":22,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3f6b34"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-script"}},"prop":"textFormat"}}},"index":0,"reference":"ExRh79btnH4u9ceIpHmYg"},{"_id":"165e17859b4bbec1f8f1e427","parent":"6672bd22019c844c2c093f18","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"ListLeft","params":{"position":{"component":"data2-core-types@Position","params":{"x":40,"y":80,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":670}},"childrenMode":"rows","repeat":[{"name":"Tigela de açaí · SL","price":"R$28,00","desc":"com frutas, granola e mel"},{"name":"Iogurte de baunilha","price":"R$20,00","desc":"com geleia de frutas vermelhas e granola"},{"name":"Eggbite · SG","price":"R$15,00","desc":"bolinho de ovo e queijo. Textura macia!"},{"name":"Ovos mexidos","price":"R$16,00","desc":"2 ovos e farofinha de bacon crocante"},{"name":"Pão de queijo ★","price":"R$10,00","desc":"Com crosta de parmesão"},{"name":"Croissant","price":"R$22,00","desc":"Perfeito com um de nossos extras"},{"name":"Fatia de pão","price":"R$10,00","desc":"Na manteiga ou no azeite"},{"name":"Quiche lorraine · SL","price":"R$22,00","desc":"Com massa de grão de bico"},{"name":"Risotinho de quinoa · SL SG ★","price":"R$28,00","desc":"E ervilhas, com mussarella fresca e pesto da casa"},{"name":"Ovo cozido","price":"R$4,00","desc":"Acompanhamento extra"}],"repeatReference":"comidaL"},"index":1,"reference":"y73klyeKURtETowo254o6"},{"_id":"819edd93b5ed299e7982cad6","parent":"4cba8b7b558b3b4be02175f5","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"CardLabelRight","params":{"position":{"component":"data2-core-types@Position","params":{"x":40,"y":32,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":500,"height":28}},"text":"é tudo pão, mas não só...","textSize":22,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3f6b34"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-script"}},"prop":"textFormat"}}},"index":0,"reference":"C6pyAJ_hxdhZbotRD8Dp3"},{"_id":"bda77da2f10571bb15f4db19","parent":"4cba8b7b558b3b4be02175f5","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"ListRight","params":{"position":{"component":"data2-core-types@Position","params":{"x":40,"y":80,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":600}},"childrenMode":"rows","repeat":[{"name":"Queijo quente","price":"R$30,00","desc":"Na focaccia, com dois queijos Atalaia"},{"name":"Croque Moontive ★","price":"R$36,00","desc":"Bagel, queijos Atalaia, presunto magro, mostarda trufada e creme de leite"},{"name":"Bauru especialíssimo","price":"R$36,00","desc":"Focaccia, dois queijos Atalaia, presunto magro e tomatinhos confitados"},{"name":"Mush & Melt · SG","price":"R$42,00","desc":"Focaccia, cogumelos Portobello e Paris, queijo de cabra e rúcula"},{"name":"Veganinho · SL V","price":"R$34,00","desc":"Focaccia, cogumelos Portobello e Paris, pasta de tomate fresco e rúcula"},{"name":"Toast Caprese","price":"R$24,00","desc":"Mussarella fresca, tomate e pesto feito da casa"},{"name":"Tostada Grega ★","price":"R$28,00","desc":"Blend de queijos de cabra, figo, nozes e mel"},{"name":"Avo-Ovo Toast ★","price":"R$32,00","desc":"Avocado, tomate picado, sementes e ovo cozido"},{"name":"Cheese toast","price":"R$26,00","desc":"Dois queijos Atalaia e tomatinhos confitados"}],"repeatReference":"paoL"},"index":1,"reference":"EO03e3bsKSmu6Eiws61Y4"},{"_id":"42f81c13c32efbae61c86d1d","parent":"79df592bc90b7779b87a0bea","component":"data2-blocks@IframeBlock","system":"6a11143bae205cd35b1d5de0","displayName":"MapIframe","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true,"exactX":0,"exactY":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":600,"height":420}}},"src":{"component":"data2-core-primitives@String","params":{"content":"https://maps.google.com/maps?q=Rua%20Pedro%20Ant%C3%B4nio%20Pierro%2C%20112%2C%20Sousas%2C%20Campinas%2C%20SP%2C%2013105-504&z=16&output=embed"}}},"reference":"UWIr1z2GyfcvXYI4xTt-o"},{"_id":"ffe3d6a62a7a7dfc034af853","parent":"126de4b29f8e8059d06c0a92","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLogoMark","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":16,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":32,"height":32}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#eaf1de"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"component":"data2-core-types@FileComponentUrlReference","params":{"reference":"dey1FpgK57lLJF5zA24EJ"}},"size":"contain","positionX":"center","positionY":"center"}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"32","rightTop":"32","rightBottom":"32","leftBottom":"32"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"#4a7c3f"}}},"index":0,"reference":"DCPYG_4JfJhKVGixToV6l"},{"_id":"3736e29a43f2a92bdd853668","parent":"126de4b29f8e8059d06c0a92","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLogoName","params":{"position":{"component":"data2-core-types@Position","params":{"x":56,"y":12,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":160,"height":20,"growWidthWithContent":true}},"text":"cooltive","textSize":16,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-display"}},"prop":"textFormat"}},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Cormorant Garamond"}}},"index":1,"reference":"ksRqtf_aqOr9IaExxUU8Q"},{"_id":"b8790ae1c05664bbf3b433fc","parent":"126de4b29f8e8059d06c0a92","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLogoSub","params":{"position":{"component":"data2-core-types@Position","params":{"x":56,"y":34,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":160,"height":12}},"text":"CAFÉ & PLANTAS","textSize":8,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#6b8a5a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-label"}},"prop":"textFormat"}},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Cormorant Garamond"}}},"index":2,"reference":"fOCwNbguPQf7B_-uTfoQ6"},{"_id":"b3feff01e2e6bf89d96aabfb","parent":"126de4b29f8e8059d06c0a92","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MHamburger","params":{"position":{"component":"data2-core-types@Position","params":{"x":346,"y":21,"freeTransform":true,"left":false,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":28,"height":22}},"icon":{"component":"data2-core-web@Icon","params":{"icon":"fa6-solid fa6-bars","iconSet":"fontawesome6"}},"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textSize":20},"index":3,"reference":"3qqwR2YtPBAiYvUL4q3ub"},{"_id":"f050f3a3472f28c4091188dd","parent":"c51ec4dc2ec427d1e512bd19","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MHeroOverlay","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":390,"height":520}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(18,14,10,0.62)"}}}}]}}},"index":0,"reference":"OYo7JEQkyVgdg1zCI8oJG"},{"_id":"f99edec469f3b19e209b0ec4","parent":"c51ec4dc2ec427d1e512bd19","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MHeroBadge","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":32,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":220,"height":26,"growWidthWithContent":true}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.12)"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"20","rightTop":"20","rightBottom":"20","leftBottom":"20"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(255,255,255,0.35)"}},"text":"SOUSAS · CAMPINAS, SP","textSize":10,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":1,"reference":"LL-NUdDQJooqtmePhZScx"},{"_id":"e13e5466ff8cb9146b7f8c01","parent":"c51ec4dc2ec427d1e512bd19","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MHeroTitle","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":72,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":320,"height":60}},"text":"Cooltive","textSize":42,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-display"}},"prop":"textFormat"}},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Cormorant Garamond"}}},"index":2,"reference":"SprN9MriYIsBeUKOq0ky4"},{"_id":"ea8d94058a69828322e9cf5c","parent":"c51ec4dc2ec427d1e512bd19","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MHeroScript","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":130,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false,"exactX":16.29,"exactY":130}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":260,"height":30}},"text":"café & plantas","textSize":20,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#9fce7f"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-script"}},"prop":"textFormat"}},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Cormorant Garamond"}}},"index":3,"reference":"Z7NmcKuBIe5qdpHjpS7Q3"},{"_id":"44098c81d6fc1f80685d05a3","parent":"c51ec4dc2ec427d1e512bd19","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MHeroParagraph","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":172,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":350,"height":90}},"text":"Café de especialidade, comidinhas de verdade e um cantinho verde pra respirar em Sousas. Vem tomar um café entre as plantas com a gente.","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.88)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"lineHeight":1.5}}},"index":4,"reference":"g3JT-OvZgdAcPr3RvCXHp"},{"_id":"b52e9cb5a77504ae22e29afa","parent":"c51ec4dc2ec427d1e512bd19","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MBtnVerCardapio","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":280,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":132,"height":42}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#6fae52"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"text":"Ver cardápio","textSize":12,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":5,"reference":"2GY44SVsh6VgoL3oj2RUH"},{"_id":"b5424095372c0cfb01447248","parent":"c51ec4dc2ec427d1e512bd19","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MBtnComoChegar","params":{"position":{"component":"data2-core-types@Position","params":{"x":156,"y":280,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":132,"height":42}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(255,255,255,0.7)"}},"text":"Como chegar","textSize":12,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":6,"reference":"JYiDcCzb60PTCR5vpgUFk"},{"_id":"d913d9f61f31f4c30660e13d","parent":"5b753f2bffa8555778ebef11","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MAboutLabel","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":40,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":300,"height":26}},"text":"nosso cantinho","textSize":17,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#5f9146"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-script"}},"prop":"textFormat"}}},"index":0,"reference":"nHO2r5K3aDsopRGKuGaUc"},{"_id":"1ae68f12967bb07fa01f06a4","parent":"5b753f2bffa8555778ebef11","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MAboutHeading","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":68,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":80}},"text":"Café e plantas, no mesmo endereço","textSize":27,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true}}},"index":1,"reference":"sFn7hkQRhujL6lS3WjDin"},{"_id":"765952c5a7af1d053742a324","parent":"5b753f2bffa8555778ebef11","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MAboutPara1","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":156,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":150}},"text":"O Cooltive nasceu em Sousas, Campinas, misturando duas paixões: café de verdade e plantas por todo canto. Cada mesa fica perto de uma folhagem diferente, a luz entra pelas janelas e o cardápio é pensado pra quem quer desacelerar.","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3a3a3a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"lineHeight":1.6}}},"index":2,"reference":"da8Lvr16v0lUVa7WZhv9u"},{"_id":"8c997d145a76409d79b0d7e0","parent":"5b753f2bffa8555778ebef11","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MAboutPara2","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":316,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":90}},"text":"Grãos selecionados, comidinhas feitas na casa e uma seleção de plantas à venda pra você levar um pedacinho do Cooltive pra casa.","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3a3a3a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"lineHeight":1.6}}},"index":3,"reference":"_7anTS74b1rqOPhPt-UG8"},{"_id":"34f62a464b899f6ac58994b7","parent":"5b753f2bffa8555778ebef11","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MStatV60","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":418,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":160,"height":46}},"text":"V60","textSize":22,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3f6b34"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-display"}},"prop":"textFormat"}}},"index":4,"reference":"Qc2ezc_5I1eALRWi53b08"},{"_id":"9c7e288568eb17329b9da12a","parent":"5b753f2bffa8555778ebef11","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MStatV60Caption","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":452,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":170,"height":18}},"text":"& PRENSA FRANCESA","textSize":9,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#6a6a6a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-label"}},"prop":"textFormat"}}},"index":5,"reference":"S_e1sg65m3OtI0jdR8pMz"},{"_id":"267ef27440ce18909303b9c1","parent":"5b753f2bffa8555778ebef11","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MStat100","params":{"position":{"component":"data2-core-types@Position","params":{"x":200,"y":418,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":160,"height":46}},"text":"100%","textSize":22,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3f6b34"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-display"}},"prop":"textFormat"}}},"index":6,"reference":"bhjAcN0_kjW0ZWHOk4hGG"},{"_id":"7d9faf94c75a089c85e88120","parent":"5b753f2bffa8555778ebef11","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MStat100Caption","params":{"position":{"component":"data2-core-types@Position","params":{"x":200,"y":452,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":170,"height":18}},"text":"FEITO NA CASA","textSize":9,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#6a6a6a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-label"}},"prop":"textFormat"}}},"index":7,"reference":"ZbeggbRTY_J0fupouyH7e"},{"_id":"d93d4db8523d5eb7b1058025","parent":"5b753f2bffa8555778ebef11","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MAboutImage","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":490,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":320}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"image":"https://cooltive-me-demo.vercel.app/_next/image?url=%2Fimages%2Finterior-1.jpg&w=3840&q=75","size":"cover","positionX":"center","positionY":"center","repeat":"no-repeat"}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"12","rightTop":"12","rightBottom":"12","leftBottom":"12"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}}},"index":8,"reference":"bBR3Ckv1-LOSvDdfg7F-P"},{"_id":"b266fe801b6af8c6da327b78","parent":"5b753f2bffa8555778ebef11","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MAboutBadge","params":{"position":{"component":"data2-core-types@Position","params":{"x":164,"y":690,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":210,"height":84}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#3f6b34"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"8","rightTop":"8","rightBottom":"8","leftBottom":"8"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}}},"index":9,"reference":"z6VdreWUAJSfphVpA0Wmw"},{"_id":"f2ec019f6e47af05b62c3605","parent":"49fb244101033d3dc229ee23","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMoodOverlay","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":390,"height":620}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(35,58,30,0.68)"}}}}]}}},"index":0,"reference":"pFaCBwhKSW0VnHD3Mzyli"},{"_id":"557d3c020901706633bd85ef","parent":"49fb244101033d3dc229ee23","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMoodQuote","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":56,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":60}},"text":"um respiro entre as plantas","textSize":22,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","wordWrap":true}}},"index":1,"reference":"tER0vPNGE-u7i42G2hEIL"},{"_id":"afb7a238fa0e720cb50b0e62","parent":"49fb244101033d3dc229ee23","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMoodLabel1","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":200,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":20}},"text":"CULTIVADO COM CALMA","textSize":11,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#c9dcb8"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center"}}},"index":2,"reference":"bpTygIOR5xn-B3pINl5Ub"},{"_id":"73f1d09b4793110aa6d91bca","parent":"49fb244101033d3dc229ee23","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMoodText1","params":{"position":{"component":"data2-core-types@Position","params":{"x":30,"y":226,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":330,"height":50}},"text":"Cada xícara é feita na hora, sem pressa.","textSize":19,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"horizontalAlignment":"center"}}},"index":3,"reference":"btF4Wx-OcrZVzY6evrfon"},{"_id":"346bc0a80d8a4804caba0c06","parent":"49fb244101033d3dc229ee23","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMoodIconCircle","params":{"position":{"component":"data2-core-types@Position","params":{"x":157,"y":292,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":76,"height":76}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#eaf1de"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"38","rightTop":"38","rightBottom":"38","leftBottom":"38"},"width":{"left":"4","top":"4","right":"4","bottom":"4"},"color":"rgba(255,255,255,0.5)"}}},"index":4,"reference":"7auf1WRRU2ELYgrnS7d89"},{"_id":"f3db192f01e19ed7a8f03a45","parent":"49fb244101033d3dc229ee23","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMoodIcon","params":{"position":{"component":"data2-core-types@Position","params":{"x":157,"y":292,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":76,"height":76}},"icon":{"component":"data2-core-web@Icon","params":{"icon":"fa6-solid fa6-seedling","iconSet":"fontawesome6"}},"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3f6b34"}},"textSize":26,"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":5,"reference":"9ng_dOiOgzbkZ9E8dzAS-"},{"_id":"5ad9a387ccac275e25bebf9e","parent":"49fb244101033d3dc229ee23","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMoodLabel2","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":402,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":20}},"text":"CULTIVADO COM PLANTAS","textSize":11,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#c9dcb8"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center"}}},"index":6,"reference":"5VNYIgwxQsiRaJmWBHtbC"},{"_id":"39742400fbea7049248f684c","parent":"49fb244101033d3dc229ee23","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMoodText2","params":{"position":{"component":"data2-core-types@Position","params":{"x":30,"y":428,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":330,"height":60}},"text":"E cada planta é cuidada por gente que gosta do que faz.","textSize":19,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"horizontalAlignment":"center"}}},"index":7,"reference":"sUanGkopzy3NfX0yXJEev"},{"_id":"1209b7987a5a40a434c8e640","parent":"eb681d89f2a363f22000ebbc","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMenuScript","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":40,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":24}},"text":"é tudo pão, mas não só","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#5f9146"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center"}}},"index":0,"reference":"o8RMw8XEarQ-6YBQidV9p"},{"_id":"16cd1233815fb19e20e68f99","parent":"eb681d89f2a363f22000ebbc","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMenuHeading","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":66,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":44}},"text":"Cardápio","textSize":32,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center"}}},"index":1,"reference":"QnliYhUsIjgLH2xyW3aaW"},{"_id":"f1532bfad8b04f3a12df836f","parent":"eb681d89f2a363f22000ebbc","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MTabComidinhas","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":126,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":150,"height":40}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#4a7c3f"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"text":"Comidinhas","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle","fontWeight":600}}},"index":2,"reference":"yRi4hyw2nLl5dYScAqelh"},{"_id":"9ed59238603f12651eb43f0d","parent":"eb681d89f2a363f22000ebbc","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MTabDoces","params":{"position":{"component":"data2-core-types@Position","params":{"x":174,"y":126,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":106,"height":40}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(0,0,0,0.15)"}},"text":"Doces","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#2a2a2a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":3,"reference":"liI14GVC9A6lYIUE400uZ"},{"_id":"90f4a1ea6f8b7d18a33d2c01","parent":"eb681d89f2a363f22000ebbc","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MTabBebidas","params":{"position":{"component":"data2-core-types@Position","params":{"x":137,"y":176,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":116,"height":40}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(0,0,0,0.15)"}},"text":"Bebidas","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#2a2a2a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":4,"reference":"QtBbSHeCzx2ppRz4Jvlw0"},{"_id":"244e042149c0b101313faab2","parent":"eb681d89f2a363f22000ebbc","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMenuCard","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":240,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":900}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#dfe6c9"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"16","rightTop":"16","rightBottom":"16","leftBottom":"16"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}}},"index":5,"reference":"2_UC-7r_MktmVlQJqqUUs"},{"_id":"655020ad500aa3c8ef85dc29","parent":"eb681d89f2a363f22000ebbc","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLegendRow1","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":1160,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":24}},"text":"SG sem glúten   ·   SL sem lactose","textSize":12,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#4a4a4a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center"}}},"index":6,"reference":"x-y2SWfqhP8LPDH7SzOhJ"},{"_id":"980be86d3736a02d762141a3","parent":"eb681d89f2a363f22000ebbc","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLegendRow2","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":1186,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":24}},"text":"SA sem açúcar   ·   V vegano   ·   ★ mais pedidos","textSize":12,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#4a4a4a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center"}}},"index":7,"reference":"mfBJMyPw-lOhFIr22ADgr"},{"_id":"126f550db4f120e01903be39","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocLabel","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":40,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":300,"height":24}},"text":"venha nos visitar","textSize":17,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#8fc36a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-script"}},"prop":"textFormat"}}},"index":0,"reference":"eWFxnClOPgGcrGj6E3u7f"},{"_id":"7d0483c5b52e3f5a96b149f7","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocHeading","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":68,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":76}},"text":"Estamos em Sousas, Campinas","textSize":26,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f7f2e6"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true}}},"index":1,"reference":"WuhLkThq7KQe8sBEGMTsY"},{"_id":"0dd4b07a2c7c2265112bd7e5","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocAddress","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":156,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":60}},"text":"Rua Pedro Antônio Pierro, 112 — Sousas, Campinas/SP · CEP 13105-504","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(247,242,230,0.75)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"lineHeight":1.5}}},"index":2,"reference":"9J5rXX3zgMyrumaDwXHqj"},{"_id":"41502c013d794526f6176303","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocMapsLink","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":222,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":220,"height":20}},"text":"Abrir no Google Maps","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#8fc36a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"fontWeight":600}}},"index":3,"reference":"NAA5mzLFRHOiwJlrVTdhu"},{"_id":"8e947b3aebab02a735c6d2eb","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocHoursLabel","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":268,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":200,"height":18}},"text":"HORÁRIOS","textSize":11,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#8fc36a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-label"}},"prop":"textFormat"}}},"index":4,"reference":"WfTgH9CO822dTTHscFKVM"},{"_id":"63da3c49c2b1a1f96cc9d2ab","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocHours1","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":296,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":22}},"text":"Terça a sexta","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":5,"reference":"mkAMZO1wCQoSxvILZVi6K"},{"_id":"88576c11eb57a4f229bee88b","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocHours1v","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":296,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":22}},"text":"9h às 18h","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(247,242,230,0.7)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"right"}}},"index":6,"reference":"a5sG78srddYHqnuoBXSK6"},{"_id":"052f326f2fc46229a82a47c9","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocHours2","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":324,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":22}},"text":"Sábado","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":7,"reference":"46VI7rq6R4N9bp2S_93xB"},{"_id":"367eee76120a1bdc33ae834b","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocHours2v","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":324,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":22}},"text":"9h às 16h","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(247,242,230,0.7)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"right"}}},"index":8,"reference":"Bn8EpMW7uVKD3-C59VQiG"},{"_id":"62e2370cefc2d0febade1cf4","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocHours3","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":352,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":22}},"text":"Domingo","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":9,"reference":"3vte10P54AaB4fXoQooW9"},{"_id":"81fbab88000bf1d5790642d5","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocHours3v","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":352,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":22}},"text":"9h às 12h","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(247,242,230,0.7)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"right"}}},"index":10,"reference":"mSXh4-UVNwunAtpSqNA3g"},{"_id":"5c83993a84f92c8099cbe042","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocHours4","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":380,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":22}},"text":"Segunda","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":11,"reference":"Zlj-JT17dOrwD-PopkSEK"},{"_id":"02f34eced2780c59bd53e4c2","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MLocHours4v","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":380,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":22}},"text":"fechado","textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(247,242,230,0.5)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"right"}}},"index":12,"reference":"ZcLlHchOb2WkurtXFKYOh"},{"_id":"267f0b010904748511842a42","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MBtnInstagram","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":424,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":165,"height":42}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(247,242,230,0.4)"}},"text":"Instagram","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":13,"reference":"L11mFrBJlk7oUra6QSgFY"},{"_id":"642264d7cdb60e6293249791","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MBtnWhatsapp","params":{"position":{"component":"data2-core-types@Position","params":{"x":197,"y":424,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":165,"height":42}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"24","rightTop":"24","rightBottom":"24","leftBottom":"24"},"width":{"left":"1","top":"1","right":"1","bottom":"1"},"color":"rgba(247,242,230,0.4)"}},"text":"WhatsApp","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":14,"reference":"REuwPPUsrdxMowV1q6MLL"},{"_id":"7d01dfbfbd379bc5593457ba","parent":"c2f4c800df4c81353b21f607","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMapCard","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":488,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":260}},"fill":{"component":"data2-core-types@Fill","params":{"backgrounds":[{"component":"data2-core-types@Background","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"#cfe0c4"}}}}]}},"border":{"component":"data2-core-types@Border","params":{"radius":{"leftTop":"12","rightTop":"12","rightBottom":"12","leftBottom":"12"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}}},"index":15,"reference":"Gi9ZeSKJPEqCyKHh6KR4g"},{"_id":"e0fdaa33ba2e47bd57716845","parent":"fa6c99133d94eb465191d4bd","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MFooterLogo","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":40,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":24}},"text":"cooltive café & plantas","textSize":15,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#f0ece0"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center"}}},"index":0,"reference":"Nlbp4LXL9TfxqkP86sLKh"},{"_id":"c1c5a6e24ea0045a307298cb","parent":"fa6c99133d94eb465191d4bd","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MFooterCopy","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":74,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":358,"height":36}},"text":"© 2026 · Cooltive Café & Plantas · Sousas, Campinas/SP","textSize":12,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"rgba(240,236,224,0.6)"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","wordWrap":true}}},"index":1,"reference":"0HTXbHXnxrXM5RBx8MEBN"},{"_id":"6031618a6a92ad17e1cf86a4","parent":"165e17859b4bbec1f8f1e427","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"RowLeft","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":74}}},"index":0,"reference":"kWk5ayJd6dIG5MJEgj0av"},{"_id":"b7a4a20545072397423dff4e","parent":"bda77da2f10571bb15f4db19","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"RowRight","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":74}}},"index":0,"reference":"qzfT5uvHpcLUCGSD2h81D"},{"_id":"7b168f114411b29e2ebabc19","parent":"b266fe801b6af8c6da327b78","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MAboutBadgeNum","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":12,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":180,"height":26}},"text":"+50","textSize":20,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-display"}},"prop":"textFormat"}}},"index":0,"reference":"yWVxZcXbrPDAIjB5ChBuU"},{"_id":"19bf750bc74039f96a12044b","parent":"b266fe801b6af8c6da327b78","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MAboutBadgeCaption","params":{"position":{"component":"data2-core-types@Position","params":{"x":16,"y":40,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":180,"height":40}},"text":"ESPÉCIES DE PLANTAS PELA CASA","textSize":9,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#e7efdc"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true,"letterSpacing":0.5,"lineHeight":1.4}}},"index":1,"reference":"AiFs8fNbjMopZq-BN3z2R"},{"_id":"5d93e52c47f4ae33ac071566","parent":"244e042149c0b101313faab2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MCardLabel","params":{"position":{"component":"data2-core-types@Position","params":{"x":20,"y":24,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":300,"height":24}},"text":"comidinhas","textSize":18,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#3f6b34"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-script"}},"prop":"textFormat"}}},"index":0,"reference":"kgYtwio3l0W7nvC-xtndr"},{"_id":"2b53c9a26be94190b14e3ed0","parent":"244e042149c0b101313faab2","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MList","params":{"position":{"component":"data2-core-types@Position","params":{"x":20,"y":64,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":318,"height":820}},"childrenMode":"rows","repeat":[{"name":"Tigela de açaí · SL","price":"R$28,00","desc":"com frutas, granola e mel"},{"name":"Iogurte de baunilha","price":"R$20,00","desc":"com geleia de frutas vermelhas e granola"},{"name":"Eggbite · SG","price":"R$15,00","desc":"bolinho de ovo e queijo. Textura macia!"},{"name":"Ovos mexidos","price":"R$16,00","desc":"2 ovos e farofinha de bacon crocante"},{"name":"Pão de queijo ★","price":"R$10,00","desc":"Com crosta de parmesão"},{"name":"Croissant","price":"R$22,00","desc":"Perfeito com um de nossos extras"},{"name":"Fatia de pão","price":"R$10,00","desc":"Na manteiga ou no azeite"},{"name":"Quiche lorraine · SL","price":"R$22,00","desc":"Com massa de grão de bico"},{"name":"Risotinho de quinoa · SL SG ★","price":"R$28,00","desc":"E ervilhas, com mussarella fresca e pesto da casa"},{"name":"Ovo cozido","price":"R$4,00","desc":"Acompanhamento extra"}],"repeatReference":"mItem"},"index":1,"reference":"ee4TkWZbQFvlSLVk9Ushr"},{"_id":"e3b0b832ba9ad82614a74457","parent":"7d01dfbfbd379bc5593457ba","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMapPinIcon","params":{"position":{"component":"data2-core-types@Position","params":{"x":154,"y":86,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":50,"height":50}},"icon":{"component":"data2-core-web@Icon","params":{"icon":"fa6-solid fa6-location-dot","iconSet":"fontawesome6"}},"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#8a3b2f"}},"textSize":34,"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","verticalAlignment":"middle"}}},"index":0,"reference":"cSDtfBWI3kjm-AwCXvuHs"},{"_id":"ad5d23b3b394b508921c9060","parent":"7d01dfbfbd379bc5593457ba","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MMapCaption","params":{"position":{"component":"data2-core-types@Position","params":{"x":29,"y":148,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":300,"height":40}},"text":"Rua Pedro Antônio Pierro, 112 · Sousas, Campinas/SP","textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#39492f"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","wordWrap":true}}},"index":1,"reference":"wGIeCmbvZA3iv8IU-zd02"},{"_id":"acc6c9b1199eb0c07fd41793","parent":"6031618a6a92ad17e1cf86a4","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"RowLeftName","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":4,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":400,"height":22}},"text":{"component":"data2-core-types@Reference","params":{"reference":"comidaLRepeater.name"}},"textSize":16,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"fontWeight":700}}},"index":0,"reference":"q3oeWVLDO5YRhXt_dx8I4"},{"_id":"1ffafb3644a79b09c47ad62a","parent":"6031618a6a92ad17e1cf86a4","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"RowLeftPrice","params":{"position":{"component":"data2-core-types@Position","params":{"x":420,"y":4,"freeTransform":true,"left":false,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":140,"height":22}},"text":{"component":"data2-core-types@Reference","params":{"reference":"comidaLRepeater.price"}},"textSize":16,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"fontWeight":700,"horizontalAlignment":"right"}}},"index":1,"reference":"yAYm47VDUqZP5ag96eY7z"},{"_id":"11043a7bd233d9decb12c0c0","parent":"6031618a6a92ad17e1cf86a4","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"RowLeftDesc","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":30,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":20}},"text":{"component":"data2-core-types@Reference","params":{"reference":"comidaLRepeater.desc"}},"textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#5a5a5a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":2,"reference":"pIjUduAPFX2Fybq9hcG5-"},{"_id":"1cbe7acc28bdbc74bdbec7fc","parent":"6031618a6a92ad17e1cf86a4","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"RowLeftDivider","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":62,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":1}},"border":{"component":"data2-core-types@Border","params":{"width":{"top":"1","right":"0","bottom":"0","left":"0"},"color":"rgba(0,0,0,0.15)"}}},"index":3,"reference":"UXrrudqx8YBNSJtYDCBTl"},{"_id":"359441cc489518097ef34c17","parent":"b7a4a20545072397423dff4e","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"RowRightName","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":4,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":400,"height":22}},"text":{"component":"data2-core-types@Reference","params":{"reference":"paoLRepeater.name"}},"textSize":16,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"fontWeight":700}}},"index":0,"reference":"gQRRBmmM3JuDPrq_cFmhN"},{"_id":"598b9a757a27abf2ac1c7c07","parent":"b7a4a20545072397423dff4e","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"RowRightPrice","params":{"position":{"component":"data2-core-types@Position","params":{"x":420,"y":4,"freeTransform":true,"left":false,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":140,"height":22}},"text":{"component":"data2-core-types@Reference","params":{"reference":"paoLRepeater.price"}},"textSize":16,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"fontWeight":700,"horizontalAlignment":"right"}}},"index":1,"reference":"ubFImdsCHl1nzQlQYLhzK"},{"_id":"99610d722e06b71555c561fc","parent":"b7a4a20545072397423dff4e","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"RowRightDesc","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":30,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":20}},"text":{"component":"data2-core-types@Reference","params":{"reference":"paoLRepeater.desc"}},"textSize":13,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#5a5a5a"}},"textFormat":{"component":"data2-core-presets@PresetLink","params":{"module":{"component":"data2-core-types@ProcessReference","params":{"reference":"font-body"}},"prop":"textFormat"}}},"index":2,"reference":"cix6REexAlNBdsbhHEtqF"},{"_id":"25a50899f919d6a752f2f435","parent":"b7a4a20545072397423dff4e","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"RowRightDivider","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":62,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":1}},"border":{"component":"data2-core-types@Border","params":{"width":{"top":"1","right":"0","bottom":"0","left":"0"},"color":"rgba(0,0,0,0.15)"}}},"index":3,"reference":"HoY6YrpKTTobX631ggaK3"},{"_id":"a861171939ad8cb9ff1e986b","parent":"2b53c9a26be94190b14e3ed0","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MRow","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":318,"height":82}}},"index":0,"reference":"GHBCFiZ2p20ahiu-B0F_N"},{"_id":"861e2afef62916475e7dcb4b","parent":"a861171939ad8cb9ff1e986b","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MRowName","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":4,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":220,"height":20}},"text":{"component":"data2-core-types@Reference","params":{"reference":"mItemRepeater.name"}},"textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"fontWeight":700,"wordWrap":true}}},"index":0,"reference":"j5kQQtL8BNS8ftJPLL8gt"},{"_id":"a09e175371ff8e64621a21c6","parent":"a861171939ad8cb9ff1e986b","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MRowPrice","params":{"position":{"component":"data2-core-types@Position","params":{"x":220,"y":4,"freeTransform":true,"left":false,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":98,"height":20}},"text":{"component":"data2-core-types@Reference","params":{"reference":"mItemRepeater.price"}},"textSize":14,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#1c1c1c"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"fontWeight":700,"horizontalAlignment":"right"}}},"index":1,"reference":"jItrvWnwgwEOA7lFQQMTu"},{"_id":"bc664c6dd1f5098bfc96bfb3","parent":"a861171939ad8cb9ff1e986b","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MRowDesc","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":28,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":318,"height":36}},"text":{"component":"data2-core-types@Reference","params":{"reference":"mItemRepeater.desc"}},"textSize":12,"textColor":{"component":"data2-core-types@Color","params":{"rgba":"#5a5a5a"}},"textFormat":{"component":"data2-core-types@TextFormat","params":{"wordWrap":true}}},"index":2,"reference":"UeHuG4LRRGch_AfEbmbzT"},{"_id":"71a1999d880794330698ffe2","parent":"a861171939ad8cb9ff1e986b","component":"data2-core-primitives@Block","system":"6a11143bae205cd35b1d5de0","displayName":"MRowDivider","params":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":70,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":318,"height":1}},"border":{"component":"data2-core-types@Border","params":{"width":{"top":"1","right":"0","bottom":"0","left":"0"},"color":"rgba(0,0,0,0.15)"}}},"index":3,"reference":"A96aoTlKD3Im19sZofFUj"}]}},{"_id":"5fc4faeec8da12be8bf3d08a","component":"data2-core@Data2TableDataSource","system":"5e3b796d6228227ca637d0f4","params":{"paginate":{"default":1000},"super":{},"table":"fileComponentInstances","schema":[{"component":"data2-core-types@Field","params":{"key":"component","title":"Component","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"system","title":"System","component":"data2-core-primitives@ObjectId"}},{"component":"data2-core-types@Field","params":{"key":"displayName","title":"Display Name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"params","title":"Params","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"reference","title":"Reference","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"fileId"}},"title":"File ID","component":"data2-core-primitives@ObjectId"},"break":true}],"hooks":{"before":{"all":[{"hook":"softDelete"}],"find":[{"hook":"useCache","options":{"additionalGroupKeys":["$and.0.system","$and.1.system"],"skipCache":true}}],"create":[{"hook":"timestamp","options":{"fields":["createdAt","updatedAt"]}}],"update":[{"hook":"timestamp","options":{"fields":["updatedAt"]}}],"patch":[{"hook":"timestamp","options":{"fields":["updatedAt"]}}]},"after":{"all":[{"hook":"fileUrl","options":{"downloadField":"params.fileUrl","uploadField":"params.uploadFileUrl","enhanceSchema":false}}],"find":[{"hook":"useCache","options":{"additionalGroupKeys":["$and.0.system","$and.1.system"],"skipCache":true}}],"create":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}],"update":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}],"patch":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}],"remove":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}]}},"multi":["create"],"useEstimatedDocumentCount":true},"reference":"FileComponentInstancesTableDataSource","updatedAt":"2022-12-19T20:42:51.837Z","createdAt":"2020-11-30T13:59:52.294Z"},{"reference":"ApplicationsTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a66a698aca5ee9722c15aa1","displayName":"New Application (6)","reference":"F-tlAhDTE8nu79_vhJvdv","params":{"domain":{"component":"data2-core-primitives@String","params":{"content":"cooltive"}},"entryPoint":{"disabled":false,"component":"data2-core-types@ComponentInstanceReference","params":{"component":"data2-core-primitives@Artboard","reference":"b8pjqmYpTCWFyfEsSghFL"}}},"system":"6a11143bae205cd35b1d5de0","component":"data2-core-primitives@Application"}]}},{"reference":"DataComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"BundlesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}}],"processesParams":{}}}}