{"context":{"domain":"fernando-playground"},"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":"66f6c624d8a4a0c67041a910","name":"Fernando Moreira","domain":"fernando-playground","visibility":"public","settings":{"defaultAccessPolicy":"private"},"updatedAt":"2026-08-14T19:26:29.927Z","createdAt":"2024-09-27T12:56:41.025Z"}]}},{"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":"6a41b8fe5de232c667b447f8","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"bot-chat-panel","package":"fernando-playground","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 { position: absolute; inset: 0; overflow: hidden; font-family: system-ui, sans-serif; }\n    .msgs {\n      position: absolute; inset: 0; overflow-y: auto; padding: 16px;\n      display: flex; flex-direction: column; gap: 8px; box-sizing: border-box;\n      --bg: #111827; --text: #e5e7eb; --muted: rgba(255,255,255,0.4); --faint: rgba(255,255,255,0.25); --thumb: rgba(255,255,255,0.15);\n      background: var(--bg); color: var(--text);\n      transition: background .25s, color .25s;\n    }\n    .msgs.light { --bg: #eceff3; --text: #111827; --muted: rgba(0,0,0,0.5); --faint: rgba(0,0,0,0.4); --thumb: rgba(0,0,0,0.2); }\n    .msgs::-webkit-scrollbar { width: 6px; }\n    .msgs::-webkit-scrollbar-track { background: transparent; }\n    .msgs::-webkit-scrollbar-thumb { background: var(--thumb); border-radius: 4px; }\n    .row { display: flex; flex-direction: column; }\n    .row.user { align-items: flex-end; }\n    .row.bot { align-items: flex-start; }\n    .bubble { max-width: 68%; padding: 8px 12px; border-radius: 14px; font-size: 14px; line-height: 1.4; word-break: break-word; white-space: pre-line; }\n    .bubble.user { background: #4f46e5; color: #fff; border-bottom-right-radius: 4px; }\n    .bubble.bot { color: var(--text); border-bottom-left-radius: 4px; }\n    .slabel { font-size: 11px; color: var(--muted); margin-top: 3px; padding: 0 2px; }\n    .dots { display: inline-flex; gap: 4px; align-items: center; }\n    .dots span { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); animation: blink 1.2s infinite; }\n    .dots span:nth-child(2) { animation-delay: .2s; }\n    .dots span:nth-child(3) { animation-delay: .4s; }\n    @keyframes blink { 0%,80%,100%{opacity:.25} 40%{opacity:1} }\n    .empty-state { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 14px; padding: 32px 24px; }\n    .es-emoji { font-size: 60px; line-height: 1; }\n    .es-title { font-size: 24px; font-weight: 700; color: var(--text); }\n    .es-desc { font-size: 15px; color: var(--muted); max-width: 400px; line-height: 1.55; }\n  `,\n  render(d2) {\n    const light = d2.store('themeStore').get('mode', 'dark') === 'light'\n    const s = d2.store('chatStore')\n    const lang = s.get('lang', 'pt')\n    const HINT = {\n      pt: { t: 'Comece uma conversa!', s: 'O bot adapta o tom conforme o contexto detectado' },\n      en: { t: 'Start a conversation!', s: 'The bot adapts its tone to the detected context' },\n      es: { t: '¡Inicia una conversación!', s: 'El bot adapta el tono según el contexto detectado' }\n    }\n    const SNAME = {\n      en: { Vendas: 'Sales', Entusiasmado: 'Excited', Descoberta: 'Discovery', Suporte: 'Support' },\n      es: { Vendas: 'Ventas', Entusiasmado: 'Entusiasmado', Descoberta: 'Descubrimiento', Suporte: 'Soporte' }\n    }\n    const trName = (n, l) => (l !== 'pt' && SNAME[l] && SNAME[l][n]) ? SNAME[l][n] : n\n    const hint = HINT[lang] || HINT.pt\n    const msgs = s.get('messages', [])\n    const loading = s.get('loading', false)\n    const rows = msgs.length\n      ? msgs.map((m, i) => {\n          const txt = (m.text || '')\n            .replace(/\\r/g, '')\n            .replace(/[ \\t]+\\n/g, '\\n')\n            .replace(/\\n{2,}/g, '\\n')\n            .trim()\n            .replace(/&/g, '&amp;')\n            .replace(/</g, '&lt;')\n            .replace(/>/g, '&gt;')\n          const label = m.stateName ? `${m.emoji || ''} ${trName(m.stateName, m.lang || lang)}`.trim() : (m.stateLabel || '')\n          return `\n          <div class=\"row ${m.role}\" data-key=\"${i}\">\n            <div class=\"bubble ${m.role}\" ${m.role === 'bot' ? `style=\"background:${m.color || '#6366f1'}22;border:1px solid ${m.color || '#6366f1'}40;\"` : ''}>${txt}</div>\n            ${m.role === 'bot' && label ? `<div class=\"slabel\">${label}</div>` : ''}\n          </div>`\n        }).join('')\n      : `<div class=\"empty-state\"><div class=\"es-emoji\">👋</div><div class=\"es-title\">${hint.t}</div><div class=\"es-desc\">${hint.s}</div></div>`\n    const dots = loading ? `\n      <div class=\"row bot\">\n        <div class=\"bubble bot\" style=\"background:${light ? 'rgba(0,0,0,0.05)' : 'rgba(255,255,255,0.05)'};border:1px solid ${light ? 'rgba(0,0,0,0.12)' : 'rgba(255,255,255,0.12)'};\">\n          <div class=\"dots\"><span></span><span></span><span></span></div>\n        </div>\n      </div>` : ''\n    return `<div class=\"msgs${light ? ' light' : ''}\" id=\"msgs\">${rows}${dots}</div>`\n  },\n  updated(d2) {\n    const el = d2.$('#msgs')\n    if (el) requestAnimationFrame(() => { el.scrollTop = el.scrollHeight })\n  }\n}"}}}}}}}},{"_id":"6a41b901f6cc6f867cf14598","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"bot-state-badge","package":"fernando-playground","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: inline-flex; align-items: center; }\n    .badge { display: inline-flex; align-items: center; gap: 6px; padding: 5px 14px; border-radius: 999px; font-size: 13px; font-weight: 600; transition: background .4s, border-color .4s, color .4s; }\n    .name { white-space: nowrap; }\n  `,\n  render(d2) {\n    const s = d2.store('chatStore')\n    const emoji = s.get('currentEmoji', '🤖')\n    const name = s.get('currentState', 'Aguardando')\n    const color = s.get('currentColor', '#6366f1')\n    return `\n      <div class=\"badge\" style=\"background:${color}22;color:${color};border:1.5px solid ${color}44;\">\n        <span>${emoji}</span>\n        <span class=\"name\">${name}</span>\n      </div>`\n  }\n}"}}}}}}}},{"_id":"6a41b914e75b1a017079ce6f","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"bot-state-panel","package":"fernando-playground","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 { position: absolute; inset: 0; overflow: hidden; font-family: system-ui, sans-serif; }\n    .panel {\n      position: absolute; inset: 0; overflow-y: auto; padding: 16px;\n      display: flex; flex-direction: column; gap: 16px; box-sizing: border-box;\n      --bg:#0f1117; --text:#e5e7eb; --muted:rgba(255,255,255,0.3); --faint:rgba(255,255,255,0.35);\n      --reason-bg:rgba(255,255,255,0.05); --reason-text:rgba(255,255,255,0.6); --reason-border:rgba(255,255,255,0.08);\n      --item-bg:rgba(255,255,255,0.04); --item-hover:rgba(255,255,255,0.08);\n      --input-bg:rgba(255,255,255,0.06); --input-border:rgba(255,255,255,0.12);\n      --ghost-bg:rgba(255,255,255,0.08); --thumb:rgba(255,255,255,0.18); --thumb-hover:rgba(255,255,255,0.32);\n      background: var(--bg); color: var(--text);\n      transition: background .25s, color .25s;\n    }\n    .panel.light {\n      --bg:#ffffff; --text:#111827; --muted:rgba(0,0,0,0.42); --faint:rgba(0,0,0,0.4);\n      --reason-bg:rgba(0,0,0,0.04); --reason-text:rgba(0,0,0,0.62); --reason-border:rgba(0,0,0,0.08);\n      --item-bg:rgba(0,0,0,0.03); --item-hover:rgba(0,0,0,0.06);\n      --input-bg:#ffffff; --input-border:rgba(0,0,0,0.15);\n      --ghost-bg:rgba(0,0,0,0.06); --thumb:rgba(0,0,0,0.22); --thumb-hover:rgba(0,0,0,0.4);\n    }\n    .panel::-webkit-scrollbar { width: 4px; }\n    .panel::-webkit-scrollbar-thumb { background: var(--thumb); border-radius: 4px; }\n    .section { display: flex; flex-direction: column; gap: 8px; flex-shrink: 0; }\n    .sec-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }\n    .sec-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }\n    .add-btn { border: 1px solid var(--input-border); background: var(--item-bg); color: var(--text); border-radius: 8px; padding: 5px 11px; cursor: pointer; font-size: 12px; font-weight: 600; font-family: inherit; display: flex; align-items: center; gap: 5px; transition: background .2s, border-color .2s; }\n    .add-btn:hover { background: var(--item-hover); border-color: #4f46e5; }\n    .reasoning-box { padding: 10px 12px; border-radius: 10px; background: var(--reason-bg); font-size: 13px; color: var(--reason-text); line-height: 1.5; border: 1px solid var(--reason-border); }\n    .state-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 10px; border: 1.5px solid transparent; background: var(--item-bg); cursor: pointer; transition: border-color .2s, background .2s; }\n    .state-item:hover { background: var(--item-hover); }\n    .state-item.current { border-color: var(--c); background: var(--c-faint); }\n    .state-item.editing { border-color: var(--c); }\n    .cbar { width: 4px; border-radius: 4px; align-self: stretch; min-height: 40px; flex-shrink: 0; }\n    .semoji { font-size: 19px; line-height: 1; flex-shrink: 0; display: flex; align-items: center; }\n    .info { flex: 1; min-width: 0; }\n    .sname { font-size: 13px; font-weight: 600; color: var(--text); }\n    .strigger { font-size: 11px; color: var(--faint); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n    .edit-panel { flex-shrink: 0; padding: 14px; border-radius: 12px; background: var(--reason-bg); display: flex; flex-direction: column; gap: 8px; border: 1px solid var(--reason-border); }\n    .edit-title { font-size: 13px; font-weight: 700; color: var(--text); margin-bottom: 2px; }\n    .elabel { font-size: 12px; font-weight: 600; color: var(--muted); }\n    .einp { width: 100%; border: 1.5px solid var(--input-border); border-radius: 8px; padding: 8px 11px; font-size: 13px; background: var(--input-bg); color: var(--text); outline: none; box-sizing: border-box; font-family: inherit; }\n    .einp::placeholder { color: var(--faint); }\n    .einp:focus { border-color: #4f46e5; }\n    .einp::-webkit-scrollbar { width: 9px; }\n    .einp::-webkit-scrollbar-track { background: transparent; margin: 4px 0; }\n    .einp::-webkit-scrollbar-thumb { background: var(--thumb); border-radius: 8px; border: 2px solid transparent; background-clip: padding-box; }\n    .einp::-webkit-scrollbar-thumb:hover { background: var(--thumb-hover); background-clip: padding-box; }\n    .eta { min-height: 88px; resize: vertical; line-height: 1.5; scrollbar-width: thin; scrollbar-color: var(--thumb) transparent; }\n    .erow { display: flex; gap: 8px; }\n    .efield { display: flex; flex-direction: column; gap: 6px; }\n    .efield.emoji { width: 76px; flex-shrink: 0; }\n    .efield.color { width: 92px; flex-shrink: 0; }\n    .einp.eemoji { text-align: center; font-size: 18px; padding: 6px; }\n    .einp.ecolor { padding: 3px; height: 38px; cursor: pointer; }\n    .einp.ecolor::-webkit-color-swatch-wrapper { padding: 0; }\n    .einp.ecolor::-webkit-color-swatch { border: none; border-radius: 6px; }\n    .btns { display: flex; gap: 8px; flex-shrink: 0; margin-top: 4px; align-items: center; }\n    .sbtn { border: none; border-radius: 8px; padding: 8px 16px; cursor: pointer; font-size: 13px; font-weight: 600; transition: opacity .2s, filter .2s; font-family: inherit; }\n    .sbtn:hover { filter: brightness(1.08); }\n    .sbtn-primary { background: #4f46e5; color: #fff; }\n    .sbtn-ghost { background: var(--ghost-bg); color: var(--text); }\n    .sbtn-danger { background: transparent; color: #ef4444; border: 1px solid rgba(239,68,68,0.4); margin-left: auto; }\n    .sbtn-danger:hover { background: rgba(239,68,68,0.12); filter: none; }\n    .empty { color: var(--faint); font-size: 13px; font-style: italic; }\n  `,\n  render(d2) {\n    const light = d2.store('themeStore').get('mode', 'dark') === 'light'\n    const cs = d2.store('chatStore')\n    const lang = cs.get('lang', 'pt')\n    const UI = {\n      pt: { reasoning: 'Raciocínio do bot', states: 'Estados', add: 'Novo', reasoningEmpty: 'Envie uma mensagem para ver o raciocínio', empty: 'Nenhum estado configurado', nome: 'Nome do estado', trig: 'Gatilho de ativação', behav: 'Comportamento do bot', emoji: 'Emoji', cor: 'Cor', save: 'Salvar', cancel: 'Cancelar', del: 'Excluir', newTitle: 'Novo estado', editTitle: 'Editar estado' },\n      en: { reasoning: 'Bot reasoning', states: 'States', add: 'New', reasoningEmpty: 'Send a message to see the reasoning', empty: 'No states configured', nome: 'State name', trig: 'Activation trigger', behav: 'Bot behavior', emoji: 'Emoji', cor: 'Color', save: 'Save', cancel: 'Cancel', del: 'Delete', newTitle: 'New state', editTitle: 'Edit state' },\n      es: { reasoning: 'Razonamiento del bot', states: 'Estados', add: 'Nuevo', reasoningEmpty: 'Envía un mensaje para ver el razonamiento', empty: 'Ningún estado configurado', nome: 'Nombre del estado', trig: 'Disparador de activación', behav: 'Comportamiento del bot', emoji: 'Emoji', cor: 'Color', save: 'Guardar', cancel: 'Cancelar', del: 'Eliminar', newTitle: 'Nuevo estado', editTitle: 'Editar estado' }\n    }\n    const STATES_TR = {\n      en: {\n        Vendas: { name: 'Sales', trigger: 'The user shows interest in buying, upgrading their plan, learning prices, or is evaluating the product.' },\n        Entusiasmado: { name: 'Excited', trigger: 'The user is excited, enthusiastic, praising, or talking about a topic/hobby they clearly enjoy (movie, team, game, series).' },\n        Descoberta: { name: 'Discovery', trigger: 'DEFAULT state. The user asks general questions, explores the product, or there is no clear sign of a complaint or purchase intent yet.' },\n        Suporte: { name: 'Support', trigger: 'The user reports a problem, bug, error, frustration, or complains that something did not work as expected.' }\n      },\n      es: {\n        Vendas: { name: 'Ventas', trigger: 'El usuario muestra interés en comprar, mejorar su plan, conocer precios o evaluar contratar el producto.' },\n        Entusiasmado: { name: 'Entusiasmado', trigger: 'El usuario está animado, entusiasmado, elogiando o hablando de un tema/afición que claramente le gusta (película, equipo, juego, serie).' },\n        Descoberta: { name: 'Descubrimiento', trigger: 'Estado POR DEFECTO. El usuario hace preguntas generales, explora el producto o aún no hay señales claras de queja ni intención de compra.' },\n        Suporte: { name: 'Soporte', trigger: 'El usuario reporta un problema, bug, error, frustración o se queja de que algo no funcionó como esperaba.' }\n      }\n    }\n    const t = UI[lang] || UI.pt\n    const tr = (rec) => (lang !== 'pt' && STATES_TR[lang] && STATES_TR[lang][rec.name]) ? STATES_TR[lang][rec.name] : { name: rec.name || '', trigger: rec.trigger || '' }\n    const reasoning = cs.get('currentReasoning', '')\n    const currentState = cs.get('currentState', '')\n    const editId = d2.state.editId\n    const records = d2.store('statesStore').get('states', []) || []\n    const items = records.map(r => {\n      const isCurrent = r.name === currentState\n      const isEditing = r.id === editId\n      const c = r.color || '#6366f1'\n      const disp = tr(r)\n      return `\n        <div class=\"state-item${isCurrent ? ' current' : ''}${isEditing ? ' editing' : ''}\" data-id=\"${r.id}\" style=\"--c:${c};--c-faint:${c}18;\">\n          <div class=\"cbar\" style=\"background:${c}\"></div>\n          <div class=\"semoji\">${r.emoji || '•'}</div>\n          <div class=\"info\">\n            <div class=\"sname\">${disp.name}</div>\n            <div class=\"strigger\">${disp.trigger}</div>\n          </div>\n        </div>`\n    }).join('')\n    const isNew = editId === '__new__'\n    let rec = null\n    if (isNew) rec = { name: '', trigger: '', behaviorPrompt: '', emoji: '✨', color: '#6366f1', isDefault: false }\n    else if (editId) rec = records.find(r => r.id === editId)\n    let editForm = ''\n    if (rec) {\n      const esc = (v) => (v || '').replace(/&/g, '&amp;').replace(/\"/g, '&quot;')\n      const taEsc = (v) => (v || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n      editForm = `\n        <div class=\"edit-panel\">\n          <div class=\"edit-title\">${isNew ? t.newTitle : t.editTitle}</div>\n          <div class=\"elabel\">${t.nome}</div>\n          <input class=\"einp\" id=\"ename\" value=\"${esc(rec.name)}\" />\n          <div class=\"elabel\">${t.trig}</div>\n          <input class=\"einp\" id=\"etrigger\" value=\"${esc(rec.trigger)}\" />\n          <div class=\"elabel\">${t.behav}</div>\n          <textarea class=\"einp eta\" id=\"ebehavior\">${taEsc(rec.behaviorPrompt)}</textarea>\n          <div class=\"erow\">\n            <div class=\"efield emoji\">\n              <div class=\"elabel\">${t.emoji}</div>\n              <input class=\"einp eemoji\" id=\"eemoji\" maxlength=\"4\" value=\"${esc(rec.emoji)}\" />\n            </div>\n            <div class=\"efield color\">\n              <div class=\"elabel\">${t.cor}</div>\n              <input class=\"einp ecolor\" id=\"ecolor\" type=\"color\" value=\"${rec.color || '#6366f1'}\" />\n            </div>\n          </div>\n          <div class=\"btns\">\n            <button class=\"sbtn sbtn-primary\" id=\"esave\">${t.save}</button>\n            <button class=\"sbtn sbtn-ghost\" id=\"ecancel\">${t.cancel}</button>\n            ${(!isNew && rec && !rec.isDefault) ? `<button class=\"sbtn sbtn-danger\" id=\"edelete\">${t.del}</button>` : ''}\n          </div>\n        </div>`\n    }\n    return `\n      <div class=\"panel${light ? ' light' : ''}\">\n        <div class=\"section\">\n          <div class=\"sec-label\">${t.reasoning}</div>\n          ${reasoning ? `<div class=\"reasoning-box\">${reasoning}</div>` : `<div class=\"empty\">${t.reasoningEmpty}</div>`}\n        </div>\n        <div class=\"section\">\n          <div class=\"sec-head\">\n            <div class=\"sec-label\">${t.states} (${records.length})</div>\n            <button class=\"add-btn\" id=\"enew\" type=\"button\">＋ ${t.add}</button>\n          </div>\n          ${items || `<div class=\"empty\">${t.empty}</div>`}\n        </div>\n        ${editForm}\n      </div>`\n  },\n  mounted(d2) {\n    d2.on('.state-item', 'click', e => {\n      const el = e.target.closest('[data-id]')\n      if (!el) return\n      d2.state.editId = d2.state.editId === el.dataset.id ? null : el.dataset.id\n    })\n    d2.on('#enew', 'click', () => { d2.state.editId = '__new__' })\n    d2.on('#ecancel', 'click', () => { d2.state.editId = null })\n    d2.on('#edelete', 'click', () => {\n      const ss = d2.store('statesStore')\n      const id = d2.state.editId\n      if (!id || id === '__new__') return\n      ss.set('states', (ss.get('states', []) || []).filter(r => r.id !== id))\n      d2.state.editId = null\n    })\n    d2.on('#esave', 'click', () => {\n      const ss = d2.store('statesStore')\n      const arr = ss.get('states', []) || []\n      const nameEl = d2.$('#ename')\n      const name = ((nameEl && nameEl.value) || '').trim()\n      if (!name) { if (nameEl) nameEl.focus(); return }\n      const trigger = ((d2.$('#etrigger') && d2.$('#etrigger').value) || '').trim()\n      const behavior = (d2.$('#ebehavior') && d2.$('#ebehavior').value) || ''\n      const emoji = ((d2.$('#eemoji') && d2.$('#eemoji').value) || '').trim() || '✨'\n      const color = (d2.$('#ecolor') && d2.$('#ecolor').value) || '#6366f1'\n      if (d2.state.editId === '__new__') {\n        const id = 's_' + Date.now()\n        ss.set('states', [...arr, { id, name, trigger, behaviorPrompt: behavior, emoji, color, isDefault: false }])\n      } else {\n        const id = d2.state.editId\n        ss.set('states', arr.map(r => r.id === id ? { ...r, name, trigger, behaviorPrompt: behavior, emoji, color } : r))\n      }\n      d2.state.editId = null\n    })\n  }\n}"}}}}}}}},{"_id":"6a42bb40a8e70e8e2bc7d708","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"bot-chat-input","package":"fernando-playground","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 { position: absolute; inset: 0; display: block; }\n    .bar {\n      position: absolute; inset: 0;\n      display: flex; align-items: center; gap: 8px;\n      box-sizing: border-box; padding: 12px 16px;\n      font-family: system-ui, sans-serif;\n      --bar-bg:#0d1320; --field-bg:#1f2937; --field-border:rgba(255,255,255,0.08);\n      --text:#e5e7eb; --ph:rgba(255,255,255,0.3); --icon:rgba(255,255,255,0.55);\n      --icon-hover:#e5e7eb; --icon-hover-bg:rgba(255,255,255,0.08);\n      --thumb:rgba(255,255,255,0.15); --topborder:rgba(255,255,255,0.08);\n      background: var(--bar-bg); border-top: 1px solid var(--topborder);\n      transition: background .25s, border-color .25s;\n    }\n    .bar.light {\n      --bar-bg:#f0f2f5; --field-bg:#ffffff; --field-border:rgba(0,0,0,0.12);\n      --text:#111827; --ph:rgba(0,0,0,0.4); --icon:rgba(0,0,0,0.5);\n      --icon-hover:#111827; --icon-hover-bg:rgba(0,0,0,0.06);\n      --thumb:rgba(0,0,0,0.2); --topborder:rgba(0,0,0,0.08);\n    }\n    .icon-btn {\n      flex-shrink: 0; width: 42px; height: 42px; border: none; border-radius: 50%;\n      background: transparent; color: var(--icon); cursor: pointer;\n      font-size: 22px; line-height: 1; display: flex; align-items: center; justify-content: center;\n      transition: background .2s, color .2s;\n    }\n    .icon-btn:hover { background: var(--icon-hover-bg); color: var(--icon-hover); }\n    .field {\n      flex: 1; display: flex; align-items: center; min-width: 0;\n      background: var(--field-bg); border: 1.5px solid var(--field-border);\n      border-radius: 22px; padding: 4px 16px; transition: border-color .2s, background .25s;\n    }\n    .field:focus-within { border-color: #4f46e5; }\n    textarea.in {\n      flex: 1; width: 100%; border: none; outline: none; background: transparent;\n      color: var(--text); font: 14px/1.5 system-ui, sans-serif; resize: none;\n      height: 24px; max-height: 96px; overflow-y: auto; padding: 4px 0; box-sizing: content-box;\n    }\n    textarea.in::placeholder { color: var(--ph); }\n    textarea.in::-webkit-scrollbar { width: 4px; }\n    textarea.in::-webkit-scrollbar-thumb { background: var(--thumb); border-radius: 4px; }\n    .send {\n      flex-shrink: 0; width: 44px; height: 44px; border: none; border-radius: 50%;\n      background: #4f46e5; color: #fff; cursor: pointer;\n      display: flex; align-items: center; justify-content: center;\n      transition: background .2s, opacity .2s, transform .08s;\n    }\n    .send:hover:not(:disabled) { background: #6366f1; }\n    .send:active:not(:disabled) { transform: scale(0.92); }\n    .send:disabled { opacity: 0.45; cursor: default; }\n    .send svg { width: 20px; height: 20px; display: block; }\n  `,\n  render(d2) {\n    const light = d2.store('themeStore').get('mode', 'dark') === 'light'\n    const lang = d2.store('chatStore').get('lang', 'pt')\n    const PH = { pt: 'Digite uma mensagem', en: 'Type a message', es: 'Escribe un mensaje' }\n    return `\n      <div class=\"bar${light ? ' light' : ''}\">\n        <div class=\"field\">\n          <textarea class=\"in\" rows=\"1\" placeholder=\"${PH[lang] || PH.pt}\"></textarea>\n        </div>\n        <button class=\"emoji-btn icon-btn\" type=\"button\" title=\"Emoji\">😊</button>\n        <button class=\"send\" type=\"button\" title=\"Enviar\">\n          <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3.4 20.4l17.45-7.48a1 1 0 0 0 0-1.84L3.4 3.6a.993.993 0 0 0-1.39.91L2 9.12c0 .5.37.93.87.99L17 12 2.87 13.88c-.5.07-.87.5-.87 1l.01 4.61c0 .67.68 1.13 1.39.91z\"/></svg>\n        </button>\n      </div>`\n  },\n  mounted(d2) {\n    const EMOJIS = ['😀','😁','😂','🤣','😊','😍','😘','😎','🤩','🥳','😅','😉','🙂','😇','🤔','😴','😭','😡','👍','👎','🙏','👏','🙌','💪','👋','🤝','❤️','🔥','✨','🎉','💰','🚀','✅','❌','💯','🤖','🛟','🧭','😢','😱']\n    const ERR = { pt: '⚠️ Erro ao falar com o bot. Tente novamente.', en: '⚠️ Error talking to the bot. Please try again.', es: '⚠️ Error al hablar con el bot. Inténtalo de nuevo.' }\n\n    const grow = () => {\n      const t = d2.$('.in')\n      if (!t) return\n      t.style.height = 'auto'\n      t.style.height = Math.min(t.scrollHeight, 96) + 'px'\n    }\n\n    const insertEmoji = (em) => {\n      const t = d2.$('.in')\n      if (!t) return\n      const s = t.selectionStart != null ? t.selectionStart : t.value.length\n      const e = t.selectionEnd != null ? t.selectionEnd : t.value.length\n      t.value = t.value.slice(0, s) + em + t.value.slice(e)\n      const pos = s + em.length\n      t.selectionStart = t.selectionEnd = pos\n      t.focus()\n      grow()\n    }\n\n    let pop = null\n    const closePop = () => { if (pop) { pop.remove(); pop = null } }\n    const openPop = () => {\n      closePop()\n      const btn = d2.$('.emoji-btn')\n      if (!btn) return\n      const light = d2.store('themeStore').get('mode', 'dark') === 'light'\n      const bg = light ? '#ffffff' : '#1f2937'\n      const bd = light ? 'rgba(0,0,0,0.12)' : 'rgba(255,255,255,0.12)'\n      const hov = light ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.1)'\n      pop = document.createElement('div')\n      pop.style.cssText = `position:fixed;z-index:2147483600;background:${bg};border:1px solid ${bd};border-radius:14px;padding:10px;display:grid;grid-template-columns:repeat(8,44px);grid-auto-rows:44px;gap:4px;box-shadow:0 10px 34px rgba(0,0,0,0.35);box-sizing:border-box;`\n      EMOJIS.forEach(em => {\n        const b = document.createElement('button')\n        b.type = 'button'\n        b.textContent = em\n        b.style.cssText = 'border:none;background:transparent;cursor:pointer;font-size:23px;line-height:1;padding:0;border-radius:8px;display:flex;align-items:center;justify-content:center;'\n        b.addEventListener('mouseenter', () => { b.style.background = hov })\n        b.addEventListener('mouseleave', () => { b.style.background = 'transparent' })\n        b.addEventListener('click', (ev) => { ev.stopPropagation(); insertEmoji(em) })\n        pop.appendChild(b)\n      })\n      document.body.appendChild(pop)\n      const r = btn.getBoundingClientRect()\n      pop.style.left = Math.min(Math.max(8, r.left), window.innerWidth - pop.offsetWidth - 8) + 'px'\n      pop.style.top = Math.max(8, r.top - pop.offsetHeight - 8) + 'px'\n    }\n\n    const send = async () => {\n      const t = d2.$('.in')\n      const btn = d2.$('.send')\n      if (!t) return\n      const text = t.value.trim()\n      const store = d2.store('chatStore')\n      if (!text || store.get('loading', false)) return\n      const lang = store.get('lang', 'pt')\n      closePop()\n      t.value = ''\n      grow()\n      if (btn) btn.disabled = true\n      const prev = store.get('messages', [])\n      store.set('messages', [...prev, { role: 'user', text }])\n      store.set('loading', true)\n      const summary = store.get('summary', '')\n      const lastBotMessage = store.get('lastBotMessage', '')\n      const states = d2.store('statesStore').get('states', [])\n      const botName = (d2.store('chatStore').get('botName', '') || '').trim()\n      try {\n        const res = await d2.rush('chat-turn').invoke({ userMessage: text, summary, lastBotMessage, lang, states, botName })\n        const r = (res && res.data && (res.data.reply !== undefined || res.data.stateName !== undefined || res.data.error !== undefined)) ? res.data : res\n        if (!r || r.error) throw new Error((r && r.error) || 'empty response')\n        const reply = r.reply || ''\n        const stateName = r.stateName || ''\n        const emoji = r.emoji || '🤖'\n        const color = r.color || '#6366f1'\n        const msgs = store.get('messages', [])\n        store.set('messages', [...msgs, { role: 'bot', text: reply, color, stateName, emoji, lang }])\n        store.set('currentState', stateName)\n        store.set('currentEmoji', emoji)\n        store.set('currentColor', color)\n        store.set('currentReasoning', r.reasoning || '')\n        store.set('summary', r.newSummary || summary)\n        store.set('lastBotMessage', reply)\n      } catch (err) {\n        d2.log('chat-turn error', err)\n        const msgs = store.get('messages', [])\n        store.set('messages', [...msgs, { role: 'bot', text: ERR[lang] || ERR.pt, color: '#ef4444' }])\n      } finally {\n        store.set('loading', false)\n        if (btn) btn.disabled = false\n        const tn = d2.$('.in')\n        if (tn) tn.focus()\n      }\n    }\n\n    d2.on('.emoji-btn', 'click', (e) => {\n      e.stopPropagation()\n      if (pop) closePop()\n      else openPop()\n    })\n    d2.on(document, 'click', () => { if (pop) closePop() })\n    d2.on(window, 'resize', closePop)\n    d2.onUnmount(closePop)\n\n    d2.on('.send', 'click', send)\n    d2.on('.in', 'keydown', (e) => {\n      if (e.key === 'Enter' && !e.shiftKey) {\n        e.preventDefault()\n        send()\n      }\n    })\n    d2.on('.in', 'input', grow)\n  }\n}"}}}}}}}},{"_id":"6a42c344fd3c05f6595aa0dc","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"bot-header","package":"fernando-playground","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 { position: absolute; inset: 0; display: block; }\n    .brand {\n      position: absolute; inset: 0;\n      display: flex; align-items: center; gap: 13px;\n      padding: 0 16px 0 26px; box-sizing: border-box;\n      font-family: system-ui, sans-serif;\n      background: #0d0f1a;\n      border-bottom: 1px solid rgba(255,255,255,0.06);\n      transition: background .25s, border-color .25s;\n    }\n    .brand.light { background: #ffffff; border-bottom: 1px solid rgba(0,0,0,0.08); }\n    .logo {\n      width: 40px; height: 40px; border-radius: 12px; flex-shrink: 0;\n      display: flex; align-items: center; justify-content: center;\n      font-size: 22px; line-height: 1;\n      background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 55%, #ec4899 100%);\n      box-shadow: 0 4px 16px rgba(99,102,241,0.45);\n    }\n    .txt { display: flex; flex-direction: column; gap: 1px; line-height: 1.15; min-width: 0; }\n    .name { font-size: 17px; font-weight: 700; letter-spacing: .2px; color: #f5f6f8; transition: color .25s; cursor: text; border-radius: 5px; padding: 0 2px; margin: 0 -2px; }\n    .name:hover { background: rgba(255,255,255,0.07); }\n    .brand.light .name { color: #111827; }\n    .brand.light .name:hover { background: rgba(0,0,0,0.05); }\n    .name-input {\n      font-size: 17px; font-weight: 700; letter-spacing: .2px; color: #f5f6f8;\n      background: rgba(255,255,255,0.12); border: 1px solid rgba(255,255,255,0.28);\n      border-radius: 6px; padding: 1px 7px; outline: none; font-family: inherit;\n      min-width: 140px; max-width: 340px;\n    }\n    .name-input:focus { border-color: #6366f1; }\n    .brand.light .name-input { color: #111827; background: rgba(0,0,0,0.05); border-color: rgba(0,0,0,0.22); }\n    .sub { font-size: 11px; font-weight: 500; color: rgba(255,255,255,0.42); white-space: nowrap; transition: color .25s; }\n    .brand.light .sub { color: rgba(0,0,0,0.45); }\n    .actions { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }\n    .lang-btn {\n      display: flex; align-items: center; gap: 7px;\n      height: 40px; padding: 0 12px; border-radius: 20px;\n      border: 1px solid rgba(255,255,255,0.12); background: rgba(255,255,255,0.06);\n      color: #e5e7eb; cursor: pointer; font-size: 13px; font-weight: 600;\n      font-family: inherit; transition: background .2s, border-color .2s, color .2s;\n    }\n    .lang-btn:hover { background: rgba(255,255,255,0.12); }\n    .lang-btn .flag { font-size: 17px; line-height: 1; }\n    .lang-btn .chev { font-size: 10px; opacity: .6; }\n    .lang-btn > span { display: flex; align-items: center; line-height: 1; }\n    .brand.light .lang-btn { border: 1px solid rgba(0,0,0,0.1); background: rgba(0,0,0,0.04); color: #111827; }\n    .brand.light .lang-btn:hover { background: rgba(0,0,0,0.08); }\n    .theme-btn {\n      flex-shrink: 0; width: 40px; height: 40px; border-radius: 50%;\n      border: 1px solid rgba(255,255,255,0.12); background: rgba(255,255,255,0.06); color: #e5e7eb;\n      cursor: pointer; font-size: 17px; line-height: 1;\n      display: flex; align-items: center; justify-content: center;\n      transition: background .2s, border-color .2s, color .2s;\n    }\n    .theme-btn:hover { background: rgba(255,255,255,0.12); }\n    .brand.light .theme-btn { border: 1px solid rgba(0,0,0,0.1); background: rgba(0,0,0,0.04); color: #111827; }\n    .brand.light .theme-btn:hover { background: rgba(0,0,0,0.08); }\n  `,\n  render(d2) {\n    const light = d2.store('themeStore').get('mode', 'dark') === 'light'\n    const cs = d2.store('chatStore')\n    const lang = cs.get('lang', 'pt')\n    const I18N = {\n      pt: { sub: 'Atendimento que muda de tom conforme o contexto', light: 'Modo claro', dark: 'Modo escuro', editHint: 'Clique duas vezes para editar o nome' },\n      en: { sub: 'Support that adapts its tone to the context', light: 'Light mode', dark: 'Dark mode', editHint: 'Double-click to edit the name' },\n      es: { sub: 'Atención que adapta el tono al contexto', light: 'Modo claro', dark: 'Modo oscuro', editHint: 'Haz doble clic para editar el nombre' }\n    }\n    const FLAGS = { pt: '🇧🇷', en: '🇺🇸', es: '🇪🇸' }\n    const t = I18N[lang] || I18N.pt\n    const name = (cs.get('botName', '') || '').trim() || 'Bot Adaptativo'\n    const editingName = d2.state.editingName\n    const nameHtml = editingName\n      ? `<input class=\"name-input\" maxlength=\"40\" value=\"${name.replace(/&/g, '&amp;').replace(/\"/g, '&quot;')}\" />`\n      : `<span class=\"name\" title=\"${t.editHint}\">${name.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</span>`\n    return `\n      <div class=\"brand${light ? ' light' : ''}\">\n        <div class=\"logo\">🤖</div>\n        <div class=\"txt\">\n          ${nameHtml}\n          <span class=\"sub\">${t.sub}</span>\n        </div>\n        <div class=\"actions\">\n          <button class=\"theme-btn\" id=\"newconv-btn\" type=\"button\" title=\"${lang === 'en' ? 'New conversation' : lang === 'es' ? 'Nueva conversación' : 'Nova conversa'}\">\n            <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 12a9 9 0 1 0 3-6.7L3 8\"/><path d=\"M3 3v5h5\"/></svg>\n          </button>\n          <button class=\"lang-btn\" type=\"button\" title=\"Idioma / Language / Idioma\">\n            <span class=\"flag\">🌐</span>\n            <span>${(lang || 'pt').toUpperCase()}</span>\n            <span class=\"chev\">▼</span>\n          </button>\n          <button class=\"theme-btn\" type=\"button\" title=\"${light ? t.dark : t.light}\">${light ? '🌙' : '☀️'}</button>\n        </div>\n      </div>`\n  },\n  mounted(d2) {\n    const LANGS = [\n      { code: 'pt', flag: '🇧🇷', label: 'Português' },\n      { code: 'en', flag: '🇺🇸', label: 'English' },\n      { code: 'es', flag: '🇪🇸', label: 'Español' }\n    ]\n    let menu = null\n    const closeMenu = () => { if (menu) { menu.remove(); menu = null } }\n\n    const chooseLang = (code) => {\n      const cs = d2.store('chatStore')\n      if (cs.get('lang', 'pt') !== code) {\n        // Troca apenas o idioma dos próximos turnos. Mantém o histórico\n        // visível e a memória (summary) — cada mensagem antiga permanece\n        // no idioma em que foi trocada, sem tradução.\n        cs.set('lang', code)\n      }\n      closeMenu()\n    }\n\n    const openMenu = () => {\n      closeMenu()\n      const btn = d2.$('.lang-btn')\n      if (!btn) return\n      const light = d2.store('themeStore').get('mode', 'dark') === 'light'\n      const cur = d2.store('chatStore').get('lang', 'pt')\n      const bg = light ? '#ffffff' : '#1b2030'\n      const bd = light ? 'rgba(0,0,0,0.12)' : 'rgba(255,255,255,0.12)'\n      const txt = light ? '#111827' : '#e5e7eb'\n      const hov = light ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.08)'\n      const accent = light ? 'rgba(99,102,241,0.12)' : 'rgba(99,102,241,0.25)'\n      menu = document.createElement('div')\n      menu.style.cssText = `position:fixed;z-index:2147483600;background:${bg};border:1px solid ${bd};border-radius:12px;padding:6px;box-shadow:0 10px 34px rgba(0,0,0,0.35);min-width:180px;box-sizing:border-box;font-family:system-ui,sans-serif;`\n      LANGS.forEach(l => {\n        const it = document.createElement('button')\n        it.type = 'button'\n        const isCur = l.code === cur\n        it.innerHTML = `<span style=\"font-size:11px;font-weight:700;opacity:.55;min-width:24px;letter-spacing:.04em\">${l.code.toUpperCase()}</span><span style=\"flex:1;text-align:left\">${l.label}</span>${isCur ? '<span style=\"color:#6366f1;font-weight:700\">✓</span>' : ''}`\n        it.style.cssText = `display:flex;align-items:center;gap:10px;width:100%;border:none;cursor:pointer;font-size:14px;font-weight:${isCur ? '600' : '500'};color:${txt};background:${isCur ? accent : 'transparent'};padding:9px 11px;border-radius:8px;font-family:inherit;`\n        it.addEventListener('mouseenter', () => { if (!isCur) it.style.background = hov })\n        it.addEventListener('mouseleave', () => { if (!isCur) it.style.background = 'transparent' })\n        it.addEventListener('click', (ev) => { ev.stopPropagation(); chooseLang(l.code) })\n        menu.appendChild(it)\n      })\n      document.body.appendChild(menu)\n      const r = btn.getBoundingClientRect()\n      const left = Math.min(r.left, window.innerWidth - menu.offsetWidth - 8)\n      menu.style.left = Math.max(8, left) + 'px'\n      menu.style.top = (r.bottom + 6) + 'px'\n    }\n\n    d2.on('.lang-btn', 'click', (e) => {\n      e.stopPropagation()\n      if (menu) closeMenu()\n      else openMenu()\n    })\n    d2.on('.theme-btn', 'click', () => {\n      const s = d2.store('themeStore')\n      s.set('mode', s.get('mode', 'dark') === 'light' ? 'dark' : 'light')\n    })\n    d2.on('#newconv-btn', 'click', () => {\n      // Limpa só a conversa — mantém idioma, marca (botName), tema e estados.\n      const cs = d2.store('chatStore')\n      cs.set('messages', [])\n      cs.set('summary', '')\n      cs.set('lastBotMessage', '')\n      cs.set('loading', false)\n      cs.set('currentReasoning', '')\n      cs.set('currentState', 'Descoberta')\n      cs.set('currentEmoji', '🧭')\n      cs.set('currentColor', '#6366f1')\n    })\n    d2.on(document, 'click', () => { if (menu) closeMenu() })\n    d2.on(window, 'resize', closeMenu)\n    d2.onUnmount(closeMenu)\n\n    // --- Nome editável (duplo clique) → chatStore.botName ---\n    const commitName = () => {\n      if (!d2.state.editingName) return\n      const inp = d2.$('.name-input')\n      if (inp) d2.store('chatStore').set('botName', (inp.value || '').trim())\n      d2.state.editingName = false\n    }\n    d2.on('.name', 'dblclick', () => { d2.state.editingName = true })\n    d2.on('.name-input', 'keydown', (e) => {\n      if (e.key === 'Enter') { e.preventDefault(); commitName() }\n      else if (e.key === 'Escape') { e.preventDefault(); d2.state.editingName = false }\n    })\n    d2.on('.name-input', 'focusout', commitName)\n  },\n  updated(d2) {\n    if (d2.state.editingName) {\n      const inp = d2.$('.name-input')\n      if (inp && document.activeElement !== inp) { inp.focus(); inp.select() }\n    }\n  }\n}"}}}}}}}},{"_id":"6a43b4d2af6a4650d3105770","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"comparador-versoes","package":"fernando-playground","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    .wrap { font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif; box-sizing: border-box;\n            position: fixed; inset: 0; width: 100vw; height: 100vh; overflow-y: auto;\n            background: #f4f6fb; color: #1c2433; padding: 24px 36px 32px;\n            display: flex; flex-direction: column; gap: 16px; }\n    .wrap *, .wrap *::before, .wrap *::after { box-sizing: border-box; }\n    header h1 { margin: 0; font-size: 24px; font-weight: 800; letter-spacing: -0.02em; }\n    header p { margin: 4px 0 0; font-size: 14px; color: #5a6577; }\n\n    .controls { display: flex; align-items: center; gap: 24px; flex-wrap: wrap;\n                background: #fff; border: 1px solid #e3e8f0; border-radius: 14px; padding: 14px 20px;\n                box-shadow: 0 1px 3px rgba(20,30,50,.05); }\n    .cgroup { display: flex; align-items: center; gap: 10px; }\n    .clabel { font-size: 12px; text-transform: uppercase; letter-spacing: .07em; color: #8a93a6; font-weight: 700; }\n    .stepper { display: flex; align-items: center; gap: 8px; }\n    .stepper button { width: 32px; height: 32px; border-radius: 8px; border: 1px solid #d3dae6; background: #fff;\n                      font-size: 19px; line-height: 1; cursor: pointer; color: #2b3445; font-weight: 700; }\n    .stepper button:hover { background: #eef2f8; }\n    .pctval { min-width: 56px; text-align: center; font-weight: 800; font-size: 19px; font-variant-numeric: tabular-nums; }\n    .pills { display: flex; gap: 6px; }\n    .pill { padding: 8px 16px; border-radius: 9px; border: 1px solid #d3dae6; background: #fff; cursor: pointer;\n            font-size: 14px; font-weight: 700; color: #5a6577; }\n    .pill:hover { background: #eef2f8; }\n    .pill.on { background: #2f6bff; border-color: #2f6bff; color: #fff; }\n    .run { margin-left: auto; padding: 12px 26px; border: none; border-radius: 10px; cursor: pointer;\n           background: #2f6bff; color: #fff; font-size: 15px; font-weight: 800; }\n    .run:hover { background: #1f57e0; }\n    .run:disabled { opacity: .6; cursor: default; }\n\n    .order { background: #fff; border: 1px solid #e3e8f0; border-radius: 14px; padding: 8px 18px;\n             box-shadow: 0 1px 3px rgba(20,30,50,.05); }\n    .item { display: grid; grid-template-columns: 1fr 110px 120px 110px; align-items: center; gap: 12px;\n            padding: 7px 0; border-bottom: 1px solid #f0f3f8; }\n    .item:last-child { border-bottom: none; }\n    .iname { font-weight: 600; font-size: 15px; }\n    .iprice { color: #7a8499; font-size: 13px; }\n    .qty { display: flex; align-items: center; gap: 8px; justify-self: center; }\n    .qty button { width: 30px; height: 30px; border-radius: 8px; border: 1px solid #d3dae6; background: #fff;\n                  font-size: 17px; line-height: 1; cursor: pointer; color: #2b3445; font-weight: 600; }\n    .qty button:hover { background: #eef2f8; }\n    .qval { min-width: 22px; text-align: center; font-weight: 700; font-size: 15px; }\n    .iline { text-align: right; font-weight: 700; font-size: 15px; }\n\n    .err { padding: 10px 14px; background: #fdecec; border: 1px solid #f5b5b5; color: #b42323;\n           border-radius: 10px; font-size: 14px; }\n    .cards { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }\n    .card { background: #fff; border: 1px solid #e3e8f0; border-radius: 14px; padding: 20px 24px;\n            box-shadow: 0 1px 3px rgba(20,30,50,.05); display: flex; flex-direction: column; min-height: 252px; }\n    .card.ran { outline: 2px solid #2f6bff; outline-offset: -1px; }\n    .chead { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }\n    .ctitle { font-size: 19px; font-weight: 800; }\n    .badge { font-size: 12px; font-weight: 800; padding: 5px 12px; border-radius: 999px; letter-spacing: .04em;\n             background: #eaf0ff; color: #2f6bff; }\n    .cardwait { flex: 1; display: flex; align-items: center; justify-content: center; text-align: center;\n                color: #aeb6c5; font-size: 15px; line-height: 1.5; }\n    .metric { display: flex; align-items: center; justify-content: space-between; padding: 9px 0;\n              border-top: 1px solid #f0f3f8; font-size: 15px; }\n    .metric .k { color: #667085; }\n    .metric .v { font-weight: 700; font-variant-numeric: tabular-nums; }\n    .metric.diff { background: #fffbeb; margin: 0 -10px; padding-left: 10px; padding-right: 10px; border-radius: 8px; }\n    .metric.diff .v { color: #d97706; }\n    .total { margin-top: auto; padding-top: 14px; border-top: 2px solid #1c2433; display: flex;\n             align-items: baseline; justify-content: space-between; }\n    .total .k { font-size: 14px; text-transform: uppercase; letter-spacing: .06em; color: #667085; }\n    .total .v { font-size: 30px; font-weight: 800; font-variant-numeric: tabular-nums; }\n    .total.diff .v { color: #d97706; }\n    .cref { font-size: 11px; color: #97a0b2; font-family: ui-monospace, monospace; margin-top: 12px; text-align: right; }\n    .deltabar { grid-column: 1 / -1; padding: 12px 18px; border-radius: 12px; font-size: 15px;\n                font-weight: 700; text-align: center; }\n    .deltabar.same { background: #eafaf0; color: #1a7f47; border: 1px solid #b7e6c9; }\n    .deltabar.changed { background: #fff4e5; color: #b45309; border: 1px solid #f5d9a8; }\n  `,\n  render(d2) {\n    const s = d2.state;\n    const items = s.items || [];\n    const pct = s.descontoPct ?? 10;\n    const alvo = s.alvo || 'ambos';\n    const fmt = (n) => d2.fmt.currency(Number(n || 0), 'BRL');\n    const pick = (r) => {\n      if (!r) return null;\n      if (r.versao !== undefined) return r;\n      if (r.data && r.data.versao !== undefined) return r.data;\n      return r.data || r;\n    };\n    const a = pick(s.a);\n    const b = pick(s.b);\n\n    const itemsHtml = items.map((it, i) => `\n      <div class=\"item\" data-key=\"item-${i}\">\n        <span class=\"iname\">${it.nome}</span>\n        <span class=\"iprice\">${fmt(it.preco)} / un</span>\n        <div class=\"qty\">\n          <button data-act=\"dec\" data-i=\"${i}\">−</button>\n          <span class=\"qval\">${it.qtd}</span>\n          <button data-act=\"inc\" data-i=\"${i}\">+</button>\n        </div>\n        <span class=\"iline\">${fmt(it.preco * it.qtd)}</span>\n      </div>`).join('');\n\n    const pill = (val, label) => `<button class=\"pill ${alvo === val ? 'on' : ''}\" data-act=\"alvo\" data-alvo=\"${val}\">${label}</button>`;\n    const runLabel = s.loading ? 'Rodando…' : (alvo === 'a' ? 'Rodar no Rush A' : alvo === 'b' ? 'Rodar no Rush B' : 'Rodar nos dois');\n\n    const card = (title, ref, self, other, isTarget) => {\n      const ran = isTarget ? ' ran' : '';\n      if (!self) {\n        return `<div class=\"card${ran}\">\n          <div class=\"chead\"><span class=\"ctitle\">${title}</span><span class=\"cref\">${ref}</span></div>\n          <div class=\"cardwait\"><span>Ainda não rodado.<br>Selecione e clique em <b>Rodar</b>.</span></div>\n        </div>`;\n      }\n      const diff = (k) => other && Number(self[k]) !== Number(other[k]) ? ' diff' : '';\n      return `<div class=\"card${ran}\">\n        <div class=\"chead\"><span class=\"ctitle\">${title}</span><span class=\"badge\">${self.versao || '—'}</span></div>\n        <div class=\"metric${diff('subtotal')}\"><span class=\"k\">Subtotal</span><span class=\"v\">${fmt(self.subtotal)}</span></div>\n        <div class=\"metric${diff('descontoValor')}\"><span class=\"k\">Desconto (${self.descontoPct ?? 0}%)</span><span class=\"v\">− ${fmt(self.descontoValor)}</span></div>\n        <div class=\"metric${diff('frete')}\"><span class=\"k\">Frete</span><span class=\"v\">${Number(self.frete) ? fmt(self.frete) : 'grátis'}</span></div>\n        <div class=\"total${diff('total')}\"><span class=\"k\">Total</span><span class=\"v\">${fmt(self.total)}</span></div>\n        <div class=\"cref\">${ref}</div>\n      </div>`;\n    };\n\n    let delta = '';\n    if (a && b) {\n      const dt = Number(a.total) - Number(b.total);\n      delta = Math.abs(dt) < 0.005\n        ? `<div class=\"deltabar same\">✓ Mesmo resultado nos dois Rushs — são a mesma versão.</div>`\n        : `<div class=\"deltabar changed\">▲ ${fmt(Math.abs(dt))} de diferença no total — as versões divergem.</div>`;\n    }\n    const resultHtml = `<div class=\"cards\">\n      ${card('Rush A', 'preco-pedido-a', a, b, alvo === 'a' || alvo === 'ambos')}\n      ${card('Rush B', 'preco-pedido-b', b, a, alvo === 'b' || alvo === 'ambos')}\n      ${delta}\n    </div>`;\n\n    return `<div class=\"wrap\">\n      <header>\n        <h1>Preço de Pedido — Comparador de Versões</h1>\n        <p>Escolha o desconto e em qual Rush rodar. O mesmo pedido é enviado ao(s) Rush(s) selecionado(s); o outro card mantém o último resultado para comparação.</p>\n      </header>\n      <div class=\"controls\">\n        <div class=\"cgroup\">\n          <span class=\"clabel\">Desconto</span>\n          <div class=\"stepper\">\n            <button data-act=\"dec-pct\">−</button>\n            <span class=\"pctval\">${pct}%</span>\n            <button data-act=\"inc-pct\">+</button>\n          </div>\n        </div>\n        <div class=\"cgroup\">\n          <span class=\"clabel\">Rodar em</span>\n          <div class=\"pills\">${pill('a', 'Rush A')}${pill('b', 'Rush B')}${pill('ambos', 'Ambos')}</div>\n        </div>\n        <button class=\"run\" data-act=\"run\" ${s.loading ? 'disabled' : ''}>${runLabel}</button>\n      </div>\n      <div class=\"order\">${itemsHtml}</div>\n      ${s.err ? `<div class=\"err\">${s.err}</div>` : ''}\n      ${resultHtml}\n    </div>`;\n  },\n  mounted(d2) {\n    try {\n      document.documentElement.style.background = '#f4f6fb';\n      document.body.style.background = '#f4f6fb';\n      document.body.style.margin = '0';\n    } catch (e) {}\n    if (!d2.state.items) {\n      d2.state.items = [\n        { nome: 'Camiseta', preco: 80, qtd: 2 },\n        { nome: 'Boné', preco: 50, qtd: 1 },\n        { nome: 'Tênis', preco: 250, qtd: 1 }\n      ];\n    }\n    if (d2.state.descontoPct == null) d2.state.descontoPct = 10;\n    if (!d2.state.alvo) d2.state.alvo = 'ambos';\n\n    const run = async () => {\n      const alvo = d2.state.alvo || 'ambos';\n      d2.state.loading = true;\n      d2.state.err = null;\n      const body = { items: d2.state.items, descontoPct: d2.state.descontoPct };\n      try {\n        if (alvo === 'a') {\n          d2.state.a = await d2.rush('preco-pedido-a').invoke(body);\n        } else if (alvo === 'b') {\n          d2.state.b = await d2.rush('preco-pedido-b').invoke(body);\n        } else {\n          const [ra, rb] = await Promise.all([\n            d2.rush('preco-pedido-a').invoke(body),\n            d2.rush('preco-pedido-b').invoke(body)\n          ]);\n          d2.state.a = ra;\n          d2.state.b = rb;\n        }\n      } catch (e) {\n        d2.state.err = String((e && e.message) || e);\n      }\n      d2.state.loading = false;\n    };\n\n    d2.on('[data-act]', 'click', (ev, el) => {\n      const act = el.getAttribute('data-act');\n      if (act === 'run') return run();\n      if (act === 'alvo') { d2.state.alvo = el.getAttribute('data-alvo'); return; }\n      if (act === 'inc-pct') { d2.state.descontoPct = (d2.state.descontoPct ?? 10) + 5; return; }\n      if (act === 'dec-pct') { d2.state.descontoPct = Math.max(0, (d2.state.descontoPct ?? 10) - 5); return; }\n      const i = Number(el.getAttribute('data-i'));\n      const items = (d2.state.items || []).map((it) => ({ ...it }));\n      if (!items[i]) return;\n      if (act === 'inc') items[i].qtd += 1;\n      if (act === 'dec') items[i].qtd = Math.max(0, items[i].qtd - 1);\n      d2.state.items = items;\n    });\n  }\n}"}}}}}}}},{"_id":"6a44876ca92372895f252b0b","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"shop-storefront","package":"fernando-playground","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 { position: absolute; inset: 0; overflow: hidden; font-family: system-ui, sans-serif; }\n    .page { position: absolute; inset: 0; overflow-y: auto; background: #ECEAF3; color: #1A1A2E; }\n    .nav {\n      position: sticky; top: 0; z-index: 5; display: flex; align-items: center; justify-content: space-between;\n      padding: 16px 40px; background: #ffffff; border-bottom: 1px solid #E4E0F0;\n    }\n    .brand { font-size: 20px; font-weight: 800; letter-spacing: -.3px; }\n    .brand span { color: #6C4DF6; }\n    .talk-btn {\n      border: none; border-radius: 999px; padding: 10px 20px; background: #6C4DF6; color: #fff;\n      font-size: 13px; font-weight: 700; cursor: pointer; display: flex; align-items: center; gap: 6px;\n    }\n    .talk-btn:hover { background: #5b3ce0; }\n    .hero { padding: 56px 40px 44px; background: linear-gradient(135deg,#6C4DF6,#E84393); color: #fff; }\n    .hero h1 { font-size: 38px; font-weight: 800; max-width: 640px; line-height: 1.15; margin: 0 0 12px; }\n    .hero p { font-size: 16px; opacity: .92; max-width: 520px; margin: 0; }\n    .section { padding: 40px; }\n    .section h2 { font-size: 22px; font-weight: 800; margin: 0 0 18px; }\n    .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 20px; }\n    .card { background: #fff; border: 1px solid #E4E0F0; border-radius: 14px; overflow: hidden; display: flex; flex-direction: column; }\n    .card img { width: 100%; height: 180px; object-fit: cover; display: block; background: #F2EFFB; }\n    .card .body { padding: 16px; display: flex; flex-direction: column; gap: 6px; flex: 1; }\n    .tag { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: #6C4DF6; }\n    .name { font-size: 15px; font-weight: 700; line-height: 1.3; }\n    .brand-name { font-size: 12px; color: #6A6A88; }\n    .desc { font-size: 12px; color: #6A6A88; line-height: 1.4; flex: 1; }\n    .price { font-size: 18px; font-weight: 800; margin-top: 4px; }\n    .ask-btn {\n      border: 1.5px solid #6C4DF6; color: #6C4DF6; background: transparent; border-radius: 999px;\n      padding: 8px 14px; font-size: 12px; font-weight: 700; cursor: pointer; margin-top: 6px;\n    }\n    .ask-btn:hover { background: #F2EFFB; }\n    .empty { padding: 20px 0; color: #6A6A88; font-size: 14px; }\n  `,\n  render(d2) {\n    const esc = (s) => (s ?? '').toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n    const ds = d2.data('products')\n    const loading = !ds || ds.loading\n    const rows = loading ? [] : ds.records()\n    const cardsHtml = rows.map((p) => `\n      <div class=\"card\" data-key=\"${p._id}\">\n        <img src=\"${esc(p.image_url)}\" alt=\"${esc(p.name)}\" />\n        <div class=\"body\">\n          <div class=\"tag\">${esc(p.category)}${p.subcategory ? ' · ' + esc(p.subcategory) : ''}</div>\n          <div class=\"name\">${esc(p.name)}</div>\n          <div class=\"brand-name\">${esc(p.brand)}</div>\n          <div class=\"desc\">${esc(p.description)}</div>\n          <div class=\"price\">${d2.fmt.currency(Number(p.price) || 0, 'BRL')}</div>\n          <button class=\"ask-btn\" data-id=\"${p._id}\" data-name=\"${esc(p.name)}\" type=\"button\">Perguntar à consultora</button>\n        </div>\n      </div>`).join('')\n    const gridInner = loading\n      ? `<div class=\"empty\">Carregando produtos…</div>`\n      : (rows.length ? cardsHtml : `<div class=\"empty\">Nenhum produto cadastrado ainda.</div>`)\n    return `\n      <div class=\"page\">\n        <div class=\"nav\">\n          <div class=\"brand\">Data2 <span>Beauty</span></div>\n          <button class=\"talk-btn\" type=\"button\">✨ Falar com a consultora</button>\n        </div>\n        <div class=\"hero\">\n          <h1>Sua rotina de beleza, sob medida.</h1>\n          <p>Descreva o que você precisa e nossa consultora de IA monta uma rotina completa — com avisos de compatibilidade entre ativos e ajuste de orçamento. Procure o ícone no canto da tela.</p>\n        </div>\n        <div class=\"section\">\n          <h2>Nossos produtos</h2>\n          <div class=\"grid\">${gridInner}</div>\n        </div>\n      </div>`\n  },\n  mounted(d2) {\n    const openWidget = (text) => {\n      d2.store('bot-state').set('widgetOpen', true)\n      if (text) d2.bus.emit('consultora:ask', { text })\n    }\n    d2.on('.talk-btn', 'click', () => openWidget())\n    d2.on('.ask-btn', 'click', (e, el) => {\n      openWidget(`Me conte mais sobre o ${el.dataset.name}`)\n    })\n  },\n}"}},"products":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"products"}}}}}}}}}},{"_id":"6a4487902e19654bbcdead94","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"consultora-widget","package":"fernando-playground","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 { position: absolute; inset: 0; pointer-events: none; font-family: system-ui, sans-serif; }\n    .bubble-wrap { position: absolute; right: 28px; bottom: 28px; display: flex; align-items: center; gap: 10px; pointer-events: auto; }\n    .hint { background: #1A1A2E; color: #fff; font-size: 13px; padding: 8px 14px; border-radius: 999px; box-shadow: 0 6px 20px rgba(0,0,0,.18); white-space: nowrap; }\n    .bubble {\n      width: 60px; height: 60px; border-radius: 50%; background: linear-gradient(135deg,#6C4DF6,#E84393);\n      display: flex; align-items: center; justify-content: center; font-size: 26px; color: #fff;\n      cursor: pointer; border: none; box-shadow: 0 10px 30px rgba(108,77,246,.45); transition: transform .15s;\n    }\n    .bubble:hover { transform: scale(1.06); }\n    .panel {\n      position: absolute; right: 28px; bottom: 28px; width: 420px; height: min(720px, calc(100% - 56px));\n      background: #16162a; color: #e5e7eb; border-radius: 18px; overflow: hidden; display: flex; flex-direction: column;\n      box-shadow: 0 24px 70px rgba(0,0,0,.4); pointer-events: auto;\n    }\n    .phead { display: flex; align-items: center; justify-content: space-between; padding: 16px 18px; background: linear-gradient(135deg,#6C4DF6,#E84393); flex-shrink: 0; }\n    .ptitle { font-size: 15px; font-weight: 800; color: #fff; }\n    .psub { font-size: 11px; color: rgba(255,255,255,.85); }\n    .close { background: rgba(255,255,255,.18); border: none; color: #fff; width: 28px; height: 28px; border-radius: 50%; cursor: pointer; font-size: 14px; line-height: 1; }\n    .log { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 10px; }\n    .empty-state { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 10px; padding: 30px; }\n    .row { display: flex; flex-direction: column; }\n    .row.user { align-items: flex-end; }\n    .row.bot { align-items: flex-start; }\n    .bubble-msg { max-width: 85%; padding: 10px 13px; border-radius: 14px; font-size: 13.5px; line-height: 1.45; white-space: pre-line; }\n    .bubble-msg.user { background: #4f46e5; color: #fff; border-bottom-right-radius: 4px; }\n    .bubble-msg.bot { background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.1); border-bottom-left-radius: 4px; }\n    .dots span { width: 6px; height: 6px; border-radius: 50%; background: rgba(255,255,255,.4); display: inline-block; margin-right: 3px; animation: blink 1.2s infinite; }\n    .dots span:nth-child(2) { animation-delay: .2s; }\n    .dots span:nth-child(3) { animation-delay: .4s; }\n    @keyframes blink { 0%,80%,100% { opacity: .25 } 40% { opacity: 1 } }\n    .routines { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; width: 100%; }\n    .rcard { background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.12); border-radius: 12px; padding: 12px; }\n    .rname { font-size: 13px; font-weight: 800; color: #fff; }\n    .rblurb { font-size: 12px; color: rgba(255,255,255,.7); margin: 4px 0 8px; }\n    .ritem { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; padding: 4px 0; border-top: 1px dashed rgba(255,255,255,.1); }\n    .ritem:first-child { border-top: none; }\n    .rtotal { display: flex; justify-content: space-between; font-size: 13px; font-weight: 800; color: #fff; margin-top: 8px; padding-top: 8px; border-top: 1px solid rgba(255,255,255,.14); }\n    .rwarn { font-size: 11px; color: #F0D9A8; background: rgba(240,217,168,.1); border: 1px solid rgba(240,217,168,.25); border-radius: 8px; padding: 6px 8px; margin-top: 8px; }\n    .bar { flex-shrink: 0; display: flex; align-items: center; gap: 8px; padding: 12px 14px; background: #0d1320; border-top: 1px solid rgba(255,255,255,.08); }\n    .field { flex: 1; background: #1f2937; border: 1.5px solid rgba(255,255,255,.08); border-radius: 20px; padding: 4px 14px; }\n    .field:focus-within { border-color: #6C4DF6; }\n    textarea.cin { width: 100%; border: none; outline: none; background: transparent; color: #e5e7eb; font: 13.5px/1.5 system-ui, sans-serif; resize: none; height: 22px; max-height: 84px; padding: 6px 0; box-sizing: content-box; }\n    .send { flex-shrink: 0; width: 40px; height: 40px; border: none; border-radius: 50%; background: #6C4DF6; color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; }\n    .send:disabled { opacity: .45; cursor: default; }\n    .send svg { width: 18px; height: 18px; }\n  `,\n  render(d2) {\n    const store = d2.store('bot-state')\n    const open = !!store.get('widgetOpen', false)\n    if (!open) {\n      return `<div class=\"bubble-wrap\"><div class=\"hint\">Precisa de ajuda? ✨</div><button class=\"bubble\" type=\"button\" title=\"Falar com a consultora\">💬</button></div>`\n    }\n    const esc = (s) => (s ?? '').toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n    const msgs = store.get('messages', [])\n    const loading = store.get('loading', false)\n    const routineCard = (r) => `\n      <div class=\"rcard\" data-key=\"${r.key}\">\n        <div class=\"rname\">${esc(r.name)}</div>\n        <div class=\"rblurb\">${esc(r.blurb)}</div>\n        ${(r.items || []).map((it) => `\n          <div class=\"ritem\"><span>${esc(it.step)} · ${esc(it.name)}</span><span>${d2.fmt.currency(Number(it.price) || 0, 'BRL')}</span></div>\n        `).join('')}\n        <div class=\"rtotal\"><span>Total</span><span>${d2.fmt.currency(Number(r.total) || 0, 'BRL')}</span></div>\n        ${(r.warnings || []).map((w) => `<div class=\"rwarn\">⚠️ ${esc(w)}</div>`).join('')}\n      </div>`\n    const rows = msgs.length ? msgs.map((m, i) => `\n      <div class=\"row ${m.role}\" data-key=\"${i}\">\n        <div class=\"bubble-msg ${m.role}\">${esc(m.text)}</div>\n        ${m.routines && m.routines.length ? `<div class=\"routines\">${m.routines.map(routineCard).join('')}</div>` : ''}\n      </div>`).join('') : `<div class=\"empty-state\">\n        <div style=\"font-size:40px\">👋</div>\n        <div style=\"font-weight:800;font-size:15px\">Oi! Sou a consultora de beleza.</div>\n        <div style=\"font-size:12.5px;color:rgba(255,255,255,.6);max-width:280px\">Descreva sua necessidade — ou peça pra eu falar sobre um produto — que eu monto uma rotina pra você.</div>\n      </div>`\n    const dots = loading ? `<div class=\"row bot\"><div class=\"bubble-msg bot\"><span class=\"dots\"><span></span><span></span><span></span></span></div></div>` : ''\n    return `\n      <div class=\"panel\">\n        <div class=\"phead\">\n          <div><div class=\"ptitle\">✨ Consultora de Beleza</div><div class=\"psub\">powered by IA Data2</div></div>\n          <button class=\"close\" type=\"button\" title=\"Minimizar\">✕</button>\n        </div>\n        <div class=\"log\" id=\"log\">${rows}${dots}</div>\n        <div class=\"bar\">\n          <div class=\"field\"><textarea class=\"cin\" rows=\"1\" placeholder=\"Descreva sua necessidade...\"></textarea></div>\n          <button class=\"send\" type=\"button\" title=\"Enviar\">\n            <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3.4 20.4l17.45-7.48a1 1 0 0 0 0-1.84L3.4 3.6a.993.993 0 0 0-1.39.91L2 9.12c0 .5.37.93.87.99L17 12 2.87 13.88c-.5.07-.87.5-.87 1l.01 4.61c0 .67.68 1.13 1.39.91z\"/></svg>\n          </button>\n        </div>\n      </div>`\n  },\n  mounted(d2) {\n    const send = async (presetText) => {\n      const store = d2.store('bot-state')\n      const t = d2.$('.cin')\n      const text = (presetText != null ? presetText : (t ? t.value : '')).trim()\n      if (!text || store.get('loading', false)) return\n      if (t) t.value = ''\n      const btn = d2.$('.send')\n      if (btn) btn.disabled = true\n      const msgs = store.get('messages', [])\n      store.set('messages', [...msgs, { role: 'user', text }])\n      store.set('loading', true)\n      try {\n        const res = await d2.rush('consultoria-turn').invoke({ userMessage: text })\n        const r = (res && res.data) ? res.data : res\n        if (!r || r.type === 'error') throw new Error((r && r.message) || 'resposta vazia')\n        if (r.type === 'question') {\n          store.set('messages', [...store.get('messages', []), { role: 'bot', text: r.question || 'Pode me dar mais detalhes?' }])\n        } else {\n          const copyByKey = {}\n          ;((r.copy && r.copy.routines) || []).forEach((c) => { copyByKey[c.key] = c })\n          const routines = (r.routines || []).map((rt) => ({\n            key: rt.key,\n            name: rt.name,\n            total: rt.total,\n            blurb: (copyByKey[rt.key] && copyByKey[rt.key].blurb) || rt.rationale || '',\n            warnings: (copyByKey[rt.key] && copyByKey[rt.key].warnings) || [],\n            items: rt.items_detailed || [],\n          }))\n          const botText = (r.copy && r.copy.diagnosis) || r.diagnosis_seed || 'Aqui estão as rotinas que montei pra você:'\n          store.set('messages', [...store.get('messages', []), { role: 'bot', text: botText, routines }])\n        }\n      } catch (err) {\n        d2.log('consultoria-turn error', err)\n        store.set('messages', [...store.get('messages', []), { role: 'bot', text: '⚠️ Erro ao falar com a consultora. Tente novamente.' }])\n      } finally {\n        store.set('loading', false)\n        if (btn) btn.disabled = false\n      }\n    }\n    d2.on('.bubble', 'click', () => { d2.store('bot-state').set('widgetOpen', true) })\n    d2.on('.close', 'click', () => { d2.store('bot-state').set('widgetOpen', false) })\n    d2.on('.send', 'click', () => send())\n    d2.on('.cin', 'keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send() } })\n    if (d2.bus) {\n      d2.bus.on('consultora:ask', ({ text }) => {\n        const t = d2.$('.cin')\n        if (t) t.value = text\n        else send(text)\n      })\n    }\n  },\n  updated(d2) {\n    const log = d2.$('#log')\n    if (log) log.scrollTop = log.scrollHeight\n  },\n}"}}}}}}}},{"_id":"6a448ab5a92372895f253061","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"consultora-app","package":"fernando-playground","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; position:relative; width:100%; height:100%; overflow:hidden; font-family: system-ui,-apple-system,Segoe UI,sans-serif; color:#262019; }\n    * { box-sizing:border-box; }\n    .page { position:absolute; inset:0; overflow-y:auto; overflow-x:hidden; background:#FBF9F5; scrollbar-width:thin; scrollbar-color:#DED4C4 transparent; }\n    .page::-webkit-scrollbar, .drawer-body::-webkit-scrollbar { width:11px; }\n    .page::-webkit-scrollbar-track, .drawer-body::-webkit-scrollbar-track { background:transparent; }\n    .page::-webkit-scrollbar-thumb, .drawer-body::-webkit-scrollbar-thumb { background:#DBD1C0; border-radius:999px; border:3px solid transparent; background-clip:content-box; }\n    .page::-webkit-scrollbar-thumb:hover, .drawer-body::-webkit-scrollbar-thumb:hover { background:#C6B8A0; background-clip:content-box; }\n    .log { scrollbar-width:thin; scrollbar-color:rgba(255,255,255,.2) transparent; }\n    .log::-webkit-scrollbar { width:8px; }\n    .log::-webkit-scrollbar-track { background:transparent; }\n    .log::-webkit-scrollbar-thumb { background:rgba(255,255,255,.16); border-radius:999px; border:2px solid transparent; background-clip:content-box; }\n    .serif { font-family: Georgia,'Times New Roman',serif; }\n\n    .topbar { background:#262019; color:#F3EFE8; text-align:center; font-size:12px; letter-spacing:.04em; padding:8px 16px; }\n    .nav { position:sticky; top:0; z-index:6; display:flex; align-items:center; justify-content:space-between; gap:24px; padding:15px 40px; background:rgba(251,249,245,.92); backdrop-filter:blur(10px); border-bottom:1px solid #EAE3D8; }\n    .cats { display:flex; gap:22px; flex:1; justify-content:center; }\n    .cat { font-size:14px; font-weight:500; color:#6E665A; cursor:pointer; padding:4px 0; border-bottom:2px solid transparent; transition:.15s; white-space:nowrap; }\n    .cat:hover { color:#262019; }\n    .cat.active { color:#262019; border-bottom-color:#B4633E; font-weight:600; }\n    .navright { display:flex; align-items:center; gap:12px; position:relative; }\n    .pmenu-scrim { position:fixed; inset:0; z-index:30; }\n    .pmenu { position:absolute; top:52px; right:0; z-index:31; background:#fff; border:1px solid #EAE3D8; border-radius:12px; box-shadow:0 14px 36px rgba(38,32,25,.18); padding:6px; min-width:210px; animation:fade .15s ease; }\n    .pmenu-head { font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:#A69C8C; padding:9px 12px 5px; }\n    .pmenu-item { display:flex; align-items:center; gap:9px; width:100%; border:none; background:none; text-align:left; padding:10px 12px; border-radius:8px; font-size:13.5px; color:#262019; cursor:pointer; font-weight:600; }\n    .pmenu-item:hover { background:#F4F0E9; }\n    .search { display:flex; align-items:center; gap:7px; background:#F1ECE4; border:1px solid #E8E1D6; border-radius:999px; padding:8px 14px; width:200px; }\n    .search .si { color:#9A9184; font-size:15px; }\n    .searchi { border:none; background:none; outline:none; font-size:13px; color:#262019; width:100%; font-family:inherit; }\n    .searchi::placeholder { color:#A69C8C; }\n    .iconbtn { position:relative; border:none; background:none; cursor:pointer; color:#262019; width:36px; height:36px; display:flex; align-items:center; justify-content:center; border-radius:50%; transition:.15s; }\n    .iconbtn:hover { background:#F1ECE4; }\n    .iconbtn svg { width:21px; height:21px; }\n    .ibadge { position:absolute; top:-1px; right:-1px; background:#B4633E; color:#fff; font-size:10px; font-weight:700; min-width:17px; height:17px; border-radius:999px; display:flex; align-items:center; justify-content:center; padding:0 4px; }\n    .banner { display:flex; align-items:stretch; min-height:340px; background:linear-gradient(120deg,#EFE7DA,#F7F2EA); overflow:hidden; }\n    .banner-in { flex:1; padding:56px 48px; display:flex; flex-direction:column; justify-content:center; }\n    .banner-eyebrow { font-size:12px; font-weight:700; letter-spacing:.18em; text-transform:uppercase; color:#B4633E; margin-bottom:14px; }\n    .banner-title { font-size:44px; line-height:1.08; font-weight:600; color:#262019; margin:0 0 14px; letter-spacing:-.5px; }\n    .banner-sub { font-size:16px; color:#6E665A; margin:0 0 24px; max-width:440px; line-height:1.55; }\n    .banner-cta { display:flex; align-items:center; gap:16px; flex-wrap:wrap; }\n    .banner-btn { border:none; background:#262019; color:#fff; border-radius:999px; padding:13px 24px; font-size:14px; font-weight:700; cursor:pointer; display:inline-flex; align-items:center; gap:7px; transition:.15s; }\n    .banner-btn:hover { background:#3d342a; }\n    .banner-coupon { font-size:12px; font-weight:700; letter-spacing:.06em; color:#B4633E; border:1px dashed rgba(180,99,62,.5); border-radius:8px; padding:9px 14px; }\n    .banner-art { width:36%; background:linear-gradient(150deg,#DBCCB6,#C4AF94); display:flex; align-items:center; justify-content:center; }\n    .banner-mono { font-family:Georgia,serif; font-size:170px; color:rgba(255,255,255,.4); font-weight:600; line-height:1; }\n    .logo { font-family:Georgia,serif; font-size:22px; font-weight:600; letter-spacing:.3px; color:#262019; cursor:pointer; }\n    .logo .dot { color:#B4633E; margin:0 4px; }\n    .cart-btn { position:relative; border:1px solid #DBD3C6; background:#fff; color:#262019; border-radius:999px;\n      padding:9px 20px; font-size:13px; font-weight:600; cursor:pointer; letter-spacing:.02em; }\n    .cart-btn:hover { border-color:#262019; }\n    .badge { position:absolute; top:-7px; right:-7px; background:#B4633E; color:#fff; font-size:11px; font-weight:700;\n      min-width:19px; height:19px; border-radius:999px; display:inline-flex; align-items:center; justify-content:center; padding:0 5px; }\n\n    .hero { padding:72px 48px 40px; }\n    .hero-in { max-width:600px; }\n    .eyebrow { font-size:12px; font-weight:700; letter-spacing:.18em; text-transform:uppercase; color:#B4633E; margin-bottom:14px; }\n    .hero h1 { font-family:Georgia,serif; font-size:52px; line-height:1.04; font-weight:600; margin:0 0 16px; letter-spacing:-.5px; }\n    .hero p { font-size:16px; line-height:1.6; color:#6E665A; margin:0; max-width:500px; }\n\n    .section { padding:20px 48px 90px; }\n    .section-head { display:flex; align-items:baseline; justify-content:space-between; margin-bottom:24px; border-bottom:1px solid #EAE3D8; padding-bottom:14px; }\n    .section-head h2 { font-family:Georgia,serif; font-size:24px; font-weight:600; margin:0; }\n    .count { font-size:13px; color:#9A9184; }\n    .grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(258px,1fr)); gap:26px; }\n\n    .pcard { background:#fff; border:1px solid #EAE3D8; border-radius:16px; overflow:hidden; display:flex; flex-direction:column; transition:box-shadow .2s, transform .2s; }\n    .pcard:hover { box-shadow:0 14px 40px rgba(38,32,25,.10); transform:translateY(-3px); }\n    .thumb { position:relative; height:210px; background:linear-gradient(140deg,#F1EBE1,#E6DCCD); display:flex; align-items:center; justify-content:center; }\n    .thumb .mono { font-family:Georgia,serif; font-size:64px; color:#CDC2B0; font-weight:600; }\n    .thumb .timg { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; }\n    .pm-img .pmimg { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; }\n    .co-thumb img { width:100%; height:100%; object-fit:cover; border-radius:10px; display:block; }\n    .sthumb img { width:100%; height:100%; object-fit:cover; border-radius:9px; display:block; }\n    .othumb img { width:100%; height:100%; object-fit:cover; border-radius:9px; display:block; }\n    .thumb-tag { position:absolute; top:14px; left:14px; background:rgba(255,255,255,.85); color:#6E665A; font-size:10.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; padding:5px 10px; border-radius:999px; }\n    .pbody { padding:18px; display:flex; flex-direction:column; gap:5px; flex:1; }\n    .pbrand { font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:#B4633E; }\n    .pname { font-size:16px; font-weight:600; line-height:1.3; color:#262019; }\n    .pdesc { font-size:12.5px; color:#8C8578; line-height:1.5; flex:1; margin-bottom:6px; }\n    .pprice { font-family:Georgia,serif; font-size:22px; font-weight:600; margin:8px 0 12px; }\n    .addbtn { width:100%; padding:12px; border:1.5px solid #262019; background:transparent; color:#262019; border-radius:10px; font-size:13.5px; font-weight:700; cursor:pointer; transition:.15s; letter-spacing:.01em; }\n    .addbtn:hover { background:#262019; color:#fff; }\n    .stepper.full { display:flex; width:100%; }\n    .stepper.full .qbtn { width:42px; }\n    .stepper.full .qn { flex:1; }\n    .askpill { position:absolute; top:12px; right:12px; width:34px; height:34px; border-radius:50%; display:inline-flex; align-items:center; justify-content:center; background:rgba(255,255,255,.92); color:#B4633E; border:1px solid rgba(180,99,62,.45); font-size:15px; cursor:pointer; box-shadow:0 2px 10px rgba(38,32,25,.1); transition:.15s; }\n    .askpill:hover { background:#B4633E; color:#fff; border-color:#B4633E; transform:scale(1.08); }\n\n    .btn { border:none; border-radius:10px; font-size:13px; font-weight:600; cursor:pointer; padding:10px 16px; transition:.15s; letter-spacing:.01em; }\n    .btn.primary { background:#262019; color:#fff; }\n    .btn.primary:hover { background:#3d342a; }\n    .btn.lg { width:100%; padding:15px; font-size:14px; }\n    .stepper { display:inline-flex; align-items:center; border:1px solid #DBD3C6; border-radius:10px; overflow:hidden; background:#fff; }\n    .stepper.sm { transform:none; }\n    .stepper.sm .qbtn { width:32px; height:32px; font-size:16px; }\n    .stepper.sm .qn { min-width:26px; }\n    .qbtn { border:none; background:transparent; width:34px; height:38px; font-size:18px; cursor:pointer; color:#262019; display:inline-flex; align-items:center; justify-content:center; line-height:1; padding:0; }\n    .qbtn:hover { background:#F4F0E9; }\n    .qn { min-width:30px; text-align:center; font-size:14px; font-weight:700; }\n    .ask { margin-top:12px; width:100%; background:rgba(180,99,62,.09); color:#B4633E; border:1px solid rgba(180,99,62,.22); border-radius:10px; padding:9px; font-size:12.5px; font-weight:700; cursor:pointer; transition:.15s; }\n    .ask:hover { background:rgba(180,99,62,.16); }\n    .empty { grid-column:1/-1; padding:40px; text-align:center; color:#9A9184; }\n    .footer-note { padding:28px 48px 60px; color:#A69C8C; font-size:12.5px; text-align:center; border-top:1px solid #EAE3D8; }\n    .pcard { cursor:pointer; }\n    .mscrim { position:absolute; inset:0; background:rgba(38,32,25,.42); z-index:24; animation:fade .2s ease; }\n    .pmodal { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:min(880px, calc(100% - 40px)); max-height:calc(100% - 40px); overflow-y:auto; background:#FBF9F5; border-radius:18px; z-index:25; display:grid; grid-template-columns:44% 1fr; box-shadow:0 30px 80px rgba(38,32,25,.35); animation:modalin .2s ease-out; scrollbar-width:thin; scrollbar-color:#DED4C4 transparent; }\n    @keyframes modalin { from { opacity:0; transform:translate(-50%,-50%) scale(.98); } to { opacity:1; transform:translate(-50%,-50%) scale(1); } }\n    .pmodal::-webkit-scrollbar { width:10px; }\n    .pmodal::-webkit-scrollbar-thumb { background:#DBD1C0; border-radius:999px; border:3px solid transparent; background-clip:content-box; }\n    .pm-close { position:absolute; top:16px; right:16px; z-index:2; border:none; background:rgba(255,255,255,.92); width:34px; height:34px; border-radius:50%; cursor:pointer; font-size:14px; color:#6E665A; box-shadow:0 2px 8px rgba(38,32,25,.12); }\n    .pm-close:hover { background:#fff; }\n    .pm-img { position:relative; background:linear-gradient(140deg,#F1EBE1,#E6DCCD); display:flex; align-items:center; justify-content:center; min-height:360px; }\n    .pm-img .mono { font-family:Georgia,serif; font-size:120px; color:#CDC2B0; font-weight:600; }\n    .pm-body { padding:34px 34px 30px; }\n    .pm-brand { font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:#B4633E; }\n    .pm-name { font-size:28px; font-weight:600; line-height:1.15; margin:6px 0 10px; }\n    .pm-price { font-family:Georgia,serif; font-size:24px; font-weight:600; margin-bottom:14px; }\n    .pm-desc { font-size:14px; color:#6E665A; line-height:1.6; margin:0 0 18px; }\n    .pm-meta { border-top:1px solid #EAE3D8; margin-bottom:20px; }\n    .pm-mrow { display:grid; grid-template-columns:140px 1fr; gap:12px; padding:11px 0; border-bottom:1px solid #EAE3D8; font-size:13px; }\n    .pm-mk { font-weight:700; color:#262019; }\n    .pm-mv { color:#6E665A; line-height:1.5; }\n    .pm-consultora { margin-top:20px; display:flex; align-items:center; gap:13px; background:rgba(180,99,62,.07); border:1px solid rgba(180,99,62,.25); border-radius:14px; padding:14px 16px; }\n    .pm-cico { width:38px; height:38px; flex-shrink:0; border-radius:50%; background:linear-gradient(135deg,#B4633E,#E0A075); color:#fff; display:flex; align-items:center; justify-content:center; font-size:18px; }\n    .pm-ctxt { flex:1; display:flex; flex-direction:column; gap:2px; }\n    .pm-ctxt strong { font-size:13px; color:#262019; }\n    .pm-ctxt span { font-size:12.5px; color:#8C7F6E; line-height:1.4; }\n    .pm-cbtn { flex-shrink:0; border:none; background:#B4633E; color:#fff; border-radius:999px; padding:10px 18px; font-size:12.5px; font-weight:700; cursor:pointer; }\n    .pm-cbtn:hover { background:#c9714a; }\n\n    /* checkout */\n    .co { padding:34px 48px 90px; max-width:1120px; margin:0 auto; }\n    .co-head { display:flex; flex-direction:column; gap:10px; margin-bottom:26px; }\n    .co-back { align-self:flex-start; border:none; background:none; color:#B4633E; font-size:13px; font-weight:600; cursor:pointer; padding:0; }\n    .co-back:hover { text-decoration:underline; }\n    .co-head h1 { font-family:Georgia,serif; font-size:34px; font-weight:600; margin:0; }\n    .co-grid { display:grid; grid-template-columns:1fr 348px; gap:28px; align-items:start; }\n    .co-main { display:flex; flex-direction:column; gap:20px; }\n    .co-card { background:#fff; border:1px solid #EAE3D8; border-radius:16px; padding:24px; }\n    .co-card h2 { font-family:Georgia,serif; font-size:18px; font-weight:600; margin:0 0 16px; }\n    .co-item { display:grid; grid-template-columns:56px 1fr auto; gap:15px; align-items:center; padding:14px 0; border-top:1px solid #F0EAE0; }\n    .co-item:first-of-type { border-top:none; padding-top:0; }\n    .co-thumb { width:56px; height:56px; border-radius:10px; background:linear-gradient(140deg,#F1EBE1,#E6DCCD); display:flex; align-items:center; justify-content:center; }\n    .co-thumb .mono { font-family:Georgia,serif; font-size:22px; color:#CDC2B0; font-weight:600; }\n    .ci-info { display:flex; flex-direction:column; gap:6px; }\n    .ci-name { font-size:14px; font-weight:600; }\n    .ci-line { display:flex; align-items:center; gap:12px; }\n    .ci-price { font-size:13px; color:#8C8578; }\n    .ci-remove { border:none; background:none; color:#A69C8C; font-size:11.5px; cursor:pointer; padding:0; text-decoration:underline; }\n    .ci-total { font-family:Georgia,serif; font-size:16px; font-weight:600; }\n\n    .co-sugg { border:1px solid rgba(180,99,62,.28); background:linear-gradient(180deg,rgba(180,99,62,.05),#fff 60px); }\n    .sugg-head { display:flex; align-items:center; gap:12px; margin-bottom:16px; }\n    .sugg-ico { width:38px; height:38px; border-radius:50%; background:linear-gradient(135deg,#B4633E,#E0A075); display:flex; align-items:center; justify-content:center; font-size:18px; color:#fff; flex-shrink:0; }\n    .sugg-head h2 { margin:0; }\n    .sugg-head p { margin:2px 0 0; font-size:12.5px; color:#8C8578; }\n    .scard { display:grid; grid-template-columns:48px 1fr auto; gap:13px; align-items:center; padding:13px 0; border-top:1px solid #F0EAE0; }\n    .scard:first-of-type { border-top:none; }\n    .sthumb { width:48px; height:48px; border-radius:9px; background:linear-gradient(140deg,#F1EBE1,#E6DCCD); display:flex; align-items:center; justify-content:center; }\n    .sthumb .mono { font-family:Georgia,serif; font-size:19px; color:#CDC2B0; font-weight:600; }\n    .s-name { font-size:13.5px; font-weight:600; }\n    .s-reason { font-size:12px; color:#B4633E; margin-top:2px; }\n    .s-right { display:flex; flex-direction:column; align-items:flex-end; gap:6px; }\n    .s-price { font-size:13px; font-weight:600; }\n    .s-add { border:1px solid #B4633E; background:#fff; color:#B4633E; border-radius:8px; padding:6px 14px; font-size:12px; font-weight:700; cursor:pointer; }\n    .s-add:hover { background:#B4633E; color:#fff; }\n    .sugg-empty { font-size:13px; color:#9A9184; padding:6px 0; }\n\n    .pay-row { display:flex; align-items:center; gap:12px; padding:13px 15px; border:1px solid #E8E1D6; border-radius:11px; margin-bottom:10px; cursor:pointer; transition:.15s; }\n    .pay-row:hover { border-color:#C9BCA6; }\n    .pay-row.sel { border-color:#262019; background:#FBF9F5; }\n    .radio { width:18px; height:18px; border-radius:50%; border:2px solid #C9BCA6; flex-shrink:0; position:relative; }\n    .pay-row.sel .radio { border-color:#262019; }\n    .pay-row.sel .radio::after { content:''; position:absolute; inset:3px; border-radius:50%; background:#262019; }\n    .pay-label { font-size:13.5px; font-weight:600; }\n    .pay-sub { font-size:12px; color:#9A9184; }\n    .field-mock { width:100%; padding:12px 14px; border:1px solid #E8E1D6; border-radius:11px; font-size:13.5px; color:#6E665A; background:#FBF9F5; margin-bottom:10px; }\n    .secure { display:flex; align-items:center; gap:8px; font-size:12px; color:#6E8B6E; background:rgba(110,139,110,.08); border:1px solid rgba(110,139,110,.2); border-radius:10px; padding:11px 14px; margin-top:6px; }\n\n    .co-summary { position:sticky; top:24px; background:#fff; border:1px solid #EAE3D8; border-radius:16px; padding:24px; }\n    .co-summary h3 { font-family:Georgia,serif; font-size:18px; font-weight:600; margin:0 0 18px; }\n    .sum-row { display:flex; justify-content:space-between; font-size:13.5px; color:#6E665A; padding:7px 0; }\n    .sum-row.total { font-size:18px; font-weight:700; color:#262019; border-top:1px solid #EAE3D8; margin-top:8px; padding-top:14px; }\n    .sum-row.total span:last-child { font-family:Georgia,serif; }\n    .free { color:#6E8B6E; font-weight:600; }\n    .co-badges { display:flex; flex-direction:column; gap:7px; margin-top:16px; font-size:11.5px; color:#9A9184; }\n    .co-success { min-height:100%; display:flex; align-items:center; justify-content:center; padding:40px 24px; }\n    .co-done { background:#fff; border:1px solid #EAE3D8; border-radius:18px; padding:64px 48px; text-align:center; max-width:540px; width:100%; margin:0; display:flex; flex-direction:column; align-items:center; gap:14px; }\n    .cd-ico { width:70px; height:70px; border-radius:50%; background:#B4633E; color:#fff; display:flex; align-items:center; justify-content:center; font-size:34px; }\n    .cd-num { font-size:13px; color:#8C8578; letter-spacing:.05em; }\n    .cd-title { font-family:Georgia,serif; font-size:28px; font-weight:600; }\n    .cd-sub { font-size:13.5px; color:#8C8578; line-height:1.55; max-width:340px; }\n\n    /* cart drawer */\n    .scrim { position:absolute; inset:0; background:rgba(38,32,25,.34); z-index:14; animation:fade .2s ease; }\n    .drawer { position:absolute; top:0; right:0; bottom:0; width:400px; max-width:92%; background:#F8F5EF; z-index:15; display:flex; flex-direction:column; box-shadow:-20px 0 60px rgba(38,32,25,.2); animation:slidein .26s cubic-bezier(.22,1,.36,1); }\n    @keyframes slidein { from{transform:translateX(40px);opacity:.4} to{transform:none;opacity:1} }\n    @keyframes fade { from{opacity:0} to{opacity:1} }\n    .drawer-head { display:flex; align-items:center; justify-content:space-between; padding:22px 24px; border-bottom:1px solid #EAE3D8; }\n    .dh-title { font-family:Georgia,serif; font-size:20px; font-weight:600; }\n    .close { border:none; background:#EFE9DF; width:32px; height:32px; border-radius:50%; cursor:pointer; font-size:14px; color:#6E665A; }\n    .close:hover { background:#E4DACB; }\n    .drawer-body { flex:1; overflow-y:auto; padding:16px 24px; display:flex; flex-direction:column; gap:16px; }\n    .citem { display:grid; grid-template-columns:60px 1fr auto; gap:13px; align-items:start; }\n    .cthumb { width:60px; height:60px; border-radius:10px; overflow:hidden; background:linear-gradient(140deg,#F1EBE1,#E6DCCD); display:flex; align-items:center; justify-content:center; flex-shrink:0; }\n    .cthumb img { width:100%; height:100%; object-fit:cover; display:block; }\n    .cthumb .mono { font-family:Georgia,serif; font-size:22px; color:#CDC2B0; font-weight:600; }\n    .cinfo { display:flex; flex-direction:column; gap:5px; min-width:0; }\n    .cname { font-size:13.5px; font-weight:600; line-height:1.3; }\n    .cprice { font-size:12px; color:#8C8578; }\n    .crow { display:flex; align-items:center; gap:14px; margin-top:3px; }\n    .cremove { border:none; background:none; color:#A69C8C; font-size:11.5px; cursor:pointer; padding:0; text-decoration:underline; }\n    .cline { font-family:Georgia,serif; font-size:14px; font-weight:600; white-space:nowrap; }\n    .cart-consultora { margin-top:6px; background:rgba(180,99,62,.06); border:1px solid rgba(180,99,62,.22); border-radius:14px; padding:14px; }\n    .cc-head { display:flex; align-items:center; gap:8px; font-size:13px; font-weight:800; color:#262019; margin-bottom:9px; }\n    .cc-ico { width:24px; height:24px; border-radius:50%; background:linear-gradient(135deg,#B4633E,#E0A075); color:#fff; display:flex; align-items:center; justify-content:center; font-size:12px; }\n    .cc-line { font-size:12px; color:#8C7F6E; margin-bottom:8px; }\n    .cc-sitem { display:flex; align-items:center; gap:10px; background:#fff; border:1px solid #EAE3D8; border-radius:10px; padding:9px 11px; margin-bottom:7px; }\n    .cc-sinfo { flex:1; min-width:0; }\n    .cc-sname { font-size:12.5px; font-weight:600; }\n    .cc-sreason { font-size:11.5px; color:#B4633E; margin-top:1px; }\n    .cc-sadd { flex-shrink:0; border:1px solid #B4633E; background:#fff; color:#B4633E; border-radius:8px; padding:6px 11px; font-size:11.5px; font-weight:700; cursor:pointer; white-space:nowrap; }\n    .cc-sadd:hover { background:#B4633E; color:#fff; }\n    .cc-chat { width:100%; margin-top:4px; border:none; background:#262019; color:#fff; border-radius:10px; padding:11px; font-size:12.5px; font-weight:700; cursor:pointer; }\n    .cc-chat:hover { background:#3d342a; }\n    .drawer-foot { padding:20px 24px; border-top:1px solid #EAE3D8; background:#fff; }\n    .subtotal { display:flex; justify-content:space-between; font-size:16px; font-weight:700; margin-bottom:14px; }\n    .subtotal span:last-child { font-family:Georgia,serif; }\n    .foot-fine { font-size:11px; color:#A69C8C; text-align:center; margin-top:10px; }\n    .empty-cart { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; color:#9A9184; font-size:14px; }\n    .empty-cart div { font-size:36px; }\n\n    /* launcher */\n    .launcher { position:absolute; right:30px; bottom:30px; z-index:12; display:flex; align-items:center; gap:11px; background:#262019; color:#fff; border-radius:999px; padding:12px 20px 12px 14px; cursor:pointer; box-shadow:0 12px 34px rgba(38,32,25,.28); transition:transform .16s; }\n    .launcher:hover { transform:translateY(-2px); }\n    .l-ico { width:34px; height:34px; border-radius:50%; background:linear-gradient(135deg,#B4633E,#D98A5B); display:flex; align-items:center; justify-content:center; font-size:17px; }\n    .l-label { font-size:14px; font-weight:600; letter-spacing:.01em; }\n    .l-pulse { position:absolute; left:14px; top:12px; width:34px; height:34px; border-radius:50%; background:#B4633E; opacity:.5; animation:pulse 2.2s infinite; }\n    @keyframes pulse { 0%{transform:scale(1);opacity:.45} 70%{transform:scale(1.9);opacity:0} 100%{opacity:0} }\n\n    /* chat panel — AI signature */\n    .panel { position:absolute; right:30px; bottom:30px; width:412px; height:min(680px,calc(100% - 60px)); z-index:16; background:#17130F; border-radius:20px; overflow:hidden; display:flex; flex-direction:column; box-shadow:0 30px 80px rgba(20,15,10,.5); animation:panelin .3s cubic-bezier(.22,1,.36,1); border:1px solid rgba(255,255,255,.06); }\n    @keyframes panelin { from{transform:translateY(24px) scale(.98);opacity:0} to{transform:none;opacity:1} }\n    .phead { position:relative; padding:20px 20px 18px; overflow:hidden; flex-shrink:0; background:linear-gradient(135deg,#2A211A,#3A2A20); border-bottom:1px solid rgba(255,255,255,.07); }\n    .phead::after { content:''; position:absolute; right:-40px; top:-60px; width:180px; height:180px; border-radius:50%; background:radial-gradient(circle,rgba(217,138,91,.5),transparent 70%); }\n    .ph-row { position:relative; display:flex; align-items:center; justify-content:space-between; z-index:1; }\n    .ph-brand { display:flex; align-items:center; gap:11px; }\n    .ph-ico { width:38px; height:38px; border-radius:50%; background:linear-gradient(135deg,#B4633E,#E0A075); display:flex; align-items:center; justify-content:center; font-size:18px; }\n    .ptitle { font-size:15px; font-weight:700; color:#fff; letter-spacing:.01em; }\n    .psub { font-size:11px; color:rgba(236,228,217,.6); margin-top:1px; }\n    .pclose { border:none; background:rgba(255,255,255,.12); color:#fff; width:30px; height:30px; border-radius:50%; cursor:pointer; font-size:13px; }\n    .pclose:hover { background:rgba(255,255,255,.22); }\n    .log { flex:1; overflow-y:auto; padding:18px; display:flex; flex-direction:column; gap:12px; }\n    .estate { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; gap:12px; padding:30px; }\n    .estate .wave { font-size:44px; }\n    .estate h3 { color:#fff; font-size:16px; font-weight:700; margin:0; font-family:Georgia,serif; }\n    .estate p { color:rgba(236,228,217,.6); font-size:13px; line-height:1.55; margin:0; max-width:280px; }\n    .chips { display:flex; flex-wrap:wrap; gap:8px; justify-content:center; margin-top:6px; }\n    .qchips { display:flex; flex-wrap:wrap; gap:8px; margin-top:9px; }\n    .mic { flex-shrink:0; width:42px; height:42px; border-radius:50%; border:none; cursor:pointer; background:rgba(255,255,255,.08); color:#ECE4D9; font-size:16px; display:flex; align-items:center; justify-content:center; transition:.15s; }\n    .mic:hover { background:rgba(255,255,255,.15); }\n    .mic.rec { background:#E9534E; color:#fff; animation:micpulse 1.1s infinite; }\n    @keyframes micpulse { 0%,100%{opacity:1} 50%{opacity:.5} }\n    .chip { background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.12); color:rgba(236,228,217,.9); font-size:12px; padding:8px 13px; border-radius:999px; cursor:pointer; transition:.15s; }\n    .chip:hover { background:rgba(180,99,62,.25); border-color:rgba(217,138,91,.5); }\n    .row { display:flex; flex-direction:column; max-width:100%; }\n    .row.user { align-items:flex-end; }\n    .row.bot { align-items:flex-start; }\n    .msg { max-width:86%; padding:11px 14px; border-radius:15px; font-size:13.5px; line-height:1.5; white-space:pre-line; }\n    .msg.user { background:#B4633E; color:#fff; border-bottom-right-radius:5px; }\n    .msg.bot { background:rgba(255,255,255,.055); color:#ECE4D9; border:1px solid rgba(255,255,255,.08); border-bottom-left-radius:5px; }\n    .dots span { width:6px; height:6px; border-radius:50%; background:rgba(236,228,217,.5); display:inline-block; margin-right:4px; animation:blink 1.2s infinite; }\n    .dots span:nth-child(2){animation-delay:.2s} .dots span:nth-child(3){animation-delay:.4s}\n    @keyframes blink { 0%,80%,100%{opacity:.25} 40%{opacity:1} }\n    .routines { display:flex; flex-direction:column; gap:11px; margin-top:10px; width:100%; }\n    .opts { display:flex; flex-direction:column; gap:9px; margin-top:10px; width:100%; }\n    .ocard { display:grid; grid-template-columns:44px 1fr auto; gap:11px; align-items:center; background:rgba(255,255,255,.045); border:1px solid rgba(255,255,255,.1); border-radius:12px; padding:10px 12px; }\n    .othumb { width:44px; height:44px; border-radius:9px; background:linear-gradient(140deg,rgba(255,255,255,.14),rgba(255,255,255,.05)); display:flex; align-items:center; justify-content:center; }\n    .othumb .mono { font-family:Georgia,serif; font-size:18px; color:rgba(255,255,255,.55); font-weight:600; }\n    .oname { font-size:13px; font-weight:700; color:#fff; }\n    .ostep { font-size:11.5px; color:rgba(236,228,217,.55); margin-top:1px; }\n    .oright { display:flex; flex-direction:column; align-items:flex-end; gap:6px; }\n    .oprice { font-size:13px; font-weight:600; color:#E0A075; font-family:Georgia,serif; }\n    .oadd { border:none; background:#B4633E; color:#fff; border-radius:8px; padding:6px 13px; font-size:12px; font-weight:700; cursor:pointer; }\n    .oadd:hover { background:#c9714a; }\n    .odetail { background:none; border:none; color:rgba(236,228,217,.6); font-size:11px; cursor:pointer; text-decoration:underline; margin-top:5px; padding:2px 0; }\n    .odetail:hover { color:#E0A075; }\n    .sidemodal { position:absolute; right:464px; bottom:28px; z-index:19; width:340px; max-height:min(680px, calc(100% - 56px)); overflow-y:auto; background:#17120E; border:1px solid rgba(255,255,255,.12); border-radius:18px; box-shadow:0 24px 70px rgba(0,0,0,.45); color:#ECE4D9; animation:fade .18s ease; }\n    .sm-head { display:flex; align-items:center; justify-content:space-between; padding:12px 14px 8px; }\n    .sm-tag { font-size:10px; font-weight:800; letter-spacing:.08em; text-transform:uppercase; color:#E0A075; background:rgba(180,99,62,.18); border-radius:999px; padding:4px 10px; }\n    .sm-close { background:rgba(255,255,255,.1); border:none; color:#fff; width:26px; height:26px; border-radius:50%; cursor:pointer; font-size:12px; }\n    .sm-close:hover { background:rgba(255,255,255,.2); }\n    .sm-img { height:180px; margin:0 14px; border-radius:12px; overflow:hidden; background:rgba(255,255,255,.05); display:flex; align-items:center; justify-content:center; }\n    .sm-img img { width:100%; height:100%; object-fit:cover; }\n    .sm-img .mono { font-family:Georgia,serif; font-size:56px; color:rgba(255,255,255,.4); }\n    .sm-body { padding:14px; display:flex; flex-direction:column; gap:5px; }\n    .sm-brand { font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:rgba(236,228,217,.55); }\n    .sm-name { font-family:Georgia,serif; font-size:17px; font-weight:600; color:#fff; line-height:1.25; }\n    .sm-price { font-size:18px; font-weight:800; color:#E0A075; margin:2px 0; }\n    .sm-desc { font-size:12.5px; line-height:1.5; color:rgba(236,228,217,.75); margin:4px 0 10px; }\n    .sm-meta { display:flex; flex-direction:column; gap:8px; margin-bottom:12px; }\n    .sm-mrow { display:flex; flex-direction:column; gap:2px; border-top:1px solid rgba(255,255,255,.08); padding-top:8px; }\n    .sm-mk { font-size:10.5px; text-transform:uppercase; letter-spacing:.05em; color:rgba(236,228,217,.5); }\n    .sm-mv { font-size:12.5px; color:#ECE4D9; line-height:1.45; }\n    .sm-add { width:100%; border:none; border-radius:10px; background:#B4633E; color:#fff; padding:12px; font-size:13.5px; font-weight:800; cursor:pointer; }\n    .sm-add:hover { background:#c9714a; }\n    .catsw { display:inline-flex; align-items:center; gap:8px; border:1px solid #E2DACB; background:#fff; color:#262019; border-radius:999px; padding:8px 14px; font-size:12.5px; font-weight:800; cursor:pointer; white-space:nowrap; }\n    .catsw:hover { border-color:#B4633E; color:#B4633E; }\n    .catsw-swap { color:#B4633E; font-weight:700; font-size:14px; }\n    .rcard { background:rgba(255,255,255,.045); border:1px solid rgba(255,255,255,.1); border-radius:14px; padding:14px; }\n    .rtop { display:flex; align-items:center; justify-content:space-between; }\n    .rname { font-size:13.5px; font-weight:800; color:#fff; }\n    .rover { display:inline-block; margin-left:8px; font-size:9.5px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:#E9A99E; background:rgba(224,138,120,.14); border:1px solid rgba(224,138,120,.4); border-radius:999px; padding:2px 7px; vertical-align:middle; }\n    .rok { display:inline-block; margin-left:8px; font-size:9.5px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:#A8D8B0; background:rgba(150,200,150,.14); border:1px solid rgba(150,200,150,.36); border-radius:999px; padding:2px 7px; vertical-align:middle; }\n    .rcard-ok { border-color:rgba(150,200,150,.42); background:rgba(150,200,150,.07); }\n    .rcard-over { border-color:rgba(224,138,120,.45); background:rgba(224,138,120,.07); }\n    .rcard-ok .rtotal { color:#A8D8B0; }\n    .rcard-over .rtotal { color:#E9A99E; }\n    .rtotal { font-family:Georgia,serif; font-size:15px; font-weight:600; color:#E0A075; }\n    .rblurb { font-size:12px; color:rgba(236,228,217,.66); margin:5px 0 9px; line-height:1.45; }\n    .ritem { display:flex; justify-content:space-between; gap:10px; font-size:12px; color:rgba(236,228,217,.86); padding:5px 0; border-top:1px dashed rgba(255,255,255,.1); }\n    .ritem:first-child { border-top:none; }\n    .rwarn { font-size:11.5px; color:#F0CDA0; background:rgba(240,205,160,.09); border:1px solid rgba(240,205,160,.22); border-radius:9px; padding:8px 10px; margin-top:9px; line-height:1.45; }\n    .radd { margin-top:11px; width:100%; background:#B4633E; color:#fff; border:none; border-radius:9px; padding:10px; font-size:12.5px; font-weight:700; cursor:pointer; transition:.15s; }\n    .radd:hover { background:#c9714a; }\n    .cpn { width:100%; background:linear-gradient(135deg,rgba(180,99,62,.16),rgba(255,255,255,.04)); border:1px dashed rgba(217,138,91,.55); border-radius:14px; padding:14px; margin-top:10px; }\n    .cpn-top { display:flex; align-items:center; justify-content:space-between; margin-bottom:4px; }\n    .cpn-code { font-size:14px; font-weight:800; color:#fff; letter-spacing:.06em; }\n    .cpn-save { font-family:Georgia,serif; font-size:15px; font-weight:700; color:#A8D8B0; }\n    .cpn-desc { font-size:12px; color:rgba(236,228,217,.7); margin-bottom:10px; }\n    .cpn-apply { width:100%; border:none; border-radius:9px; background:#B4633E; color:#fff; padding:10px; font-size:12.5px; font-weight:800; cursor:pointer; }\n    .cpn-apply:hover { background:#c9714a; }\n    .cpn-apply:disabled { background:rgba(168,216,176,.2); color:#A8D8B0; cursor:default; }\n    .cpline { display:flex; justify-content:space-between; align-items:center; font-size:12.5px; color:#B4633E; font-weight:700; margin-bottom:8px; gap:8px; }\n    .cpline.off { color:#A69C8C; font-weight:600; }\n    .cpremove { border:none; background:none; color:#A69C8C; font-size:11px; cursor:pointer; text-decoration:underline; padding:0; }\n    .unread { position:absolute; left:50%; transform:translateX(-50%); bottom:86px; z-index:3; display:none; align-items:center; gap:6px; border:none; border-radius:999px; background:#D98A5B; color:#17130F; font-size:12px; font-weight:800; padding:8px 14px; cursor:pointer; box-shadow:0 8px 24px rgba(0,0,0,.35); animation:fade .2s ease; }\n    .unread:hover { background:#E0A075; }\n    .bar { flex-shrink:0; display:flex; align-items:flex-end; gap:9px; padding:14px; background:#120E0A; border-top:1px solid rgba(255,255,255,.07); }\n    .field { flex:1; background:rgba(255,255,255,.05); border:1.5px solid rgba(255,255,255,.09); border-radius:16px; padding:6px 14px; transition:.15s; }\n    .field:focus-within { border-color:#B4633E; }\n    .cin { width:100%; border:none; background:none; color:#ECE4D9; font-size:13.5px; font-family:inherit; resize:none; outline:none; line-height:1.5; height:24px; max-height:110px; padding:0; scrollbar-width:thin; scrollbar-color:rgba(255,255,255,.22) transparent; }\n    .cin::-webkit-scrollbar { width:7px; }\n    .cin::-webkit-scrollbar-button { display:none; height:0; width:0; }\n    .cin::-webkit-scrollbar-track { background:transparent; margin:2px 0; }\n    .cin::-webkit-scrollbar-thumb { background:rgba(255,255,255,.2); border-radius:999px; border:2px solid transparent; background-clip:content-box; }\n    .cin::-webkit-scrollbar-thumb:hover { background:rgba(255,255,255,.32); background-clip:content-box; }\n    .cin::placeholder { color:rgba(236,228,217,.4); }\n    .send { flex-shrink:0; width:42px; height:42px; border-radius:50%; border:none; cursor:pointer; background:linear-gradient(135deg,#B4633E,#D98A5B); color:#fff; display:flex; align-items:center; justify-content:center; }\n    .send:disabled { opacity:.4; cursor:default; }\n    .send svg { width:18px; height:18px; }\n    .photo { flex-shrink:0; width:42px; height:42px; border-radius:50%; border:none; cursor:pointer; background:rgba(255,255,255,.08); color:#ECE4D9; font-size:18px; display:flex; align-items:center; justify-content:center; transition:.15s; }\n    .photo:hover { background:rgba(255,255,255,.15); }\n    .imgmsg { padding:5px !important; background:rgba(255,255,255,.06) !important; }\n    .imgmsg img { max-width:190px; max-height:190px; border-radius:11px; display:block; }\n\n    /* ===== REDESIGN LUXO (override, vence na cascata) ===== */\n    :host { font-family:'Inter',system-ui,-apple-system,Segoe UI,sans-serif; -webkit-font-smoothing:antialiased; }\n    .serif { font-family:'Cormorant Garamond',Georgia,'Times New Roman',serif; }\n    .page { background:#F6F3EE; color:#2A2420; }\n    .topbar { background:#2A2420; color:#F3EFE8; font-size:11px; letter-spacing:.16em; text-transform:uppercase; padding:9px 16px; font-weight:500; }\n    .nav { padding:20px 52px; background:rgba(246,243,238,.86); backdrop-filter:blur(12px); border-bottom:1px solid rgba(42,36,32,.09); }\n    .logo { font-family:'Cormorant Garamond',Georgia,serif; font-size:27px; font-weight:600; letter-spacing:.02em; color:#2A2420; }\n    .logo .dot { color:#B4633E; }\n    .cats { gap:28px; }\n    .cat { font-size:11.5px; letter-spacing:.14em; text-transform:uppercase; font-weight:500; color:#7A6F64; border-bottom:none; }\n    .cat:hover { color:#2A2420; }\n    .cat.active { color:#2A2420; font-weight:600; border-bottom:none; position:relative; }\n    .cat.active::after { content:''; position:absolute; left:0; right:0; bottom:-3px; height:1px; background:#B4633E; }\n    .navright { gap:16px; }\n    .iconbtn { color:#2A2420; }\n    .iconbtn:hover { background:rgba(42,36,32,.06); }\n    .search { background:transparent; border:1px solid rgba(42,36,32,.16); border-radius:999px; }\n    .catsw { display:inline-flex; align-items:center; gap:9px; border:1px solid rgba(42,36,32,.2); background:transparent; color:#2A2420; border-radius:999px; padding:9px 16px; font-size:11px; font-weight:600; letter-spacing:.08em; text-transform:uppercase; cursor:pointer; white-space:nowrap; transition:.2s; }\n    .catsw:hover { border-color:#B4633E; color:#B4633E; }\n    .catsw-swap { color:#B4633E; font-size:13px; }\n\n    .banner { position:relative; overflow:hidden; display:block; min-height:300px; padding:52px 52px 58px; background:linear-gradient(160deg,#F6F3EE 0%,#EFE7DB 60%,#EAE0D2 100%); }\n    .banner-in { position:relative; z-index:2; max-width:680px; animation:rise .9s cubic-bezier(.2,.7,.2,1) both; }\n    .banner-eyebrow { font-size:12px; font-weight:500; letter-spacing:.26em; text-transform:uppercase; color:#B4633E; margin-bottom:14px; }\n    .banner-title { font-family:'Cormorant Garamond',Georgia,serif; font-size:54px; line-height:1.02; font-weight:500; letter-spacing:-.01em; color:#2A2420; margin:0 0 16px; }\n    .banner-title em, .ed-title em, .ai-title em { font-style:italic; color:#8A6A4F; }\n    .banner-sub { font-size:15.5px; line-height:1.65; color:#5C534B; max-width:480px; margin:0 0 26px; }\n    .banner-cta { display:flex; align-items:center; gap:24px; flex-wrap:wrap; }\n    .banner-btn { border:none; border-radius:0; background:#2A2420; color:#F6F3EE; padding:17px 32px; font-size:11.5px; font-weight:600; letter-spacing:.16em; text-transform:uppercase; cursor:pointer; transition:.28s; }\n    .banner-btn:hover { background:#B4633E; letter-spacing:.2em; }\n    .banner-coupon { font-size:11.5px; letter-spacing:.1em; text-transform:uppercase; color:#8A7E74; border:none; padding:0; background:none; }\n    .banner-art { position:absolute; inset:0; z-index:1; width:auto; background:none; pointer-events:none; }\n    .banner-orb { position:absolute; right:8%; top:50%; transform:translateY(-50%); width:250px; height:250px; border-radius:50%; background:radial-gradient(circle at 34% 30%,#EBD0B2,#D8A97F 55%,#C0855D); opacity:.5; filter:blur(1px); animation:floatc 9s ease-in-out infinite; }\n    .banner-orb.orb2 { right:23%; top:34%; transform:none; width:112px; height:112px; background:radial-gradient(circle at 40% 35%,#F1DEC9,#CBAF97); opacity:.38; animation:float 13s ease-in-out infinite; animation-delay:-3s; }\n    @keyframes floatc { 0%,100%{transform:translateY(-50%)} 50%{transform:translateY(calc(-50% - 22px))} }\n    @keyframes float { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-28px)} }\n    @keyframes rise { from{opacity:0; transform:translateY(24px)} to{opacity:1; transform:none} }\n\n    .section { padding:84px 52px 44px; }\n    .section-head { border-bottom:1px solid rgba(42,36,32,.11); padding-bottom:20px; margin-bottom:44px; }\n    .section-head h2 { font-family:'Cormorant Garamond',Georgia,serif; font-size:38px; font-weight:500; }\n    .count { font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:#8A7E74; }\n    .grid { grid-template-columns:repeat(4,1fr); gap:16px 30px; }\n    .pcard { background:transparent; border:none; border-radius:0; overflow:visible; }\n    .pcard:hover { box-shadow:none; transform:none; }\n    .thumb { height:auto; aspect-ratio:1/1; background:#EEE7DE; border-radius:3px; overflow:hidden; }\n    .thumb .timg, .thumb .timg.timg { position:static; inset:auto; width:100%; height:100%; object-fit:cover; transition:transform .8s cubic-bezier(.2,.7,.2,1); }\n    .pcard:hover .thumb .timg { transform:scale(1.06); }\n    .thumb .mono { font-family:'Cormorant Garamond',Georgia,serif; }\n    .fav { position:absolute; top:12px; right:12px; z-index:3; width:34px; height:34px; border:none; border-radius:50%; background:rgba(255,255,255,.8); color:#2A2420; font-size:16px; line-height:1; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:.2s; }\n    .fav:hover { background:#fff; }\n    .fav.on { color:#B4633E; }\n    .askpill { top:auto; left:12px; right:12px; bottom:12px; width:auto; height:auto; border-radius:0; background:rgba(42,36,32,.93); color:#F6F3EE; border:none; box-shadow:none; display:flex; align-items:center; justify-content:center; gap:8px; padding:13px; font-size:11px; letter-spacing:.16em; text-transform:uppercase; opacity:0; transform:translateY(10px); transition:.3s; }\n    .pcard:hover .askpill { opacity:1; transform:none; }\n    .askpill:hover { background:#B4633E; color:#fff; transform:none; }\n    .askpill-ico { font-size:13px; }\n    .pbody { padding:16px 2px 0; gap:0; text-align:left; }\n    .pcat { font-size:10.5px; letter-spacing:.16em; text-transform:uppercase; color:#A2968A; margin-bottom:8px; }\n    .pname { font-family:'Cormorant Garamond',Georgia,serif; font-size:21px; font-weight:500; line-height:1.18; color:#2A2420; margin-bottom:7px; }\n    .pprice { font-family:'Inter',sans-serif; font-size:14px; font-weight:500; color:#5C534B; margin:0 0 14px; }\n    .addbtn { border-radius:0; border:1px solid rgba(42,36,32,.75); font-size:11px; letter-spacing:.14em; text-transform:uppercase; font-weight:600; padding:13px; }\n\n    .editorial { padding:104px 52px; max-width:920px; margin:0 auto; text-align:center; }\n    .ed-eyebrow { font-size:12px; font-weight:500; letter-spacing:.26em; text-transform:uppercase; color:#B4633E; margin-bottom:22px; }\n    .ed-eyebrow.light { color:#E7C9A8; }\n    .ed-title { font-family:'Cormorant Garamond',Georgia,serif; font-size:54px; font-weight:500; line-height:1.08; color:#2A2420; margin:0 0 22px; }\n    .ed-lead { font-size:17px; line-height:1.85; color:#5C534B; max-width:660px; margin:0 auto; }\n    .pillars { display:grid; grid-template-columns:repeat(3,1fr); gap:1px; background:rgba(42,36,32,.1); border-top:1px solid rgba(42,36,32,.1); border-bottom:1px solid rgba(42,36,32,.1); }\n    .pillar { background:#F6F3EE; padding:60px 46px; }\n    .pillar-n { font-family:'Cormorant Garamond',Georgia,serif; font-size:24px; color:#B4633E; margin-bottom:18px; }\n    .pillar h3 { font-family:'Cormorant Garamond',Georgia,serif; font-size:25px; font-weight:500; color:#2A2420; margin:0 0 12px; line-height:1.2; }\n    .pillar p { font-size:14.5px; line-height:1.75; color:#5C534B; margin:0; }\n    .ai-band { background:#2A2420; color:#F6F3EE; padding:116px 52px; text-align:center; }\n    .ai-band-in { max-width:780px; margin:0 auto; }\n    .ai-title { font-family:'Cormorant Garamond',Georgia,serif; font-size:58px; font-weight:500; line-height:1.06; margin:0 0 24px; }\n    .ai-title em { color:#E7C9A8; }\n    .ai-lead { font-size:17px; line-height:1.85; color:rgba(246,243,238,.72); margin:0 0 36px; }\n    .ai-cta { border:1px solid #F6F3EE; background:transparent; color:#F6F3EE; padding:17px 36px; font-size:11.5px; font-weight:600; letter-spacing:.16em; text-transform:uppercase; cursor:pointer; transition:.25s; }\n    .ai-cta:hover { background:#F6F3EE; color:#2A2420; }\n    .footer-note { background:#EFE7DB; color:#8A7E74; font-size:11px; letter-spacing:.06em; padding:44px 52px; border-top:1px solid rgba(42,36,32,.09); }\n\n    @media (max-width:1080px){ .grid{grid-template-columns:repeat(2,1fr)} .pillars{grid-template-columns:1fr} .banner-title{font-size:54px} .cats{display:none} .ed-title,.ai-title{font-size:40px} }\n  `,\n  render(d2) {\n    const esc = (s) => (s ?? '').toString().replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;')\n    const money = (n) => d2.fmt.currency(Number(n) || 0, 'BRL')\n    const mono = (name) => (name || '?').trim().slice(0,1).toUpperCase()\n    const catB = d2.state.catalog === 'b'\n    const ds = d2.data(catB ? 'bot-products-bDataSet' : 'products')\n    const loadingP = !ds || ds.loading\n    const products = loadingP ? [] : ds.records()\n    const imgOf = (id) => { const pp = products.filter((x) => x && x._id === id)[0]; return (pp && pp.image_url) ? pp.image_url : '' }\n    const selCat = d2.state.category || ''\n    const categories = [...new Set(products.map((p) => p.category).filter(Boolean))].sort()\n    const catLabel = (c) => c ? c.charAt(0).toUpperCase() + c.slice(1) : c\n    const visibleProducts = selCat ? products.filter((p) => p.category === selCat) : products\n    const cart = d2.state.cart || {}\n    const qOf = (id) => cart[id] || 0\n    const cartCount = Object.keys(cart).reduce((a,k) => a + cart[k], 0)\n    const cartItems = products.filter((p) => qOf(p._id) > 0)\n    const cartTotal = cartItems.reduce((a,p) => a + (Number(p.price) || 0) * qOf(p._id), 0)\n    const coupon = d2.state.coupon || null\n    const couponBase = coupon ? (coupon.scope === 'categoria' ? cartItems.reduce((a, p) => a + ((p.category === coupon.category) ? (Number(p.price) || 0) * qOf(p._id) : 0), 0) : cartTotal) : 0\n    const couponMinOk = coupon ? cartTotal >= (Number(coupon.min_total) || 0) : false\n    const couponDiscount = (coupon && couponMinOk) ? (coupon.discount_type === 'percent' ? couponBase * (Number(coupon.discount_value) || 0) / 100 : Math.min(Number(coupon.discount_value) || 0, couponBase)) : 0\n    const cartFinal = Math.max(0, cartTotal - couponDiscount)\n    const view = d2.state.view || 'store'\n\n    const card = (p) => {\n      const q = qOf(p._id)\n      const action = q > 0\n        ? `<div class=\"stepper full\"><button class=\"qbtn js-dec\" data-id=\"${p._id}\">−</button><span class=\"qn\">${q}</span><button class=\"qbtn js-inc\" data-id=\"${p._id}\">+</button></div>`\n        : `<button class=\"addbtn js-add\" data-id=\"${p._id}\">Adicionar à sacola</button>`\n      const fav = (d2.state.favs || {})[p._id]\n      return `<article class=\"pcard js-open-product\" data-key=\"${p._id}\" data-id=\"${p._id}\">\n        <div class=\"thumb\">${p.image_url ? `<img class=\"timg\" src=\"${esc(p.image_url)}\" alt=\"${esc(p.name || '')}\" />` : `<span class=\"mono\">${esc(mono(p.name))}</span>`}\n          <button class=\"fav js-fav ${fav ? 'on' : ''}\" data-id=\"${p._id}\" title=\"Favoritar\" aria-label=\"Favoritar\">${fav ? '♥' : '♡'}</button>\n          <button class=\"askpill js-ask\" data-id=\"${p._id}\" data-name=\"${esc(p.name || '')}\" title=\"Falar com a consultora\"><span class=\"askpill-ico\">✨</span><span class=\"askpill-txt\">Consultora</span></button></div>\n        <div class=\"pbody\">\n          <div class=\"pcat\">${esc(p.category || '')}</div>\n          <div class=\"pname\">${esc(p.name || '')}</div>\n          <div class=\"pprice\">${money(p.price)}</div>\n          ${action}\n        </div>\n      </article>`\n    }\n    const grid = loadingP ? `<div class=\"empty\">Carregando produtos…</div>` : (visibleProducts.length ? visibleProducts.map(card).join('') : `<div class=\"empty\">Nenhum produto nesta categoria.</div>`)\n    const store = `\n      <div class=\"topbar\">Frete grátis acima de R$ 279 · entrega expressa para todo o Brasil</div>\n      <header class=\"nav\">\n        <div class=\"logo js-home\">Ateliê<span class=\"dot\">·</span>Beleza</div>\n        <nav class=\"cats\">\n          <a class=\"cat js-cat ${selCat === '' ? 'active' : ''}\" data-cat=\"\">Todos</a>\n          ${categories.map((c) => `<a class=\"cat js-cat ${selCat === c ? 'active' : ''}\" data-cat=\"${esc(c)}\">${esc(catLabel(c))}</a>`).join('')}\n        </nav>\n        <div class=\"navright\">\n          <button class=\"catsw js-catsw\" type=\"button\" title=\"Trocar catálogo — demo do motor agnóstico\">${catB ? '💇 Cabelos' : '🧴 Skincare'}<span class=\"catsw-swap\">⇄</span></button>\n          <div class=\"search\"><span class=\"si\">⌕</span><input class=\"searchi\" placeholder=\"Buscar produtos...\" /></div>\n          <button class=\"iconbtn js-profile\" title=\"Minha conta\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\"><circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4.5 20c0-3.6 3.6-5.6 7.5-5.6s7.5 2 7.5 5.6\"/></svg></button>\n          <button class=\"iconbtn\" title=\"Favoritos\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\"><path d=\"M12 20.5S4 15.5 4 9.8A4.3 4.3 0 0 1 12 7a4.3 4.3 0 0 1 8 2.8c0 5.7-8 10.7-8 10.7z\"/></svg></button>\n          <button class=\"iconbtn js-cart\" title=\"Sacola\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\"><path d=\"M6.5 8h11l-1 11.5H7.5L6.5 8z\"/><path d=\"M9.2 8V6.2a2.8 2.8 0 0 1 5.6 0V8\"/></svg>${cartCount ? `<span class=\"ibadge\">${cartCount}</span>` : ''}</button>\n          ${d2.state.profileMenu ? `<div class=\"pmenu-scrim js-profile-close\"></div><div class=\"pmenu\"><div class=\"pmenu-head\">Área interna</div><button class=\"pmenu-item js-goto-painel\" type=\"button\">📊 Painel comercial</button></div>` : ''}\n        </div>\n      </header>\n      <section class=\"banner\">\n        <div class=\"banner-art\"><span class=\"banner-orb\"></span><span class=\"banner-orb orb2\"></span></div>\n        <div class=\"banner-in\">\n          <div class=\"banner-eyebrow\">${catB ? 'Ritual capilar' : 'Beleza consultiva'}</div>\n          <h1 class=\"banner-title serif\">${catB ? 'Cabelos<br>como um <em>ritual.</em>' : 'Mais do que<br>pele. <em>Cuidado.</em>'}</h1>\n          <p class=\"banner-sub\">Uma consultora de IA lê a sua necessidade — por texto, voz ou foto — e monta a rotina certa para você. Beleza que começa em entender.</p>\n          <div class=\"banner-cta\">\n            <button class=\"banner-btn js-open\">Falar com a consultora</button>\n            <span class=\"banner-coupon\">Diagnóstico gratuito · em segundos</span>\n          </div>\n        </div>\n      </section>\n      <main class=\"section\">\n        <div class=\"section-head\"><h2 class=\"serif\">Mais desejados</h2><span class=\"count\">${loadingP ? '' : visibleProducts.length + ' produtos'}</span></div>\n        <div class=\"grid\">${grid}</div>\n      </main>\n      <section class=\"editorial\">\n        <div class=\"ed-eyebrow\">A nossa filosofia</div>\n        <h2 class=\"ed-title serif\">Beleza que vai <em>além da superfície.</em></h2>\n        <p class=\"ed-lead\">Fórmulas gentis e eficazes, pensadas para revelar a sua melhor versão — com o cuidado de quem entende de pele, de cabelo e de você.</p>\n      </section>\n      <section class=\"pillars\">\n        <div class=\"pillar\"><div class=\"pillar-n\">01</div><h3 class=\"serif\">Ingredientes que fazem sentido</h3><p>Ativos selecionados e combinações que respeitam a sua pele — sem misturas que competem entre si.</p></div>\n        <div class=\"pillar\"><div class=\"pillar-n\">02</div><h3 class=\"serif\">Personalização de verdade</h3><p>Cada recomendação nasce da sua necessidade, do seu tipo de pele ou cabelo e do seu momento — não de uma vitrine genérica.</p></div>\n        <div class=\"pillar\"><div class=\"pillar-n\">03</div><h3 class=\"serif\">Inteligência que consulta</h3><p>Uma IA que conversa, analisa uma foto do seu cabelo e monta a rotina ideal — a expertise de uma consultora, na hora.</p></div>\n      </section>\n      <section class=\"ai-band\">\n        <div class=\"ai-band-in\">\n          <div class=\"ed-eyebrow light\">Tecnologia Data2</div>\n          <h2 class=\"ai-title serif\">A consultora que entende <em>de você.</em></h2>\n          <p class=\"ai-lead\">Descreva o que precisa, mande um áudio ou uma foto do seu cabelo. Em segundos, a IA diagnostica, evita combinações erradas e monta o cuidado sob medida — e responde até por voz.</p>\n          <button class=\"ai-cta js-open\">Experimentar agora</button>\n        </div>\n      </section>\n      <div class=\"footer-note\">Demonstração Data2 · vitrine fictícia · a camada de consultoria por IA se integra a qualquer e-commerce existente, sem trocar nada.</div>`\n\n    // ---- checkout view ----\n    let checkout = ''\n    if (view === 'checkout') {\n      if (d2.state.checkoutDone) {\n        checkout = `<div class=\"co-success\"><div class=\"co-done\">\n          <div class=\"cd-ico\">✓</div>\n          <div class=\"cd-num\">Pedido ${esc(d2.state.orderNumber || '')}</div>\n          <div class=\"cd-title serif\">Pedido confirmado</div>\n          <div class=\"cd-sub\">Simulação de demonstração — nenhum pagamento foi processado. Numa loja real, o cliente receberia a confirmação por e-mail.</div>\n          <button class=\"btn primary js-home\">Voltar à loja</button>\n        </div></div>`\n      } else {\n        const pay = d2.state.payMethod || 'cartao'\n        const itemsHtml = cartItems.length ? cartItems.map((p) => `<div class=\"co-item\" data-key=\"${p._id}\">\n            <div class=\"co-thumb\">${p.image_url ? `<img src=\"${esc(p.image_url)}\" alt=\"${esc(p.name)}\" />` : `<span class=\"mono\">${esc(mono(p.name))}</span>`}</div>\n            <div class=\"ci-info\"><div class=\"ci-name\">${esc(p.name)}</div>\n              <div class=\"ci-line\"><div class=\"stepper sm\"><button class=\"qbtn js-dec\" data-id=\"${p._id}\">−</button><span class=\"qn\">${qOf(p._id)}</span><button class=\"qbtn js-inc\" data-id=\"${p._id}\">+</button></div>\n                <span class=\"ci-price\">${money(p.price)} un.</span><button class=\"ci-remove js-remove\" data-id=\"${p._id}\">remover</button></div></div>\n            <div class=\"ci-total\">${money((Number(p.price) || 0) * qOf(p._id))}</div>\n          </div>`).join('') : `<div class=\"sugg-empty\">Sua sacola está vazia.</div>`\n\n        const suggs = d2.state.suggestions || []\n        const suggBody = d2.state.suggestLoading\n          ? `<div class=\"sugg-empty\">A consultora está analisando seu pedido…</div>`\n          : (suggs.length ? suggs.map((s) => `<div class=\"scard\" data-key=\"${s.product_id}\">\n              <div class=\"sthumb\">${imgOf(s.product_id) ? `<img src=\"${esc(imgOf(s.product_id))}\" alt=\"${esc(s.name)}\" />` : `<span class=\"mono\">${esc(mono(s.name))}</span>`}</div>\n              <div><div class=\"s-name\">${esc(s.name)}</div><div class=\"s-reason\">${s.synergy ? '✨ ' : ''}${esc(s.reason)}</div></div>\n              <div class=\"s-right\"><div class=\"s-price\">${money(s.price)}</div><button class=\"s-add js-add-sugg\" data-id=\"${s.product_id}\">Adicionar</button></div>\n            </div>`).join('') : `<div class=\"sugg-empty\">Seu pedido já está completo. ✨</div>`)\n\n        checkout = `<div class=\"co\">\n          <div class=\"co-head\"><button class=\"co-back js-home\">← Continuar comprando</button><h1 class=\"serif\">Finalizar pedido</h1></div>\n          <div class=\"co-grid\">\n            <div class=\"co-main\">\n              <section class=\"co-card\"><h2>1 · Revise seu pedido</h2>${itemsHtml}</section>\n              <section class=\"co-card co-sugg\">\n                <div class=\"sugg-head\"><div class=\"sugg-ico\">✨</div><div><h2>A consultora sugere</h2><p>Itens que completam sua rotina, escolhidos pela IA com base no que você já tem.</p></div></div>\n                ${suggBody}\n              </section>\n              <section class=\"co-card\"><h2>2 · Entrega e pagamento</h2>\n                <div class=\"field-mock\">Rua das Flores, 123 · São Paulo, SP · 01234-000</div>\n                <div class=\"pay-row js-pay ${pay === 'cartao' ? 'sel' : ''}\" data-pay=\"cartao\"><div class=\"radio\"></div><div><div class=\"pay-label\">Cartão de crédito</div><div class=\"pay-sub\">Visa •••• 4242</div></div></div>\n                <div class=\"pay-row js-pay ${pay === 'pix' ? 'sel' : ''}\" data-pay=\"pix\"><div class=\"radio\"></div><div><div class=\"pay-label\">Pix</div><div class=\"pay-sub\">${pay === 'pix' ? 'QR Code gerado na confirmação' : 'Aprovação imediata'}</div></div></div>\n                <div class=\"secure\">🔒 Ambiente seguro · esta é uma demonstração, nenhum pagamento é processado.</div>\n              </section>\n            </div>\n            <aside class=\"co-summary\">\n              <h3>Resumo</h3>\n              <div class=\"sum-row\"><span>Subtotal (${cartCount} ${cartCount === 1 ? 'item' : 'itens'})</span><span>${money(cartTotal)}</span></div>\n              <div class=\"sum-row\"><span>Frete</span><span class=\"free\">Grátis</span></div>\n              ${couponDiscount > 0 ? `<div class=\"sum-row\"><span>🎟️ Cupom ${esc(coupon.code)}</span><span class=\"free\">− ${money(couponDiscount)}</span></div>` : ''}<div class=\"sum-row total\"><span>Total</span><span>${money(cartFinal)}</span></div>\n              <button class=\"btn primary lg js-confirm\" style=\"margin-top:16px\">Confirmar pedido</button>\n              <div class=\"co-badges\"><span>🔒 Compra 100% segura</span><span>🚚 Frete grátis acima de R$ 279</span><span>↩️ Troca fácil em 30 dias</span></div>\n            </aside>\n          </div>\n        </div>`\n      }\n    }\n\n    const mainView = view === 'checkout' ? checkout : store\n\n    let cartHtml = ''\n    if (d2.state.cartOpen) {\n      const suggs = d2.state.suggestions || []\n      const ccBlock = `<div class=\"cart-consultora\">\n          <div class=\"cc-head\"><span class=\"cc-ico\">✨</span>Sua consultora</div>\n          ${suggs.length ? `<div class=\"cc-line\">Leve também — combina com o que você escolheu:</div>${suggs.slice(0, 2).map((s) => `<div class=\"cc-sitem\" data-key=\"${s.product_id}\"><div class=\"cc-sinfo\"><div class=\"cc-sname\">${esc(s.name)}</div><div class=\"cc-sreason\">${s.synergy ? '✨ ' : ''}${esc(s.reason)}</div></div><button class=\"cc-sadd js-add-sugg\" data-id=\"${s.product_id}\">${money(s.price)} +</button></div>`).join('')}` : ''}\n          <button class=\"cc-chat js-cart-to-chat\">Não sabe se combina com a sua pele? Fale comigo →</button>\n        </div>`\n      const body = cartItems.length\n        ? `<div class=\"drawer-body\">${cartItems.map((p) => `<div class=\"citem\" data-key=\"${p._id}\">\n            <div class=\"cthumb\">${p.image_url ? `<img src=\"${esc(p.image_url)}\" alt=\"${esc(p.name)}\" />` : `<span class=\"mono\">${esc(mono(p.name))}</span>`}</div>\n            <div class=\"cinfo\"><div class=\"cname\">${esc(p.name)}</div><div class=\"cprice\">${money(p.price)} un.</div>\n              <div class=\"crow\"><div class=\"stepper sm\"><button class=\"qbtn js-dec\" data-id=\"${p._id}\">−</button><span class=\"qn\">${qOf(p._id)}</span><button class=\"qbtn js-inc\" data-id=\"${p._id}\">+</button></div><button class=\"cremove js-remove\" data-id=\"${p._id}\">remover</button></div></div>\n            <div class=\"cline\">${money((Number(p.price) || 0) * qOf(p._id))}</div></div>`).join('')}${ccBlock}</div>\n          <div class=\"drawer-foot\">${coupon && coupon.code ? (couponDiscount > 0 ? `<div class=\"cpline\"><span>🎟️ Cupom ${esc(coupon.code)} · <button class=\"cpremove js-remove-coupon\" type=\"button\">remover</button></span><span>− ${money(couponDiscount)}</span></div>` : `<div class=\"cpline off\"><span>🎟️ Cupom ${esc(coupon.code)} — mínimo de ${money(coupon.min_total)} não atingido</span><button class=\"cpremove js-remove-coupon\" type=\"button\">remover</button></div>`) : ''}<div class=\"subtotal\"><span>${couponDiscount > 0 ? 'Total com desconto' : 'Subtotal'}</span><span>${money(cartFinal)}</span></div>\n            <button class=\"btn primary lg js-checkout\">Finalizar compra</button>\n            <div class=\"foot-fine\">Revise e finalize com segurança na próxima etapa.</div></div>`\n        : `<div class=\"empty-cart\"><div>🛍️</div><p>Sua sacola está vazia.</p></div>`\n      cartHtml = `<div class=\"scrim js-cart-close\"></div><aside class=\"drawer\">\n        <div class=\"drawer-head\"><div class=\"dh-title serif\">Sua sacola</div><button class=\"close js-cart-close\">✕</button></div>${body}</aside>`\n    }\n\n    let chatHtml\n    if (!d2.state.open) {\n      chatHtml = `<div class=\"launcher js-open\"><span class=\"l-pulse\"></span><span class=\"l-ico\">✨</span><span class=\"l-label\">Consultora de beleza</span></div>`\n    } else {\n      const msgs = d2.state.messages || []\n      const loading = !!d2.state.loading || !!d2.state.hairAnalyzing\n      const couponCard = (c) => {\n        const applied = d2.state.coupon && d2.state.coupon.code === c.code\n        return `<div class=\"cpn\" data-key=\"${esc(c.code)}\">\n          <div class=\"cpn-top\"><span class=\"cpn-code\">🎟️ ${esc(c.code)}</span><span class=\"cpn-save\">− ${money(c.savings)}</span></div>\n          <div class=\"cpn-desc\">${esc(c.description)}</div>\n          <button class=\"cpn-apply js-apply-coupon\" data-code=\"${esc(c.code)}\" data-type=\"${esc(c.discount_type)}\" data-value=\"${Number(c.discount_value) || 0}\" data-scope=\"${esc(c.scope)}\" data-category=\"${esc(c.category || '')}\" data-min=\"${Number(c.min_total) || 0}\" type=\"button\" ${applied ? 'disabled' : ''}>${applied ? 'Cupom aplicado ✓' : 'Aplicar cupom'}</button>\n        </div>`\n      }\n      const optCard = (o) => `<div class=\"ocard\" data-key=\"${o.product_id}\"><div class=\"othumb\">${imgOf(o.product_id) ? `<img src=\"${esc(imgOf(o.product_id))}\" alt=\"${esc(o.name)}\" />` : `<span class=\"mono\">${esc(mono(o.name))}</span>`}</div><div class=\"oinfo\"><div class=\"oname\">${esc(o.name)}</div><div class=\"ostep\">${esc(o.step)}</div></div><div class=\"oright\"><div class=\"oprice\">${money(o.price)}</div><button class=\"oadd js-add-opt\" data-id=\"${o.product_id}\">Adicionar</button><button class=\"odetail js-open-side\" data-id=\"${o.product_id}\">Ver detalhes</button></div></div>`\n      const routineCard = (r) => `<div class=\"rcard ${r.budget_active ? (r.over_budget ? 'rcard-over' : 'rcard-ok') : ''}\" data-key=\"${esc(r.key)}\">\n          <div class=\"rtop\"><div class=\"rname\">${esc(r.name)}${r.budget_active ? (r.over_budget ? '<span class=\"rover\">acima do orçamento</span>' : '<span class=\"rok\">dentro do orçamento</span>') : ''}</div><div class=\"rtotal\">${money(r.total)}</div></div>\n          <div class=\"rblurb\">${esc(r.blurb)}</div>\n          ${(r.items || []).map((it) => `<div class=\"ritem\"><span>${esc(it.step)} · ${esc(it.name)}</span><span>${money(it.price)}</span></div>`).join('')}\n          ${(r.warnings || []).map((w) => `<div class=\"rwarn\">⚠️ ${esc(w)}</div>`).join('')}\n          <button class=\"radd js-addroutine\" data-key=\"${esc(r.key)}\">Adicionar rotina à sacola</button></div>`\n      const rows = msgs.length\n        ? msgs.map((m,i) => `<div class=\"row ${m.role}\" data-key=\"${i}\">${m.text ? `<div class=\"msg ${m.role}\">${esc(m.text)}</div>` : ''}${m.image ? `<div class=\"msg ${m.role} imgmsg\"><img src=\"${m.image}\" alt=\"Foto enviada\" /></div>` : ''}${m.routines && m.routines.length ? `<div class=\"routines\">${m.routines.map(routineCard).join('')}</div>` : ''}${m.options && m.options.length ? `<div class=\"opts\">${m.options.slice(0, 4).map(optCard).join('')}</div>` : ''}${m.coupon && m.coupon.code ? couponCard(m.coupon) : ''}${m.qoptions && m.qoptions.length ? `<div class=\"qchips\">${m.qoptions.map((q) => `<button class=\"chip js-chip\">${esc(q)}</button>`).join('')}</div>` : ''}</div>`).join('')\n        : `<div class=\"estate\"><div class=\"wave\">👋</div><h3>Oi! Sou sua consultora de beleza.</h3>\n            <p>Descreva sua necessidade que eu monto uma rotina completa — com avisos de compatibilidade entre ativos.</p>\n            <div class=\"chips\"><button class=\"chip js-chip\">Pele oleosa, controlar o brilho</button><button class=\"chip js-chip\">Quero cuidar de linhas finas</button></div></div>`\n      const dots = loading ? `<div class=\"row bot\"><div class=\"msg bot\"><span class=\"dots\"><span></span><span></span><span></span></span></div></div>` : ''\n      chatHtml = `<div class=\"panel\">\n        <div class=\"phead\"><div class=\"ph-row\"><div class=\"ph-brand\"><div class=\"ph-ico\">✨</div>\n          <div><div class=\"ptitle\">Consultora de Beleza</div><div class=\"psub\">IA Data2 · personal shopper</div></div></div>\n          <button class=\"pclose js-close\">✕</button></div></div>\n        <div class=\"log\">${rows}${dots}</div>\n        <button class=\"unread js-unread\" style=\"display:none\" type=\"button\">↓ nova mensagem</button>\n        <div class=\"bar\"><div class=\"field\"><textarea class=\"cin\" rows=\"1\" placeholder=\"Descreva sua necessidade...\"></textarea></div>\n          ${catB ? `<label class=\"photo js-photo-label\" title=\"Enviar foto do cabelo\">📷<input type=\"file\" accept=\"image/*\" class=\"js-photo-input\" hidden /></label>` : ''}\n          <button class=\"mic js-mic ${d2.state.recording ? 'rec' : ''}\" title=\"Gravar áudio\">${d2.state.recording ? '⏹' : '🎤'}</button>\n          <button class=\"send js-send\" ${loading ? 'disabled' : ''}><svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3.4 20.4l17.45-7.48a1 1 0 0 0 0-1.84L3.4 3.6a.993.993 0 0 0-1.39.91L2 9.12c0 .5.37.93.87.99L17 12 2.87 13.88c-.5.07-.87.5-.87 1l.01 4.61c0 .67.68 1.13 1.39.91z\"/></svg></button></div>\n      </div>`\n    }\n\n    let modalHtml = ''\n    if (d2.state.selectedId) {\n      const mp = products.filter((x) => x._id === d2.state.selectedId)[0]\n      if (mp) {\n        const mq = qOf(mp._id)\n        const mact = mq > 0\n          ? `<div class=\"stepper full\"><button class=\"qbtn js-dec\" data-id=\"${mp._id}\">−</button><span class=\"qn\">${mq}</span><button class=\"qbtn js-inc\" data-id=\"${mp._id}\">+</button></div>`\n          : `<button class=\"addbtn js-add\" data-id=\"${mp._id}\">Adicionar à sacola</button>`\n        modalHtml = `<div class=\"mscrim js-modal-close\"></div>\n          <div class=\"pmodal\">\n            <button class=\"pm-close js-modal-close\">✕</button>\n            <div class=\"pm-img\">${mp.image_url ? `<img class=\"pmimg\" src=\"${esc(mp.image_url)}\" alt=\"${esc(mp.name || '')}\" />` : `<span class=\"mono\">${esc(mono(mp.name))}</span>`}<span class=\"thumb-tag\">${esc(mp.category || '')}</span></div>\n            <div class=\"pm-body\">\n              <div class=\"pm-brand\">${esc(mp.brand || '')}</div>\n              <div class=\"pm-name serif\">${esc(mp.name || '')}</div>\n              <div class=\"pm-price\">${money(mp.price)}</div>\n              <p class=\"pm-desc\">${esc(mp.description || '')}</p>\n              <div class=\"pm-meta\">\n                <div class=\"pm-mrow\"><span class=\"pm-mk\">Como usar</span><span class=\"pm-mv\">${esc(mp.how_to_use || '—')}</span></div>\n                <div class=\"pm-mrow\"><span class=\"pm-mk\">Duração</span><span class=\"pm-mv\">~${esc(mp.duration_days || '?')} dias de uso</span></div>\n                <div class=\"pm-mrow\"><span class=\"pm-mk\">Período</span><span class=\"pm-mv\">${esc(mp.usage_period || '—')}</span></div>\n                ${mp.contraindications ? `<div class=\"pm-mrow\"><span class=\"pm-mk\">Contraindicações</span><span class=\"pm-mv\">${esc(mp.contraindications)}</span></div>` : ''}\n              </div>\n              ${mact}\n              <div class=\"pm-consultora\">\n                <div class=\"pm-cico\">✨</div>\n                <div class=\"pm-ctxt\"><strong>Sua consultora</strong><span>Quer saber se combina com a sua pele e como encaixar na rotina?</span></div>\n                <button class=\"pm-cbtn js-ask-product\" data-id=\"${mp._id}\" data-name=\"${esc(mp.name || '')}\">Perguntar</button>\n              </div>\n            </div>\n          </div>`\n      }\n    }\n    let sideModalHtml = ''\n    if (d2.state.open && d2.state.sideProductId) {\n      const sp = products.filter((x) => x._id === d2.state.sideProductId)[0]\n      if (sp) {\n        const sq = qOf(sp._id)\n        const sact = sq > 0\n          ? `<div class=\"stepper full\"><button class=\"qbtn js-dec\" data-id=\"${sp._id}\">−</button><span class=\"qn\">${sq}</span><button class=\"qbtn js-inc\" data-id=\"${sp._id}\">+</button></div>`\n          : `<button class=\"sm-add js-add-side\" data-id=\"${sp._id}\">Adicionar à sacola</button>`\n        sideModalHtml = `<div class=\"sidemodal\">\n          <div class=\"sm-head\"><span class=\"sm-tag\">${esc(sp.category || '')}</span><button class=\"sm-close js-side-close\" type=\"button\">✕</button></div>\n          <div class=\"sm-img\">${sp.image_url ? `<img src=\"${esc(sp.image_url)}\" alt=\"${esc(sp.name || '')}\" />` : `<span class=\"mono\">${esc(mono(sp.name))}</span>`}</div>\n          <div class=\"sm-body\">\n            <div class=\"sm-brand\">${esc(sp.brand || '')}</div>\n            <div class=\"sm-name\">${esc(sp.name || '')}</div>\n            <div class=\"sm-price\">${money(sp.price)}</div>\n            <p class=\"sm-desc\">${esc(sp.description || '')}</p>\n            <div class=\"sm-meta\">\n              <div class=\"sm-mrow\"><span class=\"sm-mk\">Como usar</span><span class=\"sm-mv\">${esc(sp.how_to_use || '—')}</span></div>\n              <div class=\"sm-mrow\"><span class=\"sm-mk\">Quando</span><span class=\"sm-mv\">${esc(sp.usage_period || '—')}</span></div>\n              <div class=\"sm-mrow\"><span class=\"sm-mk\">Duração</span><span class=\"sm-mv\">~${esc(sp.duration_days || '?')} dias de uso</span></div>\n              ${sp.contraindications ? `<div class=\"sm-mrow\"><span class=\"sm-mk\">Contraindicações</span><span class=\"sm-mv\">${esc(sp.contraindications)}</span></div>` : ''}\n            </div>\n            ${sact}\n          </div>\n        </div>`\n      }\n    }\n    return `<div class=\"page\">${mainView}</div>${cartHtml}${chatHtml}${sideModalHtml}${modalHtml}`\n  },\n  mounted(d2) {\n    const sleep = (ms) => new Promise((r) => setTimeout(r, ms))\n    try {\n      if (!document.getElementById('d2-lux-fonts')) {\n        const l = document.createElement('link')\n        l.id = 'd2-lux-fonts'; l.rel = 'stylesheet'\n        l.href = 'https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Inter:wght@400;500;600&display=swap'\n        document.head.appendChild(l)\n      }\n    } catch (e) {}\n    // aquece o módulo de transcrição na carga da página (evita 1ª chamada em módulo frio)\n    try { d2.assistant('consultora-ouvido') } catch (e) {}\n    const addToCart = (id, n, source) => {\n      const c = { ...(d2.state.cart || {}) }\n      const src = { ...(d2.state.cartSource || {}) }\n      c[id] = (c[id] || 0) + n\n      if (c[id] <= 0) { delete c[id]; delete src[id] }\n      else if (source && !src[id]) { src[id] = source }\n      d2.state.cart = c\n      d2.state.cartSource = src\n    }\n    const loadSuggestions = async () => {\n      const ids = Object.keys(d2.state.cart || {})\n      d2.state.suggestLoading = true\n      try {\n        const res = await d2.rush('sugere-complemento').invoke({ cart_ids: ids })\n        const r = (res && res.data) ? res.data : res\n        d2.state.suggestions = (r && r.suggestions) || []\n      } catch (err) {\n        d2.log('sugere-complemento error', err)\n        d2.state.suggestions = []\n      } finally {\n        d2.state.suggestLoading = false\n      }\n    }\n    const send = async (preset, opts) => {\n      const spoken = !!(opts && opts.spoken)\n      const t = d2.$('.cin')\n      const text = (preset != null ? preset : (t ? t.value : '')).trim()\n      if (!text || d2.state.loading) return\n      if (t) { t.value = ''; t.style.height = '24px' }\n      const priorMsgs = (d2.state.messages || []).slice(-40).map((m) => ({ role: m.role, text: m.text }))\n      d2.state.messages = [...(d2.state.messages || []), { role: 'user', text }]\n      d2.state.loading = true\n      const t0 = Date.now()\n      try {\n        const pds = d2.data(d2.state.catalog === 'b' ? 'bot-products-bDataSet' : 'products')\n        const prodRows = (pds && !pds.loading) ? pds.records() : []\n        const curCart = d2.state.cart || {}\n        const cartPayload = prodRows.filter((p) => (curCart[p._id] || 0) > 0).map((p) => ({ product_id: p._id, name: p.name, qty: curCart[p._id] }))\n        const res = await d2.rush('consultoria-turn').invoke({ userMessage: text, cart: cartPayload, session_budget: d2.state.sessionBudget || {}, history: priorMsgs, applied_coupon: (d2.state.coupon && d2.state.coupon.code) || '', catalog: d2.state.catalog || 'a' })\n        const r = (res && res.data) ? res.data : res\n        if (!r || r.type === 'error') throw new Error((r && r.message) || 'resposta vazia')\n        // cadência humana: os pontinhos ficam ao menos ~1.1s na tela\n        const waitMs = 1100 - (Date.now() - t0)\n        if (waitMs > 0) await sleep(waitMs)\n        if (r && r.budget && (Number(r.budget.value) || 0) > 0) {\n          d2.state.sessionBudget = { scope: r.budget.scope || '', value: r.budget.value, hint: (r.intent && r.intent.product_hint) || (d2.state.sessionBudget && d2.state.sessionBudget.hint) || '' }\n        }\n        if (r.type === 'cart_action') {\n          const c = { ...(d2.state.cart || {}) }\n          const src = { ...(d2.state.cartSource || {}) }\n          ;(r.ops || []).forEach((o) => {\n            const id = o.product_id\n            if (!id) return\n            if (o.op === 'remove') { delete c[id]; delete src[id] }\n            else if (o.op === 'set_qty') { const q = Number(o.qty) || 0; if (q <= 0) { delete c[id]; delete src[id] } else { c[id] = q; if (!src[id]) src[id] = 'bot' } }\n            else { c[id] = (c[id] || 0) + (Number(o.qty) || 1); if (!src[id]) src[id] = 'bot' }\n            if (c[id] !== undefined && c[id] <= 0) { delete c[id]; delete src[id] }\n          })\n          d2.state.cart = c\n          d2.state.cartSource = src\n          d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: r.reply || 'Pronto, atualizei sua sacola! 🛍️' }]\n        } else if (r.type === 'coupon_offer') {\n          d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: r.reply || 'Deixa eu ver o que consigo de desconto pra você…' }]\n          if (r.has_coupon && r.coupon && r.coupon.code) {\n            await sleep(900)\n            d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: '', coupon: r.coupon }]\n          }\n        } else if (r.type === 'product_options') {\n          d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: r.reply || 'Veja as opções:' }]\n          await sleep(900)\n          d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: '', options: r.options || [] }]\n        } else if (r.type === 'chat') {\n          const csets = (r.routines || []).map((rt) => ({ key: 'set-' + Date.now() + '-' + Math.floor(Math.random() * 1000), name: rt.name, blurb: rt.rationale || '', warnings: [], items: rt.items_detailed || [], total: rt.total, budget_active: false, over_budget: false }))\n          if (csets.length) { const rmap = { ...(d2.state.routineMap || {}) }; csets.forEach((rt) => { rmap[rt.key] = rt.items.map((it) => it.product_id) }); d2.state.routineMap = rmap }\n          d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: r.reply || 'Como posso ajudar?' }]\n          if (csets.length) { await sleep(900); d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: '', routines: csets }] }\n        } else if (r.type === 'question') {\n          d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: r.question || 'Pode me dar mais detalhes?', qoptions: r.question_options || [] }]\n        } else {\n          const copyByKey = {}\n          ;((r.copy && r.copy.routines) || []).forEach((c) => { copyByKey[c.key] = c })\n          const hasBudget = !!(r.budget && (Number(r.budget.value) || 0) > 0)\n          const routines = (r.routines || []).map((rt) => ({\n            key: rt.key, name: rt.name, total: rt.total,\n            blurb: (copyByKey[rt.key] && copyByKey[rt.key].blurb) || rt.rationale || '',\n            warnings: (copyByKey[rt.key] && copyByKey[rt.key].warnings) || [],\n            items: rt.items_detailed || [],\n            over_budget: !!rt.over_budget,\n            budget_active: hasBudget,\n          }))\n          const rmap = { ...(d2.state.routineMap || {}) }\n          routines.forEach((rt) => { rmap[rt.key] = rt.items.map((it) => it.product_id) })\n          d2.state.routineMap = rmap\n          const botText = (r.copy && r.copy.diagnosis) || r.diagnosis_seed || 'Aqui estão as rotinas que montei pra você:'\n          d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: botText }]\n          await sleep(1000)\n          d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: '', routines }]\n        }\n        if (spoken) {\n          const msgs2 = d2.state.messages || []\n          let say = ''\n          for (let i = msgs2.length - 1; i >= 0; i--) { if (msgs2[i].role === 'bot' && msgs2[i].text) { say = msgs2[i].text; break } }\n          if (say) { try { d2.assistant('consultora-ouvido').speak(say, { language: 'pt-BR', voice: 'Camila' }) } catch (e) { d2.log('speak error', e) } }\n        }\n      } catch (err) {\n        d2.log('consultoria-turn error', err)\n        d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: '⚠️ Tive um problema ao montar a rotina. Pode tentar de novo?' }]\n      } finally {\n        d2.state.loading = false\n      }\n    }\n\n    let mediaRecorder = null\n    let audioChunks = []\n    let micBusy = false\n    let recStartAt = 0\n    const startRec = async () => {\n      if (micBusy || d2.state.recording) return\n      micBusy = true\n      try {\n        // primeiro uso: se a permissão ainda vai ser pedida, só libera o mic e orienta — não grava\n        let perm = null\n        try { perm = navigator.permissions ? await navigator.permissions.query({ name: 'microphone' }) : null } catch (e) { perm = null }\n        if (perm && perm.state === 'prompt') {\n          const warmup = await navigator.mediaDevices.getUserMedia({ audio: true })\n          warmup.getTracks().forEach((t) => t.stop())\n          d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: 'Microfone liberado! 🎤 Agora toque no microfone, fale sua mensagem e toque de novo para enviar.' }]\n          return\n        }\n        const stream = await navigator.mediaDevices.getUserMedia({ audio: true })\n        audioChunks = []\n        mediaRecorder = new MediaRecorder(stream)\n        mediaRecorder.ondataavailable = (e) => { if (e.data && e.data.size > 0) audioChunks.push(e.data) }\n        mediaRecorder.onstop = async () => {\n          stream.getTracks().forEach((t) => t.stop())\n          d2.state.recording = false\n          const tooShort = (Date.now() - recStartAt) < 700\n          const blob = new Blob(audioChunks, { type: mediaRecorder.mimeType || 'audio/webm' })\n          if (tooShort || !blob.size) {\n            d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: 'Não captei o áudio. 🎤 Toque no microfone, fale sua mensagem e toque de novo para enviar.' }]\n            return\n          }\n          const file = new File([blob], 'audio.webm', { type: blob.type })\n          d2.state.loading = true\n          try {\n            const hint = 'Mensagem de voz de um cliente de uma loja de beleza brasileira, falada em português do Brasil.'\n            const grab = (r) => (r && r.text) ? r.text : (typeof r === 'string' ? r : '')\n            let res = await d2.assistant('consultora-ouvido').listen(file, hint, { language: 'pt' })\n            let text = grab(res)\n            if (!text || !text.trim()) {\n              // 1º uso: módulo de transcrição pode estar inicializando — tenta 1x de novo com o mesmo áudio\n              await sleep(500)\n              res = await d2.assistant('consultora-ouvido').listen(file, hint, { language: 'pt' })\n              text = grab(res)\n            }\n            d2.state.loading = false\n            const clean = (text || '').trim()\n            const low = clean.toLowerCase().replace(/[.!?…\"'’]/g, '').trim()\n            const junk = ['amara.org', 'amara org', 'legendas pela comunidade', 'legendado pela comunidade', 'legendas pela', 'subtitles by', 'subtitle', 'thanks for watching', 'obrigado por assistir', 'inscreva-se', 'subscribe', 'www.']\n            const isJunk = !low || low.length < 2 || junk.some((b) => low.includes(b))\n            if (clean && !isJunk) { send(clean, { spoken: true }) }\n            else { d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: '🎤 Não captei sua voz — parece que o microfone estava mudo ou sem áudio. Confira se ele está habilitado e tente de novo, ou escreva sua mensagem por aqui.' }] }\n          } catch (err) {\n            d2.log('listen error', err)\n            d2.state.loading = false\n            d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: '⚠️ Tive um problema para transcrever o áudio. Pode escrever?' }]\n          }\n        }\n        mediaRecorder.start()\n        recStartAt = Date.now()\n        d2.state.recording = true\n      } catch (err) {\n        d2.log('mic error', err)\n        const denied = err && (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError' || err.name === 'SecurityError')\n        d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: denied ? 'O microfone está bloqueado pelo navegador. 🎤 Clique no cadeado ao lado do endereço do site, permita o microfone e tente de novo.' : 'Não consegui acessar o microfone. Verifique se outro aplicativo não está usando ele e tente de novo.' }]\n      } finally {\n        micBusy = false\n      }\n    }\n    d2.on('.js-mic', 'click', () => { if (d2.state.recording && mediaRecorder) { mediaRecorder.stop() } else { startRec() } })\n    const resizeImg = (file) => new Promise((resolve, reject) => {\n      const r = new FileReader()\n      r.onload = () => {\n        const img = new Image()\n        img.onload = () => {\n          const max = 768; let w = img.width; let h = img.height\n          if (w > h && w > max) { h = Math.round(h * max / w); w = max }\n          else if (h > max) { w = Math.round(w * max / h); h = max }\n          const c = document.createElement('canvas'); c.width = w; c.height = h\n          c.getContext('2d').drawImage(img, 0, 0, w, h)\n          resolve(c.toDataURL('image/jpeg', 0.85))\n        }\n        img.onerror = reject; img.src = r.result\n      }\n      r.onerror = reject; r.readAsDataURL(file)\n    })\n    const parseAnalysis = (resp) => {\n      let a = (resp && resp.data !== undefined) ? resp.data : resp\n      if (typeof a === 'string') { try { a = JSON.parse(a.replace(/```json/gi, '').replace(/```/g, '').trim()) } catch (e) { a = null } }\n      return a\n    }\n    const hairRoutine = (a) => {\n      const bds = d2.data('bot-products-bDataSet')\n      const prods = (bds && !bds.loading) ? bds.records() : []\n      if (!prods.length) return null\n      const want = []\n      const tx = (a.texture || '').toLowerCase()\n      if (/cach|crespo|ondul/.test(tx)) want.push('cach', 'crespo', 'textura')\n      const cond = (a.condition || '').toLowerCase()\n      if (cond === 'seco') want.push('seco', 'ressecad')\n      if (cond === 'danificado') want.push('danificad')\n      if (cond === 'oleoso') want.push('oleos')\n      if (a.colored) want.push('loiro', 'descolor')\n      const need = (a.needs || []).join(' ').toLowerCase()\n      const score = (p) => {\n        const sub = (p.subcategory || '').toLowerCase(); const nm = (p.name || '').toLowerCase(); let s = 0\n        want.forEach((t) => { if (sub.includes(t)) s += 3; if (nm.includes(t)) s += 1 })\n        if (need.includes('hidrat') && /hidrat/.test(nm)) s += 2\n        if (need.includes('reconstru') && /reconstru/.test(nm)) s += 2\n        if ((need.includes('cach') || need.includes('frizz') || need.includes('defini')) && /cach|frizz/.test(nm)) s += 2\n        if (need.includes('loiro') && /loiro/.test(nm)) s += 2\n        if (sub.includes('todos')) s += 0.3\n        return s\n      }\n      const cats = ['Shampoo', 'Condicionador', 'Máscara', 'Leave-in', 'Finalizador', 'Óleo']\n      const items = []\n      cats.forEach((c) => { const pool = prods.filter((p) => p.category === c); if (pool.length) { pool.sort((x, y) => score(y) - score(x)); items.push(pool[0]) } })\n      const chosen = items.slice(0, 4)\n      if (!chosen.length) return null\n      return {\n        key: 'hair-' + Date.now() + '-' + Math.floor(Math.random() * 1000),\n        name: 'Rotina para o seu cabelo',\n        blurb: a.summary || 'Selecionei com base na análise do seu cabelo.',\n        warnings: [],\n        items: chosen.map((p) => ({ product_id: p._id, name: p.name, price: p.price, step: p.category, image: p.image_url })),\n        total: chosen.reduce((t, p) => t + (Number(p.price) || 0), 0),\n        budget_active: false, over_budget: false,\n      }\n    }\n    d2.on('.js-photo-input', 'change', async (e, el) => {\n      const file = el.files && el.files[0]\n      el.value = ''\n      if (!file || !/^image\\//.test(file.type)) return\n      d2.state.open = true\n      d2.state.hairAnalyzing = true\n      let dataUrl = ''\n      try { dataUrl = await resizeImg(file) } catch (er) { d2.state.hairAnalyzing = false; d2.log('resize error', er); return }\n      try {\n        const resp = await d2.rush('analisa-cabelo').invoke({ image: dataUrl })\n        const a = parseAnalysis(resp)\n        d2.state.hairAnalyzing = false\n        if (!a || !a.is_hair) {\n          d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: '📷 ' + ((a && a.reject_reason) || 'Não consegui identificar seu cabelo nessa foto') + '. Pra eu recomendar direitinho — e pela sua segurança — me envie uma foto focada só no cabelo, tá? 💇' }]\n          return\n        }\n        d2.state.messages = [...(d2.state.messages || []), { role: 'user', text: '', image: dataUrl }]\n        const rt = hairRoutine(a)\n        d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: (a.summary || 'Analisei seu cabelo!') + (rt ? ' Montei uma rotina que combina com ele 👇' : ' Me conta o que você busca que eu recomendo.') }]\n        if (rt) {\n          const rmap = { ...(d2.state.routineMap || {}) }; rmap[rt.key] = rt.items.map((it) => it.product_id); d2.state.routineMap = rmap\n          await sleep(900)\n          d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: '', routines: [rt] }]\n        }\n      } catch (err) {\n        d2.log('analisa-cabelo error', err)\n        d2.state.hairAnalyzing = false\n        d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: '⚠️ Não consegui analisar a foto agora. Pode tentar de novo?' }]\n      }\n    })\n    d2.on('.js-open-product', 'click', (e, el) => { if (e.target && e.target.closest && e.target.closest('button')) return; d2.state.selectedId = el.dataset.id })\n    d2.on('.js-modal-close', 'click', () => { d2.state.selectedId = null })\n    d2.on('.js-open-side', 'click', (e, el) => { d2.state.sideProductId = el.dataset.id })\n    d2.on('.js-side-close', 'click', () => { d2.state.sideProductId = null })\n    d2.on('.js-add-side', 'click', (e, el) => addToCart(el.dataset.id, 1, 'bot'))\n    d2.on('.js-ask-product', 'click', (e, el) => { d2.state.selectedId = null; d2.state.open = true; send('Me conta mais sobre o ' + el.dataset.name + ' — combina com a minha pele e como eu uso?') })\n    d2.on('.js-add', 'click', (e, el) => addToCart(el.dataset.id, 1, 'manual'))\n    d2.on('.js-inc', 'click', (e, el) => addToCart(el.dataset.id, 1, 'manual'))\n    d2.on('.js-dec', 'click', (e, el) => addToCart(el.dataset.id, -1))\n    d2.on('.js-remove', 'click', (e, el) => { const c = { ...(d2.state.cart || {}) }; const src = { ...(d2.state.cartSource || {}) }; delete c[el.dataset.id]; delete src[el.dataset.id]; d2.state.cart = c; d2.state.cartSource = src })\n    d2.on('.js-cart', 'click', () => { d2.state.cartOpen = true; loadSuggestions() })\n    d2.on('.js-fav', 'click', (e, el) => { const f = { ...(d2.state.favs || {}) }; const id = el.dataset.id; if (f[id]) { delete f[id] } else { f[id] = true }; d2.state.favs = f })\n    d2.on('.js-cat', 'click', (e, el) => { d2.state.category = el.dataset.cat || '' })\n    d2.on('.js-catsw', 'click', () => {\n      d2.state.catalog = (d2.state.catalog === 'b') ? 'a' : 'b'\n      d2.state.cart = {}; d2.state.cartSource = {}; d2.state.coupon = null; d2.state.category = ''\n    })\n    d2.on('.js-profile', 'click', () => {\n      let authed = false\n      try { authed = sessionStorage.getItem('painelAuthed') === '1' } catch (e) { authed = false }\n      if (!authed) { window.location.assign('/login'); return }\n      d2.state.profileMenu = !d2.state.profileMenu\n    })\n    d2.on('.js-profile-close', 'click', () => { d2.state.profileMenu = false })\n    d2.on('.js-goto-painel', 'click', () => {\n      d2.state.profileMenu = false\n      // navegação completa: o router monta na rota e reavalia a policy do /painel\n      window.location.assign('/painel')\n    })\n    d2.on('.js-cart-to-chat', 'click', () => { d2.state.cartOpen = false; d2.state.open = true; send('Os produtos da minha sacola combinam com a minha pele? Pode conferir?') })\n    d2.on('.js-pay', 'click', (e, el) => { d2.state.payMethod = el.dataset.pay })\n    d2.on('.js-cart-close', 'click', () => { d2.state.cartOpen = false })\n    d2.on('.js-checkout', 'click', () => { d2.state.cartOpen = false; d2.state.checkoutDone = false; d2.state.view = 'checkout'; loadSuggestions() })\n    d2.on('.js-home', 'click', () => { d2.state.view = 'store'; d2.state.checkoutDone = false })\n    d2.on('.js-confirm', 'click', async () => {\n      const order = '#AB' + Math.floor(100000 + Math.random() * 900000)\n      try {\n        const pds = d2.data(d2.state.catalog === 'b' ? 'bot-products-bDataSet' : 'products')\n        const prods = (pds && !pds.loading) ? pds.records() : []\n        const cart = d2.state.cart || {}\n        const items = prods.filter((p) => (cart[p._id] || 0) > 0)\n        const itensCount = items.reduce((a, p) => a + (cart[p._id] || 0), 0)\n        const gross = items.reduce((a, p) => a + (Number(p.price) || 0) * (cart[p._id] || 0), 0)\n        const cp = d2.state.coupon\n        let discount = 0\n        if (cp && cp.code) {\n          const base = cp.scope === 'categoria' ? items.reduce((a, p) => a + ((p.category === cp.category) ? (Number(p.price) || 0) * (cart[p._id] || 0) : 0), 0) : gross\n          if (gross >= (Number(cp.min_total) || 0)) {\n            discount = cp.discount_type === 'percent' ? base * (Number(cp.discount_value) || 0) / 100 : Math.min(Number(cp.discount_value) || 0, base)\n          }\n        }\n        const ticket = Math.max(0, gross - discount)\n        const baseline = items.reduce((m, p) => Math.max(m, Number(p.price) || 0), 0)\n        const recorrencia = items.reduce((a, p) => { const dd = Number(p.duration_days) || 0; return a + (dd > 0 ? (Number(p.price) || 0) * (cart[p._id] || 0) * 30 / dd : 0) }, 0)\n        const csrc = d2.state.cartSource || {}\n        const isBot = (p) => csrc[p._id] === 'bot'\n        const itensBot = items.reduce((a, p) => a + (isBot(p) ? (cart[p._id] || 0) : 0), 0)\n        const ticketBot = items.reduce((a, p) => a + (isBot(p) ? (Number(p.price) || 0) * (cart[p._id] || 0) : 0), 0)\n        await d2.rush('registra-consulta').invoke({\n          created_at: new Date().toISOString(),\n          session_id: order,\n          itens_count: itensCount,\n          ticket_total: Math.round(ticket * 100) / 100,\n          baseline_avulso: Math.round(baseline * 100) / 100,\n          recorrencia_mensal_estimada: Math.round(recorrencia * 100) / 100,\n          devolucao_evitada_pct: 30,\n          assinatura_selecionada: false,\n          intent_summary: items.map((p) => p.name).join(', '),\n          itens_bot: itensBot,\n          itens_manual: itensCount - itensBot,\n          ticket_bot: Math.round(ticketBot * 100) / 100,\n          ticket_manual: Math.round((gross - ticketBot) * 100) / 100,\n        })\n      } catch (e) { d2.log('log consultation error', e) }\n      d2.state.orderNumber = order\n      d2.state.checkoutDone = true\n      d2.state.cart = {}\n      d2.state.cartSource = {}\n      d2.state.coupon = null\n    })\n    d2.on('.js-add-sugg', 'click', (e, el) => { addToCart(el.dataset.id, 1, 'bot'); loadSuggestions() })\n    d2.on('.js-add-opt', 'click', (e, el) => { addToCart(el.dataset.id, 1, 'bot'); d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: 'Adicionei à sua sacola! 🛍️ Quer que eu te explique como usar esse produto no dia a dia?' }] })\n    d2.on('.js-apply-coupon', 'click', (e, el) => {\n      d2.state.coupon = { code: el.dataset.code, discount_type: el.dataset.type, discount_value: Number(el.dataset.value) || 0, scope: el.dataset.scope, category: el.dataset.category || '', min_total: Number(el.dataset.min) || 0 }\n      d2.state.messages = [...(d2.state.messages || []), { role: 'bot', text: 'Cupom ' + el.dataset.code + ' aplicado! 🎉 O desconto já está valendo na sua sacola. Lembrando: vale um cupom por carrinho — aplicar outro substitui este.' }]\n    })\n    d2.on('.js-remove-coupon', 'click', () => { d2.state.coupon = null })\n    d2.on('.js-unread', 'click', () => { const log = d2.$('.log'); if (log) log.scrollTo({ top: log.scrollHeight, behavior: 'smooth' }) })\n    d2.on('.js-open', 'click', () => { d2.state.open = true })\n    d2.on('.js-close', 'click', () => { d2.state.open = false })\n    d2.on('.js-send', 'click', () => send())\n    d2.on('.js-chip', 'click', (e, el) => send(el.textContent))\n    d2.on('.js-ask', 'click', (e, el) => { d2.state.open = true; send('Me conta mais sobre o ' + el.dataset.name + ' e como ele entra numa boa rotina pra mim.') })\n    d2.on('.js-addroutine', 'click', (e, el) => {\n      const ids = (d2.state.routineMap || {})[el.dataset.key] || []\n      const c = { ...(d2.state.cart || {}) }\n      const src = { ...(d2.state.cartSource || {}) }\n      ids.forEach((id) => { c[id] = (c[id] || 0) + 1; if (!src[id]) src[id] = 'bot' })\n      d2.state.cart = c\n      d2.state.cartSource = src\n      d2.state.open = false\n      d2.state.cartOpen = true\n    })\n    d2.on('.cin', 'keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send() } })\n    d2.on('.cin', 'input', () => { const t = d2.$('.cin'); if (!t) return; t.style.height = 'auto'; t.style.height = Math.min(t.scrollHeight, 110) + 'px' })\n  },\n  updated(d2) {\n    const log = d2.$('.log')\n    if (!log) return\n    const unread = d2.$('.js-unread')\n    const syncUnread = () => {\n      if (!unread) return\n      const below = (log.scrollHeight - log.scrollTop - log.clientHeight) > 24\n      unread.style.display = below ? 'inline-flex' : 'none'\n    }\n    if (!log.dataset.sl) {\n      log.dataset.sl = '1'\n      log.addEventListener('scroll', syncUnread)\n    }\n    const msgs = d2.state.messages || []\n    const prev = Number(log.dataset.mc || 0)\n    if (msgs.length > prev) {\n      log.dataset.mc = String(msgs.length)\n      const last = msgs[msgs.length - 1]\n      const row = log.querySelector('.row[data-key=\"' + (msgs.length - 1) + '\"]')\n      if (last.role === 'bot' && row) {\n        if (last.text) {\n          // foca no TOPO da resposta do bot; o indicador avisa se houver mais abaixo\n          log.scrollTop = Math.max(0, row.offsetTop - log.offsetTop - 8)\n        }\n        // follow-up sem texto (cards): mantém a posição de leitura; o indicador sinaliza o resto\n      } else {\n        log.scrollTop = log.scrollHeight\n      }\n    } else if (d2.state.loading) {\n      log.scrollTop = log.scrollHeight\n    }\n    syncUnread()\n  },\n}"}},"products":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"products"}}}}}}}}}},{"_id":"6a464a40fd69f89bce071102","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"painel-login","package":"fernando-playground","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 { position:absolute; inset:0; overflow:auto; font-family:system-ui,-apple-system,Segoe UI,sans-serif; }\n    .wrap { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; padding:24px; background:linear-gradient(135deg,#1A1512,#2A211A); }\n    .card { width:100%; max-width:400px; background:#FBF9F5; border-radius:20px; padding:34px; box-shadow:0 30px 80px rgba(0,0,0,.4); display:flex; flex-direction:column; }\n    .logo { font-family:Georgia,serif; font-size:24px; font-weight:600; color:#262019; text-align:center; }\n    .logo .dot { color:#B4633E; margin:0 4px; }\n    .sub { font-size:13px; color:#8C8578; text-align:center; margin:6px 0 20px; }\n    .tabs { display:flex; background:#F1ECE4; border-radius:11px; padding:4px; margin-bottom:22px; }\n    .tab { flex:1; border:none; background:none; padding:10px; border-radius:8px; font-size:13.5px; font-weight:700; color:#8C8578; cursor:pointer; transition:.15s; }\n    .tab.active { background:#fff; color:#262019; box-shadow:0 2px 8px rgba(38,32,25,.08); }\n    label { font-size:12px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:#6E665A; margin-bottom:6px; }\n    .fld { width:100%; padding:12px 14px; border:1.5px solid #E2DACB; border-radius:11px; font-size:14px; color:#262019; background:#fff; margin-bottom:16px; outline:none; box-sizing:border-box; }\n    .fld:focus { border-color:#B4633E; }\n    .err { background:rgba(217,83,78,.1); border:1px solid rgba(217,83,78,.35); color:#B23B36; font-size:12.5px; border-radius:10px; padding:9px 12px; margin-bottom:14px; }\n    .btn { width:100%; border:none; border-radius:11px; background:#262019; color:#fff; padding:14px; font-size:14px; font-weight:700; cursor:pointer; transition:.15s; }\n    .btn:hover { background:#3d342a; }\n    .btn:disabled { opacity:.5; cursor:default; }\n    .fine { font-size:11.5px; color:#A69C8C; text-align:center; margin-top:12px; line-height:1.5; }\n    .back { background:none; border:none; color:#8C8578; font-size:12.5px; cursor:pointer; margin-top:14px; text-decoration:underline; }\n    .back:hover { color:#262019; }\n  `,\n  render(d2) {\n    const esc = (s) => (s ?? '').toString().replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;')\n    const mode = d2.state.mode || 'signup'\n    const err = d2.state.error || ''\n    const busy = !!d2.state.busy\n    const isLogin = mode === 'login'\n    const form = isLogin ? `\n      <label>E-mail</label>\n      <input class=\"fld js-email\" type=\"email\" placeholder=\"voce@empresa.com\" autocomplete=\"username\" />\n      <label>Senha</label>\n      <input class=\"fld js-pwd\" type=\"password\" placeholder=\"••••••••\" autocomplete=\"current-password\" />\n      ${err ? `<div class=\"err\">${esc(err)}</div>` : ''}\n      <button class=\"btn js-login\" ${busy ? 'disabled' : ''}>${busy ? 'Entrando…' : 'Entrar'}</button>` : `\n      <label>Nome</label>\n      <input class=\"fld js-name\" placeholder=\"Seu nome\" autocomplete=\"name\" />\n      <label>E-mail</label>\n      <input class=\"fld js-email\" type=\"email\" placeholder=\"voce@empresa.com\" autocomplete=\"username\" />\n      <label>Senha</label>\n      <input class=\"fld js-pwd\" type=\"password\" placeholder=\"crie uma senha\" autocomplete=\"new-password\" />\n      ${err ? `<div class=\"err\">${esc(err)}</div>` : ''}\n      <button class=\"btn js-signup\" ${busy ? 'disabled' : ''}>${busy ? 'Criando conta…' : 'Criar conta e ver o painel'}</button>\n      <div class=\"fine\">Grátis, sem cartão. Você ganha acesso ao painel comercial da demonstração — o que a consultoria por IA gera em receita.</div>`\n    return `<div class=\"wrap\"><div class=\"card\">\n      <div class=\"logo\">Ateliê<span class=\"dot\">·</span>Beleza</div>\n      <div class=\"sub\">Painel comercial — o impacto da consultoria por IA</div>\n      <div class=\"tabs\">\n        <button class=\"tab js-tab-signup ${!isLogin ? 'active' : ''}\" type=\"button\">Criar conta</button>\n        <button class=\"tab js-tab-login ${isLogin ? 'active' : ''}\" type=\"button\">Já tenho conta</button>\n      </div>\n      ${form}\n      <button class=\"back js-back\" type=\"button\">← Voltar à loja</button>\n    </div></div>`\n  },\n  mounted(d2) {\n    const val = (s) => { const el = d2.$(s); return el ? el.value : '' }\n    const enter = async (email, pwd) => {\n      // estabelece a sessão do runtime e marca o flag local; painel recarrega com policy\n      try { await d2.auth('painel-auth').login({ email, password: pwd }) } catch (e) { d2.log('strategy login', e) }\n      try { sessionStorage.setItem('painelAuthed', '1') } catch (e) {}\n      window.location.assign('/painel')\n    }\n    const doLogin = async () => {\n      const email = val('.js-email').trim().toLowerCase()\n      const pwd = val('.js-pwd')\n      if (!email || !pwd) { d2.state.error = 'Preencha e-mail e senha.'; return }\n      d2.state.error = ''\n      d2.state.busy = true\n      try {\n        const res = await d2.rush('verifica-login').invoke({ credentials: { email, password: pwd } })\n        const r = (res && res.data) ? res.data : res\n        if (!r || !r.user) { d2.state.busy = false; d2.state.error = 'E-mail ou senha inválidos.'; return }\n        await enter(email, pwd)\n      } catch (e) {\n        d2.log('login error', e)\n        d2.state.busy = false\n        d2.state.error = 'E-mail ou senha inválidos.'\n      }\n    }\n    const doSignup = async () => {\n      const name = val('.js-name').trim()\n      const email = val('.js-email').trim().toLowerCase()\n      const pwd = val('.js-pwd')\n      if (!name || !email || !pwd) { d2.state.error = 'Preencha nome, e-mail e senha.'; return }\n      d2.state.error = ''\n      d2.state.busy = true\n      try {\n        const res = await d2.rush('cria-conta').invoke({ name, email, password: pwd })\n        const r = (res && res.data) ? res.data : res\n        if (!r || !r.ok) { d2.state.busy = false; d2.state.error = (r && r.error) || 'Não foi possível criar a conta.'; return }\n        await enter(email, pwd)\n      } catch (e) {\n        d2.log('signup error', e)\n        d2.state.busy = false\n        d2.state.error = 'Não foi possível criar a conta. Tente de novo.'\n      }\n    }\n    d2.on('.js-tab-login', 'click', () => { d2.state.error = ''; d2.state.mode = 'login' })\n    d2.on('.js-tab-signup', 'click', () => { d2.state.error = ''; d2.state.mode = 'signup' })\n    d2.on('.js-login', 'click', doLogin)\n    d2.on('.js-signup', 'click', doSignup)\n    d2.on('.js-pwd', 'keydown', (e) => { if (e.key === 'Enter') { if ((d2.state.mode || 'signup') === 'login') doLogin(); else doSignup() } })\n    d2.on('.js-back', 'click', () => window.location.assign('/'))\n  },\n}"}}}}}}}},{"_id":"6a464a618c5c69fa78ecf5bf","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"painel-admin-users","package":"fernando-playground","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 { position:absolute; inset:0; overflow:auto; font-family:system-ui,-apple-system,Segoe UI,sans-serif; background:#F4F1EA; color:#262019; }\n    .page { max-width:820px; margin:0 auto; padding:40px 28px 80px; }\n    h1 { font-family:Georgia,serif; font-size:28px; font-weight:600; margin:0 0 4px; }\n    .lead { font-size:13.5px; color:#8C8578; margin:0 0 28px; }\n    .warn { background:#FFF6E6; border:1px solid #F0D9A8; color:#8a6d1f; font-size:12.5px; border-radius:10px; padding:10px 14px; margin-bottom:24px; }\n    .card { background:#fff; border:1px solid #E7E0D4; border-radius:16px; padding:24px; margin-bottom:24px; }\n    .card h2 { font-family:Georgia,serif; font-size:18px; font-weight:600; margin:0 0 16px; }\n    .row { display:grid; grid-template-columns:1fr 1fr; gap:14px; }\n    label { display:block; font-size:11px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:#6E665A; margin:0 0 6px; }\n    .fld { width:100%; padding:11px 13px; border:1.5px solid #E2DACB; border-radius:10px; font-size:13.5px; color:#262019; background:#fff; margin-bottom:14px; outline:none; box-sizing:border-box; }\n    .fld:focus { border-color:#B4633E; }\n    .sel { width:100%; padding:11px 13px; border:1.5px solid #E2DACB; border-radius:10px; font-size:13.5px; background:#fff; margin-bottom:14px; box-sizing:border-box; }\n    .btn { border:none; border-radius:10px; background:#262019; color:#fff; padding:12px 22px; font-size:13.5px; font-weight:700; cursor:pointer; }\n    .btn:hover { background:#3d342a; }\n    .btn:disabled { opacity:.5; cursor:default; }\n    .msg { font-size:12.5px; margin-top:12px; }\n    .msg.ok { color:#3f7d47; }\n    .msg.err { color:#B23B36; }\n    table { width:100%; border-collapse:collapse; font-size:13px; }\n    th { text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:.04em; color:#8C8578; padding:8px 10px; border-bottom:1px solid #EAE3D8; }\n    td { padding:10px; border-bottom:1px solid #F0EAE0; }\n    .pill { font-size:11px; font-weight:700; padding:3px 9px; border-radius:999px; }\n    .pill.on { background:rgba(63,125,71,.12); color:#3f7d47; }\n    .pill.off { background:rgba(150,140,128,.15); color:#8C8578; }\n    .act { border:1px solid #DBD3C6; background:#fff; border-radius:8px; padding:5px 11px; font-size:12px; font-weight:600; cursor:pointer; color:#262019; }\n    .act:hover { border-color:#262019; }\n    .empty { color:#8C8578; font-size:13px; padding:12px 0; }\n  `,\n  render(d2) {\n    const esc = (s) => (s ?? '').toString().replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;')\n    const ds = d2.data('bot-usersDataSet')\n    const rows = (ds && !ds.loading) ? ds.records() : []\n    const msg = d2.state.msg\n    const busy = !!d2.state.busy\n    const list = rows.length ? rows.map((u) => `\n      <tr data-key=\"${u._id}\">\n        <td>${esc(u.name)}</td>\n        <td>${esc(u.email)}</td>\n        <td>${esc(u.role || 'user')}</td>\n        <td><span class=\"pill ${u.active ? 'on' : 'off'}\">${u.active ? 'ativo' : 'inativo'}</span></td>\n        <td><button class=\"act js-toggle\" data-id=\"${u._id}\" data-active=\"${u.active ? '1' : '0'}\">${u.active ? 'Desativar' : 'Reativar'}</button></td>\n      </tr>`).join('') : `<tr><td colspan=\"5\" class=\"empty\">Nenhum usuário cadastrado ainda.</td></tr>`\n    return `<div class=\"page\">\n      <h1>Acessos ao painel</h1>\n      <p class=\"lead\">Crie e gerencie quem pode entrar no painel comercial. Esta tela roda só pelo builder — não faz parte do app publicado.</p>\n      <div class=\"warn\">⚠️ Demo: a senha é guardada em texto puro na tabela. Antes de produção, troque por verificação com hash.</div>\n      <div class=\"card\">\n        <h2>Novo acesso</h2>\n        <div class=\"row\">\n          <div><label>Nome</label><input class=\"fld js-name\" placeholder=\"Nome da pessoa\" /></div>\n          <div><label>E-mail</label><input class=\"fld js-email\" type=\"email\" placeholder=\"voce@empresa.com\" /></div>\n        </div>\n        <div class=\"row\">\n          <div><label>Senha</label><input class=\"fld js-pwd\" placeholder=\"senha inicial\" /></div>\n          <div><label>Papel</label><select class=\"sel js-role\"><option value=\"admin\">admin</option><option value=\"comercial\">comercial</option></select></div>\n        </div>\n        <button class=\"btn js-create\" ${busy ? 'disabled' : ''}>${busy ? 'Criando…' : 'Criar acesso'}</button>\n        ${msg ? `<div class=\"msg ${msg.kind}\">${esc(msg.text)}</div>` : ''}\n      </div>\n      <div class=\"card\">\n        <h2>Acessos existentes</h2>\n        <table><thead><tr><th>Nome</th><th>E-mail</th><th>Papel</th><th>Status</th><th></th></tr></thead><tbody>${list}</tbody></table>\n      </div>\n    </div>`\n  },\n  mounted(d2) {\n    const val = (s) => { const el = d2.$(s); return el ? el.value : '' }\n    d2.on('.js-create', 'click', async () => {\n      const name = val('.js-name').trim()\n      const email = val('.js-email').trim().toLowerCase()\n      const pwd = val('.js-pwd')\n      const role = val('.js-role') || 'comercial'\n      if (!name || !email || !pwd) { d2.state.msg = { kind: 'err', text: 'Preencha nome, e-mail e senha.' }; return }\n      const ds = d2.data('bot-usersDataSet')\n      const exists = ds.records().some((u) => (u.email || '').toLowerCase() === email)\n      if (exists) { d2.state.msg = { kind: 'err', text: 'Já existe um acesso com esse e-mail.' }; return }\n      d2.state.busy = true\n      try {\n        await ds.create({ name, email, password: pwd, role, active: true })\n        d2.state.busy = false\n        d2.state.msg = { kind: 'ok', text: 'Acesso criado: ' + email }\n        ;['.js-name', '.js-email', '.js-pwd'].forEach((s) => { const el = d2.$(s); if (el) el.value = '' })\n      } catch (e) {\n        d2.log('create user error', e)\n        d2.state.busy = false\n        d2.state.msg = { kind: 'err', text: 'Não foi possível criar o acesso.' }\n      }\n    })\n    d2.on('.js-toggle', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const isActive = el.dataset.active === '1'\n      const ds = d2.data('bot-usersDataSet')\n      try {\n        ds.select(id)\n        await ds.save({ active: !isActive })\n      } catch (err) { d2.log('toggle user error', err) }\n    })\n  },\n}"}}}}}}}},{"_id":"6a464d9ec4f6a71828526e27","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"painel-comercial","package":"fernando-playground","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 { position:absolute; inset:0; overflow:auto; font-family:system-ui,-apple-system,Segoe UI,sans-serif; background:#1A1A2E; color:#fff; }\n    .page { min-height:100%; padding:40px 48px 64px; box-sizing:border-box; }\n    .top { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; margin-bottom:6px; }\n    h1 { font-size:26px; font-weight:800; margin:0; }\n    .sub { font-size:15px; color:#8899bb; margin:6px 0 26px; }\n    .actions { display:flex; gap:10px; flex-shrink:0; }\n    .btn { border:none; border-radius:999px; padding:11px 20px; font-size:13.5px; font-weight:700; cursor:pointer; }\n    .btn.primary { background:#6C4DF6; color:#fff; } .btn.primary:hover { background:#5b3ce0; }\n    .btn.ghost { background:transparent; color:#8899bb; border:1px solid #3a3a55; } .btn.ghost:hover { color:#fff; border-color:#6C4DF6; }\n    .grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(280px,1fr)); gap:20px; margin-bottom:24px; }\n    .kpi { background:#26263A; border-radius:16px; padding:24px; }\n    .kpi .lbl { font-size:11px; font-weight:700; letter-spacing:1px; text-transform:uppercase; color:#8899bb; }\n    .kpi .val { font-size:34px; font-weight:800; margin:12px 0 10px; }\n    .kpi .foot { font-size:13px; color:#8899bb; }\n    .kpi .foot.hi { color:#E84393; }\n    .kpi .foot.up { color:#4ADE80; }\n    .panelcard { background:#26263A; border-radius:16px; padding:24px; margin-bottom:24px; }\n    .panelcard h2 { font-size:16px; font-weight:800; margin:0 0 16px; }\n    table { width:100%; border-collapse:collapse; font-size:13.5px; }\n    th { text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:.04em; color:#8899bb; padding:8px 10px; border-bottom:1px solid #3a3a55; }\n    td { padding:11px 10px; border-bottom:1px solid #2f2f47; color:#dfe3f0; }\n    td.r, th.r { text-align:right; }\n    .disc { background:#2A2440; border-radius:10px; color:#F0D9A8; font-size:13px; padding:12px 16px; }\n    .del { background:none; border:1px solid #3a3a55; color:#8899bb; border-radius:8px; padding:4px 9px; cursor:pointer; font-size:13px; }\n    .del:hover { border-color:#E9534E; color:#E9534E; }\n    .delc { display:inline-flex; gap:6px; justify-content:flex-end; }\n    .delc-no { background:none; border:1px solid #3a3a55; color:#8899bb; border-radius:8px; padding:4px 10px; cursor:pointer; font-size:12px; }\n    .delc-no:hover { color:#fff; border-color:#8899bb; }\n    .delc-yes { background:#E9534E; border:none; color:#fff; border-radius:8px; padding:4px 11px; cursor:pointer; font-size:12px; font-weight:700; }\n    .delc-yes:hover { background:#d6423d; }\n    .empty { background:#26263A; border-radius:16px; padding:48px; text-align:center; color:#8899bb; }\n    .empty .big { font-size:40px; margin-bottom:10px; }\n  `,\n  render(d2) {\n    const esc = (s) => (s ?? '').toString().replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')\n    const money = (n) => d2.fmt.currency(Number(n) || 0, 'BRL')\n    const ds = d2.data('consultations')\n    const loading = !ds || ds.loading\n    const rows = (loading ? [] : ds.records()).filter((r) => !(d2.state.deletedIds || {})[r._id])\n    const n = rows.length\n    const avg = (f) => n ? rows.reduce((a, r) => a + (Number(r[f]) || 0), 0) / n : 0\n    const sum = (f) => rows.reduce((a, r) => a + (Number(r[f]) || 0), 0)\n    const avgTicket = avg('ticket_total')\n    const avgItens = avg('itens_count')\n    const avgBaseline = avg('baseline_avulso')\n    const uplift = avgBaseline > 0 ? ((avgTicket - avgBaseline) / avgBaseline) * 100 : 0\n    const recorrencia = sum('recorrencia_mensal_estimada')\n    const avgDevol = avg('devolucao_evitada_pct')\n    const ticketBot = sum('ticket_bot')\n    const grossAll = ticketBot + sum('ticket_manual')\n    const botShare = grossAll > 0 ? (ticketBot / grossAll) * 100 : 0\n    const itensBotAll = sum('itens_bot')\n\n    const head = `<div class=\"top\">\n        <div><h1>Painel comercial — impacto da consultoria</h1>\n          <div class=\"sub\">O que a IA gerou nas consultorias desta loja — traduzido em receita. Dados reais das consultas registradas.</div></div>\n        <div class=\"actions\"><button class=\"btn ghost js-logout\" type=\"button\">Sair</button><button class=\"btn primary js-loja\" type=\"button\">← Voltar à loja</button></div>\n      </div>`\n\n    if (loading) return `<div class=\"page\">${head}<div class=\"empty\">Carregando…</div></div>`\n    if (!n) return `<div class=\"page\">${head}<div class=\"empty\"><div class=\"big\">📊</div><div style=\"font-weight:800;color:#fff;font-size:16px;margin-bottom:6px\">Ainda sem consultorias registradas</div><div>Finalize um pedido na loja (com a ajuda da consultora) para os números aparecerem aqui em tempo real.</div></div></div>`\n\n    const kpis = `<div class=\"grid\">\n      <div class=\"kpi\"><div class=\"lbl\">Ticket médio da recomendação</div><div class=\"val\">${money(avgTicket)}</div><div class=\"foot hi\">vs ${money(avgBaseline)} numa compra avulsa</div></div>\n      <div class=\"kpi\"><div class=\"lbl\">Uplift de ticket</div><div class=\"val\">${uplift >= 0 ? '+' : ''}${Math.round(uplift)}%</div><div class=\"foot up\">acima da compra de um item só</div></div>\n      <div class=\"kpi\"><div class=\"lbl\">Receita puxada pela IA</div><div class=\"val\">${money(ticketBot)}</div><div class=\"foot up\">${Math.round(botShare)}% do valor em produtos · ${itensBotAll} itens recomendados</div></div>\n      <div class=\"kpi\"><div class=\"lbl\">Itens por consultoria</div><div class=\"val\">${(Math.round(avgItens * 10) / 10).toString().replace('.', ',')}</div><div class=\"foot\">cross-sell guiado pela rotina</div></div>\n      <div class=\"kpi\"><div class=\"lbl\">Receita recorrente projetada</div><div class=\"val\">${money(recorrencia)}/mês</div><div class=\"foot hi\">recompra estimada pela duração dos produtos</div></div>\n      <div class=\"kpi\"><div class=\"lbl\">Redução de devolução</div><div class=\"val\">~${Math.round(avgDevol)}%</div><div class=\"foot\">produto certo para a necessidade</div></div>\n      <div class=\"kpi\"><div class=\"lbl\">Consultorias registradas</div><div class=\"val\">${n}</div><div class=\"foot\">pedidos fechados com a consultora</div></div>\n    </div>`\n\n    const recent = rows.slice().reverse().slice(0, 12).map((r) => {\n      const ib = Number(r.itens_bot) || 0\n      const im = (Number(r.itens_count) || 0) - ib\n      const origem = (!ib && !im) ? '—' : `${ib ? '🤖 ' + ib : ''}${ib && im ? ' · ' : ''}${im ? '🖱️ ' + im : ''}`\n      return `<tr data-key=\"${r._id}\">\n        <td>${r.created_at ? esc(d2.fmt.relative(r.created_at)) : '—'}</td>\n        <td>${esc(d2.fmt.truncate(r.intent_summary || '—', 46))}</td>\n        <td class=\"r\">${origem}</td>\n        <td class=\"r\">${Number(r.itens_count) || 0}</td>\n        <td class=\"r\">${money(r.ticket_total)}</td>\n        <td class=\"r\">${(d2.state.confirmDel === r._id) ? `<span class=\"delc\"><button class=\"delc-no js-del-no\" type=\"button\">Não</button><button class=\"delc-yes js-del-yes\" data-id=\"${r._id}\" type=\"button\">Excluir?</button></span>` : `<button class=\"del js-del\" data-id=\"${r._id}\" type=\"button\" title=\"Excluir compra\">🗑</button>`}</td>\n      </tr>`\n    }).join('')\n\n    return `<div class=\"page\">${head}${kpis}\n      <div class=\"panelcard\"><h2>Consultorias recentes</h2>\n        <table><thead><tr><th>Quando</th><th>Itens</th><th class=\"r\">Origem</th><th class=\"r\">Qtd</th><th class=\"r\">Ticket</th><th></th></tr></thead><tbody>${recent}</tbody></table>\n      </div>\n      <div class=\"disc\">⚠️ Projeções de demonstração (recorrência e redução de devolução são estimativas) — não são métricas auditadas. Alinhar com o time comercial antes de prometer ROI.</div>\n    </div>`\n  },\n  mounted(d2) {\n    d2.on('.js-loja', 'click', () => window.location.assign('/'))\n    d2.on('.js-del', 'click', (e, el) => { d2.state.confirmDel = el.dataset.id })\n    d2.on('.js-del-no', 'click', () => { d2.state.confirmDel = null })\n    d2.on('.js-del-yes', 'click', async (e, el) => {\n      const id = el.dataset.id\n      d2.state.confirmDel = null\n      if (!id) return\n      d2.state.deletedIds = { ...(d2.state.deletedIds || {}), [id]: true }\n      try {\n        await d2.rush('exclui-consulta').invoke({ id })\n        const ds = d2.data('consultations'); if (ds && ds.refresh) ds.refresh()\n      } catch (err) { d2.log('del consultation', err) }\n    })\n    d2.on('.js-logout', 'click', async () => {\n      try { sessionStorage.removeItem('painelAuthed') } catch (e) {}\n      try { await d2.auth('painel-auth').logout() } catch (e) { d2.log('logout error', e) }\n      window.location.assign('/')\n    })\n  },\n}"}},"consultations":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"consultations"}}}}}}}}}},{"_id":"6a4c284911352c87ee6f88a4","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"translator-app","package":"fernando-playground","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 THEME_KEY = 'data2-translator-theme'\nconst LANGS = [\n  { code: 'pt-BR', label: 'Português' },\n  { code: 'es', label: 'Español' },\n  { code: 'en', label: 'English' },\n]\n\nfunction esc(s) {\n  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\nfunction langOptions(selected) {\n  return LANGS.map((l) => `<option value=\"${l.code}\" ${l.code === selected ? 'selected' : ''}>${esc(l.label)}</option>`).join('')\n}\n\nfunction flagSvg(code) {\n  if (code === 'pt-BR') return `<svg viewBox=\"0 0 24 16\" class=\"flag-svg\"><rect width=\"24\" height=\"16\" fill=\"#009c3b\"/><polygon points=\"12,1.5 22.5,8 12,14.5 1.5,8\" fill=\"#ffdf00\"/><circle cx=\"12\" cy=\"8\" r=\"3.3\" fill=\"#002776\"/></svg>`\n  if (code === 'en') return `<svg viewBox=\"0 0 24 16\" class=\"flag-svg\"><rect width=\"24\" height=\"16\" fill=\"#b22234\"/><rect y=\"2.46\" width=\"24\" height=\"2.46\" fill=\"#fff\"/><rect y=\"7.38\" width=\"24\" height=\"2.46\" fill=\"#fff\"/><rect y=\"12.3\" width=\"24\" height=\"2.46\" fill=\"#fff\"/><rect width=\"10\" height=\"8.61\" fill=\"#3c3b6e\"/></svg>`\n  return `<svg viewBox=\"0 0 24 16\" class=\"flag-svg\"><rect width=\"24\" height=\"16\" fill=\"#c60b1e\"/><rect y=\"4\" width=\"24\" height=\"8\" fill=\"#ffc400\"/></svg>`\n}\n\nfunction micSvg() {\n  return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"2\" width=\"6\" height=\"12\" rx=\"3\"/><path d=\"M5 11a7 7 0 0 0 14 0\"/><line x1=\"12\" y1=\"18\" x2=\"12\" y2=\"22\"/><line x1=\"8\" y1=\"22\" x2=\"16\" y2=\"22\"/></svg>`\n}\n\nfunction bcp47(code) {\n  if (code === 'pt-BR') return 'pt-BR'\n  if (code === 'es') return 'es-US'\n  return 'en-US'\n}\n\nfunction copySvg() {\n  return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"12\" height=\"12\" rx=\"2\"/><path d=\"M5 15V5a2 2 0 0 1 2-2h10\"/></svg>`\n}\n\nfunction trashSvg() {\n  return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 6h18\"/><path d=\"M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2\"/><path d=\"M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14\"/></svg>`\n}\n\nfunction escAttr(s) {\n  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\nfunction copyRow(translation) {\n  return `<div class=\"card-actions\"><button class=\"copy-btn\" data-copy=\"${escAttr(translation)}\">${copySvg()}<span class=\"copy-label\">Copiar</span></button></div>`\n}\n\nfunction resultCard(translation) {\n  return `<div class=\"result-card\"><p class=\"translation\">${esc(translation)}</p>${copyRow(translation)}</div>`\n}\n\nfunction fallbackCopy(text, done) {\n  try {\n    const ta = document.createElement('textarea')\n    ta.value = text\n    ta.style.position = 'fixed'\n    ta.style.opacity = '0'\n    document.body.appendChild(ta)\n    ta.focus(); ta.select()\n    document.execCommand('copy')\n    document.body.removeChild(ta)\n    if (done) done()\n  } catch (e) {}\n}\n\nfunction renderResult(result) {\n  if (!result) return `<div class=\"empty-hint\">A tradução vai aparecer aqui.</div>`\n  if (result.inputType === 'word') {\n    const seen = {}\n    const items = (result.translations || []).filter((t) => {\n      const key = String((t && t.translation) || '').trim().toLowerCase()\n      if (!key || seen[key]) return false\n      seen[key] = true\n      return true\n    })\n    if (!items.length) return `<div class=\"empty-hint\">Nenhuma tradução encontrada.</div>`\n    if (items.length === 1) return resultCard(items[0].translation)\n    const cards = items.map((t, i) => `\n      <div class=\"word-card\">\n        <span class=\"badge\">Significado ${i + 1}</span>\n        <p class=\"translation\">${esc(t.translation)}</p>\n        <p class=\"context\">${esc(t.context || '')}</p>\n        ${copyRow(t.translation)}\n      </div>\n    `).join('')\n    return `<div class=\"word-grid\">${cards}</div>`\n  }\n  const t = (result.translations || [])[0]\n  if (!t) return `<div class=\"empty-hint\">Nenhuma tradução encontrada.</div>`\n  return resultCard(t.translation)\n}\n\nexport default {\n  css: `\n    * { box-sizing: border-box; }\n    .app { display:flex; flex-direction:column; height:100%; width:100%; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; }\n    .app.dark {\n      --bg:#0f1115; --surface:#1a1d24; --surface2:#22262f; --text:#f4f5f7; --muted:#9aa1ac;\n      --accent:#8b6bff; --accent-soft:rgba(139,107,255,0.18);\n      --danger:#ff6b6b; --danger-soft:rgba(255,107,107,0.14);\n      --border:#2a2f3a;\n      color-scheme: dark;\n    }\n    .app.light {\n      --bg:#f6f7fb; --surface:#ffffff; --surface2:#eef0f5; --text:#14161a; --muted:#666d78;\n      --accent:#6a4bff; --accent-soft:rgba(106,75,255,0.12);\n      --danger:#d92d2d; --danger-soft:rgba(217,45,45,0.10);\n      --border:#e3e5ec;\n      color-scheme: light;\n    }\n    .app { background:var(--bg); color:var(--text); }\n    .header { display:flex; align-items:center; justify-content:space-between; padding:20px 24px; border-bottom:1px solid var(--border); flex-shrink:0; }\n    .header h1 { font-size:20px; margin:0; font-weight:700; letter-spacing:-0.02em; display:flex; align-items:center; gap:8px; }\n    .theme-btn { background:var(--surface2); border:1px solid var(--border); color:var(--text); width:40px; height:40px; border-radius:10px; cursor:pointer; font-size:18px; display:flex; align-items:center; justify-content:center; }\n    .theme-btn:active { transform:scale(0.92); }\n    .body { flex:1; overflow:auto; padding:24px; display:flex; flex-direction:column; gap:18px; max-width:720px; width:100%; margin:0 auto; }\n    .lang-bar { display:flex; align-items:center; gap:12px; }\n    .lang-select { flex:1; min-width:0; display:flex; align-items:center; gap:6px; background:var(--surface); border:1px solid var(--border); border-radius:12px; padding-left:10px; }\n    .flag { flex-shrink:0; display:flex; }\n    .flag-svg { width:24px; height:16px; border-radius:3px; display:block; box-shadow:0 0 0 1px rgba(0,0,0,0.15); }\n    select.lang { flex:1; min-width:0; width:100%; padding:12px 8px 12px 2px; border-radius:12px; background:transparent; border:none; outline:none; color:var(--text); font-size:14px; cursor:pointer; }\n    select.lang option { background:var(--surface); color:var(--text); }\n    .swap-btn { flex-shrink:0; width:44px; height:44px; border-radius:12px; background:var(--surface2); border:1px solid var(--border); color:var(--accent); font-size:18px; cursor:pointer; }\n    .swap-btn:active { transform:scale(0.92); }\n    .input-card { background:var(--surface); border:1px solid var(--border); border-radius:16px; padding:16px; display:flex; flex-direction:column; gap:12px; }\n    textarea.input-text { width:100%; min-height:96px; resize:vertical; background:transparent; border:none; outline:none; color:var(--text); font-size:16px; font-family:inherit; line-height:1.5; }\n    textarea.input-text::placeholder { color:var(--muted); }\n    .input-actions { display:flex; justify-content:space-between; align-items:center; gap:10px; }\n    .actions-right { display:flex; align-items:center; gap:10px; }\n    .clear-btn { display:flex; align-items:center; gap:6px; background:transparent; border:1px solid var(--border); color:var(--muted); padding:9px 12px; border-radius:10px; font-size:13px; cursor:pointer; }\n    .clear-btn svg { width:14px; height:14px; }\n    .clear-btn:hover { color:var(--text); border-color:var(--muted); }\n    .clear-btn:active { transform:scale(0.95); }\n    .voice-status { font-size:12px; color:var(--danger); }\n    .mic-btn { flex-shrink:0; width:42px; height:42px; border-radius:10px; background:var(--surface2); border:1px solid var(--border); color:var(--accent); cursor:pointer; display:flex; align-items:center; justify-content:center; }\n    .mic-btn svg { width:18px; height:18px; }\n    .mic-btn:active { transform:scale(0.92); }\n    .mic-btn.rec { background:var(--danger-soft); border-color:var(--danger); color:var(--danger); animation:pulse 1s ease-in-out infinite; }\n    @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.5} }\n    .translate-btn { background:var(--accent); color:#fff; border:none; padding:11px 24px; border-radius:10px; font-size:14px; font-weight:600; cursor:pointer; }\n    .translate-btn:disabled { opacity:0.6; cursor:default; }\n    .error-box { background:var(--danger-soft); border:1px solid var(--danger); color:var(--danger); padding:12px 14px; border-radius:12px; font-size:14px; }\n    .result-card { background:var(--surface); border:1px solid var(--border); border-radius:16px; padding:20px; }\n    .result-card .translation { font-size:22px; font-weight:700; margin:0; line-height:1.4; }\n    .card-actions { display:flex; justify-content:flex-end; margin-top:12px; }\n    .copy-btn { display:flex; align-items:center; gap:6px; background:var(--surface2); border:1px solid var(--border); color:var(--muted); border-radius:8px; padding:6px 10px; font-size:12px; font-weight:600; cursor:pointer; }\n    .copy-btn svg { width:13px; height:13px; }\n    .copy-btn:hover { color:var(--text); }\n    .copy-btn.done { color:var(--accent); border-color:var(--accent); }\n    .copy-btn:active { transform:scale(0.95); }\n    .word-grid { display:flex; flex-direction:column; gap:12px; }\n    .word-card { background:var(--surface); border:1px solid var(--border); border-radius:16px; padding:16px; }\n    .word-card .badge { display:inline-block; font-size:11px; text-transform:uppercase; letter-spacing:.04em; color:var(--accent); background:var(--accent-soft); padding:3px 8px; border-radius:6px; margin-bottom:8px; }\n    .word-card .translation { font-size:18px; font-weight:700; margin:0 0 6px; }\n    .word-card .context { font-size:13px; color:var(--muted); line-height:1.5; margin:0; }\n    .empty-hint { color:var(--muted); font-size:14px; text-align:center; padding:36px 0; }\n    @media (max-width: 480px) {\n      .header { padding:14px 16px; }\n      .header h1 { font-size:17px; }\n      .body { padding:16px; gap:14px; }\n      .lang-bar { gap:6px; }\n      .lang-select { gap:6px; padding-left:8px; }\n      .flag-svg { width:22px; height:15px; }\n      select.lang { padding:10px 4px 10px 2px; font-size:13px; }\n      .swap-btn { width:38px; height:38px; font-size:16px; }\n      .translate-btn { width:100%; }\n      .result-card .translation { font-size:19px; }\n    }\n  `,\n  render(d2) {\n    const theme = d2.state.theme || 'dark'\n    const sourceLang = d2.state.sourceLang || 'pt-BR'\n    const targetLang = d2.state.targetLang || 'en'\n    const loading = !!d2.state.loading\n    const recording = !!d2.state.recording\n    const error = d2.state.error || ''\n    const result = d2.state.result || null\n    return `\n      <div class=\"app ${theme}\">\n        <div class=\"header\">\n          <h1>🌐 Tradutor</h1>\n          <button class=\"theme-btn\" title=\"Alternar tema\">${theme === 'dark' ? '☀️' : '🌙'}</button>\n        </div>\n        <div class=\"body\">\n          <div class=\"lang-bar\">\n            <div class=\"lang-select\"><span class=\"flag\">${flagSvg(sourceLang)}</span><select class=\"lang lang-source\">${langOptions(sourceLang)}</select></div>\n            <button class=\"swap-btn\" title=\"Inverter idiomas\">⇄</button>\n            <div class=\"lang-select\"><span class=\"flag\">${flagSvg(targetLang)}</span><select class=\"lang lang-target\">${langOptions(targetLang)}</select></div>\n          </div>\n          <div class=\"input-card\">\n            <textarea class=\"input-text\" placeholder=\"Digite uma palavra, expressão ou frase...\"></textarea>\n            <div class=\"input-actions\">\n              <button class=\"clear-btn\" title=\"Apagar tudo\">${trashSvg()}<span>Limpar</span></button>\n              <div class=\"actions-right\">\n                ${recording ? `<span class=\"voice-status\">Ouvindo… fale agora</span>` : ''}\n                <button class=\"mic-btn ${recording ? 'rec' : ''}\" title=\"Falar para traduzir\">${micSvg()}</button>\n                <button class=\"translate-btn\" ${loading ? 'disabled' : ''}>${loading ? 'Traduzindo…' : 'Traduzir'}</button>\n              </div>\n            </div>\n          </div>\n          ${error ? `<div class=\"error-box\">${esc(error)}</div>` : ''}\n          ${renderResult(result)}\n        </div>\n      </div>\n    `\n  },\n  mounted(d2) {\n    let savedTheme = null\n    try { savedTheme = window.localStorage.getItem(THEME_KEY) } catch (e) {}\n    d2.state.theme = savedTheme === 'light' ? 'light' : 'dark'\n\n    d2.on('.theme-btn', 'click', () => {\n      const next = (d2.state.theme || 'dark') === 'dark' ? 'light' : 'dark'\n      d2.state.theme = next\n      try { window.localStorage.setItem(THEME_KEY, next) } catch (e) {}\n    })\n\n    d2.on('.lang-source', 'change', (e) => { d2.state.sourceLang = e.target.value })\n    d2.on('.lang-target', 'change', (e) => { d2.state.targetLang = e.target.value })\n\n    d2.on('.swap-btn', 'click', () => {\n      const s = d2.state.sourceLang || 'pt-BR'\n      const t = d2.state.targetLang || 'en'\n      d2.state.sourceLang = t\n      d2.state.targetLang = s\n    })\n\n    const runTranslate = async () => {\n      const el = d2.$('.input-text')\n      const text = ((el && el.value) || '').trim()\n      if (!text) { d2.state.error = 'Digite algo para traduzir.'; return }\n      d2.state.error = ''\n      d2.state.loading = true\n      d2.state.result = null\n      try {\n        const sourceLang = d2.state.sourceLang || 'pt-BR'\n        const targetLang = d2.state.targetLang || 'en'\n        const res = await d2.rush(d2.params.translateApi).invoke({ text, sourceLang, targetLang })\n        const payload = (res && res.data) ? res.data : res\n        if (!payload || !payload.translations) throw new Error('bad-response')\n        d2.state.result = payload\n      } catch (err) {\n        d2.state.error = 'Não foi possível traduzir agora. Tente novamente.'\n      } finally {\n        d2.state.loading = false\n      }\n    }\n\n    d2.on('.translate-btn', 'click', runTranslate)\n    d2.on('.input-text', 'keydown', (e) => {\n      if (e.key === 'Enter' && !e.shiftKey) {\n        e.preventDefault()\n        runTranslate()\n      }\n    })\n\n    // ---- ditado por voz (Web Speech API) ----\n    const SR = window.SpeechRecognition || window.webkitSpeechRecognition\n    let recognition = null\n    const stopRec = () => { if (recognition) { try { recognition.stop() } catch (e) {} } }\n    d2.onUnmount(() => { if (recognition) { try { recognition.abort() } catch (e) {} } })\n\n    d2.on('.mic-btn', 'click', () => {\n      if (d2.state.recording) { stopRec(); return }\n      if (!SR) { d2.state.error = 'Ditado por voz não é suportado neste navegador. Use o Chrome.'; return }\n      d2.state.error = ''\n      const rec = new SR()\n      recognition = rec\n      rec.lang = bcp47(d2.state.sourceLang || 'pt-BR')\n      rec.interimResults = false\n      rec.maxAlternatives = 1\n      rec.continuous = false\n      rec.onresult = (ev) => {\n        const transcript = ((ev.results && ev.results[0] && ev.results[0][0] && ev.results[0][0].transcript) || '').trim()\n        const el = d2.$('.input-text')\n        if (el && transcript) el.value = transcript\n        d2.state.recording = false\n        if (transcript) runTranslate()\n      }\n      rec.onerror = (ev) => {\n        d2.state.recording = false\n        const code = ev && ev.error\n        if (code === 'no-speech') d2.state.error = 'Não ouvi nada. Fale mais perto do microfone.'\n        else if (code === 'not-allowed' || code === 'service-not-allowed') d2.state.error = 'Permissão de microfone negada. Libere o acesso e tente de novo.'\n        else if (code && code !== 'aborted') d2.state.error = 'Não consegui captar o áudio. Tente de novo.'\n      }\n      rec.onend = () => { d2.state.recording = false; recognition = null }\n      try { rec.start(); d2.state.recording = true } catch (e) { d2.state.error = 'Não foi possível iniciar o microfone.' }\n    })\n\n    // ---- limpar tudo ----\n    d2.on('.clear-btn', 'click', () => {\n      const el = d2.$('.input-text')\n      if (el) el.value = ''\n      d2.state.result = null\n      d2.state.error = ''\n      if (el) el.focus()\n    })\n\n    // ---- copiar apenas a tradução ----\n    d2.on('.copy-btn', 'click', (e) => {\n      const btn = e.target.closest('.copy-btn')\n      if (!btn) return\n      const text = btn.getAttribute('data-copy') || ''\n      const done = () => {\n        const label = btn.querySelector('.copy-label')\n        if (label) label.textContent = 'Copiado!'\n        btn.classList.add('done')\n        setTimeout(() => {\n          btn.classList.remove('done')\n          const l = btn.querySelector('.copy-label')\n          if (l) l.textContent = 'Copiar'\n        }, 1400)\n      }\n      if (navigator.clipboard && navigator.clipboard.writeText) {\n        navigator.clipboard.writeText(text).then(done).catch(() => fallbackCopy(text, done))\n      } else {\n        fallbackCopy(text, done)\n      }\n    })\n  },\n}\n"}},"translateApi":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"translateApi"}}}}}}}}}},{"_id":"6a4cec7d6c020b3085c1f076","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"fd-ui-esteira","package":"fernando-playground","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  *{box-sizing:border-box}\n  .fdnav{flex:0 0 auto;height:46px;background:#0E1216;border-bottom:1px solid #333B44;display:flex;align-items:center;gap:2px;padding:0 16px}\n  .fdnav .brand{font-family:'Oswald',sans-serif;font-weight:700;text-transform:uppercase;font-size:13px;color:#E8A33D;margin-right:18px;letter-spacing:.05em}\n  .fdnav a{font-size:12px;color:#A7ABA2;text-decoration:none;padding:6px 12px;cursor:pointer;border-radius:2px;font-family:'IBM Plex Sans',sans-serif}\n  .fdnav a:hover{color:#E9E6DE}\n  .fdnav a.on{color:#14181D;background:#E8A33D;font-weight:600}\n  .fdnav .fdsw{margin-left:auto;display:flex;align-items:center;gap:8px;font-size:10px;color:#A7ABA2;text-transform:uppercase;letter-spacing:.05em;cursor:pointer;user-select:none;font-family:'IBM Plex Sans',sans-serif}\n  .fdnav .fdsw input{position:absolute;opacity:0;pointer-events:none}\n  .fdnav .fdsw .tr{width:34px;height:18px;border-radius:18px;background:#333B44;position:relative;transition:background .2s;flex:0 0 auto}\n  .fdnav .fdsw .tr::after{content:'';position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#A7ABA2;transition:transform .2s,background .2s}\n  .fdnav .fdsw input:checked + .tr{background:#E8A33D}\n  .fdnav .fdsw input:checked + .tr::after{transform:translateX(16px);background:#14181D}\n  .fd{--ink:#14181D;--panel:#1B2129;--panel2:#20262E;--paper:#E9E6DE;--dim:#A7ABA2;--amber:#E8A33D;--teal:#4FA8A0;--line:#333B44;--danger:#C0604B;\n      display:flex;flex-direction:column;height:100%;background:var(--ink);color:var(--paper);font-family:'IBM Plex Sans',system-ui,sans-serif;overflow:hidden} .fdbody{flex:1;display:grid;grid-template-columns:340px 1fr;min-height:0}\n  .col{overflow:auto;height:100%}\n  .left{border-right:1px solid var(--line);padding:18px;background:var(--panel)}\n  .right{padding:24px}\n  .kick{font-family:'Oswald',sans-serif;text-transform:uppercase;font-weight:700;font-size:15px;letter-spacing:.04em;margin:0 0 2px}\n  .tag{font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}\n  label{display:block;font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.06em;margin:12px 0 4px}\n  input,textarea{width:100%;background:var(--panel2);border:1px solid var(--line);color:var(--paper);padding:9px 10px;font-family:inherit;font-size:13px;border-radius:2px}\n  textarea{min-height:88px;resize:vertical}\n  .fire{margin-top:14px;width:100%;padding:11px;background:var(--amber);color:#14181D;border:0;font-weight:700;font-family:'Oswald',sans-serif;text-transform:uppercase;letter-spacing:.04em;cursor:pointer;border-radius:2px}\n  .fire[disabled]{opacity:.5;cursor:default}\n  .title{font-family:'Oswald',sans-serif;font-weight:700;font-size:26px;text-transform:uppercase;margin:0 0 2px}\n  .sub{color:var(--dim);font-size:13px;margin:0 0 20px}\n  .pipe{display:grid;grid-template-columns:repeat(6,1fr);gap:1px;background:var(--line);border:1px solid var(--line);margin-bottom:22px}\n  .st{background:var(--panel2);padding:14px 12px;min-height:122px;position:relative;opacity:.4;transition:opacity .35s}\n  .st.done{opacity:1}\n  .st.wait{opacity:1}\n  .st.cp{border-top:2px solid var(--teal)}\n  .st.wait{border-top:2px solid var(--amber)}\n  .st .n{font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--dim);margin-bottom:8px}\n  .st.done .n{color:var(--amber)}\n  .st.cp.done .n{color:var(--teal)}\n  .st .t{font-family:'Oswald',sans-serif;text-transform:uppercase;font-size:13px;margin-bottom:6px}\n  .st .d{font-size:11px;color:var(--dim);line-height:1.4}\n  .st .dot{position:absolute;top:12px;right:12px;width:8px;height:8px;border-radius:50%;background:var(--line)}\n  .st.done .dot{background:var(--teal)}\n  .st.wait .dot{background:var(--amber);animation:pulse 1s infinite}\n  @keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}\n  .cards{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-bottom:16px}\n  .card{background:var(--panel);border:1px solid var(--line);padding:14px}\n  .card h4{margin:0 0 10px;font-family:'Oswald',sans-serif;font-size:12px;text-transform:uppercase;color:var(--amber);letter-spacing:.03em}\n  .kv{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;padding:3px 0;border-bottom:1px solid var(--panel2)}\n  .kv span:first-child{color:var(--dim)}\n  .kv b{font-weight:600}\n  .badge{display:inline-block;padding:3px 8px;font-size:11px;font-family:'IBM Plex Mono',monospace;text-transform:uppercase;border-radius:2px}\n  .b-auto{background:rgba(79,168,160,.15);color:var(--teal);border:1px solid var(--teal)}\n  .b-wait{background:rgba(232,163,61,.15);color:var(--amber);border:1px solid var(--amber)}\n  .b-rej{background:rgba(192,96,75,.15);color:var(--danger);border:1px solid var(--danger)}\n  .just{background:var(--panel);border-left:3px solid var(--teal);padding:12px 14px;font-size:13px;margin-bottom:16px;line-height:1.5}\n  .just.esc{border-left-color:var(--amber)}\n  .orc h4{font-family:'Oswald',sans-serif;font-size:12px;text-transform:uppercase;color:var(--amber);margin:0 0 8px}\n  .orc pre{white-space:pre-wrap;background:var(--panel2);border:1px solid var(--line);padding:14px;font-size:13px;line-height:1.55;font-family:'IBM Plex Sans',sans-serif;margin:0}\n  .rail{margin-top:18px}\n  .ped-item{padding:9px 10px;border:1px solid var(--line);margin-bottom:6px;cursor:pointer;font-size:12px;display:flex;justify-content:space-between;gap:8px;background:var(--panel2)}\n  .ped-item.sel{border-color:var(--amber)}\n  .ped-item .pp{font-family:'IBM Plex Mono',monospace;color:var(--dim)}\n  .empty{color:var(--dim);font-size:14px;padding:40px 0;text-align:center;border:1px dashed var(--line)}\n  `,\n  render(d2){\n    const esc=(s)=>(s==null?'':String(s)).replace(/[&<>\"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\n    const _rp=(function(){try{return window.location.pathname||''}catch(e){return ''}})();\n    const _NAV=[['/esteira','Esteira ao Vivo'],['/aprovacao','Aprovação'],['/crm','CRM'],['/metricas','Métricas']];\n    const navHtml=`<div class=\"fdnav\"><span class=\"brand\">Funcionário Digital</span>${_NAV.map(function(it){var on=(it[0]==='/esteira')?(_rp==='/'||_rp===''||_rp.endsWith('/esteira')):(_rp===it[0]||_rp.endsWith(it[0]));return `<a data-to=\"${it[0]}\" class=\"${on?'on':''}\">${it[1]}</a>`;}).join('')}<label class=\"fdsw\"><input type=\"checkbox\" class=\"fdauto\" ${d2.state.auto?'checked':''}/><span class=\"tr\"></span><span>Auto-atualizar</span></label></div>`;\n    const money=(n)=>{const v=Number(n)||0; try{return d2.fmt.currency(v,'BRL')}catch(e){return 'R$ '+v}};\n    const pedH=d2.data('pedidos'), eveH=d2.data('eventos');\n    const pedidos=(pedH&&pedH.records)?[...pedH.records()]:[];\n    const eventos=(eveH&&eveH.records)?eveH.records():[];\n    pedidos.sort((a,b)=>String(b.createdAt||b.protocolo||'').localeCompare(String(a.createdAt||a.protocolo||'')));\n    const sel=pedidos.find(p=>p.protocolo===d2.state.selProto)||pedidos[0]||null;\n    const STATIONS=[\n      {k:'01_captura',n:'01',t:'Captura',d:'Detecta e reconhece o pedido'},\n      {k:'02_interpretacao',n:'02',t:'Interpretação',d:'Extrai os campos do texto'},\n      {k:'03_consulta',n:'03',t:'Consulta',d:'Estoque e preço'},\n      {k:'04_decisao',n:'04',t:'Decisão',d:'Aplica a régua do negócio',cp:true},\n      {k:'05_execucao',n:'05',t:'Execução',d:'Gera e envia o orçamento'},\n      {k:'06_registro',n:'06',t:'Registro',d:'Grava no histórico'}\n    ];\n    const evs=sel?eventos.filter(e=>e.pedido_id===sel.protocolo):[];\n    const done=new Set(evs.map(e=>e.estacao));\n    const escalada=!!(sel&&sel.decisao==='aguardando_aprovacao');\n    const badge=(dec)=>dec==='aguardando_aprovacao'?`<span class=\"badge b-wait\">aguardando aprovação</span>`:dec==='rejeitado'?`<span class=\"badge b-rej\">rejeitado</span>`:`<span class=\"badge b-auto\">${esc(dec||'—')}</span>`;\n    const pipe=STATIONS.map(s=>{\n      let cls='st'+(s.cp?' cp':'');\n      if(done.has(s.k))cls+=' done';\n      if(s.k==='04_decisao'&&escalada&&!done.has('05_execucao'))cls+=' wait';\n      return `<div class=\"${cls}\"><div class=\"dot\"></div><div class=\"n\">EST ${s.n}</div><div class=\"t\">${esc(s.t)}</div><div class=\"d\">${esc(s.d)}</div></div>`;\n    }).join('');\n    const rail=pedidos.slice(0,8).map(p=>`<div class=\"ped-item ${sel&&p.protocolo===sel.protocolo?'sel':''}\" data-proto=\"${esc(p.protocolo)}\"><span class=\"pp\">${esc(p.protocolo)}</span><span>${esc(p.remetente_nome||p.remetente_email||'')}</span></div>`).join('')||`<div class=\"empty\" style=\"padding:16px\">Nenhum pedido ainda.</div>`;\n    let detail;\n    if(!sel){\n      detail=`<div class=\"empty\">Dispare um pedido ao lado (ou envie um e-mail real) para ver a esteira em ação.</div>`;\n    } else {\n      const c=sel.campos_extraidos||{}, q=sel.consulta_resultado||{};\n      detail=`\n      <div class=\"pipe\">${pipe}</div>\n      <div class=\"cards\">\n        <div class=\"card\"><h4>Campos extraídos</h4>\n          <div class=\"kv\"><span>Produto</span><b>${esc(c.produto||'—')}</b></div>\n          <div class=\"kv\"><span>Quantidade</span><b>${esc(c.quantidade!=null?c.quantidade:'—')}</b></div>\n          <div class=\"kv\"><span>Prazo pedido</span><b>${esc(c.prazo_dias!=null?c.prazo_dias+'d':'—')}</b></div>\n          <div class=\"kv\"><span>Desconto pedido</span><b>${esc(c.desconto_solicitado_pct!=null?c.desconto_solicitado_pct+'%':'—')}</b></div>\n        </div>\n        <div class=\"card\"><h4>Consulta estoque/preço</h4>\n          <div class=\"kv\"><span>SKU</span><b>${esc(q.sku||'—')}</b></div>\n          <div class=\"kv\"><span>Preço unit.</span><b>${money(q.preco_unit)}</b></div>\n          <div class=\"kv\"><span>Estoque</span><b>${esc(q.estoque!=null?q.estoque:'—')}</b></div>\n          <div class=\"kv\"><span>Produção</span><b>${esc(q.prazo_producao_dias!=null?q.prazo_producao_dias+'d':'—')}</b></div>\n        </div>\n        <div class=\"card\"><h4>Decisão</h4>\n          <div class=\"kv\"><span>Status</span><b>${badge(sel.decisao)}</b></div>\n          <div class=\"kv\"><span>Valor total</span><b>${money(sel.valor_total)}</b></div>\n          <div class=\"kv\"><span>Desconto</span><b>${esc(sel.desconto_solicitado_pct!=null?sel.desconto_solicitado_pct+'%':'—')}</b></div>\n          <div class=\"kv\"><span>Ciclo</span><b>${esc(sel.status_ciclo||'—')}</b></div>\n        </div>\n      </div>\n      <div class=\"just ${escalada?'esc':''}\"><b>Justificativa:</b> ${esc(sel.justificativa||'—')}</div>\n      ${sel.orcamento_texto?`<div class=\"orc\"><h4>Orçamento enviado</h4><pre>${esc(sel.orcamento_texto)}</pre></div>`:''}\n      `;\n    }\n    return `<div class=\"fd\">${navHtml}<div class=\"fdbody\">\n      <div class=\"col left\">\n        <div class=\"tag\">Cockpit · Disparo manual</div>\n        <h3 class=\"kick\">Novo pedido</h3>\n        <label>Nome do cliente</label><input class=\"f-nome\" value=\"Ana Ribeiro\"/>\n        <label>E-mail do cliente</label><input class=\"f-email\" value=\"ana.ribeiro@acmemoveis.com\"/>\n        <label>Assunto</label><input class=\"f-assunto\" value=\"Orçamento de cadeiras\"/>\n        <label>Corpo do e-mail</label><textarea class=\"f-corpo\">Bom dia, preciso de um orçamento para 40 unidades da Cadeira Ergonômica Pro, com 10% de desconto, entrega em 15 dias. Abraços, Ana.</textarea>\n        <button class=\"fire\" ${d2.state.sending?'disabled':''}>${d2.state.sending?'Processando…':'Disparar pedido'}</button>\n        <div class=\"rail\"><div class=\"tag\" style=\"margin-bottom:8px\">Pedidos recentes</div>${rail}</div>\n      </div>\n      <div class=\"col right\">\n        <div class=\"tag\">Funcionário Digital</div>\n        <h1 class=\"title\">Esteira ao Vivo</h1>\n        <p class=\"sub\">${sel?('Protocolo '+esc(sel.protocolo)+' · '+esc(sel.remetente_nome||sel.remetente_email||'')):'A linha de produção do agente, em tempo real.'}</p>\n        ${detail}\n      </div>\n      </div>\n    </div>`;\n  },\n  mounted(d2){\n    if(document.head&&!document.getElementById('fd-fonts')){var l=document.createElement('link');l.id='fd-fonts';l.rel='stylesheet';l.href='https://fonts.googleapis.com/css2?family=Oswald:wght@500;600;700&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap';document.head.appendChild(l);}\n    var pollTimer=null;\n    function doRefresh(){var p=d2.data('pedidos');if(p&&p.refresh)p.refresh();var e=d2.data('eventos');if(e&&e.refresh)e.refresh();}\n    function stopPoll(){if(pollTimer){clearInterval(pollTimer);pollTimer=null;}}\n    function startPoll(){stopPoll();doRefresh();pollTimer=setInterval(doRefresh,8000);}\n    d2.onUnmount(stopPoll);\n    d2.on('.fdauto','change',function(e,el){d2.state.auto=el.checked;if(el.checked){startPoll();}else{stopPoll();}});\n    d2.on('.fdnav a','click',function(e,el){e.preventDefault();var to=el.dataset.to;if(to){window.location.assign(to);}});\n    d2.on('.ped-item','click',function(e,el){d2.state.selProto=el.dataset.proto;});\n    d2.on('.fire','click',async function(){\n      var g=function(s){var n=d2.$(s);return n?n.value.trim():'';};\n      var email=g('.f-email'),nome=g('.f-nome'),assunto=g('.f-assunto'),corpo=g('.f-corpo');\n      if(!email||!corpo){d2.dialog.alert('Preencha e-mail e corpo do pedido.');return;}\n      d2.state.sending=true;\n      try{\n        var r=await d2.rush('captura').invoke({remetente_email:email,remetente_nome:nome,assunto:assunto,corpo:corpo});\n        if(r&&r.protocolo)d2.state.selProto=r.protocolo;\n      }catch(err){d2.dialog.alert('Erro ao disparar: '+((err&&err.message)||err));}\n      d2.state.sending=false;\n      var p=d2.data('pedidos'),e=d2.data('eventos');if(p&&p.refresh)p.refresh();if(e&&e.refresh)e.refresh();\n    });\n  }\n};"}},"pedidos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pedidos"}}}},"eventos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"eventos"}}}},"captura":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"captura"}}}}}}}}}},{"_id":"6a4cecdd5192fc0b861017f8","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"fd-ui-painel","package":"fernando-playground","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  *{box-sizing:border-box}\n  .fdnav{flex:0 0 auto;height:46px;background:#0E1216;border-bottom:1px solid #333B44;display:flex;align-items:center;gap:2px;padding:0 16px}\n  .fdnav .brand{font-family:'Oswald',sans-serif;font-weight:700;text-transform:uppercase;font-size:13px;color:#E8A33D;margin-right:18px;letter-spacing:.05em}\n  .fdnav a{font-size:12px;color:#A7ABA2;text-decoration:none;padding:6px 12px;cursor:pointer;border-radius:2px;font-family:'IBM Plex Sans',sans-serif}\n  .fdnav a:hover{color:#E9E6DE}\n  .fdnav a.on{color:#14181D;background:#E8A33D;font-weight:600}\n  .fdnav .fdsw{margin-left:auto;display:flex;align-items:center;gap:8px;font-size:10px;color:#A7ABA2;text-transform:uppercase;letter-spacing:.05em;cursor:pointer;user-select:none;font-family:'IBM Plex Sans',sans-serif}\n  .fdnav .fdsw input{position:absolute;opacity:0;pointer-events:none}\n  .fdnav .fdsw .tr{width:34px;height:18px;border-radius:18px;background:#333B44;position:relative;transition:background .2s;flex:0 0 auto}\n  .fdnav .fdsw .tr::after{content:'';position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#A7ABA2;transition:transform .2s,background .2s}\n  .fdnav .fdsw input:checked + .tr{background:#E8A33D}\n  .fdnav .fdsw input:checked + .tr::after{transform:translateX(16px);background:#14181D}\n  .fd{--ink:#14181D;--panel:#1B2129;--panel2:#20262E;--paper:#E9E6DE;--dim:#A7ABA2;--amber:#E8A33D;--teal:#4FA8A0;--line:#333B44;--danger:#C0604B;\n     height:100%;background:var(--ink);color:var(--paper);font-family:'IBM Plex Sans',system-ui,sans-serif;display:flex;flex-direction:column} .fdbody{flex:1;overflow:auto;padding:28px 32px}\n  .tag{font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}\n  .title{font-family:'Oswald',sans-serif;font-weight:700;font-size:26px;text-transform:uppercase;margin:0 0 2px}\n  .sub{color:var(--dim);font-size:13px;margin:0 0 22px}\n  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(420px,1fr));gap:16px}\n  .pcard{background:var(--panel);border:1px solid var(--line);border-top:3px solid var(--amber);padding:18px}\n  .ph{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:10px}\n  .proto{font-family:'IBM Plex Mono',monospace;color:var(--amber);font-size:13px}\n  .val{font-family:'Oswald',sans-serif;font-size:22px;font-weight:700}\n  .who{font-size:14px;margin-bottom:2px}\n  .item{color:var(--dim);font-size:13px;margin-bottom:12px}\n  .motivos{background:var(--panel2);border-left:3px solid var(--amber);padding:10px 12px;font-size:12.5px;line-height:1.5;margin-bottom:12px}\n  .motivos b{color:var(--amber);text-transform:uppercase;font-size:10px;font-family:'IBM Plex Mono',monospace;letter-spacing:.06em;display:block;margin-bottom:4px}\n  .ctrl{display:flex;gap:8px;align-items:flex-end}\n  .ovwrap{flex:0 0 130px}\n  .ovwrap label{display:block;font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px}\n  .ov{width:100%;background:var(--panel2);border:1px solid var(--line);color:var(--paper);padding:8px;font-family:inherit;font-size:13px;border-radius:2px}\n  .btn{flex:1;padding:10px;border:0;font-weight:700;font-family:'Oswald',sans-serif;text-transform:uppercase;letter-spacing:.03em;cursor:pointer;border-radius:2px;font-size:13px}\n  .ap{background:var(--teal);color:#0E1513}\n  .rj{background:transparent;color:var(--danger);border:1px solid var(--danger)}\n  .btn[disabled]{opacity:.5;cursor:default}\n  .empty{color:var(--dim);font-size:15px;padding:60px 0;text-align:center;border:1px dashed var(--line)}\n  .resolved{margin-top:34px}\n  .rrow{display:flex;justify-content:space-between;gap:12px;padding:9px 12px;border:1px solid var(--line);background:var(--panel2);margin-bottom:6px;font-size:12.5px}\n  .badge{display:inline-block;padding:2px 7px;font-size:10px;font-family:'IBM Plex Mono',monospace;text-transform:uppercase;border-radius:2px}\n  .b-auto{background:rgba(79,168,160,.15);color:var(--teal);border:1px solid var(--teal)}\n  .b-rej{background:rgba(192,96,75,.15);color:var(--danger);border:1px solid var(--danger)}\n  `,\n  render(d2){\n    const esc=(s)=>(s==null?'':String(s)).replace(/[&<>\"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\n    const _rp=(function(){try{return window.location.pathname||''}catch(e){return ''}})();\n    const _NAV=[['/esteira','Esteira ao Vivo'],['/aprovacao','Aprovação'],['/crm','CRM'],['/metricas','Métricas']];\n    const navHtml=`<div class=\"fdnav\"><span class=\"brand\">Funcionário Digital</span>${_NAV.map(function(it){var on=(it[0]==='/esteira')?(_rp==='/'||_rp===''||_rp.endsWith('/esteira')):(_rp===it[0]||_rp.endsWith(it[0]));return `<a data-to=\"${it[0]}\" class=\"${on?'on':''}\">${it[1]}</a>`;}).join('')}<label class=\"fdsw\"><input type=\"checkbox\" class=\"fdauto\" ${d2.state.auto?'checked':''}/><span class=\"tr\"></span><span>Auto-atualizar</span></label></div>`;\n    const money=(n)=>{const v=Number(n)||0; try{return d2.fmt.currency(v,'BRL')}catch(e){return 'R$ '+v}};\n    const pedH=d2.data('pedidos');\n    const pedidos=(pedH&&pedH.records)?[...pedH.records()]:[];\n    const pend=pedidos.filter(p=>p.decisao==='aguardando_aprovacao');\n    const resolved=pedidos.filter(p=>p.decisao==='aprovado'||p.decisao==='rejeitado')\n      .sort((a,b)=>String(b.createdAt||'').localeCompare(String(a.createdAt||''))).slice(0,6);\n    const busy=d2.state.busy;\n    const cards=pend.map(p=>{\n      const c=p.campos_extraidos||{};\n      const dis=busy===p.protocolo?'disabled':'';\n      return `<div class=\"pcard\" data-key=\"${esc(p.protocolo)}\">\n        <div class=\"ph\"><span class=\"proto\">${esc(p.protocolo)}</span><span class=\"val\">${money(p.valor_total)}</span></div>\n        <div class=\"who\">${esc(p.remetente_nome||'')} · <span style=\"color:var(--dim)\">${esc(p.remetente_email||'')}</span></div>\n        <div class=\"item\">${esc(c.produto||'—')} × ${esc(c.quantidade!=null?c.quantidade:'—')} · desconto pedido ${esc(p.desconto_solicitado_pct!=null?p.desconto_solicitado_pct+'%':'—')}</div>\n        <div class=\"motivos\"><b>Por que escalou</b>${esc(p.justificativa||'—')}</div>\n        <div class=\"ctrl\">\n          <div class=\"ovwrap\"><label>Desconto aprovado %</label><input class=\"ov\" data-proto=\"${esc(p.protocolo)}\" type=\"number\" value=\"${esc(p.desconto_solicitado_pct!=null?p.desconto_solicitado_pct:0)}\"/></div>\n          <button class=\"btn ap\" data-proto=\"${esc(p.protocolo)}\" ${dis}>${busy===p.protocolo?'…':'Aprovar'}</button>\n          <button class=\"btn rj\" data-proto=\"${esc(p.protocolo)}\" ${dis}>Rejeitar</button>\n        </div>\n      </div>`;\n    }).join('');\n    const resList=resolved.map(p=>`<div class=\"rrow\"><span style=\"font-family:'IBM Plex Mono',monospace;color:var(--dim)\">${esc(p.protocolo)}</span><span>${esc(p.remetente_nome||'')}</span><span>${money(p.valor_total)}</span><span class=\"badge ${p.decisao==='rejeitado'?'b-rej':'b-auto'}\">${esc(p.decisao)}</span></div>`).join('');\n    return `<div class=\"fd\">${navHtml}<div class=\"fdbody\">\n      <div class=\"tag\">Funcionário Digital · Checkpoint humano</div>\n      <h1 class=\"title\">Painel de Aprovação</h1>\n      <p class=\"sub\">Pedidos que saíram da régua e aguardam decisão comercial. O agente já preparou o resumo.</p>\n      ${pend.length?`<div class=\"grid\">${cards}</div>`:`<div class=\"empty\">Nenhum pedido aguardando aprovação. ✓</div>`}\n      ${resolved.length?`<div class=\"resolved\"><div class=\"tag\" style=\"margin-bottom:10px\">Decididos recentemente</div>${resList}</div>`:''}\n    </div>\n    </div>`;\n  },\n  mounted(d2){\n    if(document.head&&!document.getElementById('fd-fonts')){var l=document.createElement('link');l.id='fd-fonts';l.rel='stylesheet';l.href='https://fonts.googleapis.com/css2?family=Oswald:wght@500;600;700&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap';document.head.appendChild(l);}\n    var pollTimer=null;\n    function doRefresh(){var p=d2.data('pedidos');if(p&&p.refresh)p.refresh();var e=d2.data('eventos');if(e&&e.refresh)e.refresh();}\n    function stopPoll(){if(pollTimer){clearInterval(pollTimer);pollTimer=null;}}\n    function startPoll(){stopPoll();doRefresh();pollTimer=setInterval(doRefresh,8000);}\n    d2.onUnmount(stopPoll);\n    d2.on('.fdauto','change',function(e,el){d2.state.auto=el.checked;if(el.checked){startPoll();}else{stopPoll();}});\n    d2.on('.fdnav a','click',function(e,el){e.preventDefault();var to=el.dataset.to;if(to){window.location.assign(to);}});\n    var refresh=function(){var p=d2.data('pedidos');if(p&&p.refresh)p.refresh();};\n    d2.on('.ap','click',async function(e,el){\n      var proto=el.dataset.proto;\n      var ov=d2.$('.ov[data-proto=\"'+proto+'\"]');\n      var desc=ov?Number(ov.value):undefined;\n      d2.state.busy=proto;\n      try{await d2.rush('aprova').invoke({protocolo:proto,acao:'aprovar',desconto_override:desc});}\n      catch(err){d2.dialog.alert('Erro ao aprovar: '+((err&&err.message)||err));}\n      d2.state.busy=null;refresh();\n    });\n    d2.on('.rj','click',async function(e,el){\n      var proto=el.dataset.proto;\n      var motivo=await d2.dialog.prompt('Motivo da rejeição?','Fora da política comercial');\n      if(motivo==null)return;\n      d2.state.busy=proto;\n      try{await d2.rush('aprova').invoke({protocolo:proto,acao:'rejeitar',motivo:motivo});}\n      catch(err){d2.dialog.alert('Erro ao rejeitar: '+((err&&err.message)||err));}\n      d2.state.busy=null;refresh();\n    });\n  }\n};"}},"pedidos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pedidos"}}}},"aprova":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"aprova"}}}}}}}}}},{"_id":"6a4ced2091afdd4d5cd38d07","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"fd-ui-crm","package":"fernando-playground","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  *{box-sizing:border-box}\n  .fdnav{flex:0 0 auto;height:46px;background:#0E1216;border-bottom:1px solid #333B44;display:flex;align-items:center;gap:2px;padding:0 16px}\n  .fdnav .brand{font-family:'Oswald',sans-serif;font-weight:700;text-transform:uppercase;font-size:13px;color:#E8A33D;margin-right:18px;letter-spacing:.05em}\n  .fdnav a{font-size:12px;color:#A7ABA2;text-decoration:none;padding:6px 12px;cursor:pointer;border-radius:2px;font-family:'IBM Plex Sans',sans-serif}\n  .fdnav a:hover{color:#E9E6DE}\n  .fdnav a.on{color:#14181D;background:#E8A33D;font-weight:600}\n  .fdnav .fdsw{margin-left:auto;display:flex;align-items:center;gap:8px;font-size:10px;color:#A7ABA2;text-transform:uppercase;letter-spacing:.05em;cursor:pointer;user-select:none;font-family:'IBM Plex Sans',sans-serif}\n  .fdnav .fdsw input{position:absolute;opacity:0;pointer-events:none}\n  .fdnav .fdsw .tr{width:34px;height:18px;border-radius:18px;background:#333B44;position:relative;transition:background .2s;flex:0 0 auto}\n  .fdnav .fdsw .tr::after{content:'';position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#A7ABA2;transition:transform .2s,background .2s}\n  .fdnav .fdsw input:checked + .tr{background:#E8A33D}\n  .fdnav .fdsw input:checked + .tr::after{transform:translateX(16px);background:#14181D}\n  .fd{--ink:#14181D;--panel:#1B2129;--panel2:#20262E;--paper:#E9E6DE;--dim:#A7ABA2;--amber:#E8A33D;--teal:#4FA8A0;--line:#333B44;--danger:#C0604B;\n     display:flex;flex-direction:column;height:100%;background:var(--ink);color:var(--paper);font-family:'IBM Plex Sans',system-ui,sans-serif;overflow:hidden} .fdbody{flex:1;display:grid;grid-template-columns:320px 1fr;min-height:0}\n  .col{overflow:auto;height:100%}\n  .left{border-right:1px solid var(--line);background:var(--panel)}\n  .lh{padding:18px 18px 10px}\n  .tag{font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}\n  .title{font-family:'Oswald',sans-serif;font-weight:700;font-size:22px;text-transform:uppercase;margin:2px 0 0}\n  .row{padding:12px 18px;border-bottom:1px solid var(--line);cursor:pointer}\n  .row.sel{background:var(--panel2);border-left:3px solid var(--amber)}\n  .row .r1{display:flex;justify-content:space-between;gap:8px;margin-bottom:3px}\n  .row .pp{font-family:'IBM Plex Mono',monospace;color:var(--amber);font-size:12px}\n  .row .rv{font-size:12px;color:var(--dim)}\n  .row .rn{font-size:13px}\n  .badge{display:inline-block;padding:2px 7px;font-size:10px;font-family:'IBM Plex Mono',monospace;text-transform:uppercase;border-radius:2px;margin-top:5px}\n  .b-auto{background:rgba(79,168,160,.15);color:var(--teal);border:1px solid var(--teal)}\n  .b-wait{background:rgba(232,163,61,.15);color:var(--amber);border:1px solid var(--amber)}\n  .b-rej{background:rgba(192,96,75,.15);color:var(--danger);border:1px solid var(--danger)}\n  .right{padding:26px 30px}\n  .dtitle{font-family:'Oswald',sans-serif;font-weight:700;font-size:24px;text-transform:uppercase;margin:0}\n  .dsub{color:var(--dim);font-size:13px;margin:2px 0 20px}\n  .cards{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:22px}\n  .card{background:var(--panel);border:1px solid var(--line);padding:14px}\n  .card h4{margin:0 0 10px;font-family:'Oswald',sans-serif;font-size:12px;text-transform:uppercase;color:var(--amber);letter-spacing:.03em}\n  .kv{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;padding:3px 0;border-bottom:1px solid var(--panel2)}\n  .kv span:first-child{color:var(--dim)}\n  .sec{font-family:'Oswald',sans-serif;font-size:13px;text-transform:uppercase;color:var(--paper);letter-spacing:.03em;margin:0 0 12px;border-bottom:1px solid var(--line);padding-bottom:8px}\n  .tl{position:relative;margin-left:8px;padding-left:22px;border-left:2px solid var(--line)}\n  .ev{position:relative;padding-bottom:18px}\n  .ev::before{content:'';position:absolute;left:-29px;top:2px;width:12px;height:12px;border-radius:50%;background:var(--teal);border:2px solid var(--ink)}\n  .ev .et{font-family:'Oswald',sans-serif;text-transform:uppercase;font-size:13px}\n  .ev .em{font-size:12.5px;color:var(--dim);margin-top:2px;line-height:1.45}\n  .ev .ex{font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--dim);margin-top:3px}\n  .orc{margin-top:22px}\n  .orc pre{white-space:pre-wrap;background:var(--panel2);border:1px solid var(--line);padding:14px;font-size:13px;line-height:1.55;font-family:'IBM Plex Sans',sans-serif;margin:0}\n  .empty{color:var(--dim);font-size:15px;padding:60px 0;text-align:center;border:1px dashed var(--line)}\n  `,\n  render(d2){\n    const esc=(s)=>(s==null?'':String(s)).replace(/[&<>\"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\n    const _rp=(function(){try{return window.location.pathname||''}catch(e){return ''}})();\n    const _NAV=[['/esteira','Esteira ao Vivo'],['/aprovacao','Aprovação'],['/crm','CRM'],['/metricas','Métricas']];\n    const navHtml=`<div class=\"fdnav\"><span class=\"brand\">Funcionário Digital</span>${_NAV.map(function(it){var on=(it[0]==='/esteira')?(_rp==='/'||_rp===''||_rp.endsWith('/esteira')):(_rp===it[0]||_rp.endsWith(it[0]));return `<a data-to=\"${it[0]}\" class=\"${on?'on':''}\">${it[1]}</a>`;}).join('')}<label class=\"fdsw\"><input type=\"checkbox\" class=\"fdauto\" ${d2.state.auto?'checked':''}/><span class=\"tr\"></span><span>Auto-atualizar</span></label></div>`;\n    const money=(n)=>{const v=Number(n)||0; try{return d2.fmt.currency(v,'BRL')}catch(e){return 'R$ '+v}};\n    const tm=(t)=>{if(!t)return'';try{return d2.fmt.time(t)}catch(e){return String(t)}};\n    const pedH=d2.data('pedidos'), eveH=d2.data('eventos');\n    const pedidos=(pedH&&pedH.records)?[...pedH.records()]:[];\n    const eventos=(eveH&&eveH.records)?eveH.records():[];\n    pedidos.sort((a,b)=>String(b.createdAt||b.protocolo||'').localeCompare(String(a.createdAt||a.protocolo||'')));\n    const sel=pedidos.find(p=>p.protocolo===d2.state.selProto)||pedidos[0]||null;\n    const bcls=(d)=>d==='aguardando_aprovacao'?'b-wait':d==='rejeitado'?'b-rej':'b-auto';\n    const list=pedidos.map(p=>`<div class=\"row ${sel&&p.protocolo===sel.protocolo?'sel':''}\" data-proto=\"${esc(p.protocolo)}\">\n        <div class=\"r1\"><span class=\"pp\">${esc(p.protocolo)}</span><span class=\"rv\">${money(p.valor_total)}</span></div>\n        <div class=\"rn\">${esc(p.remetente_nome||p.remetente_email||'')}</div>\n        <span class=\"badge ${bcls(p.decisao)}\">${esc(p.decisao||'—')}</span>\n      </div>`).join('')||`<div class=\"empty\" style=\"padding:20px;margin:16px\">Sem pedidos.</div>`;\n    let detail;\n    if(!sel){ detail=`<div class=\"empty\">Selecione um pedido à esquerda.</div>`; }\n    else{\n      const c=sel.campos_extraidos||{}, q=sel.consulta_resultado||{};\n      const evs=eventos.filter(e=>e.pedido_id===sel.protocolo).sort((a,b)=>String(a.ts||'').localeCompare(String(b.ts||'')));\n      const tl=evs.map(e=>`<div class=\"ev\"><div class=\"et\">${esc(e.titulo||e.estacao||'')}</div><div class=\"em\">${esc(e.mensagem||'')}</div><div class=\"ex\">${esc(e.estacao||'')} · ${esc(tm(e.ts))}</div></div>`).join('')||`<div class=\"em\" style=\"color:var(--dim)\">Sem eventos.</div>`;\n      detail=`\n        <div class=\"tag\">Histórico do cliente</div>\n        <h2 class=\"dtitle\">${esc(sel.protocolo)}</h2>\n        <p class=\"dsub\">${esc(sel.remetente_nome||'')} · ${esc(sel.remetente_email||'')}</p>\n        <div class=\"cards\">\n          <div class=\"card\"><h4>Pedido</h4>\n            <div class=\"kv\"><span>Produto</span><b>${esc(c.produto||'—')}</b></div>\n            <div class=\"kv\"><span>Quantidade</span><b>${esc(c.quantidade!=null?c.quantidade:'—')}</b></div>\n            <div class=\"kv\"><span>Prazo pedido</span><b>${esc(c.prazo_dias!=null?c.prazo_dias+'d':'—')}</b></div>\n            <div class=\"kv\"><span>Assunto</span><b>${esc(sel.assunto||'—')}</b></div>\n          </div>\n          <div class=\"card\"><h4>Comercial</h4>\n            <div class=\"kv\"><span>Valor total</span><b>${money(sel.valor_total)}</b></div>\n            <div class=\"kv\"><span>Desconto</span><b>${esc(sel.desconto_solicitado_pct!=null?sel.desconto_solicitado_pct+'%':'—')}</b></div>\n            <div class=\"kv\"><span>SKU</span><b>${esc(q.sku||'—')}</b></div>\n            <div class=\"kv\"><span>Decisão</span><b>${esc(sel.decisao||'—')}</b></div>\n          </div>\n        </div>\n        <h3 class=\"sec\">Trilha de auditoria</h3>\n        <div class=\"tl\">${tl}</div>\n        ${sel.orcamento_texto?`<div class=\"orc\"><h3 class=\"sec\">Orçamento enviado</h3><pre>${esc(sel.orcamento_texto)}</pre></div>`:''}\n      `;\n    }\n    return `<div class=\"fd\">${navHtml}<div class=\"fdbody\">\n      <div class=\"col left\"><div class=\"lh\"><div class=\"tag\">Funcionário Digital</div><h1 class=\"title\">CRM / Histórico</h1></div>${list}</div>\n      <div class=\"col right\">${detail}</div>\n      </div>\n    </div>`;\n  },\n  mounted(d2){\n    if(document.head&&!document.getElementById('fd-fonts')){var l=document.createElement('link');l.id='fd-fonts';l.rel='stylesheet';l.href='https://fonts.googleapis.com/css2?family=Oswald:wght@500;600;700&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap';document.head.appendChild(l);}\n    var pollTimer=null;\n    function doRefresh(){var p=d2.data('pedidos');if(p&&p.refresh)p.refresh();var e=d2.data('eventos');if(e&&e.refresh)e.refresh();}\n    function stopPoll(){if(pollTimer){clearInterval(pollTimer);pollTimer=null;}}\n    function startPoll(){stopPoll();doRefresh();pollTimer=setInterval(doRefresh,8000);}\n    d2.onUnmount(stopPoll);\n    d2.on('.fdauto','change',function(e,el){d2.state.auto=el.checked;if(el.checked){startPoll();}else{stopPoll();}});\n    d2.on('.fdnav a','click',function(e,el){e.preventDefault();var to=el.dataset.to;if(to){window.location.assign(to);}});\n    d2.on('.row','click',function(e,el){d2.state.selProto=el.dataset.proto;});\n  }\n};"}},"pedidos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pedidos"}}}},"eventos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"eventos"}}}}}}}}}},{"_id":"6a4ced6dd300a927122ceaaa","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"fd-ui-metricas","package":"fernando-playground","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  *{box-sizing:border-box}\n  .fdnav{flex:0 0 auto;height:46px;background:#0E1216;border-bottom:1px solid #333B44;display:flex;align-items:center;gap:2px;padding:0 16px}\n  .fdnav .brand{font-family:'Oswald',sans-serif;font-weight:700;text-transform:uppercase;font-size:13px;color:#E8A33D;margin-right:18px;letter-spacing:.05em}\n  .fdnav a{font-size:12px;color:#A7ABA2;text-decoration:none;padding:6px 12px;cursor:pointer;border-radius:2px;font-family:'IBM Plex Sans',sans-serif}\n  .fdnav a:hover{color:#E9E6DE}\n  .fdnav a.on{color:#14181D;background:#E8A33D;font-weight:600}\n  .fdnav .fdsw{margin-left:auto;display:flex;align-items:center;gap:8px;font-size:10px;color:#A7ABA2;text-transform:uppercase;letter-spacing:.05em;cursor:pointer;user-select:none;font-family:'IBM Plex Sans',sans-serif}\n  .fdnav .fdsw input{position:absolute;opacity:0;pointer-events:none}\n  .fdnav .fdsw .tr{width:34px;height:18px;border-radius:18px;background:#333B44;position:relative;transition:background .2s;flex:0 0 auto}\n  .fdnav .fdsw .tr::after{content:'';position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#A7ABA2;transition:transform .2s,background .2s}\n  .fdnav .fdsw input:checked + .tr{background:#E8A33D}\n  .fdnav .fdsw input:checked + .tr::after{transform:translateX(16px);background:#14181D}\n  .fd{--ink:#14181D;--panel:#1B2129;--panel2:#20262E;--paper:#E9E6DE;--dim:#A7ABA2;--amber:#E8A33D;--teal:#4FA8A0;--line:#333B44;--danger:#C0604B;\n     height:100%;background:var(--ink);color:var(--paper);font-family:'IBM Plex Sans',system-ui,sans-serif;display:flex;flex-direction:column} .fdbody{flex:1;overflow:auto;padding:28px 32px}\n  .tag{font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}\n  .title{font-family:'Oswald',sans-serif;font-weight:700;font-size:26px;text-transform:uppercase;margin:0 0 2px}\n  .sub{color:var(--dim);font-size:13px;margin:0 0 24px}\n  .kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:26px}\n  .kpi{background:var(--panel);border:1px solid var(--line);border-top:3px solid var(--amber);padding:18px}\n  .kpi .l{font-family:'IBM Plex Mono',monospace;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--dim);margin-bottom:10px}\n  .kpi .v{font-family:'Oswald',sans-serif;font-weight:700;font-size:40px;line-height:1;color:var(--paper)}\n  .kpi .m{font-size:11px;color:var(--dim);margin-top:8px}\n  .kpi .m b{color:var(--teal)}\n  .kpi.miss .m b{color:var(--danger)}\n  .panel{background:var(--panel);border:1px solid var(--line);padding:20px}\n  .panel h4{margin:0 0 16px;font-family:'Oswald',sans-serif;font-size:13px;text-transform:uppercase;color:var(--amber);letter-spacing:.03em}\n  .bar{display:flex;height:26px;border:1px solid var(--line);overflow:hidden;margin-bottom:14px;background:var(--panel2)}\n  .seg{height:100%}\n  .s-auto{background:var(--teal)}\n  .s-aprov{background:#3f7d78}\n  .s-rej{background:var(--danger)}\n  .s-wait{background:var(--amber)}\n  .leg{display:flex;gap:20px;flex-wrap:wrap;font-size:12.5px}\n  .leg span{display:flex;align-items:center;gap:6px;color:var(--dim)}\n  .sw{width:11px;height:11px;border-radius:2px;display:inline-block}\n  .leg b{color:var(--paper)}\n  `,\n  render(d2){\n    const esc=(s)=>(s==null?'':String(s)).replace(/[&<>\"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\n    const _rp=(function(){try{return window.location.pathname||''}catch(e){return ''}})();\n    const _NAV=[['/esteira','Esteira ao Vivo'],['/aprovacao','Aprovação'],['/crm','CRM'],['/metricas','Métricas']];\n    const navHtml=`<div class=\"fdnav\"><span class=\"brand\">Funcionário Digital</span>${_NAV.map(function(it){var on=(it[0]==='/esteira')?(_rp==='/'||_rp===''||_rp.endsWith('/esteira')):(_rp===it[0]||_rp.endsWith(it[0]));return `<a data-to=\"${it[0]}\" class=\"${on?'on':''}\">${it[1]}</a>`;}).join('')}<label class=\"fdsw\"><input type=\"checkbox\" class=\"fdauto\" ${d2.state.auto?'checked':''}/><span class=\"tr\"></span><span>Auto-atualizar</span></label></div>`;\n    const pedH=d2.data('pedidos'), eveH=d2.data('eventos');\n    const pedidos=(pedH&&pedH.records)?[...pedH.records()]:[];\n    const eventos=(eveH&&eveH.records)?eveH.records():[];\n    const total=pedidos.length;\n    const auto=pedidos.filter(p=>p.decisao==='auto_aprovado').length;\n    const aprov=pedidos.filter(p=>p.decisao==='aprovado').length;\n    const rej=pedidos.filter(p=>p.decisao==='rejeitado').length;\n    const wait=pedidos.filter(p=>p.decisao==='aguardando_aprovacao').length;\n    const concl=pedidos.filter(p=>p.status_ciclo==='concluido');\n    const secs=(a,b)=>{const x=Date.parse(a),y=Date.parse(b);return (x&&y&&x>=y)?(x-y)/1000:null;};\n    const avg=(arr)=>arr.length?arr.reduce((s,x)=>s+x,0)/arr.length:null;\n    const cyc=avg(concl.map(p=>secs(p.ts_concluido,p.ts_recebido)).filter(x=>x!=null));\n    const escP=pedidos.filter(p=>p.decisao!=='auto_aprovado'&&p.ts_decidido);\n    const escT=avg(escP.map(p=>secs(p.ts_decidido,p.ts_recebido)).filter(x=>x!=null));\n    const pctAuto=total?Math.round(auto/total*100):0;\n    const reg06=new Set(eventos.filter(e=>e.estacao==='06_registro').map(e=>e.pedido_id));\n    const taxaReg=concl.length?Math.round(concl.filter(p=>reg06.has(p.protocolo)).length/concl.length*100):0;\n    const dur=(s)=>s==null?'—':s<1?'<1s':s<120?Math.round(s)+'s':(Math.round(s/60)+'min');\n    const pct=(n)=>n+'%';\n    const okCyc=cyc!=null&&cyc<120, okAuto=pctAuto>=70&&pctAuto<=85, okEsc=escT!=null&&escT<30, okReg=taxaReg>=100;\n    const kpi=(l,v,metaTxt,ok)=>`<div class=\"kpi ${ok?'':'miss'}\"><div class=\"l\">${l}</div><div class=\"v\">${v}</div><div class=\"m\">meta: <b>${metaTxt}</b></div></div>`;\n    const seg=(n,cls)=>total&&n>0?`<div class=\"seg ${cls}\" style=\"width:${(n/total*100).toFixed(1)}%\"></div>`:'';\n    return `<div class=\"fd\">${navHtml}<div class=\"fdbody\">\n      <div class=\"tag\">Funcionário Digital · Indicadores</div>\n      <h1 class=\"title\">Métricas</h1>\n      <p class=\"sub\">Desempenho do agente sobre ${total} pedido(s) processado(s).</p>\n      <div class=\"kpis\">\n        ${kpi('Tempo de ciclo médio',dur(cyc),'&lt; 2 min',okCyc)}\n        ${kpi('% resolvido sem intervenção',pct(pctAuto),'70–85%',okAuto)}\n        ${kpi('Tempo até escalar',dur(escT),'&lt; 30 seg',okEsc)}\n        ${kpi('Taxa de registro correto',pct(taxaReg),'100%',okReg)}\n      </div>\n      <div class=\"panel\">\n        <h4>Composição das decisões</h4>\n        <div class=\"bar\">${seg(auto,'s-auto')}${seg(aprov,'s-aprov')}${seg(rej,'s-rej')}${seg(wait,'s-wait')}</div>\n        <div class=\"leg\">\n          <span><i class=\"sw s-auto\"></i>Automático <b>${auto}</b></span>\n          <span><i class=\"sw s-aprov\"></i>Aprovado (humano) <b>${aprov}</b></span>\n          <span><i class=\"sw s-rej\"></i>Rejeitado <b>${rej}</b></span>\n          <span><i class=\"sw s-wait\"></i>Aguardando <b>${wait}</b></span>\n        </div>\n      </div>\n      </div>\n    </div>`;\n  },\n  mounted(d2){\n    if(document.head&&!document.getElementById('fd-fonts')){var l=document.createElement('link');l.id='fd-fonts';l.rel='stylesheet';l.href='https://fonts.googleapis.com/css2?family=Oswald:wght@500;600;700&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap';document.head.appendChild(l);}\n    var pollTimer=null;\n    function doRefresh(){var p=d2.data('pedidos');if(p&&p.refresh)p.refresh();var e=d2.data('eventos');if(e&&e.refresh)e.refresh();}\n    function stopPoll(){if(pollTimer){clearInterval(pollTimer);pollTimer=null;}}\n    function startPoll(){stopPoll();doRefresh();pollTimer=setInterval(doRefresh,8000);}\n    d2.onUnmount(stopPoll);\n    d2.on('.fdauto','change',function(e,el){d2.state.auto=el.checked;if(el.checked){startPoll();}else{stopPoll();}});\n    d2.on('.fdnav a','click',function(e,el){e.preventDefault();var to=el.dataset.to;if(to){window.location.assign(to);}});\n  }\n};"}},"pedidos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pedidos"}}}},"eventos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"eventos"}}}}}}}}}},{"_id":"6a4ec208978ef0dc37a842ed","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"incidentsDashboard","package":"fernando-playground","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 FIELDS = ['number','caller_id','opened_at','short_description','priority','state','business_service','u_jira','assignment_group','assigned_to','sys_tags','sys_updated_on','sys_updated_by'];\nconst CLOSED = ['Encerrado','Cancelado','Resolvido'];\nconst PRI_COLOR = {'1':'#e5484d','2':'#f76b15','3':'#f5a623','4':'#46a758','5':'#8b8d98'};\nconst ST_COLOR = {'Encerrado':'#46a758','Resolvido':'#12a594','Em andamento':'#3b82f6','Em espera':'#f5a623','Cancelado':'#8b8d98'};\nconst DEFAULT_EP = 'https://fernando-playground.data2apis.com/dataset/incidentsDataSet';\n\nconst esc = s => (s==null?'':String(s)).replace(/[&<>\"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst priColor = p => PRI_COLOR[String(p||'').charAt(0)] || '#6e56cf';\nconst stColor = s => ST_COLOR[s] || '#6e56cf';\nconst uniq = arr => [...new Set(arr.filter(x=>x!=null && x!==''))];\nconst ymKey = s => { const m=/^(\\d{2})\\/(\\d{2})\\/(\\d{4})/.exec(s||''); return m? m[3]+'-'+m[2] : null; };\nconst ymLabel = k => { const p=k.split('-'); return p[1]+'/'+p[0].slice(2); };\nconst dayOnly = s => (s||'').split(' ')[0];\nfunction bizHours(start,end){ if(!(start>0)||!(end>0)||end<=start) return 0; let total=0; let cur=new Date(start); while(cur.getTime()<end){ const dow=cur.getDay(); if(dow>=1&&dow<=5){ const ws=new Date(cur.getFullYear(),cur.getMonth(),cur.getDate(),9,0,0).getTime(); const we=new Date(cur.getFullYear(),cur.getMonth(),cur.getDate(),18,0,0).getTime(); const s=Math.max(start,ws); const en=Math.min(end,we); if(en>s) total+=(en-s); } cur=new Date(cur.getFullYear(),cur.getMonth(),cur.getDate()+1,0,0,0); } return total/3600000; }\nconst SLA_TARGET = {'1':2,'2':6,'3':8,'4':16,'5':24};\nconst priKey = r => String(r.priority||'').charAt(0);\nconst metaLabel = pk => pk==='1' ? '2h corridas (24x7)' : ((SLA_TARGET[pk]||'?')+'h úteis');\nfunction slaInfo(r){ const op=parseDt(r.opened_at); if(!(op>0)) return null; const closed=CLOSED.includes(r.state); const end=closed?parseDt(r.sys_updated_on):Date.now(); if(!(end>0)||end<op) return null; const pk=priKey(r); const h = pk==='1' ? (end-op)/3600000 : bizHours(op,end); const target=SLA_TARGET[pk]; return { h, ongoing:!closed, target, hasTarget: target!=null, breached: target!=null && h>target }; }\nfunction slaTimeCell(r){ const s=slaInfo(r); if(s==null) return '<span style=\"color:#b4b7bd\">—</span>'; const color = s.breached ? '#e5484d' : (s.ongoing ? '#3b82f6' : '#1c2024'); const clock = s.ongoing ? ' &#9201;' : ''; const tt = s.hasTarget ? ('Meta: '+metaLabel(priKey(r))) : 'Sem meta definida'; return '<span style=\"color:'+color+';font-weight:700\" title=\"'+tt+'\">'+s.h.toFixed(1).replace('.',',')+' h'+clock+'</span>'; }\nfunction slaStatusCell(r){ const s=slaInfo(r); if(s==null||!s.hasTarget) return '<span style=\"color:#b4b7bd\">—</span>'; const ok=!s.breached; const color = ok?'#46a758':'#e5484d'; const label = ok ? (s.ongoing?'No prazo':'Dentro') : 'Fora do SLA'; return '<span class=\"badge\" style=\"background:'+color+'\"></span>'+label; }\nconst parseDt = s => { const m=/^(\\d{2})\\/(\\d{2})\\/(\\d{4})\\s*(\\d{2})?:?(\\d{2})?:?(\\d{2})?/.exec(s||''); return m ? new Date(+m[3], +m[2]-1, +m[1], +(m[4]||0), +(m[5]||0), +(m[6]||0)).getTime() : 0; };\nconst chunk = (a,n) => { const o=[]; for(let i=0;i<a.length;i+=n) o.push(a.slice(i,i+n)); return o; };\nfunction countBy(rows, fn){ const m=new Map(); for(const r of rows){ const k=fn(r); if(k==null||k==='') continue; m.set(k,(m.get(k)||0)+1);} return m; }\nfunction topN(entries,n){ if(entries.length<=n) return entries; const top=entries.slice(0,n); const rest=entries.slice(n).reduce((a,e)=>a+e[1],0); if(rest>0) top.push(['Outros',rest]); return top; }\n\nfunction hbars(entries, colorFn){\n  if(!entries.length) return '<p class=\"empty\">Sem dados</p>';\n  const mx = Math.max(1, ...entries.map(e=>e[1]));\n  return entries.map(([k,v])=>`<div class=\"hbar\" data-key=\"${esc(k)}\"><span class=\"hbl\" title=\"${esc(k)}\">${esc(k)}</span><span class=\"htrack\"><span class=\"hfill\" style=\"width:${(v/mx*100).toFixed(1)}%;background:${colorFn(k)}\"></span></span><span class=\"hval\">${v}</span></div>`).join('');\n}\nfunction donut(entries){\n  if(!entries.length) return '<p class=\"empty\">Sem dados</p>';\n  const total = entries.reduce((a,e)=>a+e[1],0) || 1;\n  const R=52, C=2*Math.PI*R; let off=0;\n  const segs = entries.map(([k,v])=>{ const len=v/total*C; const el=`<circle r=\"${R}\" cx=\"70\" cy=\"70\" fill=\"none\" stroke=\"${stColor(k)}\" stroke-width=\"16\" stroke-dasharray=\"${len.toFixed(2)} ${(C-len).toFixed(2)}\" stroke-dashoffset=\"${(-off).toFixed(2)}\" transform=\"rotate(-90 70 70)\"></circle>`; off+=len; return el; }).join('');\n  const legend = entries.map(([k,v])=>`<div class=\"leg\" data-key=\"${esc(k)}\"><span class=\"dot\" style=\"background:${stColor(k)}\"></span><span class=\"lgl\">${esc(k)}</span><span class=\"lgv\">${v}</span></div>`).join('');\n  return `<div class=\"donutwrap\"><svg viewBox=\"0 0 140 140\" class=\"donut\">${segs}<text x=\"70\" y=\"66\" text-anchor=\"middle\" class=\"dc\">${total}</text><text x=\"70\" y=\"86\" text-anchor=\"middle\" class=\"dsm\">total</text></svg><div class=\"legend\">${legend}</div></div>`;\n}\nfunction vbars(entries){\n  if(!entries.length) return '<p class=\"empty\">Sem dados</p>';\n  const mx=Math.max(1,...entries.map(e=>e[1]));\n  return `<div class=\"vbars\">${entries.map(([k,v])=>`<div class=\"vcol\" data-key=\"${esc(k)}\" title=\"${esc(ymLabel(k))}: ${v}\"><div class=\"vwrap\"><span class=\"vv\">${v}</span><div class=\"vbar\" style=\"height:${(v/mx*100).toFixed(1)}%\"></div></div><span class=\"vlab\">${esc(ymLabel(k))}</span></div>`).join('')}</div>`;\n}\nfunction selHtml(k, cur, opts, allLabel){\n  return `<select class=\"f\" data-k=\"${k}\"><option value=\"\">${allLabel}</option>${opts.map(o=>`<option value=\"${esc(o)}\" ${o===cur?'selected':''}>${esc(o)}</option>`).join('')}</select>`;\n}\nfunction kpi(label, value, color, glyph){\n  return `<div class=\"kpi\" style=\"--kc:${color}\"><div class=\"kpi-top\"><span class=\"kpi-g\">${glyph}</span><span class=\"kpi-l\">${label}</span></div><div class=\"kpi-v\">${value}</div></div>`;\n}\n\nexport default {\n  css: `\n  :host{all:initial}\n  *{box-sizing:border-box}\n  .app{position:fixed;inset:0;overflow-y:auto;overflow-x:hidden;background:#f6f7f9;color:#1c2024;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;padding:20px}\n  .tail{width:100%;height:160px;flex:none}\n  .loading,.empty{color:#8b8d98;font-size:14px}\n  .loading{padding:80px 20px;text-align:center}\n  .empty.pad{padding:24px}\n  .errbox{max-width:520px;margin:60px auto;background:#fff;border:1px solid #e6e8eb;border-radius:14px;padding:24px;text-align:center}\n  .errbox h3{margin:0 0 8px;font-size:16px;color:#e5484d}\n  .errbox p{color:#60646c;font-size:13px;margin:0 0 16px;word-break:break-word}\n  .hd{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap;margin-bottom:16px}\n  .hd h1{margin:0;font-size:22px;font-weight:800;letter-spacing:-.01em}\n  .sub{margin:4px 0 0;font-size:13px;color:#8b8d98}\n  .actions{display:flex;align-items:center;gap:12px;flex-wrap:wrap}\n  .chk{font-size:12px;color:#60646c;display:flex;align-items:center;gap:5px;cursor:pointer;user-select:none}\n  .btn{display:inline-flex;align-items:center;gap:6px;padding:9px 16px;border-radius:9px;font-size:13px;font-weight:600;cursor:pointer;border:1px solid transparent}\n  .btn.primary{background:#6e56cf;color:#fff}\n  .btn.primary:hover{background:#5b46b3}\n  .filters{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:18px}\n  .search{flex:1;min-width:220px;padding:9px 12px;border:1px solid #d7dade;border-radius:9px;font-size:13px;background:#fff;color:#1c2024}\n  .f{padding:9px 12px;border:1px solid #d7dade;border-radius:9px;font-size:13px;background:#fff;color:#1c2024;max-width:210px}\n  .clear{padding:9px 14px;border:1px solid #d7dade;border-radius:9px;background:#fff;font-size:13px;cursor:pointer;color:#60646c}\n  .clear:hover{background:#eceef0}\n  .kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:16px}\n  .kpi{background:#fff;border:1px solid #e6e8eb;border-left:4px solid var(--kc);border-radius:12px;padding:14px 16px}\n  .kpi-top{display:flex;align-items:center;gap:7px}\n  .kpi-g{font-size:15px}\n  .kpi-l{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#8b8d98;font-weight:600}\n  .kpi-v{font-size:30px;font-weight:800;margin-top:6px;color:var(--kc)}\n  .grid{display:grid;grid-template-columns:repeat(2,1fr);gap:14px}\n  .card{background:#fff;border:1px solid #e6e8eb;border-radius:14px;padding:16px 18px}\n  .card.wide{grid-column:1/-1}\n  .card h3{margin:0 0 14px;font-size:14px;font-weight:700}\n  .hbar{display:grid;grid-template-columns:150px 1fr 42px;align-items:center;gap:10px;margin-bottom:9px}\n  .hbl{font-size:12px;color:#60646c;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .htrack{background:#eceef0;border-radius:6px;height:14px;overflow:hidden}\n  .hfill{display:block;height:100%;border-radius:6px;min-width:2px;transition:width .3s ease}\n  .hval{font-size:12px;font-weight:700;text-align:right;color:#1c2024}\n  .donutwrap{display:flex;gap:18px;align-items:center;flex-wrap:wrap}\n  .donut{width:150px;height:150px;flex:none}\n  .dc{font-size:26px;font-weight:800;fill:#1c2024}\n  .dsm{font-size:10px;fill:#8b8d98;text-transform:uppercase;letter-spacing:.05em}\n  .legend{display:flex;flex-direction:column;gap:7px;flex:1;min-width:160px}\n  .leg{display:grid;grid-template-columns:14px 1fr auto;align-items:center;gap:8px;font-size:12px;color:#60646c}\n  .dot{width:11px;height:11px;border-radius:3px}\n  .lgv{font-weight:700;color:#1c2024}\n  .vbars{display:flex;align-items:flex-end;gap:6px;height:200px;padding-top:8px}\n  .vcol{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:4px;height:100%}\n  .vv{font-size:11px;font-weight:700;color:#60646c}\n  .vwrap{width:100%;flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:4px}\n  .vbar{width:100%;background:linear-gradient(180deg,#6e56cf,#9a86e0);border-radius:5px 5px 0 0;min-height:2px;transition:height .3s ease}\n  .vlab{font-size:10px;color:#8b8d98;white-space:nowrap}\n  .tbl{width:100%;border-collapse:collapse;font-size:13px}\n  .tbl th{text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:#8b8d98;font-weight:600;padding:8px 10px;border-bottom:1px solid #e6e8eb}\n  .tbl td{padding:10px;border-bottom:1px solid #f0f1f3;color:#1c2024;white-space:nowrap}\n  .tbl tbody tr:last-child td{border-bottom:0}\n  .tbl tbody tr:hover td{background:#faf9ff}\n  .mono{font-variant-numeric:tabular-nums;font-weight:600}\n  .badge{display:inline-block;width:9px;height:9px;border-radius:3px;margin-right:7px;vertical-align:middle}\n  .slasum{display:flex;gap:28px;align-items:flex-start;flex-wrap:wrap}\n  .slabig{display:flex;flex-direction:column;align-items:center;justify-content:center;min-width:150px;padding:8px 16px 8px 0}\n  .slapct{font-size:46px;font-weight:800;line-height:1}\n  .slapctl{font-size:12px;color:#8b8d98;text-align:center;margin-top:8px}\n  .slapctl span{font-size:11px}\n  .slaright{flex:1;min-width:300px}\n  .slabar{display:flex;height:14px;border-radius:7px;overflow:hidden;background:#eceef0;margin-bottom:10px}\n  .slabar>div{height:100%}\n  .slalegend{display:flex;gap:18px;font-size:12px;color:#60646c;margin-bottom:14px;flex-wrap:wrap}\n  .slatbl{width:100%;border-collapse:collapse;font-size:12px}\n  .slatbl th{text-align:left;font-size:10px;text-transform:uppercase;letter-spacing:.04em;color:#8b8d98;font-weight:600;padding:6px 8px;border-bottom:1px solid #e6e8eb}\n  .slatbl td{padding:7px 8px;border-bottom:1px solid #f0f1f3}\n  .slatbl tbody tr:last-child td{border-bottom:0}\n  .overlay{position:absolute;inset:0;background:rgba(246,247,249,.85);display:flex;align-items:center;justify-content:center;z-index:10}\n  .ov-card{background:#fff;border:1px solid #e6e8eb;border-radius:14px;padding:24px 28px;box-shadow:0 10px 40px rgba(0,0,0,.12);min-width:300px;text-align:center}\n  .ov-title{font-size:15px;font-weight:700;margin-bottom:14px}\n  .ov-bar{height:10px;background:#eceef0;border-radius:6px;overflow:hidden}\n  .ov-fill{height:100%;background:#6e56cf;border-radius:6px;transition:width .2s ease}\n  .ov-sub{margin-top:10px;font-size:13px;color:#8b8d98}\n  @media(max-width:900px){.kpis{grid-template-columns:repeat(2,1fr)}.grid{grid-template-columns:1fr}}\n  `,\n  render(d2){\n    if(!d2.state.ready){\n      if(d2.state.error) return `<div class=\"app\"><div class=\"errbox\"><h3>Não foi possível carregar os incidentes</h3><p>${esc(d2.state.error)}</p><button class=\"retry btn primary\">Tentar novamente</button></div></div>`;\n      return `<div class=\"app\"><div class=\"loading\">Carregando incidentes…</div></div>`;\n    }\n    const all = d2.state.rows || [];\n    const f = d2.state.filters || {};\n    const rows = all.filter(r=>{\n      if(f.priority && r.priority!==f.priority) return false;\n      if(f.state && r.state!==f.state) return false;\n      if(f.service && r.business_service!==f.service) return false;\n      if(f.assignee && (r.assigned_to||'')!==f.assignee) return false;\n      if(f.q){ const q=f.q.toLowerCase(); if(!(((r.number||'')+' '+(r.short_description||'')+' '+(r.caller_id||'')).toLowerCase().includes(q))) return false; }\n      return true;\n    });\n\n    const priOpts = uniq(all.map(r=>r.priority)).sort();\n    const stOpts = uniq(all.map(r=>r.state)).sort();\n    const svcOpts = uniq(all.map(r=>r.business_service)).sort();\n    const asgOpts = uniq(all.map(r=>r.assigned_to)).sort();\n\n    const priEntries = [...countBy(rows,r=>r.priority).entries()].sort((a,b)=>String(a[0]).localeCompare(String(b[0])));\n    const stEntries = [...countBy(rows,r=>r.state).entries()].sort((a,b)=>b[1]-a[1]);\n    const svcTop = topN([...countBy(rows,r=>r.business_service).entries()].sort((a,b)=>b[1]-a[1]),8);\n    const asgTop = topN([...countBy(rows,r=>r.assigned_to).entries()].sort((a,b)=>b[1]-a[1]),8);\n    const monthEntries = [...countBy(rows,r=>ymKey(r.opened_at)).entries()].sort((a,b)=>a[0]<b[0]?-1:1);\n    const recent = [...rows].sort((a,b)=>parseDt(b.opened_at)-parseDt(a.opened_at)).slice(0,10);\n    let slaIn=0, slaOut=0, slaNo=0; const priSla={};\n    for(const r of rows){ const s=slaInfo(r); if(!s||!s.hasTarget){ slaNo++; continue; } const pk=priKey(r); if(!priSla[pk]) priSla[pk]={inC:0,outC:0}; if(s.breached){ slaOut++; priSla[pk].outC++; } else { slaIn++; priSla[pk].inC++; } }\n    const slaEval=slaIn+slaOut; const slaPct=slaEval?(slaIn/slaEval*100):0; const slaPctColor=slaPct>=90?'#46a758':(slaPct>=70?'#f5a623':'#e5484d');\n    const priRows=['1','2','3','4','5'].filter(pk=>priSla[pk]).map(pk=>{ const p=priSla[pk]; const tot=p.inC+p.outC; const pc=tot?(p.inC/tot*100):0; return `<tr><td><strong>P${pk}</strong></td><td>${metaLabel(pk)}</td><td style=\"color:#46a758;font-weight:700\">${p.inC}</td><td style=\"color:#e5484d;font-weight:700\">${p.outC}</td><td>${pc.toFixed(0)}%</td></tr>`; }).join('');\n\n    const kTotal=rows.length;\n    const kCrit=rows.filter(r=>String(r.priority||'').startsWith('1')).length;\n    const kOpen=rows.filter(r=>!CLOSED.includes(r.state)).length;\n    const kClosed=rows.filter(r=>r.state==='Encerrado').length;\n    const kCanc=rows.filter(r=>r.state==='Cancelado').length;\n\n    const imp=d2.state.importing;\n\n    return `<div class=\"app\">\n      <header class=\"hd\">\n        <div><h1>Sustentação · Dashboard de Incidentes</h1><p class=\"sub\">${kTotal} de ${all.length} incidentes ${kTotal!==all.length?'(filtrados)':''}</p></div>\n        <div class=\"actions\">\n          <label class=\"chk\"><input type=\"checkbox\" class=\"replace\"/> substituir base atual</label>\n          <label class=\"btn primary\">⬆ Subir base (CSV)<input type=\"file\" class=\"file\" accept=\".csv,text/csv\" hidden/></label>\n        </div>\n      </header>\n      <section class=\"filters\">\n        <input class=\"search\" placeholder=\"🔍 Buscar por nº, descrição ou solicitante\" value=\"${esc(f.q||'')}\"/>\n        ${selHtml('priority', f.priority, priOpts, 'Prioridade: todas')}\n        ${selHtml('state', f.state, stOpts, 'Status: todos')}\n        ${selHtml('service', f.service, svcOpts, 'Serviço: todos')}\n        ${selHtml('assignee', f.assignee, asgOpts, 'Responsável: todos')}\n        <button class=\"clear\">Limpar filtros</button>\n      </section>\n      <section class=\"kpis\">\n        ${kpi('Total', kTotal, '#6e56cf', '📊')}\n        ${kpi('Críticos (P1)', kCrit, '#e5484d', '🔥')}\n        ${kpi('Em aberto', kOpen, '#3b82f6', '🟢')}\n        ${kpi('Encerrados', kClosed, '#46a758', '✅')}\n        ${kpi('Cancelados', kCanc, '#8b8d98', '🚫')}\n      </section>\n      <section class=\"grid\"><div class=\"card wide\"><h3>Cumprimento de SLA · meta contratual por prioridade</h3><div class=\"slasum\"><div class=\"slabig\"><div class=\"slapct\" style=\"color:${slaPctColor}\">${slaPct.toFixed(0)}%</div><div class=\"slapctl\">dentro do SLA<br><span>${slaIn} de ${slaEval} avaliados</span></div></div><div class=\"slaright\"><div class=\"slabar\"><div style=\"width:${slaEval?(slaIn/slaEval*100).toFixed(1):0}%;background:#46a758\"></div><div style=\"width:${slaEval?(slaOut/slaEval*100).toFixed(1):0}%;background:#e5484d\"></div></div><div class=\"slalegend\"><span><span class=\"badge\" style=\"background:#46a758\"></span>Dentro: <b>${slaIn}</b></span><span><span class=\"badge\" style=\"background:#e5484d\"></span>Fora: <b>${slaOut}</b></span>${slaNo?`<span><span class=\"badge\" style=\"background:#b4b7bd\"></span>Sem meta: <b>${slaNo}</b></span>`:''}</div><table class=\"slatbl\"><thead><tr><th>Prioridade</th><th>Meta (restauração)</th><th>Dentro</th><th>Fora</th><th>% no prazo</th></tr></thead><tbody>${priRows||'<tr><td colspan=\"5\" style=\"color:#8b8d98;padding:10px\">Sem dados</td></tr>'}</tbody></table></div></div></div></section>\n      <section class=\"grid\">\n        <div class=\"card\"><h3>Incidentes por prioridade</h3>${hbars(priEntries, priColor)}</div>\n        <div class=\"card\"><h3>Distribuição por status</h3>${donut(stEntries)}</div>\n        <div class=\"card\"><h3>Top serviços impactados</h3>${hbars(svcTop, ()=>'#6e56cf')}</div>\n        <div class=\"card\"><h3>Incidentes por responsável</h3>${hbars(asgTop, ()=>'#0891b2')}</div>\n        <div class=\"card wide\"><h3>Volume de abertura por mês</h3>${vbars(monthEntries)}</div>\n        <div class=\"card wide\"><h3>Registros mais recentes</h3><table class=\"tbl\"><thead><tr><th>Número</th><th>Aberto em</th><th>Prioridade</th><th>Status</th><th>Responsável</th><th>SLA decorrido</th><th>Status SLA</th></tr></thead><tbody>${recent.length?recent.map(r=>`<tr data-key=\"${esc(r._id||r.number)}\"><td class=\"mono\">${esc(r.number)}</td><td>${esc(dayOnly(r.opened_at))}</td><td><span class=\"badge\" style=\"background:${priColor(r.priority)}\"></span>${esc(r.priority)}</td><td><span class=\"badge\" style=\"background:${stColor(r.state)}\"></span>${esc(r.state)}</td><td>${esc(r.assigned_to||'—')}</td><td>${slaTimeCell(r)}</td><td>${slaStatusCell(r)}</td></tr>`).join('') : `<tr><td colspan=\"5\" style=\"padding:16px;color:#8b8d98\">Nenhum registro encontrado</td></tr>`}</tbody></table></div>\n      </section>\n      <div class=\"tail\"></div>\n      ${imp?`<div class=\"overlay\"><div class=\"ov-card\"><div class=\"ov-title\">${esc(imp.phase)}</div><div class=\"ov-bar\"><div class=\"ov-fill\" style=\"width:${imp.total?(imp.done/imp.total*100).toFixed(0):0}%\"></div></div><div class=\"ov-sub\">${imp.done} / ${imp.total}</div></div></div>`:''}\n    </div>`;\n  },\n  mounted(d2){\n    if(typeof document !== 'undefined'){\n      const shadowHost = d2.root && d2.root.getRootNode && d2.root.getRootNode().host;\n      let el = shadowHost || d2.root, touched = [];\n      for(let i=0;i<10 && el && el!==document.body;i++){\n        touched.push({ el, maxW: el.style.maxWidth, w: el.style.width });\n        el.style.maxWidth = 'none'; el.style.width = '100%';\n        el = el.parentElement;\n      }\n      d2.onUnmount(()=>{ touched.forEach(t=>{ t.el.style.maxWidth = t.maxW; t.el.style.width = t.w; }); });\n    }\n    const EP = () => d2.param('endpoint') || DEFAULT_EP;\n\n    async function loadData(){\n      d2.state.error = null;\n      try{\n        const res = await fetch(EP(), { credentials:'include', headers:{'Accept':'application/json'} });\n        if(!res.ok) throw new Error('HTTP '+res.status);\n        const j = await res.json();\n        d2.state.rows = Array.isArray(j) ? j : ((j && j.data) || []);\n        d2.state.ready = true;\n      }catch(err){ d2.state.error = String((err&&err.message)||err); d2.state.ready = true; }\n    }\n    loadData();\n\n    d2.on('.retry','click',()=>{ d2.state.ready=false; d2.state.error=null; loadData(); });\n    d2.on('.f','change',(e,el)=>{ d2.state.filters = { ...(d2.state.filters||{}), [el.dataset.k]: el.value }; });\n    d2.on('.search','input',(e,el)=>{ d2.state.filters = { ...(d2.state.filters||{}), q: el.value }; });\n    d2.on('.clear','click',()=>{ d2.state.filters = {}; d2.$$('.f').forEach(s=>{ s.value=''; }); const s=d2.$('.search'); if(s) s.value=''; });\n\n    function parseCSV(text){\n      if(text.charCodeAt(0)===0xFEFF) text=text.slice(1);\n      const rows=[]; let field='', row=[], inQ=false, i=0;\n      while(i<text.length){\n        const c=text[i];\n        if(inQ){\n          if(c==='\"'){ if(text[i+1]==='\"'){ field+='\"'; i+=2; continue; } inQ=false; i++; continue; }\n          field+=c; i++; continue;\n        } else {\n          if(c==='\"'){ inQ=true; i++; continue; }\n          if(c===','){ row.push(field); field=''; i++; continue; }\n          if(c==='\\r'){ i++; continue; }\n          if(c==='\\n'){ row.push(field); rows.push(row); row=[]; field=''; i++; continue; }\n          field+=c; i++; continue;\n        }\n      }\n      if(field.length||row.length){ row.push(field); rows.push(row); }\n      return rows;\n    }\n    const createRec = (o) => fetch(EP(), { method:'POST', credentials:'include', headers:{'Content-Type':'application/json'}, body: JSON.stringify(o) }).then(r=>{ if(!r.ok) throw new Error('POST '+r.status); return r.json().catch(()=>({})); });\n    const delRec = (id) => fetch(EP()+'/'+id, { method:'DELETE', credentials:'include' }).then(r=>{ if(!r.ok && r.status!==404) throw new Error('DELETE '+r.status); });\n\n    d2.on('.file','change', async (e,el)=>{\n      const file = el.files && el.files[0];\n      if(!file) return;\n      try{\n        const buf = await file.arrayBuffer();\n        let text = new TextDecoder('utf-8').decode(buf);\n        if(text.indexOf('�')>=0) text = new TextDecoder('windows-1252').decode(buf);\n        const raw = parseCSV(text);\n        if(raw.length<2){ el.value=''; await d2.dialog.alert('CSV vazio ou inválido.'); return; }\n        const headers = raw[0].map(h=>(h||'').trim());\n        const objs = raw.slice(1).filter(r=>r.some(c=>(c||'')!=='')).map(r=>{\n          const o={}; headers.forEach((h,idx)=>{ if(FIELDS.indexOf(h)>=0) o[h] = r[idx]!=null ? r[idx] : ''; }); return o;\n        });\n        if(!objs.length){ el.value=''; await d2.dialog.alert('Nenhuma linha de dados encontrada. Verifique os cabeçalhos do CSV.'); return; }\n        const replace = d2.$('.replace') && d2.$('.replace').checked;\n        const current = d2.state.rows || [];\n        if(replace){\n          const ok = await d2.dialog.confirm('Substituir vai APAGAR os '+current.length+' registros atuais e importar '+objs.length+'. Continuar?');\n          if(!ok){ el.value=''; return; }\n          d2.state.importing={ done:0, total:current.length, phase:'Apagando base atual…' };\n          let dd=0;\n          for(const c of chunk(current,8)){ await Promise.all(c.map(r=>delRec(r._id))); dd+=c.length; d2.state.importing={ done:dd, total:current.length, phase:'Apagando base atual…' }; }\n        }\n        d2.state.importing={ done:0, total:objs.length, phase:'Importando registros…' };\n        let done=0;\n        for(const c of chunk(objs,8)){ await Promise.all(c.map(o=>createRec(o))); done+=c.length; d2.state.importing={ done, total:objs.length, phase:'Importando registros…' }; }\n        await loadData();\n        d2.state.importing=null; el.value='';\n        await d2.dialog.alert('Importação concluída: '+objs.length+' registros.');\n      }catch(err){\n        d2.state.importing=null; el.value='';\n        await d2.dialog.alert('Erro na importação: '+((err&&err.message)||err));\n      }\n    });\n  },\n}"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"endpoint":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"endpoint"}}}}}}}}}},{"_id":"6a4eecb90b2023df4563cc2c","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"loginScreen","package":"fernando-playground","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{all:initial}\n  *{box-sizing:border-box}\n  .wrap{height:100%;min-height:100%;display:flex;align-items:center;justify-content:center;background:#0e1117;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;padding:24px}\n  .card{width:100%;max-width:420px;background:#fff;border-radius:16px;padding:36px 32px;box-shadow:0 20px 60px rgba(0,0,0,.35)}\n  h1{margin:0 0 6px;font-size:22px;font-weight:800;color:#14161a;letter-spacing:-.01em}\n  .sub{margin:0 0 26px;font-size:13px;color:#8b8d98}\n  label{display:block;font-size:12px;color:#60646c;margin-bottom:6px;font-weight:600}\n  .field{margin-bottom:16px}\n  input{width:100%;padding:12px 14px;border:1px solid #d7dade;border-radius:9px;font-size:14px;color:#14161a;background:#f6f7f9;outline:none}\n  input:focus{border-color:#6e56cf;background:#fff}\n  .btn{width:100%;margin-top:8px;padding:13px;border:0;border-radius:9px;background:#6e56cf;color:#fff;font-size:14px;font-weight:700;cursor:pointer}\n  .btn:hover{background:#5b46b3}\n  .btn[disabled]{opacity:.6;cursor:default}\n  .err{margin-top:14px;font-size:13px;color:#e5484d;text-align:center}\n  .hint{margin-top:20px;font-size:11px;color:#b4b7bd;text-align:center}\n  `,\n  render(d2){\n    const busy = d2.state.busy;\n    const err = d2.state.error;\n    return `<div class=\"wrap\"><div class=\"card\">\n      <h1>Sustentação Dashboards</h1>\n      <p class=\"sub\">Entre com suas credenciais para acessar o painel.</p>\n      <div class=\"field\"><label>E-mail</label><input class=\"email\" type=\"email\" autocomplete=\"username\" placeholder=\"voce@empresa.com\" value=\"${(d2.state.email||'').replace(/\"/g,'&quot;')}\" ${busy?'disabled':''}/></div>\n      <div class=\"field\"><label>Senha</label><input class=\"password\" type=\"password\" autocomplete=\"current-password\" placeholder=\"••••••••\" ${busy?'disabled':''}/></div>\n      <button class=\"submit btn\" ${busy?'disabled':''}>${busy?'Entrando…':'Entrar'}</button>\n      ${err?`<div class=\"err\">${err.replace(/</g,'&lt;')}</div>`:''}\n    </div></div>`;\n  },\n  mounted(d2){\n    const doLogin = async () => {\n      const emailEl = d2.$('.email'), passEl = d2.$('.password');\n      const email = (emailEl.value||'').trim(), password = passEl.value||'';\n      d2.state.email = email;\n      if(!email || !password){ d2.state.error = 'Informe e-mail e senha.'; return; }\n      d2.state.busy = true; d2.state.error = null;\n      try{\n        const strategyRef = d2.param('strategy') || 'sustentacaoAuth';\n        await d2.auth(strategyRef).login({ email, password });\n        const routerRef = d2.param('router') || 'mainRouter';\n        d2.router(routerRef).goTo('/');\n      }catch(err){\n        d2.state.busy = false;\n        d2.state.error = 'E-mail ou senha inválidos.';\n      }\n    };\n    d2.on('.submit','click', doLogin);\n    d2.on('.password','keydown', (e)=>{ if(e.key==='Enter') doLogin(); });\n    d2.on('.email','keydown', (e)=>{ if(e.key==='Enter') doLogin(); });\n  },\n}"}},"strategy":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"strategy"}}}},"router":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"router"}}}}}}}}}},{"_id":"6a554e072a39c732debac298","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"neon-magnet","package":"fernando-playground","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    .wrap{ position:relative; width:100%; height:100%; overflow:hidden; }\n    canvas{ position:absolute; inset:0; width:100%; height:100%; display:block; cursor:crosshair; }\n    .ttl{ position:absolute; left:7%; top:7%; z-index:2; pointer-events:none;\n          font:800 clamp(26px,4vw,64px)/1 ui-sans-serif,system-ui,-apple-system,sans-serif; letter-spacing:-0.02em; }\n    .tag{ position:absolute; right:7%; bottom:9%; max-width:46%; text-align:right; z-index:2; pointer-events:none;\n          font:500 clamp(12px,1.25vw,18px)/1.5 ui-sans-serif,system-ui,-apple-system,sans-serif; opacity:.82; }\n    .hint{ position:absolute; left:7%; bottom:6%; z-index:2; pointer-events:none;\n           font:500 12px ui-sans-serif,system-ui,sans-serif; letter-spacing:.03em; text-transform:uppercase; opacity:.32; }\n    .nav{ position:absolute; right:6%; top:7%; z-index:3; display:flex; gap:8px; }\n    .nav-b{ font:600 12px ui-sans-serif,system-ui,sans-serif; letter-spacing:.04em; text-transform:uppercase; color:#fff; background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.16); padding:7px 14px; border-radius:999px; cursor:pointer; backdrop-filter:blur(6px); transition:background .15s; }\n    .nav-b:hover{ background:rgba(255,255,255,.16); }\n  `,\n  render(d2){\n    const bg = d2.param('bg','#0a0a12');\n    const ink = d2.param('ink','#ffffff');\n    const title = d2.param('title','Data²');\n    const tag = d2.param('tagline','Orchestrating intelligence at production scale.');\n    return `<div class=\"wrap\" style=\"background:${bg}\">\n      <canvas data-d2-static></canvas>\n      <div class=\"ttl\" style=\"color:${ink}\">${title}</div>\n      <div class=\"tag\" style=\"color:${ink}\">${tag}</div>\n      <div class=\"hint\" style=\"color:${ink}\">passe o mouse no neon</div>\n      <div class=\"nav\">\n        <button class=\"nav-b\" data-go=\"/\">Ímã</button>\n        <button class=\"nav-b\" data-go=\"/cordas\">Cordas</button>\n      </div>\n    </div>`;\n  },\n  mounted(d2, root){\n    const cv = d2.$('canvas');\n    const ctx = cv.getContext('2d');\n    let W=0, H=0, dpr = window.devicePixelRatio || 1;\n    const N = 96;\n    let rest=[], pos=[], vel=[];\n    const mouse = { x:-9999, y:-9999, on:false };\n    const stick = Math.max(0, Math.min(1, Number(d2.param('stick', 0.55)) || 0.55));\n\n    function computeRest(){\n      rest = [];\n      const midY = H*0.5, amp = H*0.24;\n      const mL = W*0.07, mR = W*0.07;\n      for(let i=0;i<N;i++){\n        const t = i/(N-1);\n        const x = mL + t*(W - mL - mR);\n        const y = midY\n          + Math.sin(t*Math.PI*2.2 + 0.4)*amp\n          + Math.sin(t*Math.PI*4.7 + 1.1)*amp*0.34\n          + Math.sin(t*Math.PI*1.1 - 0.3)*amp*0.22;\n        rest.push({x,y});\n      }\n      if(pos.length !== N){\n        pos = rest.map(p=>({x:p.x,y:p.y}));\n        vel = rest.map(()=>({x:0,y:0}));\n      }\n    }\n    function resize(){\n      W = cv.clientWidth; H = cv.clientHeight;\n      if(!W || !H) return;\n      dpr = window.devicePixelRatio || 1;\n      cv.width = Math.round(W*dpr); cv.height = Math.round(H*dpr);\n      ctx.setTransform(dpr,0,0,dpr,0,0);\n      computeRest();\n    }\n    function pathThrough(){\n      ctx.beginPath();\n      ctx.moveTo(pos[0].x, pos[0].y);\n      for(let i=1;i<N-1;i++){\n        const mx=(pos[i].x+pos[i+1].x)/2, my=(pos[i].y+pos[i+1].y)/2;\n        ctx.quadraticCurveTo(pos[i].x, pos[i].y, mx, my);\n      }\n      ctx.lineTo(pos[N-1].x, pos[N-1].y);\n    }\n    function drawNeon(){\n      const grad = ctx.createLinearGradient(0,0,W,0);\n      const stops = ['#a15bff','#4f7bff','#22d3ee','#34d399','#eab308','#f97316','#ff5db1'];\n      stops.forEach((c,i)=> grad.addColorStop(i/(stops.length-1), c));\n      const base = Math.max(6, Math.min(W,H)*0.021);\n      ctx.save();\n      ctx.lineCap='round'; ctx.lineJoin='round';\n      ctx.globalCompositeOperation='lighter';\n      ctx.shadowColor='rgba(130,150,255,0.85)';\n      ctx.shadowBlur=base*3.4; ctx.strokeStyle=grad; ctx.lineWidth=base*1.7; pathThrough(); ctx.stroke();\n      ctx.shadowBlur=base*1.4; ctx.lineWidth=base; pathThrough(); ctx.stroke();\n      ctx.shadowColor='rgba(255,255,255,0.9)'; ctx.shadowBlur=base*0.6;\n      ctx.strokeStyle='rgba(255,255,255,0.92)'; ctx.lineWidth=Math.max(1.5, base*0.26); pathThrough(); ctx.stroke();\n      ctx.restore();\n    }\n    function frame(){\n      if(!W || !H) return true;\n      ctx.clearRect(0,0,W,H);\n      const R = Math.max(100, Math.min(W,H)*0.30);\n      for(let i=0;i<N;i++){\n        const r = rest[i];\n        let tx=r.x, ty=r.y;\n        if(mouse.on){\n          const dx=mouse.x-r.x, dy=mouse.y-r.y;\n          const dist=Math.hypot(dx,dy);\n          if(dist < R){\n            const f = 1 - dist/R;\n            const pull = f*f*stick;\n            tx = r.x + dx*pull;\n            ty = r.y + dy*pull;\n          }\n        }\n        const p=pos[i], v=vel[i];\n        v.x += (tx-p.x)*0.18; v.y += (ty-p.y)*0.18;\n        v.x *= 0.78; v.y *= 0.78;\n        p.x += v.x; p.y += v.y;\n      }\n      drawNeon();\n      return true;\n    }\n    function toLocal(e){\n      const rect = cv.getBoundingClientRect();\n      mouse.x = e.clientX - rect.left;\n      mouse.y = e.clientY - rect.top;\n      mouse.on = true;\n    }\n    d2.on(root, 'pointermove', toLocal);\n    d2.on(root, 'pointerleave', ()=>{ mouse.on=false; mouse.x=-9999; mouse.y=-9999; });\n    d2.on('.nav-b', 'click', (e, el)=>{\n      const path = el.dataset.go || '/';\n      try { const r = d2.router('efeitos-malucos-router'); if(r && r.goTo) r.goTo(path); } catch(err){}\n      setTimeout(()=>{ if(location.pathname !== path) location.assign(path); }, 80);\n    });\n\n    resize();\n    if(typeof ResizeObserver!=='undefined'){\n      const ro = new ResizeObserver(()=>resize());\n      ro.observe(cv);\n      d2.onUnmount(()=>ro.disconnect());\n    }\n    d2.raf(frame);\n  },\n}"}},"bg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"bg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"title":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"title"}}}},"tagline":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"tagline"}}}},"stick":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"stick"}}}}}}}}}},{"_id":"6a5551961f9aeec8d2f153d4","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"neon-strings","package":"fernando-playground","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    .wrap{ position:relative; width:100%; height:100%; overflow:hidden; }\n    canvas{ position:absolute; inset:0; width:100%; height:100%; display:block; cursor:crosshair; }\n    .ttl{ position:absolute; left:7%; top:7%; z-index:2; pointer-events:none;\n          font:800 clamp(24px,3.4vw,56px)/1 ui-sans-serif,system-ui,-apple-system,sans-serif; letter-spacing:-.02em; }\n    .hint{ position:absolute; left:7%; bottom:6%; z-index:2; pointer-events:none;\n           font:500 12px ui-sans-serif,system-ui,sans-serif; letter-spacing:.03em; text-transform:uppercase; opacity:.32; }\n    .nav{ position:absolute; right:6%; top:7%; z-index:3; display:flex; gap:8px; }\n    .nav-b{ font:600 12px ui-sans-serif,system-ui,sans-serif; letter-spacing:.04em; text-transform:uppercase;\n            color:#fff; background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.16);\n            padding:7px 14px; border-radius:999px; cursor:pointer; backdrop-filter:blur(6px); transition:background .15s; }\n    .nav-b:hover{ background:rgba(255,255,255,.16); }\n  `,\n  render(d2){\n    const bg = d2.param('bg','#08080f');\n    const ink = d2.param('ink','#ffffff');\n    const title = d2.param('title','Strings');\n    const hint = d2.param('hint','arraste o mouse pelas cordas');\n    return `<div class=\"wrap\" style=\"background:${bg}\">\n      <canvas data-d2-static></canvas>\n      <div class=\"ttl\" style=\"color:${ink}\">${title}</div>\n      <div class=\"hint\" style=\"color:${ink}\">${hint}</div>\n      <div class=\"nav\">\n        <button class=\"nav-b\" data-go=\"/\">Ímã</button>\n        <button class=\"nav-b\" data-go=\"/cordas\">Cordas</button>\n      </div>\n    </div>`;\n  },\n  mounted(d2, root){\n    const cv = d2.$('canvas');\n    const ctx = cv.getContext('2d');\n    let W=0, H=0, dpr = window.devicePixelRatio || 1;\n    const mouse = { x:-9999, y:-9999, on:false };\n    const tension = Math.max(0.2, Number(d2.param('tension', 1)) || 1);\n    const N = Math.max(3, Math.min(16, Math.round(Number(d2.param('lines', 8)) || 8)));\n    let strings = [];\n\n    function build(){\n      strings = [];\n      const x0 = W*0.07, x1 = W*0.93;\n      const yTop = H*0.20, yBot = H*0.84;\n      for(let i=0;i<N;i++){\n        const t = N>1 ? i/(N-1) : 0;\n        const y0 = yTop + t*(yBot-yTop);\n        strings.push({\n          y0, x0, x1,\n          cx:(x0+x1)/2, disp:0, vel:0,\n          stiff: (0.028 + t*0.02) * tension,   // higher strings = tighter = higher pitch\n          hue: 282 - t*300                      // violet -> ... -> pink/red\n        });\n      }\n    }\n    function resize(){\n      W = cv.clientWidth; H = cv.clientHeight;\n      if(!W || !H) return;\n      dpr = window.devicePixelRatio || 1;\n      cv.width = Math.round(W*dpr); cv.height = Math.round(H*dpr);\n      ctx.setTransform(dpr,0,0,dpr,0,0);\n      build();\n    }\n    function strokeString(s){\n      const ctrlY = s.y0 + s.disp*2;   // quadratic apex ~= y0 + disp\n      const base = Math.max(3.5, Math.min(W,H)*0.008);\n      const col = `hsl(${Math.round(s.hue)},100%,62%)`;\n      ctx.save();\n      ctx.lineCap='round'; ctx.lineJoin='round';\n      ctx.globalCompositeOperation='lighter';\n      const pass = (blur, w, color)=>{\n        ctx.shadowColor=color; ctx.shadowBlur=blur; ctx.strokeStyle=color; ctx.lineWidth=w;\n        ctx.beginPath(); ctx.moveTo(s.x0, s.y0);\n        ctx.quadraticCurveTo(s.cx, ctrlY, s.x1, s.y0); ctx.stroke();\n      };\n      pass(base*3.6, base*1.6, col);\n      pass(base*1.4, base, col);\n      pass(base*0.6, Math.max(1.2, base*0.34), 'rgba(255,255,255,0.92)');\n      ctx.restore();\n    }\n    function frame(){\n      if(!W || !H) return true;\n      ctx.clearRect(0,0,W,H);\n      for(const s of strings){\n        const spacing = (s.x1 - s.x0);\n        const grab = Math.max(26, (H*0.64)/N * 0.7);\n        const held = mouse.on && mouse.x>=s.x0 && mouse.x<=s.x1 && Math.abs(mouse.y - s.y0) < grab;\n        if(held){\n          s.cx = mouse.x;\n          const target = mouse.y - s.y0;\n          const nd = s.disp + (target - s.disp)*0.5;\n          s.vel = nd - s.disp;   // carry finger speed for the twang on release\n          s.disp = nd;\n        } else {\n          s.vel += (-s.disp) * s.stiff;   // spring back\n          s.vel *= 0.94;                  // light damping -> oscillates (twang)\n          s.disp += s.vel;\n          s.cx += ((s.x0+s.x1)/2 - s.cx) * 0.06;  // peak eases back to center\n        }\n        strokeString(s);\n      }\n      return true;\n    }\n    function toLocal(e){\n      const rect = cv.getBoundingClientRect();\n      mouse.x = e.clientX - rect.left; mouse.y = e.clientY - rect.top; mouse.on = true;\n    }\n    d2.on(root, 'pointermove', toLocal);\n    d2.on(root, 'pointerleave', ()=>{ mouse.on=false; mouse.x=-9999; mouse.y=-9999; });\n    d2.on('.nav-b', 'click', (e, el)=>{\n      const path = el.dataset.go || '/';\n      try { const r = d2.router('efeitos-malucos-router'); if(r && r.goTo) r.goTo(path); } catch(err){}\n      setTimeout(()=>{ if(location.pathname !== path) location.assign(path); }, 80);\n    });\n\n    resize();\n    if(typeof ResizeObserver!=='undefined'){\n      const ro = new ResizeObserver(()=>resize());\n      ro.observe(cv); d2.onUnmount(()=>ro.disconnect());\n    }\n    d2.raf(frame);\n  },\n}"}},"bg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"bg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"title":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"title"}}}},"hint":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"hint"}}}},"lines":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lines"}}}},"tension":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"tension"}}}}}}}}}},{"_id":"6a5906b07a65d3cf47a68f3d","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"mv-shell","package":"fernando-playground","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 ACCENT = '#F48646';\nconst CATS = {\n  museologico:{glyph:'🏛️',color:'#B07C4F',nome:'Museológico'},\n  iconografia:{glyph:'🖼️',color:'#5E9E8C',nome:'Iconografia'},\n  biblioteca:{glyph:'📖',color:'#C77D4A',nome:'Documento de Biblioteca'},\n  arquivo:{glyph:'🗂️',color:'#7B8CAE',nome:'Documento de Arquivo'},\n  depoimento:{glyph:'🎙️',color:'#A87CA0',nome:'Depoimento'},\n  cosmetoteca:{glyph:'🧴',color:'#D98E9E',nome:'Cosmetoteca'},\n  audiovisual:{glyph:'🎬',color:'#6C8EBF',nome:'Audiovisual'}\n};\nconst NAV = [\n  {v:'inicio', label:'Início', g:'🏠'},\n  {v:'acervo', label:'Acervo', g:'🗃️'},\n  {v:'catalogar', label:'Catalogar', g:'✨'},\n  {v:'grafo', label:'Grafo de conexões', g:'🕸️'},\n  {v:'aprovar', label:'Aprovações', g:'✅'},\n  {v:'exposicoes', label:'Exposições', g:'🖼️'}\n];\nconst esc = (s)=> (s==null?'':String(s)).replace(/[&<>\"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst trunc = (s,n)=>{ s=String(s==null?'':s); return s.length>n ? s.slice(0,n-1)+'…' : s; };\nconst catOf = (slug)=> CATS[slug] || {glyph:'📦',color:'#9aa0a6',nome:slug||'—'};\nconst isUrl = (r)=> typeof r==='string' && /^https?:/i.test(r);\nconst recs = (d2,name)=>{ try{ const h=d2.data(name); return h? h.records():[]; }catch(e){ return []; } };\nconst exposOf = (d2)=> Array.isArray(d2.state.expos) ? d2.state.expos : [];\nconst preOf = (d2)=> Array.isArray(d2.state.pre) ? d2.state.pre : [];\nconst carregaPre = async (d2)=>{ try{ const r = await d2.rush('mv-pre-listar').invoke({}); const arr = (r && r.data && r.data.itens) || []; d2.state.pre = Array.isArray(arr) ? arr : []; }catch(e){ d2.log('pre-listar', e); } };\nconst carregaExpos = async (d2)=>{ try{ const r = await d2.rush('mv-expo-listar').invoke({}); const arr = (r && r.data && r.data.itens) || []; d2.state.expos = Array.isArray(arr) ? arr : []; }catch(e){ d2.log('expo-listar', e); } };\nconst findBy = (arr,k,v)=> arr.find(r=>r[k]===v);\n\nconst phTile = (slug, label, ref)=>{\n  if (isUrl(ref)) return `<img class=\"ph-img\" src=\"${esc(ref)}\" alt=\"\"/>`;\n  const c = catOf(slug);\n  return `<div class=\"ph\" style=\"--c:${c.color}\"><span class=\"ph-g\">${c.glyph}</span><span class=\"ph-t\">${esc(label||'sem imagem')}</span></div>`;\n};\n\nconst GAPS = {\n  'sem-imagem':    {label:'sem imagem',    test:(i)=> !isUrl(i.imagem_ref)},\n  'sem-ciclo':     {label:'sem ciclo',     test:(i)=> String(i.ciclo_codigo||'').trim()===''},\n  'sem-descricao': {label:'sem descrição', test:(i)=> String(i.descricao||'').trim()===''},\n  'sem-tombo':     {label:'sem nº de tombo', test:(i)=> String(i.tombo||'').trim()===''},\n  'sigiloso':      {label:'sigilosos',     test:(i)=> i.sigiloso===true}\n};\n\nconst matchItem = (r,q)=>{\n  if(!q) return true; q=q.toLowerCase();\n  return [r.titulo,r.descricao,r.tags_texto,r.marca_slug,r.ciclo_codigo,r.tombo,r.categoria_slug]\n    .some(x=> String(x==null?'':x).toLowerCase().includes(q));\n};\n\nconst buildContext = (d2)=>{\n  const lines = recs(d2,'itens').map(r=>\n    `- ${r.titulo} (${r.codigo_interno}) | categoria: ${catOf(r.categoria_slug).nome} | marca: ${r.marca_slug||'-'} | ciclo: ${r.ciclo_codigo||'-'} | campanha: ${r.campanha_slug||'-'} | ${r.descricao||''} [tags: ${(r.tags||[]).join(', ')}]`\n  );\n  return 'ITENS DO ACERVO MEMÓRIA VIVA (Natura):\\n' + lines.join('\\n');\n};\n\n// ---------- sidebar / topbar ----------\nconst sidebar = (d2,view)=>{\n  const total = recs(d2,'itens').length;\n  const pend = preOf(d2).length;\n  let email=''; try{ const u=d2.auth('mv-auth').user; email = (u && (u.email||u.nome)) || ''; }catch(e){}\n  return `<aside class=\"side\">\n    <div class=\"brand\"><span class=\"dot\"></span><div><div class=\"bt\">Memória Viva</div><div class=\"bs\">Acervo Natura</div></div></div>\n    <nav class=\"nav\">${NAV.map(n=>{\n      const active = (view===n.v) || (view==='ficha' && n.v==='acervo');\n      return `<div class=\"nav-i ${active?'on':''}\" data-view=\"${n.v}\"><span class=\"ng\">${n.g}</span>${n.label}${n.v==='aprovar' && pend?`<span class=\"nav-badge\">${pend}</span>`:''}</div>`;\n    }).join('')}</nav>\n    <div class=\"side-foot\"><div class=\"sf-k\">${total}</div><div class=\"sf-l\">itens catalogados</div>\n      <div class=\"sf-tag\">substitui o Sophia</div>\n      ${email?`<div class=\"sf-user\" title=\"${esc(email)}\">👤 ${esc(email)}</div>`:''}\n      <button class=\"sf-logout\">Sair</button></div>\n  </aside>`;\n};\n\nconst TITLES = {inicio:'Painel da Curadoria', acervo:'Acervo', ficha:'Ficha do item', catalogar:'Catalogar', grafo:'Grafo de conexões', aprovar:'Aprovações — pré-catálogo', exposicoes:'Exposições virtuais'};\nconst topbar = (d2,view)=> `<header class=\"top\"><div class=\"tt\">${TITLES[view]||''}</div>\n  <div class=\"tsub\">Curadoria completa da Natura — produtos, marcas, campanhas e memória</div></header>`;\n\n// ---------- acervo ----------\nconst acervoPage = (d2)=>{\n  const cat = d2.state.cat || 'todos';\n  const q = d2.state.q || '';\n  const all = recs(d2,'itens');\n  const midCount = {}; recs(d2,'midias').forEach(m=>{ midCount[m.item_codigo]=(midCount[m.item_codigo]||0)+1; });\n  const cats = recs(d2,'categorias').slice().sort((a,b)=>(a.ordem||0)-(b.ordem||0));\n  const chips = [`<div class=\"chip ${cat==='todos'?'on':''}\" data-cat=\"todos\">Todos <b>${all.length}</b></div>`]\n    .concat(cats.map(c=>{\n      const n = all.filter(i=>i.categoria_slug===c.slug).length;\n      return `<div class=\"chip ${cat===c.slug?'on':''}\" data-cat=\"${c.slug}\"><span class=\"cg\">${catOf(c.slug).glyph}</span>${esc(c.nome)} <b>${n}</b></div>`;\n    })).join('');\n  const gapKey = d2.state.gap; const gapDef = gapKey ? GAPS[gapKey] : null;\n  const list = all.filter(i=> (cat==='todos'||i.categoria_slug===cat) && matchItem(i,q) && (!gapDef || gapDef.test(i)));\n  const cards = list.length ? list.map(r=>`\n    <div class=\"card\" data-key=\"${r._id}\" data-codigo=\"${esc(r.codigo_interno)}\">\n      <div class=\"card-img\">${phTile(r.categoria_slug, r.tombo, r.imagem_ref)}\n        ${r.sigiloso?'<span class=\"sig\">sigiloso</span>':''}${(midCount[r.codigo_interno]||0)>1?`<span class=\"mid-badge\">🖼 ${midCount[r.codigo_interno]}</span>`:''}</div>\n      <div class=\"card-b\">\n        <div class=\"card-t\">${esc(trunc(r.titulo,60))}</div>\n        <div class=\"card-m\">${esc(r.tombo||'')}${r.marca_slug?' · '+esc(r.marca_slug):''}${r.ciclo_codigo?' · ciclo '+esc(r.ciclo_codigo):''}</div>\n      </div>\n    </div>`).join('') : `<div class=\"empty\">Nenhum item encontrado para essa busca.</div>`;\n  return `<div class=\"acervo\">\n    <div class=\"search-wrap\"><span class=\"si\">🔍</span><input class=\"search\" placeholder=\"Buscar no acervo — título, marca, tema, ciclo…\" value=\"${esc(q)}\"/></div>\n    <div class=\"chips\">${chips}</div>\n    ${gapDef?`<div class=\"gap-bar\">⚡ Mostrando só os itens <b>&nbsp;${esc(gapDef.label)}</b> — vindo do painel inicial.<button class=\"gap-clear\">Limpar filtro</button></div>`:''}\n    <div class=\"acervo-bar\"><div class=\"count\">${list.length} ${list.length===1?'item':'itens'}${(cat!=='todos'||q||gapDef)?' (filtrado)':''}</div><button class=\"exportar\">⬇ Exportar CSV (${list.length})</button></div>\n    <div class=\"grid\">${cards}</div>\n  </div>`;\n};\n\n// ---------- ficha ----------\nconst midBox = (it, m)=>{\n  if(!m) return phTile(it.categoria_slug, it.tombo, it.imagem_ref);\n  if(m.tipo==='video' && isUrl(m.ref)) return `<video class=\"media-vid\" src=\"${esc(m.ref)}\" controls preload=\"metadata\"></video>`;\n  if(m.tipo==='audio' && isUrl(m.ref)) return `<audio class=\"media-aud\" src=\"${esc(m.ref)}\" controls></audio>`;\n  return phTile(it.categoria_slug, m.tipo==='video'?'▶':it.tombo, m.ref);\n};\nconst relChip = (label,val)=> val ? `<span class=\"rel\">${esc(label)}: <b>${esc(val)}</b></span>` : '';\nconst fichaPage = (d2)=>{\n  const sel = d2.state.sel;\n  const it = findBy(recs(d2,'itens'),'codigo_interno',sel);\n  if(!it) return `<div class=\"empty\">Selecione um item no acervo.</div>`;\n  const c = catOf(it.categoria_slug);\n  const mid = recs(d2,'midias').filter(m=>m.item_codigo===sel).sort((a,b)=>(a.ordem||0)-(b.ordem||0));\n  const pref = mid.find(m=>m.preferencial) || mid[0];\n  const selMid = mid.find(m=>m._id===d2.state.midSel) || pref || null;\n  const fields = [\n    ['Fundo',it.fundo],['Notação',it.notacao],['Nível de Descrição',it.nivel_descricao],\n    ['Localização',it.localizacao],['Função',it.funcao],['Subfunção',it.subfuncao],\n    ['Ciclo',it.ciclo_codigo],['Origem',it.origem],['Responsável',it.resp_cadastro],\n    ['Data de cadastro',it.data_cadastro],['Status',it.status]\n  ];\n  const marca = findBy(recs(d2,'marcas'),'slug',it.marca_slug);\n  const prod = findBy(recs(d2,'produtos'),'slug',it.produto_slug);\n  const camp = findBy(recs(d2,'campanhas'),'slug',it.campanha_slug);\n  return `<div class=\"ficha\">\n    <div class=\"ficha-head\">\n      <button class=\"back\">← Voltar ao acervo</button>\n      <span class=\"cat-chip\" style=\"--c:${c.color}\">${c.glyph} ${esc(c.nome)}</span>\n      ${it.sigiloso?'<span class=\"sig sig-inline\">sigiloso</span>':''}\n      ${d2.state.pendingDelete===it.codigo_interno ? `<span class=\"del-confirm\"><span class=\"del-q\">Excluir mesmo?</span><button class=\"del-yes\" data-codigo=\"${esc(it.codigo_interno)}\">Sim, excluir</button><button class=\"del-no\">Cancelar</button></span>` : `<button class=\"excluir\" data-codigo=\"${esc(it.codigo_interno)}\">🗑 Excluir</button>`}\n    </div>\n    <h1 class=\"ficha-t\">${esc(it.titulo)}</h1>\n    <div class=\"ficha-sub\">${esc(it.tombo||'')} · código ${esc(it.codigo_interno)}</div>\n    <div class=\"ficha-cols\">\n      <div class=\"ficha-fields\">\n        ${fields.map(f=>`<div class=\"fld\"><div class=\"fl\">${esc(f[0])}</div><div class=\"fv\">${esc(f[1]||'—')}</div></div>`).join('')}\n        <div class=\"fld fld-full\"><div class=\"fl\">Descrição</div><div class=\"fv\">${esc(it.descricao||'—')}</div></div>\n        <div class=\"fld fld-full\"><div class=\"fl\">Relações</div><div class=\"fv rels\">\n          ${relChip('Marca', marca?marca.nome:it.marca_slug)}${relChip('Produto', prod?prod.nome:'')}${relChip('Ciclo', it.ciclo_codigo)}${relChip('Campanha', camp?camp.nome:'')}\n          ${(it.tags||[]).map(t=>`<span class=\"tag\">${esc(t)}</span>`).join('')}\n        </div></div>\n        <button class=\"tograph\" data-codigo=\"${esc(it.codigo_interno)}\">Ver no grafo de conexões →</button>\n      </div>\n      <div class=\"ficha-media\">\n        <div class=\"media-main\">${midBox(it, selMid)}</div>\n        <div class=\"media-cap\">${esc(selMid?(selMid.legenda||it.titulo):it.titulo)}</div>\n        ${selMid&&selMid.credito?`<div class=\"media-cred\">${esc(selMid.credito)}</div>`:''}\n        <div class=\"thumbs\">${mid.map(m=>`<div class=\"thumb mid-th ${(selMid&&m._id===selMid._id)?'on':''}\" data-id=\"${esc(m._id)}\" data-key=\"th${esc(m._id)}\" title=\"${esc(m.legenda||'')}\">${phTile(it.categoria_slug, m.tipo==='video'?'▶':'', m.ref)}${m.preferencial?'<span class=\"th-star\">★</span>':''}</div>`).join('')||'<div class=\"media-cap\">Nenhuma mídia ainda — adicione abaixo.</div>'}</div>\n        <div class=\"img-edit\" style=\"margin-top:12px;padding-top:12px;border-top:1px solid #eee\">\n          <div class=\"mid-mgr-h\">🖼️ ${mid.length} ${mid.length===1?'mídia':'mídias'} neste item${mid.length>1?' — clique numa miniatura para ver':''}</div>\n          ${selMid?`<div class=\"mid-row\"><input class=\"mid-leg\" value=\"${esc(selMid.legenda||'')}\" placeholder=\"Legenda desta mídia\"/><button class=\"mid-leg-save\" data-id=\"${esc(selMid._id)}\">Salvar</button></div>\n          <div class=\"mid-acts\"><button class=\"mid-star\" data-id=\"${esc(selMid._id)}\" ${selMid.preferencial?'disabled':''}>★ ${selMid.preferencial?'É a capa':'Definir como capa'}</button><button class=\"mid-up\" data-id=\"${esc(selMid._id)}\">↑ Subir</button><button class=\"mid-down\" data-id=\"${esc(selMid._id)}\">↓ Descer</button><button class=\"mid-rm\" data-id=\"${esc(selMid._id)}\">✕ Remover</button></div>`:''}\n          <div class=\"mid-add\"><div class=\"mid-add-t\">Adicionar mídia a este item</div>\n            <div class=\"mid-row\"><input class=\"mid-url\" placeholder=\"Cole uma URL (imagem ou vídeo)…\"/><button class=\"mid-url-add\">Adicionar</button></div>\n            <div><label class=\"mid-file-lb\">📎 Enviar arquivo<input class=\"mid-file\" type=\"file\" accept=\"image/*,video/*,audio/*\" hidden/></label>${d2.state.midBusy?'<span class=\"mid-busy\">enviando…</span>':''}</div>\n          </div>\n          ${d2.state.midMsgFor===it.codigo_interno && d2.state.midMsg ? `<div class=\"mid-msg\">${esc(d2.state.midMsg)}</div>`:''}\n        </div>\n      </div>\n    </div>\n  </div>`;\n};\n\n// ---------- catalogar (IA / manual / lote) ----------\nconst CAT_FIELDS = [\n  {k:'titulo', label:'Título', req:true},\n  {k:'categoria_slug', label:'Categoria', type:'select', req:true},\n  {k:'marca_slug', label:'Marca (slug)'},\n  {k:'produto_slug', label:'Produto (slug)'},\n  {k:'ciclo_codigo', label:'Ciclo'},\n  {k:'campanha_slug', label:'Campanha (slug)'},\n  {k:'tombo', label:'Nº de Tombo'},\n  {k:'origem', label:'Origem'},\n  {k:'localizacao', label:'Localização'},\n  {k:'imagem_ref', label:'Imagem (URL)'},\n  {k:'tags', label:'Tags (separadas por vírgula)'},\n  {k:'descricao', label:'Descrição', type:'textarea'}\n];\nconst CSV_COLS = ['titulo','categoria_slug','marca_slug','produto_slug','ciclo_codigo','campanha_slug','tombo','origem','localizacao','descricao','tags','sigiloso'];\nconst csvCell = (s)=>{ s=String(s==null?'':s); return /[\",\\n]/.test(s) ? '\"'+s.replace(/\"/g,'\"\"')+'\"' : s; };\nconst parseCSV = (text)=>{\n  const rows=[]; let row=[], cur='', q=false;\n  text = String(text||'').replace(/\\r\\n/g,'\\n').replace(/\\r/g,'\\n');\n  for(let i=0;i<text.length;i++){ const ch=text[i];\n    if(q){ if(ch==='\"'){ if(text[i+1]==='\"'){ cur+='\"'; i++; } else q=false; } else cur+=ch; }\n    else { if(ch==='\"') q=true; else if(ch===','){ row.push(cur); cur=''; } else if(ch==='\\n'){ row.push(cur); rows.push(row); row=[]; cur=''; } else cur+=ch; }\n  }\n  if(cur!=='' || row.length){ row.push(cur); rows.push(row); }\n  return rows.filter(r=> r.some(c=> (c||'').trim()!==''));\n};\nconst catForm = (d2, nota)=>{\n  const f = d2.state.form || {};\n  const cats = recs(d2,'categorias').slice().sort((a,b)=>(a.ordem||0)-(b.ordem||0));\n  const fh = CAT_FIELDS.map(fd=>{\n    const v = esc(f[fd.k]==null?'':f[fd.k]);\n    if(fd.type==='select') return `<div class=\"cf\"><label>${fd.label}${fd.req?' *':''}</label><select class=\"cin\" data-k=\"${fd.k}\"><option value=\"\">—</option>${cats.map(c=>`<option value=\"${c.slug}\" ${f.categoria_slug===c.slug?'selected':''}>${esc(c.nome)}</option>`).join('')}</select></div>`;\n    if(fd.type==='textarea') return `<div class=\"cf cf-full\"><label>${fd.label}</label><textarea class=\"cin\" data-k=\"${fd.k}\" rows=\"3\">${v}</textarea></div>`;\n    return `<div class=\"cf\"><label>${fd.label}${fd.req?' *':''}</label><input class=\"cin\" data-k=\"${fd.k}\" value=\"${v}\"/></div>`;\n  }).join('');\n  return `<div class=\"cat-form\">\n    ${nota?`<div class=\"cf-note\">${esc(nota)}</div>`:''}\n    <div class=\"cf-grid\">${fh}</div>\n    <label class=\"cf-check\"><input type=\"checkbox\" class=\"cin-check\" ${f.sigiloso?'checked':''}/> Item sigiloso</label>\n    <div class=\"cf-actions\"><button class=\"cat-save\">Salvar no acervo</button>${d2.state.savedMsg?`<span class=\"cat-ok\">${esc(d2.state.savedMsg)}</span>`:''}</div>\n  </div>`;\n};\nconst catalogarPage = (d2)=>{\n  const mode = d2.state.catMode || 'ia';\n  const tabs = `<div class=\"cat-tabs\">\n    <div class=\"cat-tab ${mode==='ia'?'on':''}\" data-cmode=\"ia\">✨ Com IA</div>\n    <div class=\"cat-tab ${mode==='manual'?'on':''}\" data-cmode=\"manual\">✍️ Manual</div>\n    <div class=\"cat-tab ${mode==='lote'?'on':''}\" data-cmode=\"lote\">📄 Em lote (CSV)</div>\n  </div>`;\n  let body;\n  if(mode==='lote'){\n    body = `<div class=\"lote\">\n      <div class=\"lote-step\"><div class=\"lote-n\">1</div><div class=\"lote-txt\"><div class=\"lote-t\">Baixe o modelo</div><div class=\"lote-d\">CSV com as colunas certas e uma linha de exemplo.</div></div><button class=\"lote-modelo\">⬇ Baixar CSV modelo</button></div>\n      <div class=\"lote-step\"><div class=\"lote-n\">2</div><div class=\"lote-txt\"><div class=\"lote-t\">Preencha e envie</div><div class=\"lote-d\">Cada linha do CSV vira um item do acervo.</div></div><label class=\"lote-up\">⬆ Enviar CSV<input class=\"lote-file\" type=\"file\" accept=\".csv,text/csv\" hidden/></label></div>\n      ${d2.state.loteMsg?`<div class=\"cat-ok lote-msg\">${esc(d2.state.loteMsg)}</div>`:''}\n      <div class=\"lote-cols\">Colunas do CSV: ${CSV_COLS.join(', ')}</div>\n    </div>`;\n  } else if(mode==='ia'){\n    const img = d2.state.uploadImg, busy = d2.state.autoBusy, err = d2.state.autoerr;\n    const catMids = Array.isArray(d2.state.catMids)?d2.state.catMids:[], catUp = d2.state.catUp||'';\n    const preview = img ? `<img class=\"up-img\" src=\"${esc(img)}\" alt=\"\"/>` : `<div class=\"up-empty\"><div class=\"up-g\">📷</div><div>Envie a imagem do item<br/>(revista, produto, documento, foto…)</div></div>`;\n    const status = busy ? `<div class=\"ai-busy\"><div class=\"spin\"></div>Analisando a imagem com IA…</div>` : (err?`<div class=\"ai-err\">Erro ao analisar: ${esc(err)}</div>`:'');\n    const hasForm = d2.state.form && Object.keys(d2.state.form).length;\n    const right = hasForm ? catForm(d2, 'A IA pré-preencheu a ficha — revise, ajuste qualquer campo e salve.') : (d2.state.savedMsg ? `<div class=\"cat-ok\" style=\"padding:18px\">${esc(d2.state.savedMsg)}</div>` : `<div class=\"ai-hint\">Envie uma imagem e clique em Analisar; a IA propõe os campos e você edita antes de salvar. Ou use os modos Manual / Em lote acima.</div>`);\n    body = `<div class=\"ia-wrap\">\n      <div class=\"ia-left\">\n        <div class=\"up\">${preview}</div>\n        <label class=\"up-btn\">Escolher imagens<input class=\"ai-file\" type=\"file\" accept=\"image/*,video/*\" multiple hidden/></label>\n        <button class=\"ai-run\" ${(!img||busy)?'disabled':''}>Analisar com IA</button>\n        ${status}\n        ${catUp?`<div class=\"mid-busy\">${esc(catUp)}</div>`:''}\n        ${catMids.length?`<div class=\"cat-mids\">${catMids.map((m,i)=>`<div class=\"cat-mid\">${m.tipo==='video'?'<div class=\"cat-mid-v\">▶</div>':`<img src=\"${esc(m.ref)}\"/>`}<button class=\"cat-mid-rm\" data-i=\"${i}\">✕</button></div>`).join('')}</div><div class=\"cat-mids-h\">${catMids.length} ${catMids.length===1?'mídia anexada':'mídias anexadas'} — a primeira será a capa</div>`:''}\n      </div>\n      <div class=\"ia-right\">${right}</div>\n    </div>`;\n  } else {\n    const mimg = d2.state.uploadImg, mbusy = d2.state.autoBusy, merr = d2.state.autoerr;\n    const mprev = mimg ? `<img class=\"up-img\" src=\"${esc(mimg)}\" alt=\"\"/>` : `<div class=\"up-empty\"><div class=\"up-g\">📷</div><div>Opcional — envie uma imagem e a IA pré-preenche os campos</div></div>`;\n    const mstatus = mbusy ? `<div class=\"ai-busy\"><div class=\"spin\"></div>Analisando a imagem com IA…</div>` : (merr?`<div class=\"ai-err\">Erro ao analisar: ${esc(merr)}</div>`:'');\n    body = `<div class=\"ia-left\" style=\"max-width:360px;margin-bottom:18px\"><div class=\"up\">${mprev}</div><label class=\"up-btn\">Escolher imagens<input class=\"ai-file\" type=\"file\" accept=\"image/*,video/*\" multiple hidden/></label><button class=\"ai-run\" ${(!mimg||mbusy)?'disabled':''}>Analisar com IA</button>${mstatus}${d2.state.catUp?`<div class=\"mid-busy\">${esc(d2.state.catUp)}</div>`:''}${(Array.isArray(d2.state.catMids)&&d2.state.catMids.length)?`<div class=\"cat-mids\">${d2.state.catMids.map((m,i)=>`<div class=\"cat-mid\">${m.tipo==='video'?'<div class=\"cat-mid-v\">▶</div>':`<img src=\"${esc(m.ref)}\"/>`}<button class=\"cat-mid-rm\" data-i=\"${i}\">✕</button></div>`).join('')}</div><div class=\"cat-mids-h\">${d2.state.catMids.length} ${d2.state.catMids.length===1?'mídia anexada':'mídias anexadas'}</div>`:''}</div>${catForm(d2, 'Preencha os campos do item. Só Título e Categoria são obrigatórios — ou envie uma imagem acima para a IA pré-preencher.')}`;\n  }\n  const pubUrl = (typeof window!=='undefined' && window.location ? window.location.origin : '') + '/enviar';\n  const linkBar = `<div class=\"pub-link\"><div class=\"pub-txt\"><span class=\"pub-lab\">🔗 Link público — catalogar sem login</span><span class=\"pub-url\">${esc(pubUrl)}</span></div><button class=\"copiar-link\">${d2.state.copiado?'Copiado ✓':'Copiar link'}</button></div>`;\n  return `<div class=\"cat2\">${linkBar}${tabs}${body}</div>`;\n};\n\n// (chat agora é widget flutuante — ver chatWidget)\n\n// ---------- grafo ----------\nconst graphModel = (d2)=>{\n  const f = d2.state.gfocus || {type:'root'};\n  const itens = recs(d2,'itens'), marcas = recs(d2,'marcas'), produtos = recs(d2,'produtos'), campanhas = recs(d2,'campanhas');\n  if (f.type==='root'){\n    const center = {label:'Natura', color:ACCENT, kind:'root'};\n    const nodes = marcas.filter(m=>m.tipo==='linha').map(m=>({label:m.nome, color:m.cor||'#999', kind:'marca', id:m.slug}));\n    return {center, nodes, crumb:'Marcas & linhas'};\n  }\n  if (f.type==='marca'){\n    const m = findBy(marcas,'slug',f.id) || {nome:f.id, cor:'#999'};\n    const center = {label:m.nome, color:m.cor||'#999', kind:'marca', id:m.slug};\n    const its = itens.filter(i=>i.marca_slug===f.id).slice(0,6).map(i=>({label:trunc(i.titulo,22), color:catOf(i.categoria_slug).color, kind:'item', id:i.codigo_interno}));\n    const prs = produtos.filter(p=>p.marca_slug===f.id).slice(0,3).map(p=>({label:trunc(p.nome,22), color:'#9aa0a6', kind:'produto', id:p.slug}));\n    return {center, nodes: its.concat(prs), crumb:m.nome};\n  }\n  const it = findBy(itens,'codigo_interno',f.id);\n  if(!it) return {center:{label:'—',color:'#999',kind:'root'}, nodes:[], crumb:''};\n  const center = {label:trunc(it.titulo,24), color:catOf(it.categoria_slug).color, kind:'item', id:it.codigo_interno};\n  const nodes = [];\n  if(it.marca_slug){ const m=findBy(marcas,'slug',it.marca_slug); nodes.push({label:m?m.nome:it.marca_slug, color:m?m.cor:'#999', kind:'marca', id:it.marca_slug}); }\n  if(it.produto_slug){ const p=findBy(produtos,'slug',it.produto_slug); nodes.push({label:p?trunc(p.nome,20):it.produto_slug, color:'#9aa0a6', kind:'produto', id:it.produto_slug}); }\n  if(it.ciclo_codigo) nodes.push({label:'Ciclo '+it.ciclo_codigo, color:'#C77D4A', kind:'ciclo', id:it.ciclo_codigo});\n  if(it.campanha_slug){ const c=findBy(campanhas,'slug',it.campanha_slug); nodes.push({label:c?trunc(c.nome,20):it.campanha_slug, color:'#8E6FB0', kind:'campanha', id:it.campanha_slug}); }\n  itens.filter(x=>x.marca_slug===it.marca_slug && x.codigo_interno!==it.codigo_interno).slice(0,4)\n    .forEach(x=> nodes.push({label:trunc(x.titulo,20), color:catOf(x.categoria_slug).color, kind:'item', id:x.codigo_interno}));\n  return {center, nodes, crumb: it.titulo};\n};\nconst grafoPage = (d2)=>{\n  const m = graphModel(d2);\n  const f = d2.state.gfocus || {type:'root'};\n  const cx=550, cy=280, Rx=360, Ry=195, N=m.nodes.length;\n  const pos=(i)=>{ const a=(-90+i*(360/Math.max(1,N)))*Math.PI/180; return [cx+Rx*Math.cos(a), cy+Ry*Math.sin(a)]; };\n  const edges = m.nodes.map((n,i)=>{ const [x,y]=pos(i); return `<line x1=\"${cx}\" y1=\"${cy}\" x2=\"${x}\" y2=\"${y}\" class=\"edge\"/>`; }).join('');\n  const node = (n,x,y,r)=>`<g class=\"gnode\" data-gkind=\"${n.kind}\" data-gid=\"${esc(n.id||'')}\" transform=\"translate(${x},${y})\">\n    <circle r=\"${r}\" fill=\"${n.color}\"/><circle r=\"${r}\" class=\"ring\"/>\n    <text class=\"gt\" y=\"4\">${esc(trunc(n.label,r>50?18:13))}</text></g>`;\n  const nodesSvg = m.nodes.map((n,i)=>{ const [x,y]=pos(i); return node(n, x, y, 46); }).join('');\n  const focusItem = f.type==='item';\n  return `<div class=\"grafo\">\n    <div class=\"g-ctrls\">\n      <span class=\"g-crumb\">🕸️ ${esc(m.crumb||'')}</span>\n      ${f.type!=='root'?'<button class=\"gback\">← Voltar ao mapa</button>':''}\n      ${focusItem?`<button class=\"gficha\" data-codigo=\"${esc(f.id)}\">Abrir ficha →</button>`:''}\n      <span class=\"g-hint\">Clique numa marca ou item para navegar as conexões</span>\n    </div>\n    <svg class=\"g-svg\" viewBox=\"0 0 1100 560\" preserveAspectRatio=\"xMidYMid meet\">\n      ${edges}${nodesSvg}\n      ${node(m.center, cx, cy, 58)}\n    </svg>\n  </div>`;\n};\n\nconst aprovarPage = (d2)=>{\n  const list = preOf(d2).slice().reverse();\n  if(!list.length) return `<div class=\"empty\">Nenhuma solicitação de pré-catálogo no momento.<br/>As sugestões enviadas na tela pública (<b>/enviar</b>) aparecem aqui para aprovação.</div>`;\n  return `<div class=\"aprov\">\n    <div class=\"aprov-intro\">${list.length} ${list.length===1?'solicitação':'solicitações'} aguardando aprovação — enviadas pela tela pública.</div>\n    ${list.map(p=>{\n      const c = catOf(p.categoria_slug);\n      const tags = String(p.tags_texto||'').split(',').map(s=>s.trim()).filter(Boolean);\n      const nmid = Array.isArray(p.midias)?p.midias.length:(isUrl(p.imagem_ref)?1:0);\n      const meta = [p.marca_slug&&('marca: '+p.marca_slug), p.ciclo_codigo&&('ciclo '+p.ciclo_codigo), p.tombo, p.origem, nmid?('🖼 '+nmid+(nmid===1?' mídia':' mídias')):''].filter(Boolean).map(esc).join(' · ');\n      return `<div class=\"aprov-card\" data-key=\"${p._id}\">\n        <div class=\"aprov-main\">\n          <div class=\"aprov-cat\" style=\"--c:${c.color}\">${c.glyph} ${esc(c.nome)}</div>\n          <div class=\"aprov-t\">${esc(p.titulo||'(sem título)')}</div>\n          <div class=\"aprov-meta\">${meta||'—'}</div>\n          ${p.descricao?`<div class=\"aprov-desc\">${esc(p.descricao)}</div>`:''}\n          ${tags.length?`<div class=\"aprov-tags\">${tags.map(t=>`<span class=\"tag\">${esc(t)}</span>`).join('')}</div>`:''}\n          <div class=\"aprov-by\">Enviado por: <b>${esc(p.enviado_por||'anônimo')}</b>${p.observacao?` — “${esc(p.observacao)}”`:''}</div>\n        </div>\n        <div class=\"aprov-actions\">\n          <button class=\"aprovar-item\" data-id=\"${p._id}\">✓ Aprovar</button>\n          <button class=\"rejeitar-item\" data-id=\"${p._id}\">✕ Rejeitar</button>\n          <button class=\"aprov-detalhe\" data-id=\"${p._id}\" style=\"background:#fff;border:1px solid #e6e6ea;border-radius:9px;padding:8px 14px;font-size:13px;font-weight:600;color:#5b6070;cursor:pointer\">👁 Ver detalhes</button>\n        </div>\n      </div>`;\n    }).join('')}\n    ${(function(){\n      const p = findBy(list,'_id',d2.state.preDetail); if(!p) return '';\n      const c = catOf(p.categoria_slug);\n      const rows = [['Título',p.titulo],['Categoria',c.nome],['Marca',p.marca_slug],['Produto',p.produto_slug],['Ciclo',p.ciclo_codigo],['Campanha',p.campanha_slug],['Nº de Tombo',p.tombo],['Origem',p.origem],['Localização',p.localizacao],['Tags',p.tags_texto],['Sigiloso',p.sigiloso?'Sim':'Não'],['Enviado por',p.enviado_por],['Observação',p.observacao],['Status',p.status||'pendente']];\n      const fld = rows.map(r=>`<div style=\"display:flex;gap:10px;padding:7px 0;border-bottom:1px solid #f2f2f4\"><div style=\"width:120px;flex:0 0 120px;font-size:11px;font-weight:700;color:#a0a4b0;text-transform:uppercase\">${esc(r[0])}</div><div style=\"flex:1;font-size:13.5px;color:#23262e\">${(r[1]!=null&&String(r[1]).trim()!=='')?esc(r[1]):'<span style=\"color:#c3c7d0\">— não preenchido</span>'}</div></div>`).join('');\n      const pmids = (Array.isArray(p.midias)&&p.midias.length) ? p.midias : (isUrl(p.imagem_ref)?[{ref:p.imagem_ref, tipo:'imagem'}]:[]);\n      const imgBox = pmids.length ? pmids.map((m,i)=>`<div style=\"margin-bottom:7px;position:relative\">${m.tipo==='video'?`<video src=\"${esc(m.ref)}\" controls preload=\"metadata\" style=\"width:100%;border-radius:10px;background:#000\"></video>`:`<img src=\"${esc(m.ref)}\" style=\"width:100%;border-radius:10px;border:1px solid #eee\"/>`}${i===0?'<span style=\"position:absolute;top:5px;left:5px;background:rgba(20,24,40,.72);color:#fff;font-size:9.5px;font-weight:700;padding:2px 7px;border-radius:999px\">capa</span>':''}</div>`).join('') : `<div style=\"width:100%;height:150px;border:1px dashed #dcdfe4;border-radius:10px;display:flex;align-items:center;justify-content:center;color:#c3c7d0;font-size:13px\">Sem imagem</div>`;\n      return `<div class=\"pre-modal-ov\" style=\"position:fixed;inset:0;background:rgba(20,24,40,.45);display:flex;align-items:center;justify-content:center;z-index:9999;padding:24px\"><div style=\"background:#fff;border-radius:16px;max-width:640px;width:100%;max-height:88vh;overflow-y:auto;box-shadow:0 24px 70px rgba(0,0,0,.3);padding:22px 24px\"><div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:14px\"><div style=\"font-size:17px;font-weight:750;color:#23262e\">Detalhes da solicitação</div><button class=\"pre-modal-close\" style=\"background:#f4f4f6;border:0;border-radius:8px;width:30px;height:30px;font-size:15px;cursor:pointer;color:#5b6070\">✕</button></div><div style=\"display:flex;gap:18px;flex-wrap:wrap\"><div style=\"flex:0 0 200px;max-width:200px\"><div style=\"font-size:11px;font-weight:700;color:#a0a4b0;text-transform:uppercase;margin-bottom:6px\">Mídias${pmids.length?' ('+pmids.length+')':''}</div>${imgBox}${p.imagem_ref?`<div style=\"font-size:10.5px;color:#9aa0a6;margin-top:6px;word-break:break-all\">${esc(p.imagem_ref)}</div>`:''}</div><div style=\"flex:1;min-width:260px\">${fld}</div></div><div style=\"margin-top:14px\"><div style=\"font-size:11px;font-weight:700;color:#a0a4b0;text-transform:uppercase;margin-bottom:4px\">Descrição</div><div style=\"font-size:13.5px;line-height:1.5;color:${p.descricao?'#23262e':'#c3c7d0'}\">${p.descricao?esc(p.descricao):'— não preenchido'}</div></div><div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\"><button class=\"rejeitar-item\" data-id=\"${p._id}\" style=\"background:#fff;border:1px solid #e6e6ea;border-radius:9px;padding:9px 16px;font-size:13px;font-weight:600;color:#c0392b;cursor:pointer\">✕ Rejeitar</button><button class=\"aprovar-item\" data-id=\"${p._id}\" style=\"background:#1f9d55;border:0;border-radius:9px;padding:9px 16px;font-size:13px;font-weight:700;color:#fff;cursor:pointer\">✓ Aprovar</button></div></div></div>`;\n    })()}\n  </div>`;\n};\nconst chatWidget = (d2)=>{\n  const open = !!d2.state.chatOpen;\n  const a = d2.assistant('chat');\n  const msgs = d2.state.messages || [];\n  const sug = ['Como a comunicação do Ekos evoluiu?','O que temos sobre os 50 anos da Natura?','Quais itens falam da Amazônia?'];\n  const live = a && a.busy ? `<div class=\"msg ai\">${esc(a.answer||'…')}</div>` : '';\n  const body = msgs.length ? msgs.map((m,i)=>{ const b=`<div class=\"msg ${m.role==='user'?'me':'ai'}\" data-key=\"m${i}\">${esc(m.text)}</div>`; return (m.acoes&&m.acoes.length)? b+`<div class=\"chat-acts\" data-key=\"acts${i}\">${m.acoes.map((ac,j)=>`<button class=\"chat-go\" data-t=\"${esc(ac.tipo)}\" data-codigo=\"${esc(ac.codigo||'')}\" data-pagina=\"${esc(ac.pagina||'')}\" data-key=\"a${i}_${j}\">${esc(ac.label||'Abrir')} →</button>`).join('')}</div>` : b; }).join('')+live\n    : `<div class=\"chat-welcome\"><div class=\"cw-g\">💬</div><div class=\"cw-t\">Converse com o acervo</div><div class=\"cw-s\">Pergunte sobre história, marcas, produtos e campanhas da Natura — respondo citando os itens.</div><div class=\"sugs\">${sug.map(s=>`<div class=\"suggest\" data-q=\"${esc(s)}\">${esc(s)}</div>`).join('')}</div></div>`+live;\n  const panel = open ? `<div class=\"chatw-panel\">\n    <div class=\"chatw-head\"><span class=\"chatw-title\"><span class=\"chatw-dot\"></span>Converse com o acervo</span><button class=\"chatw-min\" title=\"Fechar\">–</button></div>\n    <div class=\"chatw-log log\">${body}</div>\n    <div class=\"chatw-bar\"><input class=\"chatin\" placeholder=\"Pergunte ao acervo…\" ${a&&a.busy?'disabled':''}/><button class=\"send\" ${a&&a.busy?'disabled':''}>➤</button></div>\n  </div>` : '';\n  return `<div class=\"chatw\">${panel}<button class=\"chatw-fab\">${open?'✕ Fechar':'💬 Converse com o acervo'}</button></div>`;\n};\nconst expoListPage = (d2)=>{\n  const list = exposOf(d2).slice().reverse();\n  const rows = list.length ? list.map(e=>{\n    const n = Array.isArray(e.itens)?e.itens.length:0;\n    const pub = e.status==='publicada';\n    return `<div class=\"expo-row\" data-key=\"${e._id}\">\n      <div class=\"expo-row-main\"><div class=\"expo-row-t\">${esc(e.titulo)}</div><div class=\"expo-row-m\">${esc(e.subtitulo||e.tema||'')}</div></div>\n      <span class=\"expo-badge ${pub?'pub':'rasc'}\">${pub?'publicada':'rascunho'}</span>\n      <span class=\"expo-count\">${n} ${n===1?'peça':'peças'}</span>\n      <div class=\"expo-row-acts\"><button class=\"expo-editar\" data-slug=\"${esc(e.slug)}\">Editar</button>${pub?`<button class=\"expo-ver\" data-slug=\"${esc(e.slug)}\">Ver</button><button class=\"expo-copiar\" data-slug=\"${esc(e.slug)}\">Copiar link</button>`:''}</div>\n    </div>`;\n  }).join('') : `<div class=\"empty\">Nenhuma exposição ainda. Crie a primeira em “+ Nova exposição”.</div>`;\n  return `<div class=\"expo-mgr\">\n    <div class=\"expo-mgr-top\"><button class=\"expo-novo\">+ Nova exposição</button>${d2.state.expoMsg?`<span class=\"cat-ok\">${esc(d2.state.expoMsg)}</span>`:''}</div>\n    <div class=\"expo-list\">${rows}</div>\n  </div>`;\n};\nconst expoEditorPage = (d2)=>{\n  const dr = d2.state.expoDraft || {itens:[]};\n  const acervo = recs(d2,'itens');\n  const byCode = {}; acervo.forEach(r=> byCode[r.codigo_interno]=r);\n  const itens = Array.isArray(dr.itens)?dr.itens:[];\n  const opts = acervo.slice().sort((a,b)=> String(a.titulo||'').localeCompare(String(b.titulo||''))).map(r=>`<option value=\"${esc(r.codigo_interno)}\">${esc(r.titulo)}</option>`).join('');\n  const entryHtml = (it,i)=>{\n    const ord = `<span class=\"ei-ord\"><button class=\"expo-item-up\" data-idx=\"${i}\" ${i===0?'disabled':''}>↑</button><button class=\"expo-item-down\" data-idx=\"${i}\" ${i===itens.length-1?'disabled':''}>↓</button><button class=\"expo-item-rm\" data-idx=\"${i}\">✕</button></span>`;\n    if(it.tipo==='texto'){\n      const busy = d2.state.etIaBusy===i;\n      return `<div class=\"ei ei-txt\" data-key=\"et${i}\"><div class=\"ei-h\"><span class=\"ei-badge et-badge\">✎</span><span class=\"ei-t\">Etapa de texto — transição</span>${ord}</div><input class=\"et-title\" data-idx=\"${i}\" value=\"${esc(it.titulo||'')}\" placeholder=\"Título da etapa (opcional)\"/><textarea class=\"et-tx\" data-idx=\"${i}\" rows=\"3\" placeholder=\"Texto de transição para aprofundar a narrativa…\">${esc(it.texto||'')}</textarea><button class=\"et-ia\" data-idx=\"${i}\" ${busy?'disabled':''}>${busy?'Gerando…':'✨ Gerar texto com IA'}</button></div>`;\n    }\n    const item = byCode[it.codigo]||{}; const cat = catOf(item.categoria_slug);\n    return `<div class=\"ei\" data-key=\"ei${i}\"><div class=\"ei-h\"><span class=\"ei-badge\" style=\"--c:${cat.color}\">${cat.glyph}</span><span class=\"ei-t\">${esc(item.titulo||it.codigo)}</span>${ord}</div><textarea class=\"ei-tx\" data-idx=\"${i}\" rows=\"2\" placeholder=\"Texto curatorial desta peça…\">${esc(it.texto||'')}</textarea></div>`;\n  };\n  const insLine = (at)=> `<div class=\"ins-row\"><button class=\"expo-ins-txt\" data-at=\"${at}\">+ etapa de texto aqui</button></div>`;\n  const itemRows = itens.length ? (insLine(0)+itens.map((it,i)=> entryHtml(it,i)+insLine(i+1)).join('')) : `<div class=\"ei-empty\">Nada no roteiro ainda. Adicione itens do acervo, etapas de texto, ou use “Montar com IA”.</div>`;\n  return `<div class=\"expo-ed\">\n    <div class=\"expo-ed-top\"><button class=\"expo-voltar\">← Exposições</button>${dr.slug&&dr.status==='publicada'?`<button class=\"expo-ver\" data-slug=\"${esc(dr.slug)}\">Ver</button><button class=\"expo-copiar\" data-slug=\"${esc(dr.slug)}\">Copiar link</button>`:''}</div>\n    <div class=\"expo-ed-grid\">\n      <div class=\"ee-f cf-full\"><label>Título *</label><input class=\"ex-in\" data-k=\"titulo\" value=\"${esc(dr.titulo||'')}\"/></div>\n      <div class=\"ee-f\"><label>Subtítulo</label><input class=\"ex-in\" data-k=\"subtitulo\" value=\"${esc(dr.subtitulo||'')}\"/></div>\n      <div class=\"ee-f\"><label>Status</label><select class=\"ex-in\" data-k=\"status\"><option value=\"rascunho\" ${dr.status!=='publicada'?'selected':''}>Rascunho</option><option value=\"publicada\" ${dr.status==='publicada'?'selected':''}>Publicada</option></select></div>\n      <div class=\"ee-f cf-full\"><label>Tema (guia a IA)</label><input class=\"ex-in\" data-k=\"tema\" value=\"${esc(dr.tema||'')}\" placeholder=\"ex.: Ekos e a Amazônia\"/></div>\n      <div class=\"ee-f cf-full ia-row\"><button class=\"expo-ia\" ${d2.state.expoBusy?'disabled':''}>${d2.state.expoBusy?'Montando com IA…':'✨ Montar com IA (a partir do tema)'}</button>${d2.state.expoIaErr?`<span class=\"ai-err-inline\">${esc(d2.state.expoIaErr)}</span>`:''}</div>\n      <div class=\"ee-f cf-full\"><label>Texto de curadoria (abertura)</label><textarea class=\"ex-in\" data-k=\"curadoria\" rows=\"4\">${esc(dr.curadoria||'')}</textarea></div>\n    </div>\n    <div class=\"ee-items-head\"><span>Roteiro da exposição (${itens.length})</span><span class=\"ee-add\"><button class=\"expo-add-txt\">+ Etapa de texto</button><select class=\"expo-add-sel\"><option value=\"\">+ Adicionar item do acervo…</option>${opts}</select></span></div>\n    <div class=\"ee-items\">${itemRows}</div>\n    <div class=\"expo-ed-actions\"><button class=\"expo-salvar\">Salvar exposição</button>${dr._id?(d2.state.expoDelConfirm?`<span class=\"del-confirm2\">Excluir esta exposição? <button class=\"expo-del-yes\">Sim</button><button class=\"expo-del-no\">Não</button></span>`:`<button class=\"expo-excluir2\">Excluir</button>`):''}${d2.state.expoMsg?`<span class=\"cat-ok\">${esc(d2.state.expoMsg)}</span>`:''}</div>\n  </div>`;\n};\nconst homePage = (d2)=>{\n  const itens = recs(d2,'itens');\n  const cats  = recs(d2,'categorias').slice().sort((a,b)=>(a.ordem||0)-(b.ordem||0));\n  const pend  = preOf(d2);\n  const expos = exposOf(d2);\n  const N = itens.length, T = N||1;\n  const nImg   = itens.filter(i=>isUrl(i.imagem_ref)).length;\n  const nDesc  = itens.filter(i=>String(i.descricao||'').trim()!=='').length;\n  const nTombo = itens.filter(i=>String(i.tombo||'').trim()!=='').length;\n  const nCiclo = itens.filter(i=>String(i.ciclo_codigo||'').trim()!=='').length;\n  const nSig   = itens.filter(i=>i.sigiloso===true).length;\n  const pub  = expos.filter(e=>e.status==='publicada');\n  const rasc = expos.filter(e=>e.status!=='publicada');\n  const pct = (n)=> Math.round((n/T)*100);\n  const hora = (function(){ try{ const h=new Date().getHours(); return h<12?'Bom dia':(h<18?'Boa tarde':'Boa noite'); }catch(_){ return 'Olá'; } })();\n  let quem=''; try{ const u=d2.auth('mv-login').user; quem=(u&&(u.nome||u.name||u.email))||''; }catch(e){}\n  quem = quem ? String(quem).split('@')[0] : '';\n  const hero = `<div class=\"hhero\"><div><div class=\"hh-t\">${hora}${quem?', '+esc(quem):''} 👋</div><div class=\"hh-s\">${N} itens catalogados · ${cats.length} categorias · ${pub.length} ${pub.length===1?'exposição publicada':'exposições publicadas'}</div></div><div class=\"hh-actions\"><div class=\"hh-search\"><span>🔍</span><input class=\"home-q\" placeholder=\"Buscar no acervo e abrir…\"/></div><button class=\"home-chat\">💬 Pergunte ao acervo</button></div></div>`;\n  const kpi = (v,l,view,gap,warn)=>`<div class=\"hk ${warn?'warn':''}\" data-view=\"${view||''}\" data-gap=\"${gap||''}\"><div class=\"hk-v\">${v}</div><div class=\"hk-l\">${esc(l)}</div></div>`;\n  const kpis = `<div class=\"hkpis\">${kpi(N,'itens no acervo','acervo','')}${kpi(cats.length,'categorias','acervo','')}${kpi(pub.length,pub.length===1?'exposição publicada':'exposições publicadas','exposicoes','')}${kpi(pend.length,pend.length===1?'aprovação pendente':'aprovações pendentes','aprovar','',pend.length>0)}</div>`;\n  const task = (n,label,hint,gap,view)=> n>0 ? `<div class=\"ht\" data-gap=\"${gap||''}\" data-view=\"${view||''}\"><span class=\"ht-n\">${n}</span><div class=\"ht-b\"><div class=\"ht-t\">${esc(label)}</div><div class=\"ht-h\">${esc(hint)}</div></div><span class=\"ht-go\">→</span></div>` : '';\n  const tarefas = [\n    task(pend.length, pend.length===1?'solicitação no pré-catálogo':'solicitações no pré-catálogo','Enviadas pela tela pública — aprovar ou rejeitar','','aprovar'),\n    task(N-nImg,'itens sem imagem','Abrir o acervo já filtrado nesses itens','sem-imagem','acervo'),\n    task(N-nCiclo,'itens sem ciclo informado','Completar o vínculo com o ciclo da campanha','sem-ciclo','acervo'),\n    task(N-nDesc,'itens sem descrição','Revisar a ficha — a IA pode ajudar a redigir','sem-descricao','acervo'),\n    task(rasc.length,rasc.length===1?'exposição em rascunho':'exposições em rascunho','Terminar a curadoria e publicar','','exposicoes'),\n    task(nSig,'itens sigilosos','Requerem cuidado no acesso e no manuseio','sigiloso','acervo')\n  ].filter(Boolean).join('');\n  const tarefasHtml = tarefas || `<div class=\"h-ok\"><b>✅</b><div style=\"margin-top:7px\">Nada pendente — o acervo está em ordem.</div></div>`;\n  const bar = (label,n)=>`<div class=\"hb\"><div class=\"hb-h\"><span>${esc(label)}</span><b>${pct(n)}%</b></div><div class=\"hb-t\"><i style=\"width:${pct(n)}%\"></i></div></div>`;\n  const catRows = cats.map(c=>{\n    const arr = itens.filter(i=>i.categoria_slug===c.slug);\n    const ok = arr.filter(i=>isUrl(i.imagem_ref)).length;\n    return `<div class=\"hc ${(arr.length&&!ok)?'zero':''}\" data-cat=\"${esc(c.slug)}\"><span class=\"hc-g\">${catOf(c.slug).glyph}</span><span class=\"hc-n\">${esc(c.nome)}</span><span class=\"hc-v\">${ok}/${arr.length}</span></div>`;\n  }).join('');\n  const last = itens.slice().sort((a,b)=> String(b.createdAt||'').localeCompare(String(a.createdAt||''))).slice(0,4);\n  const lastHtml = last.length ? last.map(r=>`<div class=\"hi\" data-key=\"hi${esc(r.codigo_interno)}\" data-codigo=\"${esc(r.codigo_interno)}\"><div class=\"hi-img\">${phTile(r.categoria_slug,r.tombo,r.imagem_ref)}</div><div class=\"hi-t\">${esc(trunc(r.titulo,44))}</div></div>`).join('') : `<div class=\"hi-t\" style=\"color:#a0a4b0\">Nada catalogado ainda.</div>`;\n  const dest = N ? itens[(d2.state.destaque||0)%N] : null;\n  const destHtml = dest ? `<div class=\"hdest\"><div class=\"hdest-img\">${phTile(dest.categoria_slug,dest.tombo,dest.imagem_ref)}</div><div class=\"hdest-b\"><div class=\"hdest-t\">${esc(trunc(dest.titulo,60))}</div><div class=\"hdest-d\">${esc(trunc(dest.descricao||catOf(dest.categoria_slug).nome,110))}</div><div class=\"hdest-acts\"><button class=\"home-dest-ficha\" data-codigo=\"${esc(dest.codigo_interno)}\">Abrir ficha</button><button class=\"home-dest-outro\">Outro item</button></div></div></div>` : `<div class=\"hi-t\" style=\"color:#a0a4b0\">Acervo vazio.</div>`;\n  const expoHtml = pub.length ? pub.slice(0,3).map(e=>`<div class=\"hexpo\"><div class=\"hexpo-b\"><div class=\"hexpo-t\">${esc(trunc(e.titulo,46))}</div><div class=\"hexpo-m\">${Array.isArray(e.itens)?e.itens.length:0} etapas · link público</div></div><button class=\"expo-ver\" data-slug=\"${esc(e.slug)}\">Ver</button><button class=\"expo-copiar\" data-slug=\"${esc(e.slug)}\">Copiar link</button></div>`).join('') : `<div class=\"hi-t\" style=\"color:#a0a4b0\">Nenhuma exposição publicada ainda.</div>`;\n  const atalhos = `<div class=\"hacts\"><button class=\"home-go hact\" data-view=\"catalogar\">✨ Catalogar com IA</button><button class=\"expo-novo hact\">🖼️ Nova exposição</button><button class=\"home-copiar-pub hact\">🔗 ${d2.state.copiadoHome?'Link copiado ✓':'Copiar link público'}</button><button class=\"home-go hact\" data-view=\"grafo\">🕸️ Grafo de conexões</button><button class=\"home-go hact\" data-view=\"acervo\">🗃️ Ver todo o acervo</button></div>`;\n  return `<div class=\"home\">${hero}${kpis}\n    <div class=\"hgrid\">\n      <div class=\"hcol\">\n        <div class=\"hcard\"><div class=\"hcard-t\">⚡ Precisa de você <small>o que fazer agora</small></div>${tarefasHtml}</div>\n        <div class=\"hcard\"><div class=\"hcard-t\">🕐 Últimos catalogados</div><div class=\"hlast\">${lastHtml}</div></div>\n        <div class=\"hcard\"><div class=\"hcard-t\">🖼️ Exposições virtuais</div>${expoHtml}</div>\n      </div>\n      <div class=\"hcol\">\n        <div class=\"hcard\"><div class=\"hcard-t\">📊 Saúde do acervo <small>completude dos registros</small></div>${bar('Com imagem',nImg)}${bar('Com descrição',nDesc)}${bar('Com nº de tombo',nTombo)}${bar('Com ciclo',nCiclo)}<div class=\"hcats\"><div class=\"hcats-t\">Imagens por categoria</div>${catRows}</div></div>\n        <div class=\"hcard\"><div class=\"hcard-t\">✨ Redescubra o acervo</div>${destHtml}</div>\n      </div>\n    </div>\n    ${atalhos}\n  </div>`;\n};\n\nconst page = (d2,view)=>{\n  try{\n    if(view==='inicio') return homePage(d2);\n    if(view==='ficha') return fichaPage(d2);\n    if(view==='catalogar') return catalogarPage(d2);\n    if(view==='grafo') return grafoPage(d2);\n    if(view==='aprovar') return aprovarPage(d2);\n    if(view==='exposicoes') return d2.state.expoMode==='edit' ? expoEditorPage(d2) : expoListPage(d2);\n    return acervoPage(d2);\n  }catch(err){ return '<div class=\"empty\">ERRO ('+esc(view)+'): '+esc(err&&err.stack||err&&err.message||err)+'</div>'; }\n};\n\nexport default {\n  css: `\n  *{box-sizing:border-box}\n  .app{display:grid;grid-template-columns:250px 1fr;height:100%;position:relative;background:#fff;color:#23262e;\n    font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased}\n  .side{background:linear-gradient(180deg,#ffffff,#fbfaf9);border-right:1px solid #ececef;display:flex;flex-direction:column;padding:22px 16px;box-sizing:border-box;height:100%;overflow-y:auto}\n  .brand{display:flex;align-items:center;gap:11px;margin-bottom:26px;padding:0 6px}\n  .brand .dot{width:30px;height:30px;border-radius:9px;background:${ACCENT};box-shadow:0 4px 12px rgba(244,134,70,.35);flex:0 0 auto}\n  .brand .bt{font-weight:750;font-size:16px;letter-spacing:-.01em}\n  .brand .bs{font-size:11.5px;color:#9096a4;margin-top:1px}\n  .nav{display:flex;flex-direction:column;gap:3px}\n  .nav-i{display:flex;align-items:center;gap:10px;font-size:13.5px;font-weight:550;color:#4b5160;padding:9px 11px;border-radius:9px;cursor:pointer;transition:.12s}\n  .nav-i .ng{font-size:15px}\n  .nav-i:hover{background:#fff2ea;color:${ACCENT}}\n  .nav-i.on{background:#fff2ea;color:${ACCENT};box-shadow:inset 3px 0 0 ${ACCENT}}\n  .side-foot{margin-top:28px;padding:14px 8px 4px;border-top:1px solid #ececef}\n  .sf-k{font-size:26px;font-weight:800;color:#23262e}\n  .sf-l{font-size:11.5px;color:#9096a4}\n  .sf-tag{margin-top:10px;display:inline-block;font-size:10.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:${ACCENT};background:#fff2ea;padding:4px 9px;border-radius:999px}\n  .sf-user{margin-top:12px;font-size:11.5px;color:#6b7280;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .sf-logout{margin-top:8px;width:100%;background:#fff;border:1px solid #e6e6ea;border-radius:9px;padding:8px;font-size:12.5px;font-weight:600;color:#6b7280;cursor:pointer}\n  .sf-logout:hover{border-color:${ACCENT};color:${ACCENT}}\n  .main{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}\n  .top{padding:20px 30px 16px;border-bottom:1px solid #f0f0f2}\n  .tt{font-size:20px;font-weight:750;letter-spacing:-.01em}\n  .tsub{font-size:12.5px;color:#9096a4;margin-top:2px}\n  .content{flex:1;min-height:0;overflow-y:auto;padding:24px 30px}\n  .empty{color:#9096a4;font-size:14px;padding:40px;text-align:center}\n  /* acervo */\n  .search-wrap{position:relative;margin-bottom:16px}\n  .search-wrap .si{position:absolute;left:14px;top:50%;transform:translateY(-50%);opacity:.5;font-size:14px}\n  .search{width:100%;box-sizing:border-box;padding:12px 14px 12px 38px;border:1px solid #e6e6ea;border-radius:11px;font-size:14px;outline:none;background:#fff}\n  .search:focus{border-color:${ACCENT};box-shadow:0 0 0 3px #fff2ea}\n  .chips{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px}\n  .chip{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;color:#5b6070;background:#fff;border:1px solid #e6e6ea;padding:6px 12px;border-radius:999px;cursor:pointer;transition:.12s}\n  .chip .cg{font-size:13px}.chip b{color:#9096a4;font-weight:700}\n  .chip:hover{border-color:#d7c0b2}\n  .chip.on{background:${ACCENT};border-color:${ACCENT};color:#fff}.chip.on b{color:#fff}\n  .count{font-size:12px;color:#9096a4;margin-bottom:12px}\n  .acervo-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}\n  .acervo-bar .count{margin-bottom:0}\n  .exportar{background:#fff;border:1px solid #e6e6ea;border-radius:9px;padding:8px 14px;font-size:12.5px;font-weight:650;color:#5b6070;cursor:pointer;font-family:inherit;white-space:nowrap}\n  .exportar:hover{border-color:${ACCENT};color:${ACCENT}}\n  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(212px,1fr));gap:16px}\n  .card{border:1px solid #ececef;border-radius:14px;overflow:hidden;cursor:pointer;background:#fff;transition:.15s}\n  .card:hover{box-shadow:0 8px 24px rgba(20,24,40,.10);transform:translateY(-2px);border-color:#e0e0e4}\n  .card-img{position:relative;height:128px}\n  .ph{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;\n    background:linear-gradient(135deg,color-mix(in srgb,var(--c) 22%,#fff),color-mix(in srgb,var(--c) 42%,#fff))}\n  .ph-g{font-size:34px;filter:saturate(.9)}\n  .ph-t{font-size:10.5px;font-weight:700;letter-spacing:.04em;color:color-mix(in srgb,var(--c) 65%,#333);text-transform:uppercase}\n  .ph-img{width:100%;height:100%;object-fit:cover;display:block}\n  .sig{position:absolute;top:8px;right:8px;background:#c0392b;color:#fff;font-size:9.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;padding:3px 7px;border-radius:6px}\n  .card-b{padding:11px 13px 13px}\n  .card-t{font-size:13.5px;font-weight:650;line-height:1.3;color:#23262e}\n  .card-m{font-size:11.5px;color:#9096a4;margin-top:5px}\n  /* ficha */\n  .ficha-head{display:flex;align-items:center;gap:12px;margin-bottom:14px}\n  .back,.tograph,.gback,.gficha,.up-btn,.ai-run,.ai-save,.send{font-family:inherit}\n  .back{background:#fff;border:1px solid #e6e6ea;border-radius:9px;padding:7px 12px;font-size:12.5px;font-weight:600;color:#5b6070;cursor:pointer}\n  .excluir{margin-left:auto;background:#fff;border:1px solid #f0d0cb;border-radius:9px;padding:7px 12px;font-size:12.5px;font-weight:600;color:#c0392b;cursor:pointer}\n  .excluir:hover{background:#fdf1ef;border-color:#c0392b}\n  .del-confirm{margin-left:auto;display:inline-flex;align-items:center;gap:8px}\n  .del-q{font-size:12.5px;color:#c0392b;font-weight:600}\n  .del-yes{background:#c0392b;color:#fff;border:0;border-radius:9px;padding:7px 12px;font-size:12.5px;font-weight:700;cursor:pointer}\n  .del-no{background:#fff;border:1px solid #e6e6ea;border-radius:9px;padding:7px 12px;font-size:12.5px;font-weight:600;color:#5b6070;cursor:pointer}\n  .back:hover{border-color:${ACCENT};color:${ACCENT}}\n  .cat-chip{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:650;color:color-mix(in srgb,var(--c) 70%,#333);background:color-mix(in srgb,var(--c) 14%,#fff);padding:5px 11px;border-radius:999px}\n  .sig-inline{position:static}\n  .ficha-t{font-size:24px;font-weight:780;letter-spacing:-.02em;margin:0}\n  .ficha-sub{font-size:12.5px;color:#9096a4;margin:4px 0 20px}\n  .ficha-cols{display:grid;grid-template-columns:1.35fr 1fr;gap:28px;align-items:start}\n  .ficha-fields{display:grid;grid-template-columns:1fr 1fr;gap:14px 22px}\n  .fld-full{grid-column:1/-1}\n  .fl{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#a0a4b0;margin-bottom:3px}\n  .fv{font-size:13.5px;color:#2b2f39;line-height:1.45}\n  .rels{display:flex;flex-wrap:wrap;gap:7px}\n  .rel{font-size:12px;color:#5b6070;background:#f6f6f8;border:1px solid #ececef;padding:4px 9px;border-radius:8px}.rel b{color:#23262e}\n  .tag{font-size:11px;color:${ACCENT};background:#fff2ea;padding:3px 8px;border-radius:7px;font-weight:600}\n  .tograph{margin-top:8px;grid-column:1/-1;justify-self:start;background:${ACCENT};color:#fff;border:0;border-radius:10px;padding:10px 16px;font-size:13px;font-weight:650;cursor:pointer;box-shadow:0 6px 16px rgba(244,134,70,.3)}\n  .ficha-media{border:1px solid #ececef;border-radius:14px;padding:14px;background:#fbfbfc}\n  .media-main{height:230px;border-radius:10px;overflow:hidden}\n  .media-cap{font-size:12px;color:#7c8494;margin-top:9px}\n  .thumbs{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap}\n  .thumb{width:60px;height:60px;border-radius:8px;overflow:hidden;border:1px solid #ececef}\n  .thumb .ph-g{font-size:20px}.thumb .ph-t{display:none}\n  .media-note{font-size:11px;color:#a0a4b0;margin-top:12px;line-height:1.4}\n  /* catalogar */\n  .cat{display:grid;grid-template-columns:1fr 1fr;gap:26px;align-items:start;max-width:900px}\n  .up{height:300px;border:2px dashed #e2d3c8;border-radius:14px;overflow:hidden;background:#fbfaf9;display:flex;align-items:center;justify-content:center}\n  .up-img{width:100%;height:100%;object-fit:contain}\n  .up-empty{text-align:center;color:#a99;font-size:13px;line-height:1.5}.up-g{font-size:40px;margin-bottom:8px}\n  .up-btn{display:inline-block;margin:14px 10px 0 0;background:#fff;border:1px solid #e6e6ea;border-radius:10px;padding:10px 16px;font-size:13px;font-weight:600;color:#5b6070;cursor:pointer}\n  .up-btn:hover{border-color:${ACCENT};color:${ACCENT}}\n  .ai-run{margin-top:14px;background:${ACCENT};color:#fff;border:0;border-radius:10px;padding:10px 18px;font-size:13px;font-weight:650;cursor:pointer;box-shadow:0 6px 16px rgba(244,134,70,.3)}\n  .ai-run[disabled]{opacity:.45;cursor:default;box-shadow:none}\n  .ai-hint,.ai-err,.ai-busy{font-size:13px;color:#7c8494;background:#fbfbfc;border:1px solid #ececef;border-radius:12px;padding:20px;line-height:1.5}\n  .ai-err{color:#c0392b;background:#fdf1ef;border-color:#f3d6d0}\n  .ai-busy{display:flex;align-items:center;gap:12px;color:#5b6070}\n  .spin{width:18px;height:18px;border:2px solid #f0dccd;border-top-color:${ACCENT};border-radius:50%;animation:sp .8s linear infinite}\n  @keyframes sp{to{transform:rotate(360deg)}}\n  .ai-card{border:1px solid #ececef;border-radius:14px;overflow:hidden}\n  .ai-h{display:flex;justify-content:space-between;align-items:center;background:#fff2ea;padding:12px 15px;font-weight:700;font-size:13.5px;color:${ACCENT}}\n  .conf{font-size:11px;font-weight:700;color:#a06a3a;background:#fff;padding:3px 9px;border-radius:999px}\n  .ai-row{display:grid;grid-template-columns:100px 1fr;gap:10px;padding:10px 15px;border-top:1px solid #f2f2f4;font-size:13px}\n  .ai-k{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#a0a4b0;padding-top:2px}\n  .ai-v{color:#2b2f39;display:flex;flex-wrap:wrap;gap:6px}\n  .ai-save{margin:14px 15px;background:${ACCENT};color:#fff;border:0;border-radius:10px;padding:10px 18px;font-size:13px;font-weight:650;cursor:pointer}\n  .ai-ok{margin:0 15px 15px;color:#0f9d58;font-size:12.5px;font-weight:600}\n  /* catalogar v2 */\n  .cat2{max-width:940px}\n  .pub-link{display:flex;align-items:center;gap:14px;background:#fff2ea;border:1px solid #f3e2d3;border-radius:12px;padding:12px 16px;margin-bottom:18px}\n  .pub-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}\n  .pub-lab{font-size:11px;font-weight:700;color:#a06a3a;text-transform:uppercase;letter-spacing:.03em}\n  .pub-url{font-size:12.5px;color:#5b6070;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .copiar-link{background:${ACCENT};color:#fff;border:0;border-radius:9px;padding:9px 16px;font-size:12.5px;font-weight:700;cursor:pointer;white-space:nowrap;font-family:inherit;flex:0 0 auto}\n  .cat-tabs{display:flex;gap:6px;background:#f5f5f7;border-radius:11px;padding:4px;margin-bottom:22px;max-width:480px}\n  .cat-tab{flex:1;text-align:center;font-size:13px;font-weight:600;color:#6b7280;padding:9px;border-radius:8px;cursor:pointer;transition:.12s}\n  .cat-tab.on{background:#fff;color:${ACCENT};box-shadow:0 1px 4px rgba(0,0,0,.06)}\n  .ia-wrap{display:grid;grid-template-columns:300px 1fr;gap:24px;align-items:start}\n  .ia-left .up{height:240px}\n  .cat-form{border:1px solid #ececef;border-radius:14px;padding:18px;background:#fbfbfc}\n  .cf-note{font-size:12.5px;color:#8a5a34;margin-bottom:14px;background:#fff2ea;border:1px solid #f3e2d3;border-radius:9px;padding:9px 11px}\n  .cf-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px 16px}\n  .cf{display:flex;flex-direction:column}\n  .cf-full{grid-column:1/-1}\n  .cf label{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#a0a4b0;margin-bottom:5px}\n  .cin{width:100%;padding:9px 11px;border:1px solid #e6e6ea;border-radius:9px;font-size:13.5px;color:#23262e;background:#fff;outline:none;font-family:inherit;box-sizing:border-box}\n  .cin:focus{border-color:${ACCENT};box-shadow:0 0 0 3px #fff2ea}\n  textarea.cin{resize:vertical;min-height:64px}\n  .cf-check{display:flex;align-items:center;gap:8px;margin-top:14px;font-size:13px;color:#5b6070}\n  .cf-actions{display:flex;align-items:center;gap:14px;margin-top:16px}\n  .cat-save{background:${ACCENT};color:#fff;border:0;border-radius:10px;padding:11px 20px;font-size:13.5px;font-weight:700;cursor:pointer;box-shadow:0 6px 16px rgba(244,134,70,.3);font-family:inherit}\n  .cat-ok{color:#0f9d58;font-size:12.5px;font-weight:600}\n  .lote{max-width:660px;display:flex;flex-direction:column;gap:14px}\n  .lote-step{display:flex;align-items:center;gap:14px;border:1px solid #ececef;border-radius:14px;padding:16px 18px;background:#fbfbfc}\n  .lote-n{width:30px;height:30px;border-radius:50%;background:${ACCENT};color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:14px;flex:0 0 auto}\n  .lote-txt{flex:1}\n  .lote-t{font-size:14px;font-weight:700;color:#23262e}\n  .lote-d{font-size:12px;color:#9096a4;margin-top:2px}\n  .lote-modelo,.lote-up{background:#fff;border:1px solid #e6e6ea;border-radius:10px;padding:10px 16px;font-size:13px;font-weight:650;color:#5b6070;cursor:pointer;white-space:nowrap;font-family:inherit}\n  .lote-modelo:hover,.lote-up:hover{border-color:${ACCENT};color:${ACCENT}}\n  .lote-cols{font-size:11px;color:#a0a4b0;line-height:1.5}\n  /* aprovações */\n  .nav-badge{margin-left:auto;background:#c0392b;color:#fff;font-size:10.5px;font-weight:700;border-radius:999px;padding:1px 7px;min-width:18px;text-align:center}\n  .aprov{max-width:860px;display:flex;flex-direction:column;gap:14px}\n  .aprov-intro{font-size:12.5px;color:#7c8494;margin-bottom:2px}\n  .aprov-card{display:flex;gap:16px;border:1px solid #ececef;border-radius:14px;padding:16px 18px;background:#fbfbfc}\n  .aprov-main{flex:1;min-width:0}\n  .aprov-cat{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:650;color:color-mix(in srgb,var(--c) 70%,#333);background:color-mix(in srgb,var(--c) 14%,#fff);padding:4px 9px;border-radius:999px}\n  .aprov-t{font-size:15px;font-weight:700;color:#23262e;margin-top:8px}\n  .aprov-meta{font-size:12px;color:#9096a4;margin-top:4px}\n  .aprov-desc{font-size:13px;color:#4b5160;margin-top:8px;line-height:1.45}\n  .aprov-tags{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}\n  .aprov-by{font-size:11.5px;color:#8a8f9c;margin-top:10px}\n  .aprov-actions{display:flex;flex-direction:column;gap:8px;justify-content:center;flex:0 0 auto}\n  .aprovar-item{background:#0f9d58;color:#fff;border:0;border-radius:9px;padding:9px 16px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap}\n  .rejeitar-item{background:#fff;color:#c0392b;border:1px solid #f0d0cb;border-radius:9px;padding:9px 16px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit;white-space:nowrap}\n  .rejeitar-item:hover{background:#fdf1ef}\n  /* exposições (gestão) */\n  .expo-mgr{max-width:860px}\n  .expo-mgr-top{display:flex;align-items:center;gap:14px;margin-bottom:16px}\n  .expo-novo{background:${ACCENT};color:#fff;border:0;border-radius:10px;padding:10px 18px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit;box-shadow:0 6px 16px rgba(244,134,70,.3)}\n  .expo-list{display:flex;flex-direction:column;gap:10px}\n  .expo-row{display:flex;align-items:center;gap:14px;border:1px solid #ececef;border-radius:12px;padding:14px 16px;background:#fbfbfc}\n  .expo-row-main{flex:1;min-width:0}\n  .expo-row-t{font-size:14.5px;font-weight:700;color:#23262e}\n  .expo-row-m{font-size:12px;color:#9096a4;margin-top:3px}\n  .expo-badge{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;padding:3px 8px;border-radius:999px;white-space:nowrap}\n  .expo-badge.pub{color:#0f7a45;background:#e8f6ee}\n  .expo-badge.rasc{color:#8a6d3b;background:#fbf3e3}\n  .expo-count{font-size:11.5px;color:#9096a4;white-space:nowrap}\n  .expo-row-acts{display:flex;gap:6px}\n  .expo-editar,.expo-ver,.expo-copiar,.expo-voltar{background:#fff;border:1px solid #e6e6ea;border-radius:9px;padding:7px 12px;font-size:12px;font-weight:600;color:#5b6070;cursor:pointer;font-family:inherit;white-space:nowrap}\n  .expo-editar:hover,.expo-ver:hover,.expo-copiar:hover,.expo-voltar:hover{border-color:${ACCENT};color:${ACCENT}}\n  .expo-ed{max-width:860px}\n  .expo-ed-top{display:flex;gap:8px;margin-bottom:16px}\n  .expo-ed-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px 16px}\n  .ee-f{display:flex;flex-direction:column}\n  .ee-f.cf-full{grid-column:1/-1}\n  .ee-f label{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#a0a4b0;margin-bottom:5px}\n  .ex-in{width:100%;padding:10px 12px;border:1px solid #e6e6ea;border-radius:9px;font-size:13.5px;color:#23262e;background:#fff;outline:none;font-family:inherit;box-sizing:border-box}\n  .ex-in:focus{border-color:${ACCENT};box-shadow:0 0 0 3px #fff2ea}\n  textarea.ex-in{resize:vertical}\n  .ia-row{flex-direction:row;align-items:center;gap:12px}\n  .expo-ia{background:#fff2ea;color:${ACCENT};border:1px solid #f3d6bf;border-radius:10px;padding:10px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n  .expo-ia[disabled]{opacity:.6;cursor:default}\n  .ai-err-inline{font-size:12px;color:#c0392b}\n  .ee-items-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:22px 0 12px;font-size:13px;font-weight:700;color:#23262e}\n  .expo-add-sel{padding:8px 10px;border:1px solid #e6e6ea;border-radius:9px;font-size:12.5px;color:#5b6070;font-family:inherit;background:#fff;max-width:280px}\n  .ee-items{display:flex;flex-direction:column;gap:10px}\n  .ei{border:1px solid #ececef;border-radius:11px;padding:12px 14px;background:#fbfbfc}\n  .ei-h{display:flex;align-items:center;gap:10px}\n  .ei-badge{width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--c) 18%,#fff);font-size:14px;flex:0 0 auto}\n  .ei-t{flex:1;font-size:13.5px;font-weight:650;color:#23262e;min-width:0}\n  .ei-ord{display:flex;gap:4px}\n  .ei-ord button{width:28px;height:28px;border:1px solid #e6e6ea;background:#fff;border-radius:7px;cursor:pointer;color:#5b6070;font-size:13px}\n  .ei-ord button[disabled]{opacity:.4;cursor:default}\n  .ei-tx{width:100%;margin-top:10px;padding:9px 11px;border:1px solid #e6e6ea;border-radius:9px;font-size:13px;color:#23262e;outline:none;font-family:inherit;resize:vertical;box-sizing:border-box}\n  .ei-tx:focus{border-color:${ACCENT};box-shadow:0 0 0 3px #fff2ea}\n  .ei-empty{color:#9096a4;font-size:13px;padding:18px;border:1px dashed #e2d3c8;border-radius:11px;text-align:center}\n  .ei-txt{background:#fff8f2;border-color:#f3e2d3}\n  .et-badge{background:#fff2ea!important;color:${ACCENT};font-size:15px}\n  .et-title{width:100%;margin-top:10px;padding:9px 11px;border:1px solid #e6e6ea;border-radius:9px;font-size:13px;font-weight:650;color:#23262e;outline:none;font-family:inherit;box-sizing:border-box}\n  .et-title:focus{border-color:${ACCENT};box-shadow:0 0 0 3px #fff2ea}\n  .et-tx{width:100%;margin-top:8px;padding:9px 11px;border:1px solid #e6e6ea;border-radius:9px;font-size:13px;color:#23262e;outline:none;font-family:inherit;resize:vertical;box-sizing:border-box}\n  .et-tx:focus{border-color:${ACCENT};box-shadow:0 0 0 3px #fff2ea}\n  .et-ia{margin-top:10px;background:#fff2ea;color:${ACCENT};border:1px solid #f3d6bf;border-radius:9px;padding:8px 14px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit}\n  .et-ia[disabled]{opacity:.6;cursor:default}\n  .ee-add{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n  .expo-add-txt{background:#fff;border:1px dashed #e2d3c8;border-radius:9px;padding:8px 12px;font-size:12.5px;font-weight:650;color:#8a5a34;cursor:pointer;font-family:inherit;white-space:nowrap}\n  .expo-add-txt:hover{border-color:${ACCENT};color:${ACCENT}}\n  .ins-row{display:flex;justify-content:center;padding:1px 0}\n  .expo-ins-txt{background:transparent;border:1px dashed #e6d6c8;border-radius:999px;padding:3px 12px;font-size:10.5px;font-weight:650;color:#bfa084;cursor:pointer;font-family:inherit;opacity:.65;transition:.15s}\n  .expo-ins-txt:hover{opacity:1;border-color:${ACCENT};color:${ACCENT}}\n  .expo-ed-actions{display:flex;align-items:center;gap:14px;margin-top:20px;flex-wrap:wrap}\n  .expo-salvar{background:${ACCENT};color:#fff;border:0;border-radius:10px;padding:11px 20px;font-size:13.5px;font-weight:700;cursor:pointer;font-family:inherit;box-shadow:0 6px 16px rgba(244,134,70,.3)}\n  .expo-excluir2{background:#fff;border:1px solid #f0d0cb;border-radius:10px;padding:11px 16px;font-size:12.5px;font-weight:600;color:#c0392b;cursor:pointer;font-family:inherit}\n  .del-confirm2{font-size:12.5px;color:#c0392b;display:inline-flex;align-items:center;gap:6px}\n  .del-confirm2 button{border:0;border-radius:8px;padding:6px 12px;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit}\n  .expo-del-yes{background:#c0392b;color:#fff}\n  .expo-del-no{background:#eee;color:#333}\n  /* chat */\n  .chat{display:flex;flex-direction:column;height:100%;max-width:820px;margin:0 auto}\n  .log{flex:1;overflow:auto;padding:6px 4px 16px;display:flex;flex-direction:column;gap:10px}\n  .msg{max-width:78%;padding:11px 14px;border-radius:14px;font-size:13.5px;line-height:1.5;white-space:pre-wrap}\n  .me{align-self:flex-end;background:${ACCENT};color:#fff;border-bottom-right-radius:4px}\n  .ai{align-self:flex-start;background:#f5f5f7;color:#2b2f39;border-bottom-left-radius:4px}\n  .chat-welcome{text-align:center;margin:auto;max-width:460px;color:#7c8494}\n  .cw-g{font-size:40px}.cw-t{font-size:18px;font-weight:750;color:#23262e;margin-top:8px}\n  .cw-s{font-size:13px;margin-top:6px;line-height:1.5}\n  .sugs{display:flex;flex-direction:column;gap:8px;margin-top:18px}\n  .suggest{background:#fff;border:1px solid #e6e6ea;border-radius:10px;padding:11px 14px;font-size:13px;color:#4b5160;cursor:pointer;transition:.12s;text-align:left}\n  .suggest:hover{border-color:${ACCENT};color:${ACCENT};background:#fff2ea}\n  .bar{display:flex;gap:8px;padding:12px 4px}\n  .chatin{flex:1;padding:12px 14px;border:1px solid #e6e6ea;border-radius:11px;font-size:14px;outline:none}\n  .chatin:focus{border-color:${ACCENT};box-shadow:0 0 0 3px #fff2ea}\n  .send{background:${ACCENT};color:#fff;border:0;border-radius:11px;padding:0 20px;font-size:13.5px;font-weight:650;cursor:pointer}\n  .send[disabled]{opacity:.5;cursor:default}\n  /* chat flutuante */\n  .chatw{position:absolute;right:22px;bottom:22px;z-index:60;display:flex;flex-direction:column;align-items:flex-end;gap:12px}\n  .chatw-fab{background:${ACCENT};color:#fff;border:0;border-radius:999px;padding:13px 20px;font-size:13.5px;font-weight:700;cursor:pointer;box-shadow:0 10px 26px rgba(244,134,70,.42);font-family:inherit;white-space:nowrap}\n  .chatw-fab:hover{filter:brightness(1.04)}\n  .chatw-panel{width:min(370px,calc(100vw - 44px));height:min(540px,calc(100vh - 130px));background:#fff;border:1px solid #ececef;border-radius:18px;box-shadow:0 24px 70px rgba(20,24,40,.28);display:flex;flex-direction:column;overflow:hidden}\n  .chatw-head{background:${ACCENT};color:#fff;padding:14px 16px;display:flex;align-items:center;justify-content:space-between}\n  .chatw-title{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:700}\n  .chatw-dot{width:9px;height:9px;border-radius:50%;background:#7CF0A6;box-shadow:0 0 0 3px rgba(255,255,255,.25)}\n  .chatw-min{background:rgba(255,255,255,.2);border:0;color:#fff;width:28px;height:28px;border-radius:8px;font-size:16px;cursor:pointer;line-height:1}\n  .chatw-log{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:10px}\n  .chatw-bar{display:flex;gap:8px;padding:12px;border-top:1px solid #f0f0f2}\n  .chatw-bar .chatin{flex:1;padding:11px 13px;border:1px solid #e6e6ea;border-radius:11px;font-size:13.5px;outline:none;font-family:inherit}\n  .chatw-bar .chatin:focus{border-color:${ACCENT};box-shadow:0 0 0 3px #fff2ea}\n  .chatw-bar .send{background:${ACCENT};color:#fff;border:0;border-radius:11px;padding:0 15px;font-size:15px;font-weight:700;cursor:pointer}\n  .chatw-log .chat-welcome{margin:auto 0;max-width:none}\n  .chatw-log .cw-g{font-size:34px}\n  .chatw-log .cw-t{font-size:16px;margin-top:6px}\n  .chatw-log .cw-s{font-size:12.5px}\n  .chat-acts{display:flex;flex-wrap:wrap;gap:6px;margin:-4px 0 2px}\n  .chat-go{align-self:flex-start;background:#fff2ea;color:${ACCENT};border:1px solid #f3d6bf;border-radius:9px;padding:7px 11px;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit}\n  .chat-go:hover{background:${ACCENT};color:#fff;border-color:${ACCENT}}\n  /* grafo */\n  .grafo{display:flex;flex-direction:column;justify-content:center;height:100%}\n  .g-ctrls{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:8px}\n  .g-crumb{font-size:14px;font-weight:700;color:#23262e}\n  .gback,.gficha{background:#fff;border:1px solid #e6e6ea;border-radius:9px;padding:7px 13px;font-size:12.5px;font-weight:600;cursor:pointer;color:#5b6070}\n  .gficha{background:${ACCENT};color:#fff;border:0}\n  .gback:hover{border-color:${ACCENT};color:${ACCENT}}\n  .g-hint{font-size:11.5px;color:#a0a4b0;margin-left:auto}\n  .g-svg{width:100%;height:min(60vh,540px);display:block;margin:0 auto}\n  .edge{stroke:#e6e2de;stroke-width:2}\n  .gnode{cursor:pointer}\n  .gnode .ring{fill:none;stroke:#fff;stroke-width:3}\n  .gnode:hover circle{filter:brightness(1.06)}\n  .gt{text-anchor:middle;fill:#fff;font-size:12px;font-weight:650;font-family:-apple-system,'Segoe UI',sans-serif;pointer-events:none}\n  /* ---------- home / painel da curadoria ---------- */\n  .home{display:flex;flex-direction:column;gap:16px}\n  .hcol{display:flex;flex-direction:column;gap:16px;min-width:0}\n  .hhero{display:flex;justify-content:space-between;align-items:center;gap:18px;flex-wrap:wrap;background:linear-gradient(135deg,#fff6f0,#ffffff 65%);border:1px solid #f3e2d3;border-radius:16px;padding:20px 22px}\n  .hh-t{font-size:22px;font-weight:800;letter-spacing:-.02em}\n  .hh-s{font-size:12.5px;color:#8a8f98;margin-top:5px}\n  .hh-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}\n  .hh-search{display:flex;align-items:center;gap:7px;background:#fff;border:1px solid #e6e6ea;border-radius:10px;padding:9px 12px;min-width:250px}\n  .hh-search input{border:0;outline:0;font-size:13.5px;flex:1;min-width:0;font-family:inherit;color:#23262e;background:transparent}\n  .home-chat{background:${ACCENT};color:#fff;border:0;border-radius:10px;padding:11px 15px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap}\n  .home-chat:hover{filter:brightness(1.05)}\n  .hkpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}\n  .hk{background:#fff;border:1px solid #ececef;border-radius:14px;padding:15px 18px;cursor:pointer;transition:.12s}\n  .hk:hover{border-color:${ACCENT};box-shadow:0 8px 20px rgba(20,24,40,.06)}\n  .hk-v{font-size:27px;font-weight:820;letter-spacing:-.02em}\n  .hk-l{font-size:11.5px;color:#8a8f98;margin-top:2px}\n  .hk.warn{background:#fff8f2;border-color:#f3d6bf}\n  .hk.warn .hk-v{color:${ACCENT}}\n  .hgrid{display:grid;grid-template-columns:1.3fr 1fr;gap:16px;align-items:start}\n  .hcard{background:#fff;border:1px solid #ececef;border-radius:14px;padding:17px 18px}\n  .hcard-t{font-size:13px;font-weight:750;margin-bottom:12px;display:flex;align-items:center;gap:7px;flex-wrap:wrap}\n  .hcard-t small{font-weight:500;color:#a0a4b0;font-size:11.5px}\n  .ht{display:flex;align-items:center;gap:12px;padding:10px 11px;border-radius:11px;cursor:pointer;transition:.12s;border:1px solid transparent}\n  .ht:hover{background:#fff6f0;border-color:#f3e2d3}\n  .ht-n{flex:0 0 auto;min-width:30px;height:30px;padding:0 8px;border-radius:9px;background:${ACCENT};color:#fff;font-size:13.5px;font-weight:800;display:flex;align-items:center;justify-content:center}\n  .ht-b{flex:1;min-width:0}\n  .ht-t{font-size:13.5px;font-weight:650}\n  .ht-h{font-size:11.5px;color:#9096a4;margin-top:1px}\n  .ht-go{color:${ACCENT};font-weight:800;opacity:.3}\n  .ht:hover .ht-go{opacity:1}\n  .h-ok{display:flex;flex-direction:column;align-items:center;padding:20px 10px;color:#8a8f98;font-size:13px;text-align:center}\n  .h-ok b{font-size:28px}\n  .hb{margin-bottom:11px}\n  .hb-h{display:flex;justify-content:space-between;font-size:12px;color:#5b6070;margin-bottom:5px}\n  .hb-t{height:7px;border-radius:999px;background:#f2f2f4;overflow:hidden}\n  .hb-t i{display:block;height:100%;border-radius:999px;background:linear-gradient(90deg,${ACCENT},#f8b184)}\n  .hcats{margin-top:14px;padding-top:12px;border-top:1px solid #f2f2f4}\n  .hcats-t{font-size:10.5px;font-weight:700;color:#a0a4b0;text-transform:uppercase;letter-spacing:.04em;margin-bottom:5px}\n  .hc{display:flex;align-items:center;gap:8px;font-size:12.5px;padding:6px 7px;border-radius:8px;cursor:pointer}\n  .hc:hover{background:#fbf7f3}\n  .hc-n{flex:1;color:#4b5160;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n  .hc-v{font-weight:700;color:#8a8f98;font-size:11.5px}\n  .hc.zero .hc-v{color:#c0392b}\n  .hdest{display:flex;gap:14px;align-items:center}\n  .hdest-img{flex:0 0 92px;width:92px;height:92px;border-radius:11px;overflow:hidden}\n  .hdest-img .ph,.hdest-img .ph-img{width:100%;height:100%;object-fit:cover}\n  .hdest-b{flex:1;min-width:0}\n  .hdest-t{font-size:14px;font-weight:700;line-height:1.25}\n  .hdest-d{font-size:12px;color:#8a8f98;margin-top:5px;line-height:1.45}\n  .hdest-acts{display:flex;gap:6px;margin-top:9px;flex-wrap:wrap}\n  .hdest-acts button,.hexpo button{background:#fff;border:1px solid #e6e6ea;border-radius:8px;padding:6px 11px;font-size:11.5px;font-weight:650;color:#5b6070;cursor:pointer;font-family:inherit}\n  .hdest-acts button:hover,.hexpo button:hover{border-color:${ACCENT};color:${ACCENT}}\n  .hlast{display:grid;grid-template-columns:repeat(auto-fill,minmax(112px,1fr));gap:11px}\n  .hi{cursor:pointer;min-width:0}\n  .hi-img{height:78px;border-radius:10px;overflow:hidden;background:#faf8f6}\n  .hi-img .ph,.hi-img .ph-img{width:100%;height:100%;object-fit:cover}\n  .hi-t{font-size:11.5px;font-weight:600;margin-top:6px;line-height:1.3;color:#3b4150}\n  .hi:hover .hi-t{color:${ACCENT}}\n  .hexpo{display:flex;align-items:center;gap:9px;padding:9px 0;border-bottom:1px solid #f4f4f6;flex-wrap:wrap}\n  .hexpo:last-child{border-bottom:0}\n  .hexpo-b{flex:1;min-width:0}\n  .hexpo-t{font-size:13px;font-weight:680}\n  .hexpo-m{font-size:11px;color:#9096a4;margin-top:1px}\n  .hacts{display:flex;gap:9px;flex-wrap:wrap}\n  .hact{background:#fff;border:1px solid #e6e6ea;border-radius:11px;padding:11px 15px;font-size:13px;font-weight:650;color:#4b5160;cursor:pointer;font-family:inherit;transition:.12s}\n  .hact:hover{border-color:${ACCENT};color:${ACCENT};background:#fff8f4}\n  .gap-bar{display:flex;align-items:center;gap:9px;background:#fff6f0;border:1px solid #f3e2d3;border-radius:10px;padding:9px 13px;margin-bottom:12px;font-size:12.5px;color:#8a5a34;flex-wrap:wrap}\n  .gap-clear{background:#fff;border:1px solid #e6d3c2;border-radius:8px;padding:5px 11px;font-size:11.5px;font-weight:650;color:${ACCENT};cursor:pointer;font-family:inherit;margin-left:auto}\n  @media (max-width:1080px){ .hgrid{grid-template-columns:1fr} }\n  /* ---------- galeria de mídias do item ---------- */\n  .card-img{position:relative}\n  .media-vid{width:100%;border-radius:12px;background:#000;display:block}\n  .media-aud{width:100%;background:#f4f4f6;border-radius:10px;display:block}\n  .media-cred{font-size:10.5px;color:#a0a4b0;margin-top:4px;line-height:1.4}\n  .mid-th{position:relative;cursor:pointer;border:2px solid transparent;border-radius:9px;overflow:hidden}\n  .mid-th.on{border-color:${ACCENT}}\n  .th-star{position:absolute;top:2px;right:3px;font-size:10px;color:#fff;text-shadow:0 1px 3px rgba(0,0,0,.6)}\n  .mid-mgr-h{font-size:11.5px;font-weight:750;color:#5b6070;margin-bottom:8px}\n  .mid-row{display:flex;gap:6px;align-items:center;margin-bottom:7px}\n  .mid-row input{flex:1;min-width:0;padding:7px 9px;border:1px solid #dcdfe4;border-radius:8px;font-size:12.5px;font-family:inherit;color:#23262e;background:#fff}\n  .mid-row input:focus{outline:0;border-color:${ACCENT}}\n  .mid-row button{background:${ACCENT};color:#fff;border:0;border-radius:8px;padding:7px 13px;font-size:12.5px;font-weight:650;cursor:pointer;font-family:inherit;white-space:nowrap}\n  .mid-acts{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:4px}\n  .mid-acts button{background:#fff;border:1px solid #e6e6ea;border-radius:8px;padding:6px 10px;font-size:11.5px;font-weight:650;color:#5b6070;cursor:pointer;font-family:inherit}\n  .mid-acts button:hover:not([disabled]){border-color:${ACCENT};color:${ACCENT}}\n  .mid-acts button[disabled]{opacity:.5;cursor:default}\n  .mid-acts .mid-rm{color:#c0392b}\n  .mid-add{margin-top:10px;padding-top:10px;border-top:1px dashed #ececef}\n  .mid-add-t{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#a0a4b0;margin-bottom:7px}\n  .mid-file-lb{display:inline-block;background:#fff;border:1px solid #e6e6ea;border-radius:8px;padding:7px 12px;font-size:12px;font-weight:650;color:#5b6070;cursor:pointer}\n  .mid-file-lb:hover{border-color:${ACCENT};color:${ACCENT}}\n  .mid-busy{font-size:11.5px;color:${ACCENT};margin-left:8px}\n  .mid-msg{font-size:11.5px;color:#2e7d32;margin-top:7px}\n  .mid-badge{position:absolute;bottom:6px;left:6px;background:rgba(20,24,40,.72);color:#fff;font-size:10px;font-weight:700;padding:2px 7px;border-radius:999px}\n  .cat-mids{display:flex;gap:6px;flex-wrap:wrap;margin-top:9px}\n  .cat-mid{position:relative;width:56px;height:56px;border-radius:8px;overflow:hidden;border:1px solid #eee}\n  .cat-mid img{width:100%;height:100%;object-fit:cover;display:block}\n  .cat-mid-v{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#f4f4f6;font-size:17px}\n  .cat-mid-rm{position:absolute;top:1px;right:1px;background:rgba(20,24,40,.7);color:#fff;border:0;border-radius:6px;width:16px;height:16px;font-size:9px;cursor:pointer;line-height:1;padding:0}\n  .cat-mids-h{font-size:11px;color:#9096a4;margin-top:5px}\n  `,\n  render(d2){\n    const view = d2.state.view || 'inicio';\n    return `<div class=\"app\">${sidebar(d2,view)}<main class=\"main\">${topbar(d2,view)}<div class=\"content\">${page(d2,view)}</div></main>${chatWidget(d2)}</div>`;\n  },\n  mounted(d2){\n    try{ const _v=d2.assistant('visao'); if(_v) void _v.state; const _c=d2.assistant('chat'); if(_c) void _c.state; }catch(e){}\n    try{ setTimeout(()=>{ try{ const n=recs(d2,'itens').length; if(n && d2.state.destaque==null) d2.state.destaque=Math.floor(Math.random()*n); }catch(_){} }, 900); }catch(e){}\n    carregaPre(d2);\n    carregaExpos(d2);\n    const preItem = (id)=> findBy(preOf(d2),'_id',id);\n    d2.on('.aprovar-item','click', async (e,el)=>{ const p=preItem(el.dataset.id); if(!p) return; const tags=String(p.tags_texto||'').split(',').map(s=>s.trim()).filter(Boolean); const code='mv-9'+String(Date.now()).slice(-5); try{ await d2.data('itens').create({ codigo_interno:code, titulo:p.titulo||'(sem título)', categoria_slug:p.categoria_slug||'biblioteca', marca_slug:p.marca_slug||'', produto_slug:p.produto_slug||'', ciclo_codigo:p.ciclo_codigo||'', campanha_slug:p.campanha_slug||'', tombo:p.tombo||'', notacao:p.tombo||'', origem:p.origem||'', localizacao:p.localizacao||'', descricao:p.descricao||'', tags, tags_texto:tags.join(' '), sigiloso:!!p.sigiloso, nivel_descricao:'Item', fundo:'1. Natura', status:'Disponível', resp_cadastro:'Aprovado ('+(p.enviado_por||'externo')+')', imagem_ref:(p.imagem_ref||'') }); const _pm=(Array.isArray(p.midias)&&p.midias.length)?p.midias:(isUrl(p.imagem_ref)?[{ref:p.imagem_ref, tipo:tipoDe(p.imagem_ref)}]:[]); for(let k=0;k<_pm.length;k++){ try{ await d2.data('midias').create({item_codigo:code, tipo:(_pm[k].tipo||tipoDe(_pm[k].ref)), ref:_pm[k].ref, legenda:(_pm[k].legenda||p.titulo||''), ordem:k+1, preferencial:(k===0), is_placeholder:false, credito:''}); }catch(_){} } await d2.rush('mv-pre-excluir').invoke({_id:p._id}); await carregaPre(d2); d2.state.preDetail=null; }catch(err){ d2.log('aprovar', err); } });\n    d2.on('.rejeitar-item','click', async (e,el)=>{ const p=preItem(el.dataset.id); if(!p) return; try{ const refs=[]; if(Array.isArray(p.midias)) p.midias.forEach(m=>{ if(m&&m.ref&&refs.indexOf(m.ref)<0) refs.push(m.ref); }); if(p.imagem_ref&&refs.indexOf(p.imagem_ref)<0) refs.push(p.imagem_ref); for(let k=0;k<refs.length;k++){ if(String(refs[k]).indexOf('data2.media')>=0){ try{ await d2.drive('mv-assets').remove(refs[k]); }catch(_){} } } await d2.rush('mv-pre-excluir').invoke({_id:p._id}); await carregaPre(d2); d2.state.preDetail=null; }catch(err){ d2.log('rejeitar', err); } });\n    d2.on('.aprov-detalhe','click',(e,el)=>{ d2.state.preDetail = el.dataset.id; });\n    d2.on('.pre-modal-close','click',()=>{ d2.state.preDetail = null; });\n    d2.on('.pre-modal-ov','click',(e,el)=>{ if(e.target===el) d2.state.preDetail = null; });\n    const NAV_PAGES = {acervo:1, grafo:1, catalogar:1, aprovar:1};\n    const send = async (text)=>{\n      text = (text||'').trim(); if(!text) return;\n      const inp = d2.$('.chatin'); if(inp) inp.value='';\n      d2.state.messages = [...(d2.state.messages||[]), {role:'user',text}];\n      try{\n        const instr = 'Responda SEMPRE em JSON com duas chaves: \"resposta\" (string, sua resposta curatorial citando os itens pelo título e código) e \"acoes\" (array, no máximo 4). Cada ação é {\"tipo\":\"item\",\"codigo\":\"<codigo_interno EXATO de um item citado>\",\"label\":\"<texto curto do botão>\"} para abrir a ficha de um item, OU {\"tipo\":\"pagina\",\"pagina\":\"acervo|grafo|catalogar|aprovar\",\"label\":\"<texto>\"} para abrir uma tela. Inclua ações só quando forem realmente úteis à pergunta; se não houver, use [].';\n        const out = await d2.assistant('chat').ask(text + '\\n\\n' + instr, {context: buildContext(d2), json:true});\n        const obj = (typeof out==='string') ? (function(){ try{ return JSON.parse(out); }catch(_){ return {resposta:out}; } })() : (out||{});\n        const codes = new Set(recs(d2,'itens').map(r=>r.codigo_interno));\n        const acoes = (Array.isArray(obj.acoes)?obj.acoes:[]).filter(ac=> ac && ((ac.tipo==='item' && codes.has(ac.codigo)) || (ac.tipo==='pagina' && NAV_PAGES[ac.pagina]))).slice(0,4);\n        d2.state.messages = [...(d2.state.messages||[]), {role:'ai', text: (obj.resposta || (typeof out==='string'?out:'')), acoes}];\n      }catch(e){\n        d2.state.messages = [...(d2.state.messages||[]), {role:'ai', text:'Não consegui responder agora ('+String(e&&e.message||e)+').'}];\n      }\n    };\n    const runVisao = async ()=>{\n      const img = d2.state.uploadImg; if(!img) return;\n      d2.state.autoBusy = true; d2.state.autoerr = null; d2.state.savedMsg='';\n      try{\n        const out = await d2.assistant('visao').ask('Analise a imagem e sugira os campos da ficha catalográfica do acervo Natura.', {images:[img], json:true});\n        const a = (function(o){ if(o && typeof o==='object') return o; const s=String(o||''); const i=s.indexOf('{'), j=s.lastIndexOf('}'); if(i<0||j<=i) return {}; try{ return JSON.parse(s.slice(i,j+1)); }catch(_){ return {}; } })(out) || {};\n        d2.state.form = { titulo:a.titulo||'', categoria_slug:a.categoria_slug||'', marca_slug:a.marca_slug||'', produto_slug:a.produto_slug||'', ciclo_codigo:a.ciclo_codigo||'', campanha_slug:a.campanha_slug||'', tombo:a.tombo||'', origem:a.origem||'', localizacao:a.localizacao||'', tags:(Array.isArray(a.tags)?a.tags.join(', '):(a.tags||'')), descricao:a.descricao||'', sigiloso:false };\n      }catch(e){ d2.state.autoerr = String(e&&e.message||e); }\n      d2.state.autoBusy = false;\n    };\n    const readForm = ()=>{ const rec={}; qAll('.cin').forEach(el=>{ rec[el.dataset.k]=(el.value||'').trim(); }); const chk=d2.$('.cin-check'); rec.sigiloso = chk? chk.checked : false; return rec; };\n    const buildItem = (r, code)=>{\n      const tags=String(r.tags||'').split(',').map(s=>s.trim()).filter(Boolean);\n      return { codigo_interno:code, titulo:r.titulo||'(sem título)', categoria_slug:r.categoria_slug||'biblioteca', marca_slug:r.marca_slug||'', produto_slug:r.produto_slug||'', ciclo_codigo:r.ciclo_codigo||'', campanha_slug:r.campanha_slug||'', tombo:r.tombo||'', notacao:r.tombo||'', origem:r.origem||'', localizacao:r.localizacao||'', descricao:r.descricao||'', tags, tags_texto:tags.join(' '), sigiloso:(r.sigiloso===true || String(r.sigiloso||'').toLowerCase()==='true'), nivel_descricao:'Item', fundo:'1. Natura', status:'Disponível', resp_cadastro:'Catalogação', imagem_ref:(r.imagem_ref||'') };\n    };\n    const saveForm = async ()=>{\n      const r = readForm();\n      if(!r.titulo || !r.categoria_slug){ d2.state.savedMsg='Preencha ao menos Título e Categoria.'; return; }\n      const code='mv-9'+String(Date.now()).slice(-5);\n      const _cm = Array.isArray(d2.state.catMids)?d2.state.catMids.slice():[];\n      if(isUrl(r.imagem_ref) && _cm.map(m=>m.ref).indexOf(String(r.imagem_ref).trim())<0) _cm.push({ref:String(r.imagem_ref).trim(), tipo:tipoDe(r.imagem_ref), legenda:''});\n      const _rec = buildItem(r, code); if(_cm.length) _rec.imagem_ref = _cm[0].ref;\n      try{ await d2.data('itens').create(_rec);\n        for(let k=0;k<_cm.length;k++){ try{ await d2.data('midias').create({item_codigo:code, tipo:(_cm[k].tipo||tipoDe(_cm[k].ref)), ref:_cm[k].ref, legenda:(_cm[k].legenda||r.titulo||''), ordem:k+1, preferencial:(k===0), is_placeholder:false, credito:''}); }catch(_){} }\n        d2.state.savedMsg='Item '+code+' salvo no acervo'+(_cm.length?(' com '+_cm.length+' '+(_cm.length===1?'mídia':'mídias')):'')+' ✅'; d2.state.form={}; d2.state.uploadImg=null; d2.state.catMids=[]; }\n      catch(e){ d2.state.savedMsg='Erro ao salvar: '+String(e&&e.message||e); }\n    };\n\n    d2.on('.nav-i','click',(e,el)=>{ d2.state.gap=null; d2.state.view = el.dataset.view; });\n    d2.on('.hk','click',(e,el)=>{ const v=el.dataset.view, g=el.dataset.gap; d2.state.gap = g||null; if(g){ d2.state.cat='todos'; d2.state.q=''; } if(v) d2.state.view=v; });\n    d2.on('.ht','click',(e,el)=>{ const v=el.dataset.view, g=el.dataset.gap; d2.state.gap = g||null; if(g){ d2.state.cat='todos'; d2.state.q=''; } if(v) d2.state.view=v; });\n    d2.on('.hc','click',(e,el)=>{ d2.state.gap='sem-imagem'; d2.state.cat=el.dataset.cat; d2.state.q=''; d2.state.view='acervo'; });\n    d2.on('.hi','click',(e,el)=>{ d2.state.pendingDelete=null; d2.state.sel=el.dataset.codigo; d2.state.view='ficha'; });\n    d2.on('.home-go','click',(e,el)=>{ d2.state.gap=null; d2.state.view=el.dataset.view; });\n    d2.on('.home-chat','click',()=>{ d2.state.chatOpen=true; });\n    d2.on('.home-q','keydown',(e,el)=>{ if(e.key==='Enter'){ d2.state.q=el.value; d2.state.gap=null; d2.state.cat='todos'; d2.state.view='acervo'; } });\n    d2.on('.home-dest-ficha','click',(e,el)=>{ d2.state.pendingDelete=null; d2.state.sel=el.dataset.codigo; d2.state.view='ficha'; });\n    d2.on('.home-dest-outro','click',()=>{ const n=recs(d2,'itens').length; if(n>1){ const cur=d2.state.destaque||0; let x=cur; for(let k=0;k<10 && x===cur;k++){ x=Math.floor(Math.random()*n); } d2.state.destaque=x; } });\n    d2.on('.home-copiar-pub','click', async ()=>{ const url=((window.location&&window.location.origin)||'')+'/enviar'; try{ if(navigator.clipboard&&navigator.clipboard.writeText){ await navigator.clipboard.writeText(url); } else { const ta=document.createElement('textarea'); ta.value=url; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); } d2.state.copiadoHome=true; setTimeout(()=>{ d2.state.copiadoHome=false; },2200); }catch(_){} });\n    d2.on('.gap-clear','click',()=>{ d2.state.gap=null; });\n    d2.on('.sf-logout','click', async ()=>{ try{ await d2.auth('mv-login').logout(); }catch(e){} window.location.assign('/'); });\n    d2.on('.chip','click',(e,el)=>{ d2.state.cat = el.dataset.cat; });\n    d2.on('.search','input',(e,el)=>{ d2.state.q = el.value; });\n    d2.on('.exportar','click',()=>{ try{ const cat=d2.state.cat||'todos'; const q=d2.state.q||''; const gk=d2.state.gap; const gd=gk?GAPS[gk]:null; const list=recs(d2,'itens').filter(i=> (cat==='todos'||i.categoria_slug===cat) && matchItem(i,q) && (!gd||gd.test(i))); const cols=['codigo_interno','tombo','titulo','categoria_slug','marca_slug','produto_slug','ciclo_codigo','campanha_slug','origem','localizacao','status','sigiloso','descricao','tags']; const header=cols.join(','); const lines=list.map(r=> cols.map(c=>{ let v=r[c]; if(c==='tags') v=Array.isArray(r.tags)?r.tags.join(', '):(r.tags_texto||''); return csvCell(v); }).join(',')); const csv=header+'\\n'+lines.join('\\n')+'\\n'; const blob=new Blob(['﻿'+csv],{type:'text/csv;charset=utf-8'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download='acervo-memoria-viva'+(cat!=='todos'?('-'+cat):'')+(gd?('-'+gk):'')+'.csv'; document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>URL.revokeObjectURL(url),1500); }catch(err){ d2.log('exportar', err); } });\n    d2.on('.card','click',(e,el)=>{ d2.state.pendingDelete=null; d2.state.sel = el.dataset.codigo; d2.state.view = 'ficha'; });\n    d2.on('.back','click',()=>{ d2.state.pendingDelete=null; d2.state.view = 'acervo'; });\n    d2.on('.excluir','click',(e,el)=>{ d2.state.pendingDelete = el.dataset.codigo; });\n    d2.on('.del-no','click',()=>{ d2.state.pendingDelete = null; });\n    d2.on('.del-yes','click', async (e,el)=>{ const cod=el.dataset.codigo; const it=findBy(recs(d2,'itens'),'codigo_interno',cod); d2.state.pendingDelete=null; if(!it) return; try{ await d2.data('itens').remove(it._id); d2.state.sel=null; d2.state.view='acervo'; }catch(err){ d2.log('excluir', err); } });\n    d2.on('.tograph','click',(e,el)=>{ d2.state.gfocus = {type:'item', id:el.dataset.codigo}; d2.state.view = 'grafo'; });\n    const midsDo = (cod)=> recs(d2,'midias').filter(m=>m.item_codigo===cod).sort((a,b)=>((+a.ordem||0)-(+b.ordem||0)));\n    const capaDe = (mids)=>{ if(!mids||!mids.length) return ''; const reais=mids.filter(m=>isUrl(m.ref)); const pool=reais.length?reais:mids; const p=pool.find(m=>m.preferencial)||pool[0]; return (p&&p.ref)||''; };\n    const setCapa = async (cod, novos)=>{ try{ const it=findBy(recs(d2,'itens'),'codigo_interno',cod); if(!it) return; const capa=capaDe(novos); if(String(it.imagem_ref||'')!==String(capa||'')) await d2.data('itens').update({_id:it._id, imagem_ref:capa}); }catch(e){ d2.log('setCapa',e); } };\n    const midMsg = (t)=>{ d2.state.midMsgFor=d2.state.sel; d2.state.midMsg=t; };\n    const tipoDe = (u)=>{ const s=String(u||'').toLowerCase(); if(/\\.(mp4|webm|mov|m4v)(\\?|#|$)/.test(s)) return 'video'; if(/\\.(mp3|wav|ogg|m4a)(\\?|#|$)/.test(s)) return 'audio'; return 'imagem'; };\n    const addMid = async (cod, url)=>{ url=String(url||'').trim(); if(!cod||!url) return; const list=midsDo(cod); try{ const ord=list.length?Math.max.apply(null,list.map(m=>+m.ordem||0))+1:1; const rec={item_codigo:cod, tipo:tipoDe(url), ref:url, legenda:'', ordem:ord, preferencial:!list.length, is_placeholder:false, credito:''}; const saved=await d2.data('midias').create(rec); await setCapa(cod, list.concat([rec])); if(saved&&saved._id) d2.state.midSel=saved._id; midMsg('Mídia adicionada ✅'); }catch(err){ midMsg('Erro ao adicionar: '+String(err&&err.message||err)); d2.log('addMid',err); } };\n    const moveMid = async (id, dir)=>{ const cod=d2.state.sel; const list=midsDo(cod); const i=list.findIndex(m=>m._id===id); const j=i+dir; if(i<0||j<0||j>=list.length) return; const arr=list.slice(); const t=arr[i]; arr[i]=arr[j]; arr[j]=t; try{ for(let k=0;k<arr.length;k++){ if((+arr[k].ordem||0)!==(k+1)) await d2.data('midias').update({_id:arr[k]._id, ordem:k+1}); } await setCapa(cod, arr.map((m,k)=>Object.assign({}, m, {ordem:k+1}))); }catch(err){ midMsg('Erro ao reordenar: '+String(err&&err.message||err)); d2.log('moveMid',err); } };\n    d2.on('.mid-th','click',(e,el)=>{ d2.state.midSel=el.dataset.id; });\n    d2.on('.mid-url-add','click', async ()=>{ const inp=d2.$('.mid-url'); const v=inp?inp.value:''; if(!String(v||'').trim()){ midMsg('Cole uma URL primeiro.'); return; } await addMid(d2.state.sel, v); });\n    d2.on('.mid-file','change',(e,el)=>{ const f=el.files&&el.files[0]; if(!f) return; const cod=d2.state.sel; const rd=new FileReader(); rd.onload=async ()=>{ d2.state.midBusy=true; try{ const r=await d2.rush('mv-midia-upload').invoke({b64:String(rd.result||''), fileName:(f.name||'midia')}); const out=(r&&r.data)?r.data:r; if(out&&out.url){ await addMid(cod, out.url); } else { midMsg('Falha no upload.'); } }catch(err){ midMsg('Erro no upload: '+String(err&&err.message||err)); d2.log('mid-upload',err); } d2.state.midBusy=false; }; rd.readAsDataURL(f); });\n    d2.on('.mid-leg-save','click', async (e,el)=>{ const id=el.dataset.id; const inp=d2.$('.mid-leg'); const v=inp?String(inp.value||''):''; try{ await d2.data('midias').update({_id:id, legenda:v}); midMsg('Legenda salva ✅'); }catch(err){ midMsg('Erro: '+String(err&&err.message||err)); d2.log('mid-leg',err); } });\n    d2.on('.mid-star','click', async (e,el)=>{ const id=el.dataset.id, cod=d2.state.sel; if(!id||!cod) return; const list=midsDo(cod); try{ for(let k=0;k<list.length;k++){ const m=list[k]; const want=(m._id===id); if(!!m.preferencial!==want) await d2.data('midias').update({_id:m._id, preferencial:want}); } await setCapa(cod, list.map(m=>Object.assign({}, m, {preferencial:m._id===id}))); midMsg('Capa do item atualizada ✅'); }catch(err){ midMsg('Erro: '+String(err&&err.message||err)); d2.log('mid-star',err); } });\n    d2.on('.mid-rm','click', async (e,el)=>{ const id=el.dataset.id, cod=d2.state.sel; if(!id||!cod) return; const list=midsDo(cod); const rest=list.filter(m=>m._id!==id); try{ await d2.data('midias').remove(id); if(rest.length && !rest.some(m=>m.preferencial)){ await d2.data('midias').update({_id:rest[0]._id, preferencial:true}); rest[0]=Object.assign({}, rest[0], {preferencial:true}); } await setCapa(cod, rest); d2.state.midSel=null; midMsg('Mídia removida.'); }catch(err){ midMsg('Erro ao remover: '+String(err&&err.message||err)); d2.log('mid-rm',err); } });\n    d2.on('.mid-up','click',(e,el)=>{ moveMid(el.dataset.id,-1); });\n    d2.on('.mid-down','click',(e,el)=>{ moveMid(el.dataset.id,1); });\n    d2.on('.suggest','click',(e,el)=>{ send(el.dataset.q); });\n    d2.on('.send','click',()=>{ const el=d2.$('.chatin'); send(el?el.value:''); });\n    d2.on('.chatin','keydown',(e)=>{ if(e.key==='Enter'){ const el=d2.$('.chatin'); send(el?el.value:''); } });\n    d2.on('.chatw-fab','click',()=>{ d2.state.chatOpen = !d2.state.chatOpen; });\n    d2.on('.chatw-min','click',()=>{ d2.state.chatOpen = false; });\n    d2.on('.chat-go','click',(e,el)=>{ const t=el.dataset.t; if(t==='item' && el.dataset.codigo){ d2.state.sel=el.dataset.codigo; d2.state.view='ficha'; d2.state.chatOpen=false; } else if(t==='pagina' && el.dataset.pagina){ d2.state.view=el.dataset.pagina; d2.state.chatOpen=false; } });\n    const slugify = (s)=> (String(s||'').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'').slice(0,48) || 'exposicao');\n    const qAll = (sel)=>{ try{ const r = d2.$$ ? d2.$$(sel) : null; if(r && typeof r.forEach==='function') return r; if(r && r.length!=null) return Array.from(r); }catch(e){} try{ const root = (d2.root && d2.root.querySelectorAll) ? d2.root : document; return Array.from(root.querySelectorAll(sel)); }catch(e){} return []; };\n    const syncExpo = ()=>{ const dr=Object.assign({}, d2.state.expoDraft||{}); qAll('.ex-in').forEach(el=>{ dr[el.dataset.k]=(el.value||''); }); const its=Array.isArray(dr.itens)?dr.itens.slice():[]; qAll('.ei-tx').forEach(el=>{ const i=+el.dataset.idx; if(its[i]) its[i]=Object.assign({}, its[i], {texto:(el.value||'')}); }); qAll('.et-title').forEach(el=>{ const i=+el.dataset.idx; if(its[i]) its[i]=Object.assign({}, its[i], {titulo:(el.value||'')}); }); qAll('.et-tx').forEach(el=>{ const i=+el.dataset.idx; if(its[i]) its[i]=Object.assign({}, its[i], {texto:(el.value||'')}); }); dr.itens=its; return dr; };\n    d2.on('.expo-novo','click',()=>{ d2.state.expoDraft={slug:'',titulo:'',subtitulo:'',tema:'',curadoria:'',status:'rascunho',itens:[]}; d2.state.expoMsg=''; d2.state.expoIaErr=''; d2.state.expoDelConfirm=false; d2.state.view='exposicoes'; d2.state.expoMode='edit'; });\n    d2.on('.expo-editar','click',(e,el)=>{ const ex=findBy(exposOf(d2),'slug',el.dataset.slug); if(!ex) return; d2.state.expoDraft={_id:ex._id, slug:ex.slug, titulo:ex.titulo||'', subtitulo:ex.subtitulo||'', tema:ex.tema||'', curadoria:ex.curadoria||'', status:ex.status||'rascunho', itens:(Array.isArray(ex.itens)?ex.itens.map(x=> x.tipo==='texto' ? {tipo:'texto', titulo:x.titulo||'', texto:x.texto||''} : {tipo:'item', codigo:x.codigo, texto:x.texto||''}):[])}; d2.state.expoMsg=''; d2.state.expoIaErr=''; d2.state.expoDelConfirm=false; d2.state.expoMode='edit'; });\n    d2.on('.expo-voltar','click',()=>{ d2.state.expoMode='list'; d2.state.expoMsg=''; });\n    d2.on('.expo-add-sel','change',(e,el)=>{ const cod=el.value; if(!cod) return; const dr=syncExpo(); dr.itens=[...(dr.itens||[]), {tipo:'item', codigo:cod, texto:''}]; d2.state.expoDraft=dr; });\n    d2.on('.expo-add-txt','click',()=>{ const dr=syncExpo(); dr.itens=[...(dr.itens||[]), {tipo:'texto', titulo:'', texto:''}]; d2.state.expoDraft=dr; });\n    d2.on('.expo-ins-txt','click',(e,el)=>{ const at=+el.dataset.at; const dr=syncExpo(); const its=(dr.itens||[]).slice(); its.splice(isNaN(at)?its.length:at, 0, {tipo:'texto', titulo:'', texto:''}); dr.itens=its; d2.state.expoDraft=dr; });\n    d2.on('.et-ia','click', async (e,el)=>{ if(d2.state.etIaBusy!=null) return; const idx=+el.dataset.idx; const dr=syncExpo(); const ent=dr.itens[idx]; if(!ent) return; d2.state.etIaBusy=idx; try{ const acervo=recs(d2,'itens'); const byCode={}; acervo.forEach(r=>byCode[r.codigo_interno]=r); const near=(dir)=>{ for(let j=idx+dir; j>=0 && j<dr.itens.length; j+=dir){ const e2=dr.itens[j]; if(e2 && e2.tipo!=='texto' && e2.codigo && byCode[e2.codigo]) return byCode[e2.codigo]; } return null; }; const pv=near(-1), nx=near(1); const ctx='Exposição: '+(dr.titulo||'')+'. Tema: '+(dr.tema||dr.titulo||'')+'. '+(dr.curadoria?('Abertura: '+dr.curadoria+'. '):''); const bt=(pv?('Vem logo depois da peça \"'+(pv.titulo||'')+'\" ('+(pv.descricao||'')+'). '):'')+(nx?('E antes da peça \"'+(nx.titulo||'')+'\" ('+(nx.descricao||'')+'). '):''); const prompt='Você é o curador da exposição virtual Memória Viva, da Natura. Escreva um TEXTO DE TRANSIÇÃO curto e envolvente (2 a 4 frases) para ser lido entre duas peças, aprofundando a narrativa. '+ctx+bt+(ent.titulo?('Foco desta transição: '+ent.titulo+'. '):'')+'Responda APENAS com o texto, sem aspas e sem título.'; const out=await d2.assistant('chat').ask(prompt, {context: buildContext(d2)}); const txt=(typeof out==='string')?out:((out&&(out.resposta||out.texto))||''); const its=dr.itens.slice(); its[idx]=Object.assign({}, its[idx], {texto:String(txt).trim()}); dr.itens=its; d2.state.expoDraft=dr; }catch(err){ d2.log('et-ia',err); } d2.state.etIaBusy=null; });\n    d2.on('.expo-item-rm','click',(e,el)=>{ const dr=syncExpo(); const i=+el.dataset.idx; dr.itens=dr.itens.filter((_,j)=>j!==i); d2.state.expoDraft=dr; });\n    d2.on('.expo-item-up','click',(e,el)=>{ const dr=syncExpo(); const i=+el.dataset.idx; if(i>0){ const a=dr.itens.slice(); const t=a[i-1]; a[i-1]=a[i]; a[i]=t; dr.itens=a; d2.state.expoDraft=dr; } });\n    d2.on('.expo-item-down','click',(e,el)=>{ const dr=syncExpo(); const i=+el.dataset.idx; if(i<dr.itens.length-1){ const a=dr.itens.slice(); const t=a[i+1]; a[i+1]=a[i]; a[i]=t; dr.itens=a; d2.state.expoDraft=dr; } });\n    d2.on('.expo-ia','click', async ()=>{ if(d2.state.expoBusy) return; const dr=syncExpo(); const tema=String(dr.tema||dr.titulo||'').trim(); if(!tema){ d2.state.expoIaErr='Escreva um tema primeiro.'; return; } d2.state.expoBusy=true; d2.state.expoIaErr=''; try{ const instr='Monte uma EXPOSIÇÃO VIRTUAL sobre o tema abaixo usando SOMENTE itens do acervo do contexto. Responda em JSON: {\\\"titulo\\\":\\\"...\\\",\\\"subtitulo\\\":\\\"...\\\",\\\"curadoria\\\":\\\"<abertura 2-4 frases>\\\",\\\"itens\\\":[{\\\"codigo\\\":\\\"<codigo_interno EXATO>\\\",\\\"texto\\\":\\\"<legenda 1-2 frases>\\\"}]}. Escolha de 3 a 7 peças relevantes numa ordem que conte uma história. TEMA: '+tema; const out=await d2.assistant('chat').ask(instr,{context:buildContext(d2), json:true}); const obj=(typeof out==='string')?(function(){try{return JSON.parse(out);}catch(_){return {};}})():(out||{}); const codes=new Set(recs(d2,'itens').map(r=>r.codigo_interno)); const its=(Array.isArray(obj.itens)?obj.itens:[]).filter(x=>x&&codes.has(x.codigo)).map(x=>({codigo:x.codigo, texto:String(x.texto||'')})); d2.state.expoDraft=Object.assign({}, dr, {titulo:dr.titulo||obj.titulo||tema, subtitulo:dr.subtitulo||obj.subtitulo||'', curadoria:obj.curadoria||dr.curadoria||'', itens: its.length?its:dr.itens}); }catch(e){ d2.state.expoIaErr='Não consegui montar agora.'; d2.log('expo-ia',e); } d2.state.expoBusy=false; });\n    d2.on('.expo-salvar','click', async ()=>{ const dr=syncExpo(); if(!String(dr.titulo||'').trim()){ d2.state.expoMsg='Dê um título à exposição.'; return; } if(!dr.slug){ dr.slug=slugify(dr.titulo)+'-'+String(Date.now()).slice(-4); } const rec={ slug:dr.slug, titulo:dr.titulo, subtitulo:dr.subtitulo||'', tema:dr.tema||'', curadoria:dr.curadoria||'', status:dr.status||'rascunho', itens:(dr.itens||[]).map(x=>{ if(x.tipo==='texto') return {tipo:'texto', titulo:x.titulo||'', texto:x.texto||''}; const _it=findBy(recs(d2,'itens'),'codigo_interno',x.codigo)||{}; return {tipo:'item', codigo:x.codigo, texto:x.texto||'', titulo:_it.titulo||'', categoria_slug:_it.categoria_slug||'', imagem_ref:_it.imagem_ref||'', ciclo_codigo:_it.ciclo_codigo||'', descricao:_it.descricao||''}; }), criado_por:'Curadoria' }; try{ const payload = dr._id ? Object.assign({_id:dr._id}, rec) : rec; const res = await d2.rush('mv-expo-salvar').invoke({registro:payload}); const savedRec = (res && res.data && res.data.registro) || null; if(savedRec && savedRec._id){ dr._id=savedRec._id; } d2.state.expoDraft=dr; await carregaExpos(d2); d2.state.expoMsg='Exposição salva ✅'; }catch(e){ d2.state.expoMsg='Erro ao salvar: '+String(e&&e.message||e); } });\n    d2.on('.expo-ver','click',(e,el)=>{ try{ window.open(((window.location&&window.location.origin)||'')+'/exposicao/'+el.dataset.slug, '_blank'); }catch(_){} });\n    d2.on('.expo-copiar','click', async (e,el)=>{ const url=((window.location&&window.location.origin)||'')+'/exposicao/'+el.dataset.slug; try{ if(navigator.clipboard&&navigator.clipboard.writeText){ await navigator.clipboard.writeText(url); } else { const ta=document.createElement('textarea'); ta.value=url; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); } d2.state.expoMsg='Link copiado ✓'; }catch(_){} });\n    d2.on('.expo-excluir2','click',()=>{ d2.state.expoDelConfirm=true; });\n    d2.on('.expo-del-no','click',()=>{ d2.state.expoDelConfirm=false; });\n    d2.on('.expo-del-yes','click', async ()=>{ const dr=d2.state.expoDraft; d2.state.expoDelConfirm=false; if(!dr||!dr._id) return; try{ await d2.rush('mv-expo-excluir').invoke({_id:dr._id}); await carregaExpos(d2); d2.state.expoMode='list'; d2.state.expoDraft=null; d2.state.expoMsg='Exposição excluída.'; }catch(e){ d2.log('expo-del',e); } });\n    d2.on('.cat-tab','click',(e,el)=>{ d2.state.catMode=el.dataset.cmode; d2.state.form={}; d2.state.uploadImg=null; d2.state.autoerr=null; d2.state.savedMsg=''; d2.state.loteMsg=''; d2.state.catMids=[]; d2.state.catUp=''; });\n    d2.on('.ai-file','change', async (e,el)=>{\n      const files = el.files ? Array.prototype.slice.call(el.files) : []; if(!files.length) return;\n      d2.state.form={}; d2.state.autoerr=null; d2.state.savedMsg='';\n      const cur = Array.isArray(d2.state.catMids)?d2.state.catMids.slice():[];\n      for(let i=0;i<files.length;i++){\n        const f=files[i];\n        d2.state.catUp='Enviando '+(i+1)+' de '+files.length+'…';\n        try{\n          const b64 = await new Promise((res,rej)=>{ const rd=new FileReader(); rd.onload=()=>res(String(rd.result||'')); rd.onerror=rej; rd.readAsDataURL(f); });\n          const isImg = String(f.type||'').indexOf('image')===0;\n          if(isImg && !d2.state.uploadImg) d2.state.uploadImg=b64;\n          const r = await d2.rush('mv-midia-upload').invoke({b64:b64, fileName:(f.name||'midia')});\n          const out=(r&&r.data)?r.data:r;\n          if(out&&out.url) cur.push({ref:out.url, tipo:(isImg?'imagem':'video'), legenda:''});\n        }catch(err){ d2.log('cat-upload',err); }\n      }\n      d2.state.catMids=cur; d2.state.catUp='';\n    });\n    d2.on('.cat-mid-rm','click',(e,el)=>{ const i=+el.dataset.i; const cur=(Array.isArray(d2.state.catMids)?d2.state.catMids:[]).slice(); if(i>=0&&i<cur.length){ cur.splice(i,1); d2.state.catMids=cur; if(!cur.length) d2.state.uploadImg=null; } });\n    d2.on('.ai-run','click',()=>{ runVisao(); });\n    d2.on('.cat-save','click',()=>{ saveForm(); });\n    d2.on('.copiar-link','click', async ()=>{ const url=((window.location?window.location.origin:'')||'')+'/enviar'; try{ if(navigator.clipboard&&navigator.clipboard.writeText){ await navigator.clipboard.writeText(url); } else { const ta=document.createElement('textarea'); ta.value=url; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); } d2.state.copiado=true; setTimeout(()=>{ d2.state.copiado=false; }, 2200); }catch(e){ d2.log('copiar', e); } });\n    d2.on('.lote-modelo','click',()=>{ try{ const ex=['Revista Exemplo Ciclo 01/2027','biblioteca','natura','','01/2027','','DB 99999','São Paulo - SP - Brasil','AD2 F01 P01','Descrição de exemplo do item','revista,exemplo','false'].map(csvCell).join(','); const csv=CSV_COLS.join(',')+'\\n'+ex+'\\n'; const blob=new Blob(['﻿'+csv],{type:'text/csv;charset=utf-8'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download='memoria-viva-modelo.csv'; document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>URL.revokeObjectURL(url),1500); }catch(err){ d2.state.loteMsg='Não foi possível gerar o modelo: '+String(err&&err.message||err); } });\n    d2.on('.lote-file','change',(e,el)=>{ const f=el.files&&el.files[0]; if(!f) return; const rd=new FileReader(); rd.onload=async ()=>{ try{ const rows=parseCSV(rd.result); if(rows.length<2){ d2.state.loteMsg='CSV vazio ou só com o cabeçalho.'; return; } const header=rows[0].map(h=>h.trim()); let ok=0, fail=0; d2.state.loteMsg='Importando…'; for(let i=1;i<rows.length;i++){ const cells=rows[i]; const obj={}; header.forEach((h,idx)=> obj[h]=(cells[idx]||'').trim()); if(!obj.titulo || !obj.categoria_slug){ fail++; continue; } const code='mv-9'+String(Date.now()).slice(-4)+i; try{ await d2.data('itens').create(buildItem(obj, code)); ok++; }catch(er){ fail++; } } d2.state.loteMsg='Importação concluída: '+ok+' itens criados'+(fail?(' · '+fail+' ignorados (sem título/categoria ou erro)'):'')+'.'; }catch(err){ d2.state.loteMsg='Erro ao ler o CSV: '+String(err&&err.message||err); } }; rd.readAsText(f,'utf-8'); });\n    d2.on('.gnode','click',(e,el)=>{ const k=el.dataset.gkind, id=el.dataset.gid; if(k==='marca') d2.state.gfocus={type:'marca',id}; else if(k==='item') d2.state.gfocus={type:'item',id}; });\n    d2.on('.gback','click',()=>{ d2.state.gfocus = {type:'root'}; });\n    d2.on('.gficha','click',(e,el)=>{ d2.state.sel = el.dataset.codigo; d2.state.view='ficha'; });\n  },\n  updated(d2){ const log = d2.$('.log'); if(log && (d2.state.messages||[]).length) log.scrollTop = log.scrollHeight; }\n};\n"}},"itens":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"itens"}}}},"categorias":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"categorias"}}}},"marcas":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"marcas"}}}},"produtos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"produtos"}}}},"ciclos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ciclos"}}}},"campanhas":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"campanhas"}}}},"midias":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"midias"}}}},"chat":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"chat"}}}},"visao":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"visao"}}}}}}}}}},{"_id":"6a59457455b95245d58e6928","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"mv-auth-shell","package":"fernando-playground","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 ACCENT = '#F48646';\nconst esc = (s)=> (s==null?'':String(s)).replace(/[&<>\"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\n\nexport default {\n  css: `\n  *{box-sizing:border-box}\n  .wrap{height:100%;display:flex;align-items:center;justify-content:center;padding:24px;\n    background:radial-gradient(1100px 560px at 50% -8%, #fff4ec, #ffffff 62%);\n    font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#23262e;-webkit-font-smoothing:antialiased}\n  .card{width:100%;max-width:400px;background:#fff;border:1px solid #ececef;border-radius:18px;box-shadow:0 20px 60px rgba(20,24,40,.10);padding:30px 28px}\n  .brand{display:flex;align-items:center;gap:11px;justify-content:center;margin-bottom:20px}\n  .brand .dot{width:34px;height:34px;border-radius:10px;background:${ACCENT};box-shadow:0 6px 16px rgba(244,134,70,.4);flex:0 0 auto}\n  .brand .bt{font-weight:750;font-size:18px;letter-spacing:-.01em}\n  .brand .bs{font-size:11.5px;color:#9096a4;margin-top:1px}\n  .tabs{display:flex;background:#f5f5f7;border-radius:11px;padding:4px;margin-bottom:8px}\n  .tab{flex:1;text-align:center;font-size:13.5px;font-weight:600;color:#6b7280;padding:9px;border-radius:8px;cursor:pointer;transition:.12s}\n  .tab.on{background:#fff;color:${ACCENT};box-shadow:0 1px 4px rgba(0,0,0,.06)}\n  .sub{text-align:center;font-size:13px;color:#7c8494;margin:12px 0 18px}\n  .field{margin-bottom:14px}\n  .lab{display:block;font-size:11.5px;font-weight:650;color:#6b7280;margin-bottom:6px}\n  .in{width:100%;padding:11px 13px;border:1px solid #e6e6ea;border-radius:10px;font-size:14px;outline:none;color:#23262e;background:#fff}\n  .in:focus{border-color:${ACCENT};box-shadow:0 0 0 3px #fff2ea}\n  .btn{width:100%;margin-top:6px;background:${ACCENT};color:#fff;border:0;border-radius:11px;padding:12px;font-size:14px;font-weight:700;cursor:pointer;box-shadow:0 8px 20px rgba(244,134,70,.32)}\n  .btn[disabled]{opacity:.6;cursor:default;box-shadow:none}\n  .err{margin-top:12px;font-size:12.5px;color:#c0392b;background:#fdf1ef;border:1px solid #f3d6d0;border-radius:9px;padding:9px 11px}\n  .hint{margin-top:16px;text-align:center;font-size:11.5px;color:#a0a4b0}\n  `,\n  render(d2){\n    const mode = d2.state.mode || 'login';\n    const busy = !!d2.state.busy;\n    const err = d2.state.error;\n    const isSignup = mode==='signup';\n    return `<div class=\"wrap\"><div class=\"card\">\n      <div class=\"brand\"><span class=\"dot\"></span><div><div class=\"bt\">Memória Viva</div><div class=\"bs\">Acervo Natura</div></div></div>\n      <div class=\"tabs\">\n        <div class=\"tab ${!isSignup?'on':''}\" data-mode=\"login\">Entrar</div>\n        <div class=\"tab ${isSignup?'on':''}\" data-mode=\"signup\">Criar conta</div>\n      </div>\n      <div class=\"sub\">${isSignup?'Crie seu acesso ao acervo':'Acesse a curadoria da Natura'}</div>\n      ${isSignup?`<div class=\"field\"><label class=\"lab\">Nome</label><input class=\"in f-nome\" type=\"text\" placeholder=\"Seu nome\" autocomplete=\"name\"/></div>`:''}\n      <div class=\"field\"><label class=\"lab\">E-mail</label><input class=\"in f-email\" type=\"email\" placeholder=\"voce@exemplo.com\" autocomplete=\"email\"/></div>\n      <div class=\"field\"><label class=\"lab\">Senha</label><input class=\"in f-pass\" type=\"password\" placeholder=\"••••••••\" autocomplete=\"${isSignup?'new-password':'current-password'}\"/></div>\n      <button class=\"btn go\" ${busy?'disabled':''}>${busy?(isSignup?'Criando…':'Entrando…'):(isSignup?'Criar conta':'Entrar')}</button>\n      ${err?`<div class=\"err\">${esc(err)}</div>`:''}\n      <div class=\"hint\">${isSignup?'Já tem conta? Toque em Entrar.':'Novo por aqui? Toque em Criar conta.'}</div>\n    </div></div>`;\n  },\n  mounted(d2){\n    const auth = ()=> d2.auth('mv-login');\n    const val = (s)=>{ const el=d2.$(s); return el? (el.value||'').trim() : ''; };\n    const go = async ()=>{\n      if (d2.state.busy) return;\n      const mode = d2.state.mode || 'login';\n      const email = val('.f-email').toLowerCase(), pass = val('.f-pass'), nome = val('.f-nome');\n      d2.state.error = '';\n      if (!email || !pass || (mode==='signup' && !nome)) { d2.state.error = 'Preencha todos os campos.'; return; }\n      d2.state.busy = true;\n      try {\n        if (mode==='signup') {\n          const rc = await d2.rush('mv-cria-conta').invoke({ name: nome, email, password: pass });\n          const c = (rc && rc.data) ? rc.data : rc;\n          if (!c || !c.ok) { d2.state.busy=false; d2.state.error = (c && c.error) || 'Não foi possível criar a conta.'; return; }\n        }\n        let _res=null, _err=null;\n        try { _res = await d2.auth('mv-login').login({ email, password: pass }); } catch(err) { _err=err; d2.log('login', err); }\n        const _a = d2.auth('mv-login');\n        const _authed = !!(_a && (_a.isAuthed || _a.session || _a.user));\n        d2.state.busy = false;\n        if (!_authed && (_err || (_res && (_res.error || _res.isData2Error)))) { d2.state.error = 'E-mail ou senha inválidos.'; return; }\n        try{ d2.router('mv-router').goTo('/app'); }catch(_){}\n        setTimeout(()=>{ try{ var p=(window.location&&window.location.pathname)||''; if(p.indexOf('/app')<0) window.location.assign('/app'); }catch(_){} }, 400);\n      } catch(e) {\n        d2.state.busy = false;\n        d2.state.error = mode==='signup' ? 'Não foi possível criar a conta. Tente de novo.' : 'E-mail ou senha inválidos.';\n      }\n    };\n    d2.on('.tab','click',(e,el)=>{ d2.state.mode = el.dataset.mode; d2.state.error=''; });\n    d2.on('.go','click', go);\n    d2.on('.in','keydown',(e)=>{ if(e.key==='Enter') go(); });\n  }\n};\n"}}}}}}}},{"_id":"6a59511472d72648f3164d2d","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"mv-pre-form","package":"fernando-playground","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 ACCENT = '#F48646';\nconst CATS = [['museologico','Museológico'],['iconografia','Iconografia'],['biblioteca','Documento de Biblioteca'],['arquivo','Documento de Arquivo'],['depoimento','Depoimento'],['cosmetoteca','Cosmetoteca'],['audiovisual','Audiovisual']];\nconst esc = (s)=> (s==null?'':String(s)).replace(/[&<>\"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst FIELDS = [\n  {k:'enviado_por', label:'Seu nome (opcional)', full:true},\n  {k:'titulo', label:'Título do item', req:true, full:true},\n  {k:'categoria_slug', label:'Categoria', type:'select'},\n  {k:'marca_slug', label:'Marca / linha (ex.: ekos)'},\n  {k:'ciclo_codigo', label:'Ciclo (ex.: 13/2026)'},\n  {k:'tombo', label:'Nº de Tombo (se souber)'},\n  {k:'origem', label:'Origem'},\n  {k:'tags', label:'Tags (separadas por vírgula)', full:true},\n  {k:'imagem_ref', label:'Imagem — cole uma URL (opcional)', full:true},\n  {k:'descricao', label:'Descrição', type:'textarea', full:true},\n  {k:'observacao', label:'Observação para a curadoria', type:'textarea', full:true}\n];\n\nexport default {\n  css: `\n  *{box-sizing:border-box}\n  .wrap{height:100%;overflow-y:auto;display:flex;justify-content:center;padding:32px 24px;\n    background:radial-gradient(1100px 560px at 50% -8%, #fff4ec, #ffffff 62%);\n    font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#23262e;-webkit-font-smoothing:antialiased}\n  .card{width:100%;max-width:540px;height:fit-content;background:#fff;border:1px solid #ececef;border-radius:18px;box-shadow:0 20px 60px rgba(20,24,40,.10);padding:30px 28px}\n  .brand{display:flex;align-items:center;gap:11px;margin-bottom:14px}\n  .brand .dot{width:32px;height:32px;border-radius:9px;background:${ACCENT};box-shadow:0 6px 16px rgba(244,134,70,.4);flex:0 0 auto}\n  .brand .bt{font-weight:750;font-size:17px}\n  .brand .bs{font-size:11.5px;color:#9096a4;margin-top:1px}\n  .sub{font-size:13.5px;color:#5b6070;margin:0 0 14px;line-height:1.5}\n  .info{font-size:12px;color:#8a5a34;background:#fff2ea;border:1px solid #f3e2d3;border-radius:10px;padding:10px 12px;margin-bottom:18px;line-height:1.45}\n  .grid{display:grid;grid-template-columns:1fr 1fr;gap:12px 16px}\n  .f{display:flex;flex-direction:column}.f-full{grid-column:1/-1}\n  .f label{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#a0a4b0;margin-bottom:5px}\n  .pin{width:100%;padding:10px 12px;border:1px solid #e6e6ea;border-radius:9px;font-size:13.5px;color:#23262e;background:#fff;outline:none;font-family:inherit}\n  .pin:focus{border-color:${ACCENT};box-shadow:0 0 0 3px #fff2ea}\n  textarea.pin{resize:vertical;min-height:60px}\n  .btn{width:100%;margin-top:18px;background:${ACCENT};color:#fff;border:0;border-radius:11px;padding:13px;font-size:14px;font-weight:700;cursor:pointer;box-shadow:0 8px 20px rgba(244,134,70,.32);font-family:inherit}\n  .btn[disabled]{opacity:.6;cursor:default;box-shadow:none}\n  .err{margin-top:12px;font-size:12.5px;color:#c0392b;background:#fdf1ef;border:1px solid #f3d6d0;border-radius:9px;padding:9px 12px}\n  .ok-wrap{text-align:center;padding:14px 4px}\n  .ok-g{font-size:46px}\n  .ok-t{font-size:20px;font-weight:780;margin-top:8px}\n  .ok-s{font-size:13.5px;color:#5b6070;margin-top:8px;line-height:1.55}\n  .ok-btn{margin-top:20px;background:#fff;border:1px solid #e6e6ea;border-radius:10px;padding:11px 18px;font-size:13px;font-weight:650;color:#5b6070;cursor:pointer;font-family:inherit}\n  .ok-btn:hover{border-color:${ACCENT};color:${ACCENT}}\n  .foot{font-size:11px;color:#a0a4b0;text-align:center;margin-top:16px}\n  `,\n  render(d2){\n    if (d2.state.sent){\n      return `<div class=\"wrap\"><div class=\"card\"><div class=\"ok-wrap\">\n        <div class=\"ok-g\">✅</div>\n        <div class=\"ok-t\">Sugestão enviada!</div>\n        <div class=\"ok-s\">Obrigado por contribuir com a Memória Viva da Natura. Sua sugestão entrou na <b>fila de aprovação da curadoria</b> — quando for aprovada, o item passa a fazer parte do acervo.</div>\n        <button class=\"novo ok-btn\">Enviar outro item</button>\n      </div></div></div>`;\n    }\n    const busy = !!d2.state.busy;\n    const err = d2.state.error;\n    const f = d2.state.form || {};\n    const mids = Array.isArray(d2.state.pMids)?d2.state.pMids:[];\n    const img = d2.state.pB64 || (mids.length?mids[0].ref:''), aiBusy = !!d2.state.aiBusy, aierr = d2.state.aierr, upN = d2.state.pUp||'';\n    const fh = FIELDS.map(fd=>{\n      const cls = fd.full ? 'f f-full' : 'f';\n      const v = esc(f[fd.k]==null?'':f[fd.k]);\n      if(fd.type==='select') return `<div class=\"${cls}\"><label>${fd.label}</label><select class=\"pin\" data-k=\"${fd.k}\"><option value=\"\">—</option>${CATS.map(c=>`<option value=\"${c[0]}\" ${f[fd.k]===c[0]?'selected':''}>${esc(c[1])}</option>`).join('')}</select></div>`;\n      if(fd.type==='textarea') return `<div class=\"${cls}\"><label>${fd.label}</label><textarea class=\"pin\" data-k=\"${fd.k}\" rows=\"3\">${v}</textarea></div>`;\n      return `<div class=\"${cls}\"><label>${fd.label}${fd.req?' *':''}</label><input class=\"pin\" data-k=\"${fd.k}\" value=\"${v}\"/></div>`;\n    }).join('');\n    return `<div class=\"wrap\"><div class=\"card\">\n      <div class=\"brand\"><span class=\"dot\"></span><div><div class=\"bt\">Memória Viva</div><div class=\"bs\">Acervo Natura</div></div></div>\n      <p class=\"sub\">Catalogue um item da história da Natura. Qualquer pessoa pode sugerir — não precisa de login.</p>\n      <div class=\"info\">📝 O que você enviar entra como <b>pré-catálogo</b> e passa por aprovação da curadoria antes de virar item do acervo.</div>\n      <div style=\"margin-bottom:16px\"><div style=\"display:flex;gap:12px;align-items:flex-start;flex-wrap:wrap\"><div style=\"flex:0 0 118px\">${img?`<img src=\"${esc(img)}\" style=\"width:118px;height:118px;object-fit:cover;border-radius:10px;border:1px solid #eee\"/>`:`<div style=\"width:118px;height:118px;border:1px dashed #dcdfe4;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:26px;color:#c3c7d0\">📷</div>`}</div><div style=\"flex:1;min-width:190px\"><div style=\"font-size:12.5px;color:#5b6070;margin-bottom:8px\">Opcional: envie <b>uma ou várias</b> fotos do item — a <b>IA preenche os campos</b> a partir da primeira.</div><label class=\"ok-btn\" style=\"cursor:pointer;display:inline-block\">Escolher imagens<input class=\"p-file\" type=\"file\" accept=\"image/*,video/*\" multiple hidden/></label> <button class=\"p-run btn\" style=\"margin-top:8px;padding:9px 14px;width:auto\" ${(!d2.state.pB64||aiBusy)?'disabled':''}>${aiBusy?'Analisando…':'✨ Analisar com IA'}</button>${upN?`<div style=\"font-size:12px;color:${ACCENT};margin-top:8px;font-weight:650\">${esc(upN)}</div>`:''}${aierr?`<div class=\"err\" style=\"margin-top:8px\">Erro ao analisar: ${esc(aierr)}</div>`:''}</div></div>${mids.length?`<div style=\"display:flex;gap:7px;flex-wrap:wrap;margin-top:11px\">${mids.map((m,i)=>`<div style=\"position:relative;width:62px;height:62px;border-radius:9px;overflow:hidden;border:1px solid #eee\">${m.tipo==='video'?`<div style=\"width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#f4f4f6;font-size:19px\">▶</div>`:`<img src=\"${esc(m.ref)}\" style=\"width:100%;height:100%;object-fit:cover\"/>`}<button class=\"p-mid-rm\" data-i=\"${i}\" style=\"position:absolute;top:1px;right:1px;background:rgba(20,24,40,.7);color:#fff;border:0;border-radius:6px;width:17px;height:17px;font-size:10px;cursor:pointer;line-height:1;padding:0\">✕</button></div>`).join('')}</div><div style=\"font-size:11px;color:#9096a4;margin-top:6px\">${mids.length} ${mids.length===1?'mídia anexada':'mídias anexadas'} — a primeira será a capa</div>`:''}</div>\n      <div class=\"grid\">${fh}</div>\n      <button class=\"enviar btn\" ${busy?'disabled':''}>${busy?'Enviando…':'Enviar para curadoria'}</button>\n      ${err?`<div class=\"err\">${esc(err)}</div>`:''}\n      <div class=\"foot\">Memória Viva · curadoria do acervo Natura</div>\n    </div></div>`;\n  },\n  mounted(d2){\n    try{ const _v=d2.assistant('mv-autofill-visao'); if(_v) void _v.state; }catch(e){}\n    const enviar = async ()=>{\n      if(d2.state.busy) return;\n      const body={}; d2.$$('.pin').forEach(el=>{ body[el.dataset.k]=(el.value||'').trim(); });\n      if(!body.titulo){ d2.state.error='Informe ao menos o título do item.'; return; }\n      body.tags_texto = body.tags || '';\n      const _m=Array.isArray(d2.state.pMids)?d2.state.pMids:[]; if(_m.length){ body.midias=_m; }\n      d2.state.error=''; d2.state.busy=true;\n      try{\n        const res = await d2.rush('mv-pre-enviar').invoke(body);\n        const r = (res && res.data) ? res.data : res;\n        if(r && r.ok){ d2.state.sent=true; }\n        else { d2.state.error=(r && r.error) || 'Não foi possível enviar. Tente de novo.'; }\n      }catch(e){ d2.state.error='Não foi possível enviar. Tente de novo.'; d2.log('pre-enviar', e); }\n      d2.state.busy=false;\n    };\n    d2.on('.p-file','change', async (e,el)=>{\n      const files = el.files ? Array.prototype.slice.call(el.files) : []; if(!files.length) return;\n      const cur = Array.isArray(d2.state.pMids)?d2.state.pMids.slice():[];\n      for(let i=0;i<files.length;i++){\n        const f=files[i];\n        d2.state.pUp = 'Enviando '+(i+1)+' de '+files.length+'…';\n        try{\n          const b64 = await new Promise((res,rej)=>{ const rd=new FileReader(); rd.onload=()=>res(String(rd.result||'')); rd.onerror=rej; rd.readAsDataURL(f); });\n          const isImg = String(f.type||'').indexOf('image')===0;\n          if(isImg && !d2.state.pB64) d2.state.pB64 = b64;\n          const r = await d2.rush('mv-midia-upload').invoke({b64:b64, fileName:(f.name||'midia')});\n          const out=(r&&r.data)?r.data:r;\n          if(out&&out.url) cur.push({ref:out.url, tipo:(isImg?'imagem':'video'), legenda:''});\n        }catch(err){ d2.log('p-upload',err); }\n      }\n      d2.state.pMids = cur; d2.state.pUp='';\n    });\n    d2.on('.p-mid-rm','click',(e,el)=>{ const i=+el.dataset.i; const cur=(Array.isArray(d2.state.pMids)?d2.state.pMids:[]).slice(); if(i>=0&&i<cur.length){ cur.splice(i,1); d2.state.pMids=cur; if(!cur.length) d2.state.pB64=''; } });\n    d2.on('.p-run','click', async ()=>{ const im=d2.state.pB64; if(!im||d2.state.aiBusy) return; d2.state.aiBusy=true; d2.state.aierr=''; try{ const out=await d2.assistant('mv-autofill-visao').ask('Analise a imagem e sugira os campos da ficha catalográfica do acervo Natura.', {images:[im], json:true}); const a=(function(o){ if(o&&typeof o==='object') return o; const s=String(o||''); const i=s.indexOf('{'), j=s.lastIndexOf('}'); if(i<0||j<=i) return {}; try{ return JSON.parse(s.slice(i,j+1)); }catch(_){ return {}; } })(out)||{}; const cur=d2.state.form||{}; d2.state.form=Object.assign({}, cur, { titulo:a.titulo||cur.titulo||'', categoria_slug:a.categoria_slug||cur.categoria_slug||'', marca_slug:a.marca_slug||cur.marca_slug||'', ciclo_codigo:a.ciclo_codigo||cur.ciclo_codigo||'', tombo:a.tombo||cur.tombo||'', origem:a.origem||cur.origem||'', descricao:a.descricao||cur.descricao||'', tags:(Array.isArray(a.tags)?a.tags.join(', '):(a.tags||cur.tags||'')) }); }catch(er){ d2.state.aierr=String(er&&er.message||er); d2.log('pre-visao',er); } d2.state.aiBusy=false; });\n    d2.on('.enviar','click', enviar);\n    d2.on('.novo','click',()=>{ d2.state.sent=false; d2.state.error=''; d2.state.pMids=[]; d2.state.pB64=''; d2.state.pUp=''; d2.state.form={}; });\n  }\n};\n"}}}}}}}},{"_id":"6a5a12bff6fdf852ef416ded","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"mv-expo-view","package":"fernando-playground","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 ACCENT = '#F48646';\nconst CATS = {\n  museologico:{glyph:'🏛️',color:'#B07C4F',nome:'Museológico'},\n  iconografia:{glyph:'🖼️',color:'#5E9E8C',nome:'Iconografia'},\n  biblioteca:{glyph:'📖',color:'#C77D4A',nome:'Documento de Biblioteca'},\n  arquivo:{glyph:'🗂️',color:'#7B8CAE',nome:'Documento de Arquivo'},\n  depoimento:{glyph:'🎙️',color:'#A87CA0',nome:'Depoimento'},\n  cosmetoteca:{glyph:'🧴',color:'#D98E9E',nome:'Cosmetoteca'},\n  audiovisual:{glyph:'🎬',color:'#6C8EBF',nome:'Audiovisual'}\n};\nconst esc = (s)=> (s==null?'':String(s)).replace(/[&<>\"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst catOf = (slug)=> CATS[slug] || {glyph:'📦',color:'#9aa0a6',nome:slug||'—'};\nconst isUrl = (r)=> typeof r==='string' && /^https?:/i.test(r);\nconst recs = (d2,name)=>{ try{ const h=d2.data(name); return h? h.records():[]; }catch(e){ return []; } };\nconst slugOf = (d2)=>{\n  try{ const p=d2.router('mv-router').params; if(p && p.slug) return p.slug; }catch(e){}\n  try{ const parts=((location&&location.pathname)||'').split('/').filter(Boolean); const i=parts.indexOf('exposicao'); if(i>=0 && parts[i+1]) return decodeURIComponent(parts[i+1]); }catch(e){}\n  return '';\n};\nconst curExpo = (d2)=>{ return d2.state.pubExpo || null; };\nconst stf = (off)=>{\n  const a = Math.max(-3, Math.min(3, off)); const abs = Math.abs(a);\n  const tx = a * 520; const tz = -150 + abs*140; const ry = -a * 48; const sc = 1 - abs*0.06;\n  return `translate(-50%,-50%) translateX(${tx}px) translateZ(${tz}px) rotateY(${ry}deg) scale(${sc})`;\n};\n\nexport default {\n  css: `\n  *{box-sizing:border-box}\n  .expo{height:100%;display:flex;flex-direction:column;overflow:hidden;position:relative;color:#23262e;\n    font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;\n    background:radial-gradient(1200px 700px at 50% 40%, #ffffff 0%, #fbf7f3 55%, #f2e9e1 100%)}\n  .top{display:flex;align-items:center;gap:8px;padding:12px 24px;font-size:12.5px;font-weight:700;color:#5b6070;z-index:6}\n  .et-dot{width:18px;height:18px;border-radius:6px;background:${ACCENT}}\n  .head{text-align:center;padding:6px 24px 0;z-index:6}\n  .h-tema{font-size:11px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:${ACCENT}}\n  .h-t{font-size:clamp(22px,3.4vw,36px);font-weight:820;letter-spacing:-.02em;line-height:1.06;margin:8px 0 0}\n  .h-sub{font-size:13.5px;color:#6b7280;margin-top:6px}\n  .stage{flex:1;position:relative;perspective:1500px;transform-style:preserve-3d;min-height:0;touch-action:pan-y}\n  .slide{position:absolute;top:50%;left:50%;width:min(440px,72vw);height:min(340px,52vh);\n    transition:transform .55s cubic-bezier(.22,.61,.36,1), opacity .45s ease;will-change:transform,opacity;cursor:pointer}\n  .slide .card{width:100%;height:100%;border-radius:18px;overflow:hidden;box-shadow:0 30px 70px rgba(40,24,10,.28);background:#fff}\n  .slide.center{cursor:default}\n  .slide.center .card{box-shadow:0 40px 90px rgba(40,24,10,.34);outline:3px solid rgba(255,255,255,.7)}\n  .ph{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:74px;\n    background:linear-gradient(135deg,color-mix(in srgb,var(--c) 24%,#fff),color-mix(in srgb,var(--c) 48%,#fff))}\n  .s-img{width:100%;height:100%;object-fit:cover;display:block}\n  .s-tag{position:absolute;left:14px;bottom:14px;background:rgba(255,255,255,.9);border-radius:8px;padding:4px 9px;font-size:10.5px;font-weight:700;color:#5b6070}\n  .txt-card{position:relative;background:linear-gradient(150deg,#fff4ea,#fbe7d6);display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:34px 34px}\n  .tc-mark{font-size:58px;line-height:.5;color:${ACCENT};opacity:.55;font-family:Georgia,'Times New Roman',serif}\n  .tc-title{font-size:19px;font-weight:770;color:#23262e;margin:14px 0 8px}\n  .tc-text{font-size:16px;line-height:1.65;color:#4b3f36;max-height:210px;overflow:auto}\n  .tc-tag{position:absolute;left:14px;bottom:14px;background:rgba(255,255,255,.85);border-radius:8px;padding:4px 9px;font-size:10.5px;font-weight:700;color:#a06a3a}\n  .ctrl-row{display:flex;align-items:center;gap:16px}\n  .arrow{width:52px;height:52px;border-radius:50%;border:1px solid #ececef;background:#fff;box-shadow:0 10px 26px rgba(20,24,40,.14);color:#5b6070;font-size:24px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:.15s;padding-bottom:3px}\n  .arrow:hover{border-color:${ACCENT};color:${ACCENT};transform:translateY(-2px);box-shadow:0 14px 30px rgba(244,134,70,.28)}\n  .arrow[disabled]{opacity:.3;cursor:default;box-shadow:none;transform:none}\n  .cap{z-index:6;text-align:center;padding:6px 24px 10px;max-width:720px;margin:0 auto}\n  .cap-n{font-size:12px;font-weight:800;color:${ACCENT};letter-spacing:.1em}\n  .cap-t{font-size:clamp(17px,2.4vw,24px);font-weight:760;letter-spacing:-.01em;margin:4px 0 0}\n  .cap-s{font-size:11.5px;color:#9096a4;margin-top:4px;text-transform:uppercase;letter-spacing:.04em;font-weight:700}\n  .cap-p{font-size:14.5px;color:#3b4150;line-height:1.6;margin-top:10px}\n  .dots{display:flex;gap:7px;justify-content:center;padding:6px 0 16px;z-index:6}\n  .dot{width:8px;height:8px;border-radius:50%;background:#d8ccc0;cursor:pointer;transition:.2s}\n  .dot.on{background:${ACCENT};transform:scale(1.25)}\n  .controls{display:flex;flex-direction:column;align-items:center;gap:10px;padding:6px 0 12px;z-index:6}\n  .controls .dots{padding:0}\n  .expo-play{background:${ACCENT};color:#fff;border:0;border-radius:999px;padding:13px 26px;font-size:13.5px;font-weight:700;letter-spacing:.01em;cursor:pointer;font-family:inherit;box-shadow:0 10px 24px rgba(244,134,70,.38)}\n  .expo-play:hover{filter:brightness(1.05)}\n  .expo-empty{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:#9096a4;padding:40px}\n  .ee-g{font-size:52px}.ee-t{font-size:22px;font-weight:750;color:#23262e;margin-top:10px}.ee-s{font-size:14px;margin-top:6px}\n  .hint{text-align:center;font-size:11px;color:#a99;padding-bottom:6px}\n  `,\n  render(d2){\n    const expo = curExpo(d2);\n    if(!expo){\n      if(!d2.state.pubLoaded) return `<div class=\"expo\"><div class=\"expo-empty\"><div class=\"ee-g\">🖼️</div><div class=\"ee-t\">Carregando exposição…</div></div></div>`;\n      return `<div class=\"expo\"><div class=\"expo-empty\"><div class=\"ee-g\">🖼️</div><div class=\"ee-t\">Exposição não encontrada</div><div class=\"ee-s\">Ela pode não existir ou ainda não ter sido publicada pela curadoria.</div></div></div>`;\n    }\n    const itens = Array.isArray(expo.itens)?expo.itens:[];\n    const n = itens.length;\n    let idx = d2.state.expoIdx||0; if(idx<0) idx=0; if(idx>n-1) idx=Math.max(0,n-1);\n    const slides = itens.map((it,i)=>{\n      const off=i-idx; const abs=Math.abs(off); const op=off===0?1:Math.max(0,0.82-(abs-1)*0.32); const vis=abs<=3;\n      const wrap=(inner,key)=>`<div class=\"slide ${off===0?'center':''}\" data-i=\"${i}\" data-key=\"${key}\" style=\"transform:${stf(off)};opacity:${vis?op:0};z-index:${60-abs};pointer-events:${vis&&abs<=2?'auto':'none'}\">${inner}</div>`;\n      if(it.tipo==='texto'){ return wrap(`<div class=\"card txt-card\"><div class=\"tc-mark\">“</div>${it.titulo?`<div class=\"tc-title\">${esc(it.titulo)}</div>`:''}<div class=\"tc-text\">${esc(it.texto||'')}</div><div class=\"tc-tag\">Nota de curadoria</div></div>`, 'et'+i); }\n      const item=it; const cat=catOf(item.categoria_slug);\n      const media=isUrl(item.imagem_ref)?`<img class=\"s-img\" src=\"${esc(item.imagem_ref)}\" alt=\"\"/>`:`<div class=\"ph\" style=\"--c:${cat.color}\">${cat.glyph}</div>`;\n      return wrap(`<div class=\"card\">${media}<div class=\"s-tag\">${cat.glyph} ${esc(cat.nome)}</div></div>`, esc(it.codigo)+'_'+i);\n    }).join('');\n    const cur = itens[idx]||{}; let capT,capS,capP; if(cur.tipo==='texto'){ capT=cur.titulo||'Intermezzo'; capS='Nota de curadoria'; capP=''; } else { const ci=cur; const cc=catOf(ci.categoria_slug); capT=ci.titulo||cur.codigo||''; capS=cc.nome+(ci.ciclo_codigo?' · ciclo '+ci.ciclo_codigo:''); capP=cur.texto||ci.descricao||''; }\n    return `<div class=\"expo\">\n      <div class=\"top\"><span class=\"et-dot\"></span>Memória Viva · Acervo Natura</div>\n      <div class=\"head\"><div class=\"h-tema\">Exposição${expo.tema?' · '+esc(expo.tema):''}</div><h1 class=\"h-t\">${esc(expo.titulo)}</h1>${expo.subtitulo?`<div class=\"h-sub\">${esc(expo.subtitulo)}</div>`:''}</div>\n      ${n? `<div class=\"stage\">${slides}</div>\n      <div class=\"cap\"><div class=\"cap-n\">${String(idx+1).padStart(2,'0')} / ${String(n).padStart(2,'0')}</div><div class=\"cap-t\">${esc(capT)}</div><div class=\"cap-s\">${esc(capS)}</div>${capP?`<div class=\"cap-p\">${esc(capP)}</div>`:''}</div>\n      <div class=\"controls\"><div class=\"ctrl-row\"><button class=\"arrow prev\" ${idx===0?'disabled':''}>‹</button><button class=\"expo-play\">${d2.state.expoPlaying?'⏸ Pausar':'▶ Reproduzir'}</button><button class=\"arrow next\" ${idx===n-1?'disabled':''}>›</button></div><div class=\"dots\">${itens.map((_,i)=>`<span class=\"dot ${i===idx?'on':''}\" data-i=\"${i}\"></span>`).join('')}</div></div>\n      <div class=\"hint\">use ‹ › · clique nas laterais · arraste · setas do teclado</div>`\n      : `<div class=\"cap\"><div class=\"cap-p\">${esc(expo.curadoria||'Esta exposição ainda não tem peças.')}</div></div>`}\n    </div>`;\n  },\n  mounted(d2){\n    const loadPub=async ()=>{ try{ const s=slugOf(d2); const r=await d2.rush('mv-expo-publica').invoke({slug:s}); const data=(r&&r.data)?r.data:r; d2.state.pubExpo=(data&&data.expo)?data.expo:null; }catch(e){ d2.log('expo-pub',e); } d2.state.pubLoaded=true; }; loadPub();\n    const total = ()=>{ const ex=curExpo(d2); return (ex&&Array.isArray(ex.itens))?ex.itens.length:0; };\n    const go = (i)=>{ const n=total(); if(!n) return; let v=i; if(v<0)v=0; if(v>n-1)v=n-1; d2.state.expoIdx=v; };\n    const prev=()=> go((d2.state.expoIdx||0)-1);\n    const next=()=> go((d2.state.expoIdx||0)+1);\n    d2.on('.arrow.prev','click',prev);\n    d2.on('.arrow.next','click',next);\n    d2.on('.slide','click',(e,el)=>{ const i=+el.dataset.i; if(!isNaN(i)) go(i); });\n    d2.on('.dot','click',(e,el)=>{ const i=+el.dataset.i; if(!isNaN(i)) go(i); });\n    d2.on(window,'keydown',(e)=>{ if(e.key==='ArrowLeft') prev(); else if(e.key==='ArrowRight') next(); });\n    let sx=null;\n    d2.on('.stage','pointerdown',(e)=>{ sx=e.clientX; });\n    d2.on(window,'pointerup',(e)=>{ if(sx==null) return; const dx=e.clientX-sx; sx=null; if(dx<-45) next(); else if(dx>45) prev(); });\n    let timer=null;\n    const stopAuto=()=>{ if(timer){ clearInterval(timer); timer=null; } };\n    const startAuto=()=>{ stopAuto(); timer=setInterval(()=>{ const n=total(); if(!n) return; const cur=d2.state.expoIdx||0; d2.state.expoIdx=(cur>=n-1)?0:cur+1; }, 4500); };\n    d2.on('.expo-play','click',()=>{ if(d2.state.expoPlaying){ d2.state.expoPlaying=false; stopAuto(); } else { d2.state.expoPlaying=true; startAuto(); } });\n    if(d2.state.expoPlaying) startAuto();\n    d2.onUnmount(stopAuto);\n  }\n};\n"}}}}}}}},{"_id":"6a5f61860fa17fc6eb3f4a42","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"naturaNav","package":"fernando-playground","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 findRouterProc = () => {\n  try {\n    const env = (typeof window !== 'undefined') ? window.D2Env : null\n    if (!env || !env.layers) return null\n    const layers = Object.values(env.layers)\n    for (let i = 0; i < layers.length; i++){\n      const procs = layers[i] && layers[i].processes\n      if (!procs) continue\n      if (typeof procs.has === 'function' && procs.has('MainRouterInterface')) return procs.get('MainRouterInterface')\n      let found = null\n      if (typeof procs.forEach === 'function') procs.forEach((v,k)=>{ if(k==='MainRouterInterface') found=v })\n      else if (procs['MainRouterInterface']) found = procs['MainRouterInterface']\n      if (found) return found\n    }\n  } catch(e){}\n  return null\n}\nconst goRoute = (path) => {\n  if (!path) return\n  const proc = findRouterProc()\n  if (proc){\n    try { const h = proc.get && proc.get('history'); if (h && typeof h.push === 'function'){ h.push(path); return } } catch(e){}\n    try { if (typeof proc.invoke === 'function'){ proc.invoke('history.push', path); return } } catch(e){}\n  }\n}\n\nexport default {\n  css: `\n:host{ all:initial; }\n*{ box-sizing:border-box; margin:0; padding:0; }\n.n-root{\n  --font:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;\n  font-family:var(--font); width:100%; height:100%; -webkit-font-smoothing:antialiased;\n}\n.n-root.light{\n  --bg:#ffffff;--panel:#ffffff;--text:#0a0a0a;--muted:#666666;--faint:#8f8f8f;\n  --border:#ebebeb;--border-strong:#d4d4d4;--accent:#EB6619;--accent-soft:#fff2ea;\n}\n.n-root.dark{\n  --bg:#0a0a0a;--panel:#161616;--text:#ededed;--muted:#a0a0a0;--faint:#707070;\n  --border:#262626;--border-strong:#383838;--accent:#EB6619;--accent-soft:#2a1509;\n}\n.hdr{ height:100%; width:100%; background:var(--bg); border-bottom:1px solid var(--border);\n      display:flex; align-items:center; gap:24px; padding:0 24px; }\n.brand{ display:flex; align-items:center; gap:10px; flex:none; }\n.dot{ width:22px;height:22px;border-radius:6px;background:var(--accent);\n      box-shadow:0 2px 8px rgba(235,102,25,.4); flex:none; }\n.bname{ font-size:16px; font-weight:700; color:var(--text); letter-spacing:-.02em; }\n.bsep{ font-size:12px; color:var(--faint); border-left:1px solid var(--border-strong); padding-left:10px; white-space:nowrap; }\n.tabs{ display:flex; align-items:center; gap:4px; }\n.tab{ font-family:var(--font); font-size:14px; color:var(--muted); background:transparent; border:0;\n      padding:8px 12px; border-radius:8px; cursor:pointer; font-weight:500; white-space:nowrap; transition:background .15s,color .15s; }\n.tab:hover{ color:var(--text); background:var(--accent-soft); }\n.tab.act{ color:var(--accent); background:var(--accent-soft); font-weight:600; }\n.spacer{ flex:1; }\n.theme{ width:36px;height:36px;border-radius:8px;border:1px solid var(--border);background:var(--panel);\n        cursor:pointer;font-size:16px;color:var(--text);display:flex;align-items:center;justify-content:center;flex:none;transition:border-color .15s; }\n.theme:hover{ border-color:var(--border-strong); }\n`,\n  render(d2){\n    const theme = d2.shared.get('theme','light',{global:true})\n    const active = d2.param('active','reclamacao')\n    const tabs = [\n      { id:'reclamacao', label:'Nova reclamação', path:'/' },\n      { id:'casos', label:'Casos analisados', path:'/casos' },\n      { id:'config', label:'Configuração do robô', path:'/config' },\n    ]\n    return `<div class=\"n-root ${theme}\">\n      <header class=\"hdr\">\n        <div class=\"brand\"><span class=\"dot\"></span><span class=\"bname\">Natura</span><span class=\"bsep\">Garantia Visual</span></div>\n        <nav class=\"tabs\">${tabs.map(t => `<button class=\"tab ${t.id===active ? 'act':''}\" data-path=\"${t.path}\">${t.label}</button>`).join('')}</nav>\n        <span class=\"spacer\"></span>\n        <button class=\"theme\" title=\"Alternar tema claro/escuro\">${theme==='dark' ? '☀' : '☾'}</button>\n      </header>\n    </div>`\n  },\n  mounted(d2){\n    d2.on('.tab','click',(e,el)=>{\n      const path = (el && el.dataset) ? el.dataset.path : null\n      if (path) goRoute(path)\n    })\n    d2.on('.theme','click',()=>{\n      const cur=d2.shared.get('theme','light',{global:true})\n      d2.shared.set('theme', cur==='dark' ? 'light' : 'dark', {global:true})\n    })\n  },\n}"}},"active":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"active"}}}}}}}}}},{"_id":"6a5f619ae57bc5c1962da46c","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"naturaPlaceholder","package":"fernando-playground","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 findRouterProc = () => {\n  try {\n    const env = (typeof window !== 'undefined') ? window.D2Env : null\n    if (!env || !env.layers) return null\n    const layers = Object.values(env.layers)\n    for (let i = 0; i < layers.length; i++){\n      const procs = layers[i] && layers[i].processes\n      if (!procs) continue\n      if (typeof procs.has === 'function' && procs.has('MainRouterInterface')) return procs.get('MainRouterInterface')\n      let found = null\n      if (typeof procs.forEach === 'function') procs.forEach((v,k)=>{ if(k==='MainRouterInterface') found=v })\n      else if (procs['MainRouterInterface']) found = procs['MainRouterInterface']\n      if (found) return found\n    }\n  } catch(e){}\n  return null\n}\nconst goRoute = (d2, path) => {\n  if (!path) return\n  const proc = findRouterProc()\n  if (proc){\n    try { const h = proc.get && proc.get('history'); if (h && typeof h.push === 'function'){ h.push(path); return } } catch(e){}\n    try { if (typeof proc.invoke === 'function'){ proc.invoke('history.push', path); return } } catch(e){}\n  }\n  try { const r = d2.router && (d2.router('MainRouter') || d2.router()); if (r && typeof r.goTo === 'function'){ r.goTo(path); return } } catch(e){}\n}\n\nexport default {\n  css: `\n:host{ all:initial; }\n*{ box-sizing:border-box; margin:0; padding:0; }\n.n-root{\n  --font:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;\n  font-family:var(--font); width:100%; height:100%; -webkit-font-smoothing:antialiased;\n  display:flex; flex-direction:column;\n}\n.n-root.light{\n  --bg:#ffffff;--bg-subtle:#fafafa;--panel:#ffffff;--text:#0a0a0a;--muted:#666666;--faint:#8f8f8f;\n  --border:#ebebeb;--border-strong:#d4d4d4;--accent:#EB6619;--accent-soft:#fff2ea;\n  --shadow:0 1px 2px rgba(0,0,0,.04),0 8px 24px rgba(0,0,0,.06);\n}\n.n-root.dark{\n  --bg:#0a0a0a;--bg-subtle:#111111;--panel:#161616;--text:#ededed;--muted:#a0a0a0;--faint:#707070;\n  --border:#262626;--border-strong:#383838;--accent:#EB6619;--accent-soft:#2a1509;\n  --shadow:0 1px 2px rgba(0,0,0,.4),0 8px 24px rgba(0,0,0,.5);\n}\n.hdr-slot{ height:60px; flex:none; width:100%; }\n.body{ flex:1; width:100%; background:var(--bg-subtle); display:flex; align-items:center; justify-content:center; padding:40px; }\n.card{ max-width:540px; width:100%; text-align:center; background:var(--panel); border:1px solid var(--border);\n       border-radius:16px; padding:48px 40px; box-shadow:var(--shadow); }\n.tag{ display:inline-block; font-size:12px; font-weight:600; color:var(--accent); background:var(--accent-soft);\n      padding:5px 12px; border-radius:999px; margin-bottom:22px; letter-spacing:.02em; }\n.glyph{ font-size:40px; margin-bottom:18px; }\n.card h1{ font-size:30px; font-weight:700; color:var(--text); letter-spacing:-.025em; margin-bottom:14px; line-height:1.15; }\n.card p{ font-size:15px; color:var(--muted); line-height:1.65; }\n`,\n  render(d2){\n    const theme = d2.shared.get('theme','light',{global:true})\n    const heading = (d2.param('heading','') + '').replace(/</g,'&lt;')\n    const blurb = (d2.param('blurb','') + '').replace(/</g,'&lt;')\n    const phase = (d2.param('phase','') + '').replace(/</g,'&lt;')\n    return `<div class=\"n-root ${theme}\">\n      <div class=\"hdr-slot\">${d2.render('fernando-playground@naturaNav', { active: d2.param('active','reclamacao') }, { key:'nav', onNav:(p)=>goRoute(d2,p) })}</div>\n      <main class=\"body\">\n        <div class=\"card\">\n          <div class=\"tag\">${phase || 'Em construção'}</div>\n          <div class=\"glyph\">🚧</div>\n          <h1>${heading}</h1>\n          <p>${blurb}</p>\n        </div>\n      </main>\n    </div>`\n  },\n}"}},"active":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"active"}}}},"heading":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"heading"}}}},"blurb":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"blurb"}}}},"phase":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"phase"}}}}}}}}}},{"_id":"6a5f647b8992caa3fba45c94","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"naturaReclamacao","package":"fernando-playground","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 esc = (s) => ('' + (s == null ? '' : s)).replace(/</g,'&lt;').replace(/>/g,'&gt;')\n\nconst maskMMYYYY = (v) => {\n  const d = ('' + (v == null ? '' : v)).replace(/\\D/g, '').slice(0, 6)\n  if (d.length <= 2) return d\n  return d.slice(0, 2) + '/' + d.slice(2)\n}\n\nconst findRouterProc = () => {\n  try {\n    const env = (typeof window !== 'undefined') ? window.D2Env : null\n    if (!env || !env.layers) return null\n    const layers = Object.values(env.layers)\n    for (let i = 0; i < layers.length; i++){\n      const procs = layers[i] && layers[i].processes\n      if (!procs) continue\n      if (typeof procs.has === 'function' && procs.has('MainRouterInterface')) return procs.get('MainRouterInterface')\n      let found = null\n      if (typeof procs.forEach === 'function') procs.forEach((v,k)=>{ if(k==='MainRouterInterface') found=v })\n      else if (procs['MainRouterInterface']) found = procs['MainRouterInterface']\n      if (found) return found\n    }\n  } catch(e){}\n  return null\n}\nconst goRoute = (d2, path) => {\n  if (!path) return\n  const proc = findRouterProc()\n  if (proc){\n    try { const h = proc.get && proc.get('history'); if (h && typeof h.push === 'function'){ h.push(path); return } } catch(e){}\n    try { if (typeof proc.invoke === 'function'){ proc.invoke('history.push', path); return } } catch(e){}\n  }\n  try { const r = d2.router && (d2.router('MainRouter') || d2.router()); if (r && typeof r.goTo === 'function'){ r.goTo(path); return } } catch(e){}\n}\n\nconst parseAnalysis = (res) => {\n  let raw = res\n  if (res && typeof res === 'object') raw = (res.analiseRaw !== undefined ? res.analiseRaw : (res.data && res.data.analiseRaw))\n  if (raw && typeof raw === 'object') return raw\n  if (!raw) return null\n  let s = ('' + raw).trim().replace(/^```(?:json)?\\s*/i,'').replace(/```\\s*$/,'').trim()\n  try { return JSON.parse(s) } catch(e){}\n  const m = s.match(/\\{[\\s\\S]*\\}/)\n  if (m){ try { return JSON.parse(m[0]) } catch(e){} }\n  return null\n}\n\nconst guideItems = [\n  { t:'Onde achar o lote e a validade', d:'Procure um código impresso (em relevo ou tinta) na base da embalagem, na aba da caixa ou perto do código de barras — costuma vir junto da validade.' },\n  { t:'Fotografe o produto inteiro', d:'Uma foto mostrando o produto completo, para identificarmos o item da reclamação.' },\n  { t:'Dê um close no defeito', d:'Aproxime na área danificada (vazamento, trinca, amassado, rótulo) com boa luz e sem tremer.' },\n  { t:'Registre o lote legível', d:'Uma foto onde dá pra ler o código do lote e a validade. Evite fotos escuras, tremidas ou muito de longe.' },\n]\n\nconst V = {\n  procede:      { cls:'v-procede', cvar:'--ok',   icon:'✓', label:'Procede',      desc:'O dano/defeito relatado foi confirmado nas imagens.' },\n  nao_procede:  { cls:'v-bad',     cvar:'--bad',  icon:'✕', label:'Não procede',  desc:'As imagens não confirmam o problema relatado.' },\n  inconclusivo: { cls:'v-warn',    cvar:'--warn', icon:'⚠', label:'Inconclusivo', desc:'As imagens não permitem uma conclusão definitiva.' },\n}\n\nconst renderForm = (d2) => {\n  const photos = d2.state.photos || []\n  const errs = d2.state.errors || []\n  return `\n  <div class=\"ptitle\">Abrir uma reclamação de garantia</div>\n  <div class=\"psub\">Informe os dados do produto, descreva o problema e envie fotos. Nosso robô analisa as imagens e verifica se o dano/defeito relatado procede.</div>\n  <div class=\"grid\">\n    <div>\n      <div class=\"card\">\n        <div class=\"clabel\">Dados do produto</div>\n        <div class=\"field\"><label>Produto</label><input id=\"f-produto\" class=\"in\" placeholder=\"Ex.: Desodorante Natura Kaiak Masculino\"/></div>\n        <div class=\"row2\">\n          <div class=\"field\"><label>Lote</label><input id=\"f-lote\" class=\"in\" placeholder=\"Ex.: L2025-0231\"/><div class=\"hint\">Impresso na embalagem</div></div>\n          <div class=\"field\"><label>Validade</label><input id=\"f-venc\" class=\"in\" inputmode=\"numeric\" maxlength=\"7\" placeholder=\"MM/AAAA\"/><div class=\"hint\">Data de vencimento</div></div>\n        </div>\n        <div class=\"field\"><label>Qual o problema?</label><textarea id=\"f-problema\" class=\"ta\" placeholder=\"Descreva o dano ou defeito: o que aconteceu e o que você observou...\"></textarea></div>\n      </div>\n      <div class=\"card\">\n        <div class=\"clabel\">Fotos do produto</div>\n        <div class=\"drop\"><div class=\"dicon\">📷</div><div class=\"dtitle\">Arraste as fotos aqui ou clique para escolher</div><div class=\"dsub\">JPG, PNG ou WEBP · você pode enviar várias</div></div>\n        <input type=\"file\" class=\"hidden-file\" accept=\"image/*\" multiple />\n        ${photos.length ? `<div class=\"thumbs\">${photos.map(p => `<div class=\"thumb\" data-key=\"${p.id}\"><img src=\"${p.url}\" alt=\"\"/><button class=\"rm\" data-id=\"${p.id}\" title=\"Remover\">✕</button></div>`).join('')}</div>` : ''}\n        ${errs.length ? `<div class=\"errs\"><ul>${errs.map(e => `<li>${esc(e)}</li>`).join('')}</ul></div>` : ''}\n        <button class=\"submit btn\">Enviar para análise do robô</button>\n      </div>\n    </div>\n    <div class=\"card guide\">\n      <h3>Como enviar boas fotos</h3>\n      ${guideItems.map((g,i) => `<div class=\"gitem\"><div class=\"gnum\">${i+1}</div><div><div class=\"gt\">${g.t}</div><div class=\"gd\">${g.d}</div></div></div>`).join('')}\n      <div class=\"note\">Quanto mais nítidas as fotos do defeito e do lote, mais rápida e precisa é a análise do robô.</div>\n    </div>\n  </div>`\n}\n\nconst renderAnalyzing = (d2) => {\n  const photos = d2.state.photos || []\n  return `<div class=\"analyzing\">\n    <div class=\"robot\">🤖</div>\n    <div class=\"ah\">Analisando as imagens…</div>\n    <div class=\"as\">O robô está examinando as fotos e comparando com o problema relatado.</div>\n    <div class=\"dots\"><span></span><span></span><span></span></div>\n    ${photos.length ? `<div class=\"mini\">${photos.map(p => `<img src=\"${p.url}\" alt=\"\"/>`).join('')}</div>` : ''}\n  </div>`\n}\n\nconst renderResult = (d2) => {\n  const r = d2.state.result || {}\n  const v = V[r.veredicto] || V.inconclusivo\n  const conf = Math.max(0, Math.min(100, Number(r.confianca) || 0))\n  const itens = Array.isArray(r.itensObservados) ? r.itensObservados : []\n  const manifBlock = r.veredicto === 'procede'\n    ? (r.manifestacaoN3\n        ? `<div class=\"sec\"><div class=\"st\">Manifestação N3 (ServiceNow)</div><div class=\"manif\">${esc(r.manifestacaoN3)}</div></div>`\n        : `<div class=\"sec\"><div class=\"st\">Manifestação N3 (ServiceNow)</div><div class=\"manif alerta\">Nenhuma opção da base se aplica — encaminhar para análise humana.</div></div>`)\n    : ''\n  return `\n  <div class=\"ptitle\">Resultado da análise</div>\n  <div class=\"psub\">Protocolo <b>${esc(r.protocolo)}</b> · ${d2.state.saveFailed\n    ? `<span style=\"color:var(--warn);font-weight:600\">⚠ Não foi possível registrar este caso em Casos analisados. Anote o protocolo e tente novamente mais tarde.</span>`\n    : 'este caso foi registrado em Casos analisados.'}</div>\n  <div class=\"grid\">\n    <div class=\"card\">\n      <div class=\"verdict\">\n        <div class=\"vbadge ${v.cls}\">${v.icon}</div>\n        <div class=\"vtext\"><div class=\"vt\" style=\"color:var(${v.cvar})\">${v.label}</div><div class=\"vd\">${v.desc}</div></div>\n      </div>\n      <div class=\"conf\"><div class=\"cl\"><span>Confiança do robô</span><span>${conf}%</span></div><div class=\"bar\"><i style=\"width:${conf}%\"></i></div></div>\n      ${manifBlock}\n      <div class=\"sec\"><div class=\"st\">Justificativa</div><p>${esc(r.justificativa) || '—'}</p></div>\n      ${itens.length ? `<div class=\"sec\"><div class=\"st\">O que o robô observou</div><div class=\"chips\">${itens.map(it => `<span class=\"chip\">${esc(it)}</span>`).join('')}</div></div>` : ''}\n      ${r.recomendacao ? `<div class=\"sec\"><div class=\"st\">Recomendação</div><div class=\"reco\">${esc(r.recomendacao)}</div></div>` : ''}\n      <div class=\"rbtns\"><button class=\"again btn2\">＋ Nova reclamação</button><button class=\"goto-casos btn2\">Ver todos os casos</button></div>\n    </div>\n    <div class=\"card\">\n      <div class=\"clabel\">Resumo do caso</div>\n      <div class=\"sec\"><div class=\"st\">Produto</div><p>${esc(r.produto) || '—'}</p></div>\n      <div class=\"sec\"><div class=\"st\">Lote / Validade</div><p>${esc(r.lote) || '—'} · ${esc(r.dataVencimento) || '—'}</p></div>\n      <div class=\"sec\"><div class=\"st\">Problema relatado</div><p>${esc(r.problema) || '—'}</p></div>\n      ${(r.imagens && r.imagens.length) ? `<div class=\"sec\"><div class=\"st\">Fotos enviadas</div><div class=\"mini\" style=\"justify-content:flex-start\">${r.imagens.map(u => `<img src=\"${u}\" alt=\"\"/>`).join('')}</div></div>` : ''}\n    </div>\n  </div>`\n}\n\nconst renderError = (d2) => `<div class=\"analyzing\"><div class=\"robot\" style=\"animation:none\">⚠️</div><div class=\"ah\">Não foi possível analisar</div><div class=\"as\">${esc(d2.state.errorMsg) || 'Tente novamente.'}</div><div class=\"rbtns\" style=\"justify-content:center;margin-top:20px\"><button class=\"again btn2\">Tentar de novo</button></div></div>`\n\nexport default {\n  css: `\n:host{ all:initial; }\n*{ box-sizing:border-box; margin:0; padding:0; }\n.n-root{ --font:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;\n  font-family:var(--font); width:100%; height:100%; -webkit-font-smoothing:antialiased; display:flex; flex-direction:column; }\n.n-root.light{ --bg:#ffffff;--bg-subtle:#fafafa;--panel:#ffffff;--text:#0a0a0a;--muted:#666666;--faint:#8f8f8f;\n  --border:#ebebeb;--border-strong:#d4d4d4;--accent:#EB6619;--accent-hover:#d25610;--accent-soft:#fff2ea;\n  --shadow:0 1px 2px rgba(0,0,0,.04),0 8px 24px rgba(0,0,0,.06);\n  --ok:#0a7d38;--ok-soft:#e9f7ee;--warn:#9a6700;--warn-soft:#fdf3e0;--bad:#c02626;--bad-soft:#fdeaea; }\n.n-root.dark{ --bg:#0a0a0a;--bg-subtle:#111111;--panel:#161616;--text:#ededed;--muted:#a0a0a0;--faint:#707070;\n  --border:#262626;--border-strong:#383838;--accent:#EB6619;--accent-hover:#ff7a2e;--accent-soft:#2a1509;\n  --shadow:0 1px 2px rgba(0,0,0,.4),0 8px 24px rgba(0,0,0,.5);\n  --ok:#3fcf6b;--ok-soft:#0f2417;--warn:#e0a92b;--warn-soft:#2a2007;--bad:#f26a6a;--bad-soft:#2a1010; }\n.hdr-slot{ height:60px; flex:none; width:100%; }\n.body{ flex:1; width:100%; background:var(--bg-subtle); overflow:auto; }\n.page{ max-width:1080px; margin:0 auto; padding:32px 24px 64px; }\n.ptitle{ font-size:26px; font-weight:700; color:var(--text); letter-spacing:-.025em; margin-bottom:6px; }\n.psub{ font-size:14px; color:var(--muted); margin-bottom:28px; line-height:1.5; }\n.grid{ display:grid; grid-template-columns:1.3fr .9fr; gap:24px; align-items:start; }\n@media(max-width:820px){ .grid{ grid-template-columns:1fr; } }\n.card{ background:var(--panel); border:1px solid var(--border); border-radius:16px; padding:24px; box-shadow:var(--shadow); }\n.card + .card{ margin-top:20px; }\n.clabel{ font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; color:var(--faint); margin-bottom:16px; }\n.field{ margin-bottom:16px; }\n.field label{ display:block; font-size:13px; font-weight:600; color:var(--text); margin-bottom:6px; }\n.field .hint{ font-size:12px; color:var(--faint); margin-top:5px; }\n.in,.ta{ width:100%; font-family:var(--font); font-size:14px; color:var(--text); background:var(--bg); border:1px solid var(--border-strong); border-radius:10px; padding:10px 12px; outline:none; transition:border-color .15s,box-shadow .15s; }\n.in:focus,.ta:focus{ border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft); }\n.ta{ resize:vertical; min-height:96px; line-height:1.5; }\n.row2{ display:grid; grid-template-columns:1fr 1fr; gap:12px; }\n.drop{ border:2px dashed var(--border-strong); border-radius:12px; padding:24px; text-align:center; cursor:pointer; transition:border-color .15s,background .15s; background:var(--bg); }\n.drop:hover,.drop[data-over]{ border-color:var(--accent); background:var(--accent-soft); }\n.drop .dicon{ font-size:26px; margin-bottom:8px; }\n.drop .dtitle{ font-size:14px; font-weight:600; color:var(--text); }\n.drop .dsub{ font-size:12px; color:var(--faint); margin-top:4px; }\n.thumbs{ display:flex; flex-wrap:wrap; gap:10px; margin-top:14px; }\n.thumb{ position:relative; width:76px; height:76px; border-radius:10px; overflow:hidden; border:1px solid var(--border); }\n.thumb img{ width:100%; height:100%; object-fit:cover; display:block; }\n.thumb .rm{ position:absolute; top:3px; right:3px; width:20px; height:20px; border-radius:6px; border:0; background:rgba(0,0,0,.6); color:#fff; font-size:11px; cursor:pointer; display:flex; align-items:center; justify-content:center; line-height:1; }\n.hidden-file{ display:none; }\n.btn{ width:100%; margin-top:20px; padding:12px 16px; border:0; border-radius:10px; background:var(--accent); color:#fff; font-family:var(--font); font-size:15px; font-weight:600; cursor:pointer; transition:background .15s; }\n.btn:hover{ background:var(--accent-hover); }\n.btn:disabled{ opacity:.55; cursor:default; }\n.errs{ background:var(--bad-soft); border:1px solid var(--bad); color:var(--bad); border-radius:10px; padding:10px 14px; font-size:13px; margin-top:14px; }\n.errs ul{ margin:0; padding-left:16px; }\n.guide h3{ font-size:15px; font-weight:700; color:var(--text); margin-bottom:16px; }\n.gitem{ display:flex; gap:12px; margin-bottom:16px; }\n.gnum{ flex:none; width:26px; height:26px; border-radius:8px; background:var(--accent-soft); color:var(--accent); font-size:13px; font-weight:700; display:flex; align-items:center; justify-content:center; }\n.gitem .gt{ font-size:13px; font-weight:600; color:var(--text); margin-bottom:2px; }\n.gitem .gd{ font-size:13px; color:var(--muted); line-height:1.5; }\n.note{ font-size:12px; color:var(--faint); line-height:1.5; border-top:1px solid var(--border); padding-top:14px; margin-top:4px; }\n.analyzing{ display:flex; flex-direction:column; align-items:center; justify-content:center; padding:72px 24px; text-align:center; }\n.robot{ font-size:52px; animation:bob 1.4s ease-in-out infinite; }\n@keyframes bob{ 0%,100%{ transform:translateY(0); } 50%{ transform:translateY(-10px); } }\n.ah{ font-size:20px; font-weight:700; color:var(--text); margin-top:18px; }\n.as{ font-size:14px; color:var(--muted); margin-top:6px; max-width:420px; line-height:1.5; }\n.dots{ display:inline-flex; gap:6px; margin-top:20px; }\n.dots span{ width:8px; height:8px; border-radius:50%; background:var(--accent); animation:pls 1.2s ease-in-out infinite; }\n.dots span:nth-child(2){ animation-delay:.2s; } .dots span:nth-child(3){ animation-delay:.4s; }\n@keyframes pls{ 0%,100%{ opacity:.3; transform:scale(.8); } 50%{ opacity:1; transform:scale(1); } }\n.mini{ display:flex; gap:8px; margin-top:22px; flex-wrap:wrap; justify-content:center; }\n.mini img{ width:56px; height:56px; object-fit:cover; border-radius:8px; border:1px solid var(--border); }\n.verdict{ display:flex; align-items:center; gap:14px; margin-bottom:6px; }\n.vbadge{ flex:none; width:52px; height:52px; border-radius:14px; display:flex; align-items:center; justify-content:center; font-size:26px; font-weight:700; }\n.vtext .vt{ font-size:20px; font-weight:700; letter-spacing:-.01em; }\n.vtext .vd{ font-size:13px; color:var(--muted); margin-top:2px; }\n.v-procede{ background:var(--ok-soft); color:var(--ok); }\n.v-bad{ background:var(--bad-soft); color:var(--bad); }\n.v-warn{ background:var(--warn-soft); color:var(--warn); }\n.conf{ margin:20px 0; }\n.conf .cl{ display:flex; justify-content:space-between; font-size:12px; color:var(--faint); margin-bottom:6px; }\n.bar{ height:8px; border-radius:999px; background:var(--border); overflow:hidden; }\n.bar i{ display:block; height:100%; border-radius:999px; background:var(--accent); transition:width .5s ease; }\n.sec{ margin-top:18px; }\n.sec .st{ font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; color:var(--faint); margin-bottom:8px; }\n.sec p{ font-size:14px; color:var(--text); line-height:1.6; }\n.chips{ display:flex; flex-wrap:wrap; gap:8px; }\n.chip{ font-size:12px; color:var(--text); background:var(--bg-subtle); border:1px solid var(--border); border-radius:8px; padding:6px 10px; }\n.reco{ background:var(--accent-soft); border:1px solid var(--accent); border-radius:10px; padding:12px 14px; font-size:14px; color:var(--text); line-height:1.5; }\n.manif{ background:var(--bg-subtle); border:1px solid var(--border-strong); border-left:3px solid var(--accent); border-radius:8px; padding:11px 13px; font-size:14px; font-weight:600; color:var(--text); line-height:1.5; }\n.manif.alerta{ border-left-color:var(--warn); color:var(--warn); font-weight:600; }\n.rbtns{ display:flex; gap:10px; margin-top:24px; flex-wrap:wrap; }\n.btn2{ padding:10px 16px; border-radius:10px; border:1px solid var(--border-strong); background:var(--panel); color:var(--text); font-family:var(--font); font-size:14px; font-weight:600; cursor:pointer; transition:border-color .15s,color .15s; }\n.btn2:hover{ border-color:var(--accent); color:var(--accent); }\n`,\n  render(d2){\n    const theme = d2.shared.get('theme','light',{global:true})\n    const step = d2.state.step || 'form'\n    let body = ''\n    if (step === 'analyzing') body = renderAnalyzing(d2)\n    else if (step === 'result') body = renderResult(d2)\n    else if (step === 'error') body = renderError(d2)\n    else body = renderForm(d2)\n    return `<div class=\"n-root ${theme}\">\n      <div class=\"hdr-slot\">${d2.render('fernando-playground@naturaNav', { active:'reclamacao' }, { key:'nav', onNav:(p)=>goRoute(d2,p) })}</div>\n      <main class=\"body\"><div class=\"page\">${body}</div></main>\n    </div>`\n  },\n  mounted(d2){\n    const addFiles = (list) => {\n      const arr = Array.from(list || []).filter(f => f && f.type && f.type.indexOf('image/') === 0)\n      if (!arr.length) return\n      const add = arr.map(f => ({ id: 'p' + Math.random().toString(36).slice(2), file: f, url: URL.createObjectURL(f) }))\n      d2.state.photos = [ ...(d2.state.photos || []), ...add ]\n    }\n    const removePhoto = (id) => {\n      const cur = d2.state.photos || []\n      const p = cur.find(x => x.id === id); if (p) URL.revokeObjectURL(p.url)\n      d2.state.photos = cur.filter(x => x.id !== id)\n    }\n    const reset = () => {\n      (d2.state.photos || []).forEach(p => URL.revokeObjectURL(p.url))\n      d2.setState({ photos: [], errors: [], result: null, errorMsg: '', saveFailed: false, step: 'form' })\n    }\n    const submit = async () => {\n      const val = (s) => { const el = d2.$(s); return el ? (el.value || '').trim() : '' }\n      const produto = val('#f-produto'), lote = val('#f-lote'), venc = val('#f-venc'), problema = val('#f-problema')\n      const photos = d2.state.photos || []\n      const errs = []\n      if (!problema) errs.push('Descreva o problema relatado.')\n      if (!photos.length) errs.push('Envie ao menos uma foto do produto.')\n      if (errs.length){ d2.state.errors = errs; return }\n      d2.setState({ errors: [], step: 'analyzing' })\n      try {\n        const up = await d2.drive('complaintImages').uploadFromFiles(photos.map(p => p.file))\n        const urls = (up || []).map(f => f.fileUrl).filter(Boolean)\n        const res = await d2.rush('analisar-reclamacao').invoke({ produto, lote, dataVencimento: venc, problema, imagens: urls })\n        const a = parseAnalysis(res) || {}\n        const now = new Date().toISOString()\n        const procede = a.veredicto === 'procede'\n        const manif = (procede && typeof a.manifestacao_n3 === 'string') ? a.manifestacao_n3.trim() : ''\n        const rec = {\n          protocolo: 'NAT-' + Date.now().toString(36).toUpperCase(),\n          produto, lote, dataVencimento: venc, problema, imagens: urls,\n          status: 'analisado',\n          veredicto: a.veredicto || 'inconclusivo',\n          confianca: Number(a.confianca) || 0,\n          justificativa: a.justificativa || '',\n          itensObservados: Array.isArray(a.itensObservados) ? a.itensObservados : [],\n          danoVisivel: !!a.danoVisivel,\n          recomendacao: a.recomendacao || '',\n          manifestacaoN3: manif,\n          manifestacaoNenhuma: procede && !manif,\n          analisadoEm: now, createdAt: now,\n        }\n        let salvo = false\n        try {\n          const sv = await d2.rush('salvar-caso').invoke({ registro: rec })\n          salvo = !!(sv && (sv.ok === true || (sv.data && sv.data.ok === true)))\n        } catch(e){ salvo = false }\n        d2.setState({ result: rec, saveFailed: !salvo, step: 'result' })\n      } catch(e){\n        d2.setState({ errorMsg: (e && e.message) || 'Falha ao analisar as imagens. Tente novamente.', step: 'error' })\n      }\n    }\n    d2.on('.drop', 'click', () => { const f = d2.$('.hidden-file'); if (f) f.click() })\n    d2.on('.hidden-file', 'change', (e, el) => { addFiles(el.files); el.value = '' })\n    d2.on('.drop', 'dragover', (e, el) => { e.preventDefault(); el.setAttribute('data-over','') })\n    d2.on('.drop', 'dragleave', (e, el) => { el.removeAttribute('data-over') })\n    d2.on('.drop', 'drop', (e, el) => { e.preventDefault(); el.removeAttribute('data-over'); if (e.dataTransfer) addFiles(e.dataTransfer.files) })\n    d2.on('.rm', 'click', (e, el) => { e.stopPropagation(); removePhoto(el.dataset.id) })\n    d2.on('.submit', 'click', submit)\n    d2.on('#f-venc', 'input', (e, el) => { el.value = maskMMYYYY(el.value) })\n    d2.on('.again', 'click', reset)\n    d2.on('.goto-casos', 'click', () => goRoute(d2, '/casos'))\n    d2.onUnmount(() => { (d2.state.photos || []).forEach(p => URL.revokeObjectURL(p.url)) })\n  },\n}"}},"cases":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"cases"}}}}}}}}}},{"_id":"6a5f752ac7fc0e9222d87788","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"naturaCasos","package":"fernando-playground","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 esc = (s) => ('' + (s == null ? '' : s)).replace(/</g,'&lt;').replace(/>/g,'&gt;')\nconst clamp = (x) => Math.max(0, Math.min(100, Number(x) || 0))\n\nconst findRouterProc = () => {\n  try {\n    const env = (typeof window !== 'undefined') ? window.D2Env : null\n    if (!env || !env.layers) return null\n    const layers = Object.values(env.layers)\n    for (let i = 0; i < layers.length; i++){\n      const procs = layers[i] && layers[i].processes\n      if (!procs) continue\n      if (typeof procs.has === 'function' && procs.has('MainRouterInterface')) return procs.get('MainRouterInterface')\n      let found = null\n      if (typeof procs.forEach === 'function') procs.forEach((v,k)=>{ if(k==='MainRouterInterface') found=v })\n      else if (procs['MainRouterInterface']) found = procs['MainRouterInterface']\n      if (found) return found\n    }\n  } catch(e){}\n  return null\n}\nconst goRoute = (path) => {\n  if (!path) return\n  const proc = findRouterProc()\n  if (proc){\n    try { const h = proc.get && proc.get('history'); if (h && typeof h.push === 'function'){ h.push(path); return } } catch(e){}\n    try { if (typeof proc.invoke === 'function'){ proc.invoke('history.push', path); return } } catch(e){}\n  }\n}\n\nconst V = {\n  procede:      { cls:'v-procede', cvar:'--ok',   icon:'✓', label:'Procede' },\n  nao_procede:  { cls:'v-bad',     cvar:'--bad',  icon:'✕', label:'Não procede' },\n  inconclusivo: { cls:'v-warn',    cvar:'--warn', icon:'⚠', label:'Inconclusivo' },\n}\nconst FILTERS = [\n  { key:'todos', label:'Todos' },\n  { key:'procede', label:'Procede' },\n  { key:'nao_procede', label:'Não procede' },\n  { key:'inconclusivo', label:'Inconclusivo' },\n]\n\nconst renderCard = (d2, c, selId) => {\n  const v = V[c.veredicto] || V.inconclusivo\n  const date = c.createdAt ? d2.fmt.date(c.createdAt) : ''\n  return `<button class=\"citem ${c._id===selId ? 'sel':''}\" data-key=\"${c._id}\" data-id=\"${c._id}\">\n    <div class=\"cr1\">\n      <span class=\"cprod\">${esc(c.produto) || 'Produto não informado'}</span>\n      <span class=\"pill ${v.cls}\">${v.icon} ${v.label}</span>\n    </div>\n    <div class=\"cmeta\"><span>${esc(c.protocolo)}</span><span>${clamp(c.confianca)}% · ${esc(date)}</span></div>\n  </button>`\n}\n\nconst renderDetail = (d2, c) => {\n  if (!c) return `<div class=\"detail\"><div class=\"empty-d\">Selecione um caso na lista para ver os detalhes da análise.</div></div>`\n  const v = V[c.veredicto] || V.inconclusivo\n  const conf = clamp(c.confianca)\n  const itens = Array.isArray(c.itensObservados) ? c.itensObservados : []\n  const imgs = Array.isArray(c.imagens) ? c.imagens : []\n  const manifBlock = c.veredicto === 'procede'\n    ? (c.manifestacaoN3\n        ? `<div class=\"sec\"><div class=\"st\">Manifestação N3 (ServiceNow)</div><div class=\"manif\">${esc(c.manifestacaoN3)}</div></div>`\n        : `<div class=\"sec\"><div class=\"st\">Manifestação N3 (ServiceNow)</div><div class=\"manif alerta\">Nenhuma opção da base se aplica — encaminhar para análise humana.</div></div>`)\n    : ''\n  return `<div class=\"detail\">\n    <div class=\"verdict\">\n      <div class=\"vbadge ${v.cls}\">${v.icon}</div>\n      <div class=\"vtext\"><div class=\"vt\" style=\"color:var(${v.cvar})\">${v.label}</div><div class=\"vd\">${esc(c.protocolo)}</div></div>\n    </div>\n    <div class=\"conf\"><div class=\"cl\"><span>Confiança do robô</span><span>${conf}%</span></div><div class=\"bar\"><i style=\"width:${conf}%\"></i></div></div>\n    ${manifBlock}\n    <div class=\"sec\"><div class=\"st\">Justificativa</div><p>${esc(c.justificativa) || '—'}</p></div>\n    ${itens.length ? `<div class=\"sec\"><div class=\"st\">O que o robô observou</div><div class=\"chips\">${itens.map(it => `<span class=\"chip\">${esc(it)}</span>`).join('')}</div></div>` : ''}\n    ${c.recomendacao ? `<div class=\"sec\"><div class=\"st\">Recomendação</div><div class=\"reco\">${esc(c.recomendacao)}</div></div>` : ''}\n    <div class=\"hr\"></div>\n    <div class=\"sec\"><div class=\"st\">Produto</div><p>${esc(c.produto) || '—'}</p></div>\n    <div class=\"sec\"><div class=\"st\">Lote / Validade</div><p>${esc(c.lote) || '—'} · ${esc(c.dataVencimento) || '—'}</p></div>\n    <div class=\"sec\"><div class=\"st\">Problema relatado</div><p>${esc(c.problema) || '—'}</p></div>\n    ${imgs.length ? `<div class=\"sec\"><div class=\"st\">Fotos enviadas</div><div class=\"thumbs2\">${imgs.map(u => `<a href=\"${u}\" target=\"_blank\" rel=\"noopener\"><img src=\"${u}\" alt=\"\"/></a>`).join('')}</div></div>` : ''}\n  </div>`\n}\n\nexport default {\n  css: `\n:host{ all:initial; }\n*{ box-sizing:border-box; margin:0; padding:0; }\n.n-root{ --font:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;\n  font-family:var(--font); width:100%; height:100%; -webkit-font-smoothing:antialiased; display:flex; flex-direction:column; }\n.n-root.light{ --bg:#ffffff;--bg-subtle:#fafafa;--panel:#ffffff;--text:#0a0a0a;--muted:#666666;--faint:#8f8f8f;\n  --border:#ebebeb;--border-strong:#d4d4d4;--accent:#EB6619;--accent-hover:#d25610;--accent-soft:#fff2ea;\n  --shadow:0 1px 2px rgba(0,0,0,.04),0 8px 24px rgba(0,0,0,.06);\n  --ok:#0a7d38;--ok-soft:#e9f7ee;--warn:#9a6700;--warn-soft:#fdf3e0;--bad:#c02626;--bad-soft:#fdeaea; }\n.n-root.dark{ --bg:#0a0a0a;--bg-subtle:#111111;--panel:#161616;--text:#ededed;--muted:#a0a0a0;--faint:#707070;\n  --border:#262626;--border-strong:#383838;--accent:#EB6619;--accent-hover:#ff7a2e;--accent-soft:#2a1509;\n  --shadow:0 1px 2px rgba(0,0,0,.4),0 8px 24px rgba(0,0,0,.5);\n  --ok:#3fcf6b;--ok-soft:#0f2417;--warn:#e0a92b;--warn-soft:#2a2007;--bad:#f26a6a;--bad-soft:#2a1010; }\n.hdr-slot{ height:60px; flex:none; width:100%; }\n.body{ flex:1; width:100%; background:var(--bg-subtle); overflow:auto; }\n.page{ max-width:1180px; margin:0 auto; padding:32px 24px 64px; }\n.ptitle{ font-size:26px; font-weight:700; color:var(--text); letter-spacing:-.025em; margin-bottom:6px; }\n.psub{ font-size:14px; color:var(--muted); margin-bottom:24px; line-height:1.5; }\n.toolbar{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:20px; }\n.chip-f{ font-family:var(--font); font-size:13px; font-weight:600; padding:7px 13px; border-radius:999px; border:1px solid var(--border); background:var(--panel); color:var(--muted); cursor:pointer; display:flex; align-items:center; gap:7px; transition:border-color .15s,color .15s,background .15s; }\n.chip-f:hover{ border-color:var(--border-strong); color:var(--text); }\n.chip-f.on{ background:var(--accent-soft); border-color:var(--accent); color:var(--accent); }\n.chip-f .n{ font-size:11px; background:var(--bg-subtle); border-radius:999px; padding:1px 7px; color:var(--faint); }\n.chip-f.on .n{ background:#fff; color:var(--accent); }\n.spacer{ flex:1; }\n.refresh{ font-family:var(--font); font-size:13px; font-weight:600; padding:7px 13px; border-radius:8px; border:1px solid var(--border); background:var(--panel); color:var(--muted); cursor:pointer; }\n.refresh:hover{ border-color:var(--border-strong); color:var(--text); }\n.layout{ display:grid; grid-template-columns:380px 1fr; gap:20px; align-items:start; }\n@media(max-width:860px){ .layout{ grid-template-columns:1fr; } }\n.list{ display:flex; flex-direction:column; gap:10px; }\n.citem{ text-align:left; width:100%; background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:14px; cursor:pointer; transition:border-color .15s,box-shadow .15s; font-family:var(--font); }\n.citem:hover{ border-color:var(--border-strong); }\n.citem.sel{ border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft); }\n.cr1{ display:flex; justify-content:space-between; align-items:center; gap:8px; margin-bottom:7px; }\n.cprod{ font-size:14px; font-weight:600; color:var(--text); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n.pill{ flex:none; font-size:11px; font-weight:700; padding:3px 9px; border-radius:999px; display:inline-flex; align-items:center; gap:4px; }\n.cmeta{ font-size:12px; color:var(--faint); display:flex; justify-content:space-between; gap:8px; }\n.detail{ background:var(--panel); border:1px solid var(--border); border-radius:16px; padding:24px; box-shadow:var(--shadow); position:sticky; top:0; }\n.empty-d{ font-size:14px; color:var(--faint); text-align:center; padding:40px 0; }\n.verdict{ display:flex; align-items:center; gap:14px; margin-bottom:6px; }\n.vbadge{ flex:none; width:52px; height:52px; border-radius:14px; display:flex; align-items:center; justify-content:center; font-size:26px; font-weight:700; }\n.vtext .vt{ font-size:20px; font-weight:700; letter-spacing:-.01em; }\n.vtext .vd{ font-size:13px; color:var(--muted); margin-top:2px; }\n.v-procede{ background:var(--ok-soft); color:var(--ok); }\n.v-bad{ background:var(--bad-soft); color:var(--bad); }\n.v-warn{ background:var(--warn-soft); color:var(--warn); }\n.conf{ margin:20px 0; }\n.conf .cl{ display:flex; justify-content:space-between; font-size:12px; color:var(--faint); margin-bottom:6px; }\n.bar{ height:8px; border-radius:999px; background:var(--border); overflow:hidden; }\n.bar i{ display:block; height:100%; border-radius:999px; background:var(--accent); }\n.sec{ margin-top:18px; }\n.sec .st{ font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; color:var(--faint); margin-bottom:8px; }\n.sec p{ font-size:14px; color:var(--text); line-height:1.6; }\n.chips{ display:flex; flex-wrap:wrap; gap:8px; }\n.chip{ font-size:12px; color:var(--text); background:var(--bg-subtle); border:1px solid var(--border); border-radius:8px; padding:6px 10px; }\n.reco{ background:var(--accent-soft); border:1px solid var(--accent); border-radius:10px; padding:12px 14px; font-size:14px; color:var(--text); line-height:1.5; }\n.manif{ background:var(--bg-subtle); border:1px solid var(--border-strong); border-left:3px solid var(--accent); border-radius:8px; padding:11px 13px; font-size:14px; font-weight:600; color:var(--text); line-height:1.5; }\n.manif.alerta{ border-left-color:var(--warn); color:var(--warn); }\n.hr{ height:1px; background:var(--border); margin:20px 0 4px; }\n.thumbs2{ display:flex; gap:10px; flex-wrap:wrap; }\n.thumbs2 a{ display:block; width:88px; height:88px; border-radius:10px; overflow:hidden; border:1px solid var(--border); }\n.thumbs2 img{ width:100%; height:100%; object-fit:cover; display:block; }\n.empty{ background:var(--panel); border:1px solid var(--border); border-radius:16px; padding:56px 40px; text-align:center; box-shadow:var(--shadow); }\n.empty .eg{ font-size:40px; margin-bottom:14px; }\n.empty h2{ font-size:20px; font-weight:700; color:var(--text); margin-bottom:8px; }\n.empty p{ font-size:14px; color:var(--muted); margin-bottom:20px; }\n.btn{ padding:11px 18px; border:0; border-radius:10px; background:var(--accent); color:#fff; font-family:var(--font); font-size:14px; font-weight:600; cursor:pointer; }\n.btn:hover{ background:var(--accent-hover); }\n.loading{ font-size:14px; color:var(--faint); padding:40px 0; text-align:center; }\n`,\n  render(d2){\n    const theme = d2.shared.get('theme','light',{global:true})\n    const navSlot = `<div class=\"hdr-slot\">${d2.render('fernando-playground@naturaNav', { active:'casos' }, { key:'nav' })}</div>`\n    const wrap = (inner) => `<div class=\"n-root ${theme}\">${navSlot}<main class=\"body\"><div class=\"page\">${inner}</div></main></div>`\n\n    const rows = d2.state.rows\n    if (!Array.isArray(rows)) return wrap(`<div class=\"ptitle\">Casos analisados</div><div class=\"loading\">Carregando casos…</div>`)\n    const all = rows.slice().sort((a,b)=> ('' + (b.createdAt||'')).localeCompare('' + (a.createdAt||'')))\n\n    if (!all.length){\n      return wrap(`<div class=\"ptitle\">Casos analisados</div>\n        <div class=\"psub\">Aqui aparecem todas as reclamações já analisadas pelo robô.</div>\n        <div class=\"empty\"><div class=\"eg\">🗂️</div><h2>Nenhum caso ainda</h2><p>Assim que uma reclamação for analisada, ela aparece aqui.</p><button class=\"open-recl btn\">Abrir uma reclamação</button></div>`)\n    }\n\n    const counts = { todos: all.length, procede:0, nao_procede:0, inconclusivo:0 }\n    all.forEach(c => { if (counts[c.veredicto] != null) counts[c.veredicto]++ })\n    const filter = d2.state.filter || 'todos'\n    const list = filter === 'todos' ? all : all.filter(c => c.veredicto === filter)\n    const selected = list.find(c => c._id === d2.state.selectedId) || list[0] || null\n\n    const chips = FILTERS.map(f => `<button class=\"chip-f ${f.key===filter ? 'on':''}\" data-f=\"${f.key}\">${f.label}<span class=\"n\">${counts[f.key]||0}</span></button>`).join('')\n    const cards = list.length\n      ? list.map(c => renderCard(d2, c, selected ? selected._id : null)).join('')\n      : `<div class=\"loading\">Nenhum caso \"${esc((FILTERS.find(f=>f.key===filter)||{}).label)}\".</div>`\n\n    return wrap(`\n      <div class=\"ptitle\">Casos analisados</div>\n      <div class=\"psub\">${all.length} ${all.length===1 ? 'caso analisado' : 'casos analisados'} pelo robô. Clique num caso para ver os detalhes.</div>\n      <div class=\"toolbar\">${chips}<span class=\"spacer\"></span><button class=\"refresh\">↻ Atualizar</button></div>\n      <div class=\"layout\"><div class=\"list\">${cards}</div>${renderDetail(d2, selected)}</div>\n    `)\n  },\n  mounted(d2){\n    const load = async () => {\n      try {\n        const res = await d2.rush('listar-casos').invoke({})\n        const payload = (res && res.casos) ? res : ((res && res.data) ? res.data : res)\n        const rows = (payload && Array.isArray(payload.casos)) ? payload.casos : []\n        d2.setState({ rows })\n      } catch(e){ d2.setState({ rows: [] }) }\n    }\n    load()\n    d2.on('.chip-f', 'click', (e, el) => { d2.setState({ filter: el.dataset.f, selectedId: null }) })\n    d2.on('.citem', 'click', (e, el) => { d2.state.selectedId = el.dataset.id })\n    d2.on('.refresh', 'click', () => load())\n    d2.on('.open-recl', 'click', () => goRoute('/'))\n  },\n}"}},"cases":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"cases"}}}}}}}}}},{"_id":"6a612eaac6a6323441839b11","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"pp-auth","package":"fernando-playground","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 ppNav(d2, path){\n  try{\n    var r = d2.router('ppRouter');\n    if(r && r.goTo){\n      r.goTo(path);\n      setTimeout(function(){\n        try{ if(String((d2.router('ppRouter').path)||'').indexOf(path) === -1){ window.location.href = path; } }catch(_){}\n      }, 700);\n      return;\n    }\n  }catch(_){}\n  window.location.href = path;\n}\n\nexport default {\n  css: `\n    .wrap{display:flex;align-items:center;justify-content:center;min-height:100%;box-sizing:border-box;padding:24px;\n      background:radial-gradient(900px 500px at 50% -20%, rgba(99,102,241,0.18), transparent 60%), #0b0e14;\n      font-family:'Inter',system-ui,-apple-system,sans-serif;color:#e6e9ef}\n    .card{width:100%;max-width:400px;background:#141926;border:1px solid rgba(255,255,255,.08);border-radius:18px;\n      padding:30px;box-shadow:0 20px 60px rgba(0,0,0,.45)}\n    .brand{display:flex;align-items:center;gap:10px}\n    .logo{width:36px;height:36px;border-radius:10px;background:linear-gradient(135deg,#6366f1,#14b8a6);\n      display:flex;align-items:center;justify-content:center;font-size:19px}\n    .title{font-size:20px;font-weight:700;letter-spacing:-.02em}\n    .sub{color:#8b93a7;font-size:13px;margin:6px 0 22px}\n    .tabs{display:flex;gap:6px;background:#0e131d;border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:4px;margin-bottom:20px}\n    .tab{flex:1;padding:9px;border:0;background:transparent;color:#8b93a7;border-radius:9px;font-size:13px;font-weight:600;cursor:pointer;transition:.15s}\n    .tab[data-on]{background:#222a3a;color:#fff}\n    .field{margin-bottom:14px}\n    .label{display:block;font-size:12px;color:#8b93a7;margin-bottom:6px;font-weight:500}\n    .input{width:100%;box-sizing:border-box;padding:11px 13px;background:#0e131d;border:1px solid rgba(255,255,255,.1);\n      border-radius:10px;color:#e6e9ef;font-size:14px;outline:none;transition:.15s}\n    .input:focus{border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)}\n    .btn{width:100%;padding:12px;margin-top:6px;border:0;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;\n      background:linear-gradient(135deg,#6366f1,#4f46e5);color:#fff;transition:.15s}\n    .btn:hover{filter:brightness(1.08)} .btn:disabled{opacity:.6;cursor:default}\n    .err{background:rgba(239,68,68,.12);border:1px solid rgba(239,68,68,.3);color:#fca5a5;font-size:13px;\n      padding:9px 12px;border-radius:9px;margin-bottom:14px}\n  `,\n  render(d2){\n    const mode = d2.state.mode || 'login';\n    const isReg = mode === 'register';\n    const err = d2.state.error;\n    const busy = d2.state.busy;\n    const esc = (s)=> String(s==null?'':s).replace(/\"/g,'&quot;');\n    return `<div class=\"wrap\"><div class=\"card\">\n      <div class=\"brand\"><div class=\"logo\">🃏</div><div class=\"title\">Poker Planning</div></div>\n      <div class=\"sub\">Estimativas ágeis em tempo real</div>\n      <div class=\"tabs\">\n        <button class=\"tab\" data-mode=\"login\" ${!isReg?'data-on':''}>Entrar</button>\n        <button class=\"tab\" data-mode=\"register\" ${isReg?'data-on':''}>Criar conta</button>\n      </div>\n      ${err?`<div class=\"err\">${esc(err)}</div>`:''}\n      ${isReg?`<div class=\"field\"><label class=\"label\">Nome</label>\n        <input class=\"input js-name\" value=\"${esc(d2.state.name)}\" placeholder=\"Seu nome\" autocomplete=\"name\"/></div>`:''}\n      <div class=\"field\"><label class=\"label\">E-mail</label>\n        <input class=\"input js-email\" type=\"email\" value=\"${esc(d2.state.email)}\" placeholder=\"voce@empresa.com\" autocomplete=\"username\"/></div>\n      <div class=\"field\"><label class=\"label\">Senha</label>\n        <input class=\"input js-pwd\" type=\"password\" value=\"${esc(d2.state.password)}\" placeholder=\"••••••••\" autocomplete=\"${isReg?'new-password':'current-password'}\"/></div>\n      <button class=\"btn go\" ${busy?'disabled':''}>${busy?'Aguarde…':(isReg?'Criar conta':'Entrar')}</button>\n    </div></div>`;\n  },\n  mounted(d2){\n    (async()=>{ const a=d2.auth('pp-auth-strat'); for(let i=0;i<4 && !a.isAuthed;i++){ await new Promise(r=>setTimeout(r,200)); } if(a.isAuthed){ ppNav(d2, '/app'); } })();\n    const val = (s)=>{ const el=d2.$(s); return el?el.value:''; };\n    const enter = async (email, pwd, user)=>{\n      try{ if(user){ localStorage.setItem('pp_user', JSON.stringify(user)); } }catch(_){}\n      try{ await d2.auth('pp-auth-strat').login({ email, password: pwd }); }catch(e){ d2.log('login strat', e); }\n      ppNav(d2, '/app');\n    };\n    d2.on('.tab','click',(e,el)=>{ d2.state.mode = el.dataset.mode; d2.state.error=''; });\n    d2.on('.js-name','input',(e,el)=>{ d2.state.name = el.value; });\n    d2.on('.js-email','input',(e,el)=>{ d2.state.email = el.value; });\n    d2.on('.js-pwd','input',(e,el)=>{ d2.state.password = el.value; });\n    d2.on('.go','click', async ()=>{\n      const email=(val('.js-email')||'').trim().toLowerCase();\n      const password=val('.js-pwd')||'';\n      const name=(val('.js-name')||'').trim();\n      const isReg = (d2.state.mode||'login')==='register';\n      if(!email||!password){ d2.state.error='Informe e-mail e senha.'; return; }\n      if(isReg && !name){ d2.state.error='Informe seu nome.'; return; }\n      d2.state.busy=true; d2.state.error='';\n      try{\n        if(isReg){\n          const res = await d2.rush('pp-cria-conta').invoke({ name, email, password });\n          const r = (res && res.data) ? res.data : res;\n          if(!r || !r.ok){ d2.state.busy=false; d2.state.error=(r && r.error)||'Não foi possível criar a conta.'; return; }\n          const vr0 = await d2.rush('pp-verifica-login').invoke({ credentials:{ email, password } });\n          const vr = (vr0 && vr0.data) ? vr0.data : vr0;\n          await enter(email, password, vr && vr.user);\n        } else {\n          const res = await d2.rush('pp-verifica-login').invoke({ credentials:{ email, password } });\n          const r = (res && res.data) ? res.data : res;\n          if(!r || !r.user){ d2.state.busy=false; d2.state.error='E-mail ou senha inválidos.'; return; }\n          await enter(email, password, r.user);\n        }\n      } catch(err){ d2.state.busy=false; d2.state.error=(err&&err.message)?err.message:'Falha na autenticação.'; }\n    });\n  },\n}"}}}}}}}},{"_id":"6a612f87866e4238fc20d792","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"pp-home","package":"fernando-playground","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 ppNav(d2, path){\n  try{\n    var r = d2.router('ppRouter');\n    if(r && r.goTo){\n      r.goTo(path);\n      setTimeout(function(){\n        try{ if(String((d2.router('ppRouter').path)||'').indexOf(path) === -1){ window.location.href = path; } }catch(_){}\n      }, 700);\n      return;\n    }\n  }catch(_){}\n  window.location.href = path;\n}\n\nexport default {\n  css: `\n    .page{min-height:100%;box-sizing:border-box;background:radial-gradient(1000px 500px at 80% -10%, rgba(20,184,166,.12), transparent 55%), #0b0e14;\n      font-family:'Inter',system-ui,-apple-system,sans-serif;color:#e6e9ef}\n    .top{display:flex;align-items:center;justify-content:space-between;padding:16px 28px;border-bottom:1px solid rgba(255,255,255,.07);position:sticky;top:0;background:rgba(11,14,20,.85);backdrop-filter:blur(8px)}\n    .brand{display:flex;align-items:center;gap:10px}\n    .logo{width:32px;height:32px;border-radius:9px;background:linear-gradient(135deg,#6366f1,#14b8a6);display:flex;align-items:center;justify-content:center;font-size:17px}\n    .bname{font-weight:700;letter-spacing:-.02em}\n    .uinfo{display:flex;align-items:center;gap:12px;font-size:13px;color:#8b93a7}\n    .lo{background:transparent;border:1px solid rgba(255,255,255,.12);color:#c7cdda;padding:7px 12px;border-radius:9px;cursor:pointer;font-size:13px}\n    .lo:hover{background:rgba(255,255,255,.05)}\n    .wrap{max-width:900px;margin:0 auto;padding:34px 28px}\n    .hero{display:flex;align-items:center;justify-content:space-between;gap:20px;background:#141926;border:1px solid rgba(255,255,255,.08);border-radius:18px;padding:26px 28px;margin-bottom:30px}\n    .htitle{font-size:22px;font-weight:800;letter-spacing:-.02em;margin-bottom:4px}\n    .hsub{color:#8b93a7;font-size:14px}\n    .new{background:linear-gradient(135deg,#6366f1,#4f46e5);color:#fff;border:0;padding:13px 20px;border-radius:11px;font-size:14px;font-weight:700;cursor:pointer;white-space:nowrap}\n    .new:hover{filter:brightness(1.08)}\n    .sec{font-size:13px;text-transform:uppercase;letter-spacing:.08em;color:#8b93a7;font-weight:700;margin:0 0 14px}\n    .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px}\n    .rcard{background:#141926;border:1px solid rgba(255,255,255,.08);border-radius:14px;padding:18px;display:flex;flex-direction:column;gap:12px}\n    .rname{font-size:16px;font-weight:700}\n    .rmeta{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n    .badge{font-size:11px;padding:3px 9px;border-radius:999px;background:rgba(99,102,241,.15);color:#a5b4fc;font-weight:600}\n    .code{font-size:12px;color:#8b93a7;font-family:ui-monospace,monospace}\n    .acts{display:flex;gap:8px;margin-top:2px}\n    .b{flex:1;padding:9px;border-radius:9px;font-size:13px;font-weight:600;cursor:pointer;border:1px solid transparent;text-align:center}\n    .enter{background:#222a3a;color:#fff}.enter:hover{background:#2b3448}\n    .copy{background:transparent;border-color:rgba(255,255,255,.14);color:#c7cdda}.copy:hover{background:rgba(255,255,255,.05)}\n    .empty{background:#111621;border:1px dashed rgba(255,255,255,.12);border-radius:14px;padding:40px;text-align:center;color:#8b93a7;font-size:14px}\n  `,\n  render(d2){\n    let user = d2.auth('pp-auth-strat').user;\n    if(!(user && user._id)){ try{ const s=localStorage.getItem('pp_user'); if(s) user=JSON.parse(s); }catch(_){} }\n    user = user || {};\n    const uid = user._id;\n    const ds = d2.data('rooms');\n    const esc = (s)=> String(s==null?'':s).replace(/</g,'&lt;').replace(/\"/g,'&quot;');\n    const mine = (ds.records()||[]).filter(r => r.host === uid && r.status !== 'closed');\n    const cards = mine.map(r => `\n      <div class=\"rcard\" data-key=\"${r._id}\">\n        <div class=\"rname\">${esc(r.name)||'Sala sem nome'}</div>\n        <div class=\"rmeta\"><span class=\"badge\">${esc(r.deck)||'—'}</span><span class=\"code\">#${esc(r.code)}</span></div>\n        <div class=\"acts\">\n          <button class=\"b enter\" data-code=\"${esc(r.code)}\">Entrar</button>\n          <button class=\"b copy\" data-code=\"${esc(r.code)}\">${d2.state.copied===r.code?'Copiado ✓':'Copiar convite'}</button>\n        </div>\n      </div>`).join('');\n    return `<div class=\"page\">\n      <div class=\"top\">\n        <div class=\"brand\"><div class=\"logo\">🃏</div><div class=\"bname\">Poker Planning</div></div>\n        <div class=\"uinfo\"><span>${esc(user.name)||esc(user.email)||''}</span><button class=\"lo\">Sair</button></div>\n      </div>\n      <div class=\"wrap\">\n        <div class=\"hero\">\n          <div><div class=\"htitle\">Suas sessões de planning</div><div class=\"hsub\">Crie uma sala e convide o time para estimar em tempo real.</div></div>\n          <button class=\"new\">+ Nova sala</button>\n        </div>\n        <p class=\"sec\">Minhas salas</p>\n        ${mine.length ? `<div class=\"grid\">${cards}</div>` : `<div class=\"empty\">Você ainda não criou nenhuma sala.<br/>Clique em <b>+ Nova sala</b> para começar.</div>`}\n      </div>\n    </div>`;\n  },\n  mounted(d2){\n    d2.on('.new','click',()=> ppNav(d2, '/new'));\n    d2.on('.enter','click',(e,el)=> ppNav(d2, '/room/'+el.dataset.code));\n    d2.on('.copy','click', async (e,el)=>{\n      const url = window.location.origin + '/join/' + el.dataset.code;\n      try{ await navigator.clipboard.writeText(url); }catch(_){}\n      d2.state.copied = el.dataset.code;\n      setTimeout(()=>{ if(d2.state.copied===el.dataset.code) d2.state.copied=null; }, 1600);\n    });\n    d2.on('.lo','click', async ()=>{ try{ localStorage.removeItem('pp_user'); }catch(_){}; try{ await d2.auth('pp-auth-strat').logout(); }catch(_){}; ppNav(d2, '/'); });\n  },\n}"}},"rooms":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rooms"}}}}}}}}}},{"_id":"6a612fcacc01aafcca1e6b56","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"pp-create-room","package":"fernando-playground","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 ppNav(d2, path){\n  try{\n    var r = d2.router('ppRouter');\n    if(r && r.goTo){\n      r.goTo(path);\n      setTimeout(function(){\n        try{ if(String((d2.router('ppRouter').path)||'').indexOf(path) === -1){ window.location.href = path; } }catch(_){}\n      }, 700);\n      return;\n    }\n  }catch(_){}\n  window.location.href = path;\n}\n\nexport default {\n  css: `\n    .page{min-height:100%;box-sizing:border-box;display:flex;align-items:flex-start;justify-content:center;padding:40px 24px;\n      background:radial-gradient(900px 500px at 50% -20%, rgba(99,102,241,.16), transparent 60%), #0b0e14;\n      font-family:'Inter',system-ui,-apple-system,sans-serif;color:#e6e9ef}\n    .card{width:100%;max-width:520px;background:#141926;border:1px solid rgba(255,255,255,.08);border-radius:18px;padding:28px 30px;box-shadow:0 20px 60px rgba(0,0,0,.45)}\n    .back{background:transparent;border:0;color:#8b93a7;font-size:13px;cursor:pointer;padding:0;margin-bottom:14px}\n    .back:hover{color:#c7cdda}\n    .title{font-size:21px;font-weight:800;letter-spacing:-.02em;margin-bottom:22px}\n    .field{margin-bottom:18px}\n    .label{display:block;font-size:12px;color:#8b93a7;margin-bottom:8px;font-weight:600;text-transform:uppercase;letter-spacing:.05em}\n    .input{width:100%;box-sizing:border-box;padding:11px 13px;background:#0e131d;border:1px solid rgba(255,255,255,.1);border-radius:10px;color:#e6e9ef;font-size:14px;outline:none}\n    .input:focus{border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)}\n    .decks{display:flex;flex-wrap:wrap;gap:8px}\n    .deck{padding:9px 13px;border-radius:10px;border:1px solid rgba(255,255,255,.12);background:#0e131d;color:#c7cdda;font-size:13px;font-weight:600;cursor:pointer;transition:.15s}\n    .deck[data-on]{border-color:#6366f1;background:rgba(99,102,241,.15);color:#fff}\n    .deck small{display:block;font-weight:400;color:#8b93a7;font-size:11px;margin-top:2px}\n    .toggles{display:flex;flex-direction:column;gap:2px}\n    .tg{display:flex;align-items:center;justify-content:space-between;padding:11px 0;border-bottom:1px solid rgba(255,255,255,.06)}\n    .tg:last-child{border-bottom:0}\n    .tgl{font-size:14px}.tgl small{display:block;color:#8b93a7;font-size:12px;margin-top:1px}\n    .sw{width:42px;height:24px;border-radius:999px;background:#2b3243;border:0;cursor:pointer;position:relative;transition:.18s;flex:none}\n    .sw[data-on]{background:#6366f1}\n    .sw::after{content:'';position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#fff;transition:.18s}\n    .sw[data-on]::after{left:21px}\n    .row2{display:flex;gap:14px}.row2 .field{flex:1}\n    .btn{width:100%;padding:13px;margin-top:8px;border:0;border-radius:11px;font-size:15px;font-weight:700;cursor:pointer;background:linear-gradient(135deg,#6366f1,#4f46e5);color:#fff}\n    .btn:hover{filter:brightness(1.08)}.btn:disabled{opacity:.6;cursor:default}\n    .err{background:rgba(239,68,68,.12);border:1px solid rgba(239,68,68,.3);color:#fca5a5;font-size:13px;padding:9px 12px;border-radius:9px;margin-bottom:16px}\n  `,\n  render(d2){\n    const esc = (s)=> String(s==null?'':s).replace(/</g,'&lt;').replace(/\"/g,'&quot;');\n    const decks = (d2.data('decks').records()||[]).slice().sort((a,b)=>(a.order||0)-(b.order||0));\n    const selDeck = d2.state.deck || (decks[0] && decks[0].title);\n    const val = (k, def)=> d2.state[k]===undefined ? def : d2.state[k];\n    const sw = (k, def, title, desc)=>`\n      <div class=\"tg\"><div class=\"tgl\">${title}<small>${desc}</small></div>\n        <button class=\"sw\" data-tg=\"${k}\" ${val(k,def)?'data-on':''}></button></div>`;\n    const deckBtns = decks.map(dk=>`\n      <button class=\"deck\" data-deck=\"${esc(dk.title)}\" ${selDeck===dk.title?'data-on':''}>${esc(dk.title)}\n        <small>${(dk.values||[]).slice(0,6).join(' · ')}…</small></button>`).join('');\n    return `<div class=\"page\"><div class=\"card\">\n      <button class=\"back\">← Voltar</button>\n      <div class=\"title\">Nova sala de planning</div>\n      ${d2.state.error?`<div class=\"err\">${esc(d2.state.error)}</div>`:''}\n      <div class=\"field\"><label class=\"label\">Nome da sala</label>\n        <input class=\"input\" data-field=\"name\" value=\"${esc(d2.state.name)}\" placeholder=\"Ex.: Sprint 42 — Planning\"/></div>\n      <div class=\"field\"><label class=\"label\">Deck de estimativa</label><div class=\"decks\">${deckBtns}</div></div>\n      <div class=\"field\"><label class=\"label\">Primeira tarefa (opcional)</label>\n        <input class=\"input\" data-field=\"story\" value=\"${esc(d2.state.story)}\" placeholder=\"Ex.: Login com SSO\"/></div>\n      <div class=\"field\"><label class=\"label\">Opções</label><div class=\"toggles\">\n        ${sw('hide_votes',true,'Esconder votos','Só revela quando o host mandar')}\n        ${sw('auto_reveal',false,'Revelar automático','Vira as cartas quando todos votarem')}\n        ${sw('allow_observers',true,'Permitir observadores','Quem entra pode só assistir')}\n      </div></div>\n      <div class=\"field\"><label class=\"label\">Máx. participantes</label>\n        <input class=\"input\" data-field=\"max_participants\" type=\"number\" value=\"${esc(val('max_participants',12))}\" min=\"2\" max=\"50\"/></div>\n      <button class=\"btn go\" ${d2.state.busy?'disabled':''}>${d2.state.busy?'Criando…':'Criar sala'}</button>\n    </div></div>`;\n  },\n  mounted(d2){\n    d2.on('.back','click',()=> ppNav(d2, '/app'));\n    d2.on('.deck','click',(e,el)=>{ d2.state.deck = el.dataset.deck; });\n    d2.on('.sw','click',(e,el)=>{ const k=el.dataset.tg; const cur=d2.state[k]; const def=(k==='hide_votes'||k==='allow_observers');\n      d2.state[k] = !(cur===undefined?def:cur); });\n    d2.on('.input','input',(e,el)=>{ const f=el.dataset.field;\n      d2.state[f] = (f==='max_participants') ? (parseInt(el.value,10)||0) : el.value; });\n    d2.on('.go','click', async ()=>{\n      const decks=(d2.data('decks').records()||[]).slice().sort((a,b)=>(a.order||0)-(b.order||0));\n      const deck = d2.state.deck || (decks[0] && decks[0].title);\n      const name=(d2.state.name||'').trim();\n      if(!name){ d2.state.error='Dê um nome para a sala.'; return; }\n      if(!deck){ d2.state.error='Escolha um deck.'; return; }\n      let user = d2.auth('pp-auth-strat').user;\n      if(!(user && user._id)){ try{ const s=localStorage.getItem('pp_user'); if(s) user=JSON.parse(s); }catch(_){} }\n      user = user || {};\n      d2.state.busy=true; d2.state.error='';\n      try{\n        const res = await d2.rush('pp-engine').invoke({\n          action:'create-room', name, deck,\n          host: user._id, host_name: user.name || user.email,\n          hide_votes: d2.state.hide_votes===undefined?true:d2.state.hide_votes,\n          auto_reveal: d2.state.auto_reveal===undefined?false:d2.state.auto_reveal,\n          allow_observers: d2.state.allow_observers===undefined?true:d2.state.allow_observers,\n          max_participants: d2.state.max_participants||12,\n          story: (d2.state.story||'').trim() || 'Tarefa 1'\n        });\n        const r=(res&&res.data)?res.data:res;\n        if(r && r.ok && r.code){ ppNav(d2, '/room/'+r.code); }\n        else { d2.state.error=(r&&r.error)||'Não foi possível criar a sala.'; }\n      }catch(err){ d2.state.error=(err&&err.message)?err.message:'Erro ao criar a sala.'; }\n      finally{ d2.state.busy=false; }\n    });\n  },\n}"}},"decks":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"decks"}}}}}}}}}},{"_id":"6a6130643bba43b2373aa833","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"pp-join","package":"fernando-playground","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 ppNav(d2, path){\n  try{\n    var r = d2.router('ppRouter');\n    if(r && r.goTo){\n      r.goTo(path);\n      setTimeout(function(){\n        try{ if(String((d2.router('ppRouter').path)||'').indexOf(path) === -1){ window.location.href = path; } }catch(_){}\n      }, 700);\n      return;\n    }\n  }catch(_){}\n  window.location.href = path;\n}\n\nfunction ppCode(d2){\n  try{ var p = d2.router('ppRouter').params; if(p && p.code){ return String(p.code); } }catch(_){}\n  var parts = (window.location.pathname || '').split('/').filter(Boolean);\n  return parts.length ? decodeURIComponent(parts[parts.length-1]) : '';\n}\n\nexport default {\n  css: `\n    .page{min-height:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;padding:24px;\n      background:radial-gradient(900px 500px at 50% -20%, rgba(20,184,166,.16), transparent 60%), #0b0e14;\n      font-family:'Inter',system-ui,-apple-system,sans-serif;color:#e6e9ef}\n    .card{width:100%;max-width:420px;background:#141926;border:1px solid rgba(255,255,255,.08);border-radius:18px;padding:30px;box-shadow:0 20px 60px rgba(0,0,0,.45)}\n    .logo{width:38px;height:38px;border-radius:11px;background:linear-gradient(135deg,#6366f1,#14b8a6);display:flex;align-items:center;justify-content:center;font-size:20px;margin-bottom:16px}\n    .k{font-size:12px;color:#8b93a7;text-transform:uppercase;letter-spacing:.06em}\n    .rname{font-size:22px;font-weight:800;letter-spacing:-.02em;margin:2px 0 4px}\n    .rmeta{color:#8b93a7;font-size:13px;margin-bottom:22px}\n    .field{margin-bottom:16px}\n    .label{display:block;font-size:12px;color:#8b93a7;margin-bottom:7px;font-weight:600}\n    .input{width:100%;box-sizing:border-box;padding:12px 14px;background:#0e131d;border:1px solid rgba(255,255,255,.1);border-radius:10px;color:#e6e9ef;font-size:15px;outline:none}\n    .input:focus{border-color:#14b8a6;box-shadow:0 0 0 3px rgba(20,184,166,.15)}\n    .roles{display:flex;gap:8px}\n    .role{flex:1;padding:11px;border-radius:10px;border:1px solid rgba(255,255,255,.12);background:#0e131d;color:#c7cdda;font-size:13px;font-weight:600;cursor:pointer;text-align:center}\n    .role[data-on]{border-color:#14b8a6;background:rgba(20,184,166,.14);color:#fff}\n    .role small{display:block;font-weight:400;color:#8b93a7;font-size:11px;margin-top:2px}\n    .btn{width:100%;padding:13px;margin-top:6px;border:0;border-radius:11px;font-size:15px;font-weight:700;cursor:pointer;background:linear-gradient(135deg,#14b8a6,#0d9488);color:#fff}\n    .btn:hover{filter:brightness(1.08)}.btn:disabled{opacity:.6;cursor:default}\n    .msg{text-align:center;color:#8b93a7;font-size:14px;padding:20px 0}\n    .err{background:rgba(239,68,68,.12);border:1px solid rgba(239,68,68,.3);color:#fca5a5;font-size:13px;padding:9px 12px;border-radius:9px;margin-bottom:16px}\n  `,\n  render(d2){\n    const esc=(s)=>String(s==null?'':s).replace(/</g,'&lt;').replace(/\"/g,'&quot;');\n    if(d2.state.notFound) return `<div class=\"page\"><div class=\"card\"><div class=\"logo\">🃏</div><div class=\"msg\">Sala não encontrada.<br/>Confira o link do convite.</div></div></div>`;\n    if(!d2.state.room) return `<div class=\"page\"><div class=\"card\"><div class=\"logo\">🃏</div><div class=\"msg\">Carregando sala…</div></div></div>`;\n    const room=d2.state.room;\n    const role=d2.state.role || 'player';\n    const canObs = room.allow_observers;\n    return `<div class=\"page\"><div class=\"card\">\n      <div class=\"logo\">🃏</div>\n      <div class=\"k\">Convite para</div>\n      <div class=\"rname\">${esc(room.name)||'Sala de planning'}</div>\n      <div class=\"rmeta\">Deck ${esc(room.deck)||'—'} · você foi convidado a estimar</div>\n      ${d2.state.error?`<div class=\"err\">${esc(d2.state.error)}</div>`:''}\n      <div class=\"field\"><label class=\"label\">Como você aparece na mesa</label>\n        <input class=\"input\" data-field=\"name\" value=\"${esc(d2.state.name)}\" placeholder=\"Seu nome\" maxlength=\"40\"/></div>\n      ${canObs?`<div class=\"field\"><label class=\"label\">Participação</label><div class=\"roles\">\n        <button class=\"role\" data-role=\"player\" ${role==='player'?'data-on':''}>Votar<small>participa das estimativas</small></button>\n        <button class=\"role\" data-role=\"observer\" ${role==='observer'?'data-on':''}>Observar<small>só acompanha</small></button>\n      </div></div>`:''}\n      <button class=\"btn go\" ${d2.state.busy?'disabled':''}>${d2.state.busy?'Entrando…':'Entrar na sala'}</button>\n    </div></div>`;\n  },\n  mounted(d2){\n    const code = ppCode(d2);\n    d2.state.name = localStorage.getItem('pp_name') || '';\n    (async ()=>{\n      let u = d2.auth('pp-auth-strat').user;\n      if(!(u && u._id)){ try{ const s=localStorage.getItem('pp_user'); if(s) u=JSON.parse(s); }catch(_){} }\n      if(u && u._id){ ppNav(d2, '/room/'+code); return; }\n      try{\n        const raw = await d2.rush('pp-engine').invoke({ action:'state', code });\n        const snap = (raw && raw.data) ? raw.data : raw;\n        if(snap && snap.ok && snap.room){ d2.state.room = snap.room; }\n        else { d2.state.notFound = true; }\n      }catch(_){ d2.state.notFound = true; }\n    })();\n    d2.on('.input','input',(e,el)=>{ d2.state[el.dataset.field]=el.value; });\n    d2.on('.role','click',(e,el)=>{ d2.state.role=el.dataset.role; });\n    d2.on('.go','click', ()=>{\n      const name=(d2.state.name||'').trim();\n      if(!name){ d2.state.error='Digite um nome para entrar.'; return; }\n      let pid = localStorage.getItem('pp_pid');\n      if(!pid){ pid = 'g_'+Math.random().toString(36).slice(2,10)+Date.now().toString(36); localStorage.setItem('pp_pid',pid); }\n      localStorage.setItem('pp_name', name);\n      localStorage.setItem('pp_role_'+code, d2.state.role||'player');\n      ppNav(d2, '/room/'+code);\n    });\n  },\n}"}}}}}}}},{"_id":"6a613101c19be2bc56e81907","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"pp-room","package":"fernando-playground","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 ppNav(d2, path){\n  try{\n    var r = d2.router('ppRouter');\n    if(r && r.goTo){\n      r.goTo(path);\n      setTimeout(function(){\n        try{ if(String((d2.router('ppRouter').path)||'').indexOf(path) === -1){ window.location.href = path; } }catch(_){}\n      }, 700);\n      return;\n    }\n  }catch(_){}\n  window.location.href = path;\n}\n\nfunction ppCode(d2){\n  try{ var p = d2.router('ppRouter').params; if(p && p.code){ return String(p.code); } }catch(_){}\n  var parts = (window.location.pathname || '').split('/').filter(Boolean);\n  return parts.length ? decodeURIComponent(parts[parts.length-1]) : '';\n}\n\nexport default {\n  css: `\n    .page{min-height:100%;box-sizing:border-box;display:flex;flex-direction:column;\n      background:radial-gradient(1100px 600px at 50% -10%, rgba(99,102,241,.10), transparent 55%), #0b0e14;\n      font-family:'Inter',system-ui,-apple-system,sans-serif;color:#e6e9ef}\n    .top{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 24px;border-bottom:1px solid rgba(255,255,255,.07);flex-wrap:wrap}\n    .rl{display:flex;align-items:center;gap:12px;min-width:0}\n    .logo{width:30px;height:30px;border-radius:8px;background:linear-gradient(135deg,#6366f1,#14b8a6);display:flex;align-items:center;justify-content:center;font-size:16px;flex:none}\n    .rtit{font-weight:700;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n    .code{font-size:12px;color:#8b93a7;font-family:ui-monospace,monospace}\n    .rr{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n    .b{padding:8px 13px;border-radius:9px;font-size:13px;font-weight:600;cursor:pointer;border:1px solid transparent}\n    .ghost{background:transparent;border-color:rgba(255,255,255,.13);color:#c7cdda}.ghost:hover{background:rgba(255,255,255,.06)}\n    .prim{background:linear-gradient(135deg,#6366f1,#4f46e5);color:#fff}.prim:hover{filter:brightness(1.08)}\n    .accent{background:linear-gradient(135deg,#14b8a6,#0d9488);color:#fff}.accent:hover{filter:brightness(1.08)}\n    .b:disabled{opacity:.5;cursor:default}\n    .main{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:28px 24px;gap:22px}\n    .story{text-align:center}\n    .slabel{font-size:12px;color:#8b93a7;text-transform:uppercase;letter-spacing:.07em}\n    .stitle{font-size:24px;font-weight:800;letter-spacing:-.02em;margin-top:4px}\n    .status{font-size:13px;color:#8b93a7;margin-top:6px}\n    .seats{display:flex;flex-wrap:wrap;gap:14px;justify-content:center;max-width:820px}\n    .seat{width:104px;background:#141926;border:1px solid rgba(255,255,255,.08);border-radius:14px;padding:14px 10px;display:flex;flex-direction:column;align-items:center;gap:9px}\n    .seat[data-obs]{opacity:.7}\n    .card3d{width:52px;height:72px;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:800;\n      background:#0e131d;border:1px solid rgba(255,255,255,.12);color:#e6e9ef;transition:.2s}\n    .card3d[data-voted]{background:linear-gradient(135deg,#1e2740,#26304d);border-color:#6366f1;color:#c7d0ff}\n    .card3d[data-rev]{background:linear-gradient(135deg,#6366f1,#4f46e5);border-color:transparent;color:#fff}\n    .card3d[data-obs]{background:transparent;border-style:dashed;color:#5b647a;font-size:18px}\n    .nm{font-size:13px;font-weight:600;max-width:96px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n    .tag{font-size:10px;color:#8b93a7}\n    .foot{border-top:1px solid rgba(255,255,255,.07);padding:16px 24px;display:flex;justify-content:center;background:rgba(11,14,20,.7)}\n    .deck{display:flex;gap:10px;flex-wrap:wrap;justify-content:center}\n    .pcard{width:52px;height:74px;border-radius:10px;border:1px solid rgba(255,255,255,.14);background:#141926;color:#e6e9ef;font-size:19px;font-weight:800;cursor:pointer;transition:.14s}\n    .pcard:hover{transform:translateY(-6px);border-color:#6366f1}\n    .pcard[data-on]{background:linear-gradient(135deg,#6366f1,#4f46e5);border-color:transparent;color:#fff;transform:translateY(-6px)}\n    .res{display:flex;gap:26px;align-items:center;flex-wrap:wrap;justify-content:center;background:#141926;border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:18px 26px}\n    .stat{text-align:center}.snum{font-size:30px;font-weight:800}.slab{font-size:11px;color:#8b93a7;text-transform:uppercase;letter-spacing:.06em}\n    .dist{display:flex;gap:14px;align-items:flex-end;height:80px}\n    .bar{display:flex;flex-direction:column;align-items:center;gap:5px;justify-content:flex-end}\n    .barf{width:34px;background:linear-gradient(180deg,#6366f1,#4f46e5);border-radius:6px 6px 0 0;min-height:6px}\n    .barv{font-size:13px;font-weight:700}.barc{font-size:11px;color:#8b93a7}\n    .cons{background:rgba(34,197,94,.15);border:1px solid rgba(34,197,94,.35);color:#86efac;padding:4px 11px;border-radius:999px;font-size:12px;font-weight:700}\n    .msg{color:#8b93a7;font-size:15px}\n    .toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#222a3a;border:1px solid rgba(255,255,255,.12);padding:9px 16px;border-radius:10px;font-size:13px}\n  `,\n  render(d2){\n    const esc=(s)=>String(s==null?'':s).replace(/</g,'&lt;').replace(/\"/g,'&quot;');\n    if(d2.state.notFound) return `<div class=\"page\"><div class=\"main\"><div class=\"msg\">Sala não encontrada.</div></div></div>`;\n    const self=d2.state.self;\n    const snap=d2.state.snap;\n    if(!self||!snap) return `<div class=\"page\"><div class=\"main\"><div class=\"msg\">Entrando na mesa…</div></div></div>`;\n    const room=snap.room, round=snap.round, votes=snap.votes||[], members=snap.members||[];\n    const revealed = round.status==='revealed';\n    const isHost = self.userId && room.host===self.userId;\n    const votedSet = {}; votes.forEach(v=>{ votedSet[v.participantId]=v; });\n    const players = members.filter(m=>m.role!=='observer');\n    const observers = members.filter(m=>m.role==='observer');\n    const votedCount = players.filter(p=>votedSet[p.participantId]).length;\n\n    const seat=(m)=>{\n      const isObs=m.role==='observer';\n      const v=votedSet[m.participantId];\n      let card;\n      if(isObs) card=`<div class=\"card3d\" data-obs>👁</div>`;\n      else if(revealed) card=`<div class=\"card3d\" data-rev>${esc(v?v.value:'–')}</div>`;\n      else card=`<div class=\"card3d\" ${v?'data-voted':''}>${v?'✓':'?'}</div>`;\n      return `<div class=\"seat\" ${isObs?'data-obs':''} data-key=\"${m.participantId}\">\n        ${card}<div class=\"nm\">${esc(m.name)||'—'}</div>\n        <div class=\"tag\">${isObs?'observador':(v?(revealed?'':'votou'):'pensando…')}</div></div>`;\n    };\n\n    // results (client-side stats)\n    let resHtml='';\n    if(revealed){\n      const vals=players.map(p=>votedSet[p.participantId]).filter(Boolean).map(v=>v.value);\n      const nums=vals.map(x=> x==='½'?0.5:parseFloat(x)).filter(n=>!isNaN(n));\n      const avg=nums.length?(nums.reduce((a,b)=>a+b,0)/nums.length):null;\n      const dist={}; vals.forEach(x=>{dist[x]=(dist[x]||0)+1;});\n      const order=(room.deck_values||[]).filter(x=>dist[x]);\n      const maxc=Math.max(1,...Object.values(dist));\n      const consensus=Object.keys(dist).length===1 && vals.length>1;\n      const bars=order.map(x=>`<div class=\"bar\"><div class=\"barc\">${dist[x]}</div>\n        <div class=\"barf\" style=\"height:${Math.round(dist[x]/maxc*64)}px\"></div><div class=\"barv\">${esc(x)}</div></div>`).join('');\n      resHtml=`<div class=\"res\">\n        ${bars?`<div class=\"dist\">${bars}</div>`:''}\n        <div class=\"stat\"><div class=\"snum\">${avg!=null?(Math.round(avg*10)/10):'—'}</div><div class=\"slab\">Média</div></div>\n        <div class=\"stat\"><div class=\"snum\">${vals.length}</div><div class=\"slab\">Votos</div></div>\n        ${consensus?'<div class=\"cons\">Consenso 🎉</div>':''}\n        ${isHost?`<button class=\"b prim newstory\">Nova tarefa</button>`:''}\n      </div>`;\n    }\n\n    const canVote = self.role!=='observer' && !revealed;\n    const deck=(room.deck_values||[]);\n    const footHtml = revealed\n      ? ''\n      : (canVote\n        ? `<div class=\"deck\">${deck.map(v=>`<button class=\"pcard\" data-val=\"${esc(v)}\" ${d2.state.myVote===v?'data-on':''}>${esc(v)}</button>`).join('')}</div>`\n        : `<div class=\"msg\">${self.role==='observer'?'Você está observando esta rodada.':'Aguardando as cartas serem reveladas…'}</div>`);\n\n    return `<div class=\"page\">\n      <div class=\"top\">\n        <div class=\"rl\"><div class=\"logo\">🃏</div>\n          <div><div class=\"rtit\">${esc(room.name)||'Sala'}</div><div class=\"code\">#${esc(room.code)} · ${esc(room.deck)}</div></div></div>\n        <div class=\"rr\">\n          ${isHost&&!revealed?`<button class=\"b accent reveal\" ${votedCount===0?'disabled':''}>Revelar cartas</button>`:''}\n          ${isHost&&!revealed?`<button class=\"b ghost newstory\">Nova tarefa</button>`:''}\n          <button class=\"b ghost invite\">${d2.state.copied?'Copiado ✓':'Convidar'}</button>\n          <button class=\"b ghost leave\">Sair</button>\n        </div>\n      </div>\n      <div class=\"main\">\n        <div class=\"story\"><div class=\"slabel\">${revealed?'Resultado':'Estimando'}</div>\n          <div class=\"stitle\">${esc(round.story)||'—'}</div>\n          <div class=\"status\">${revealed?`${round.final_estimate?('Estimativa final: '+esc(round.final_estimate)):'Cartas reveladas'}`:`${votedCount} de ${players.length} votaram`}</div>\n        </div>\n        <div class=\"seats\">${members.map(seat).join('')||'<div class=\"msg\">Ninguém na sala ainda.</div>'}</div>\n        ${resHtml}\n      </div>\n      <div class=\"foot\">${footHtml}</div>\n      ${d2.state.copied?'<div class=\"toast\">Link de convite copiado</div>':''}\n    </div>`;\n  },\n  mounted(d2){\n    const code = ppCode(d2);\n    const engine=()=>d2.rush('pp-engine');\n    let refreshing=false;\n    const refresh=async()=>{\n      if(refreshing) return; refreshing=true;\n      try{\n        const raw=await engine().invoke({action:'state', code}); const snap=(raw&&raw.data)?raw.data:raw;\n        if(snap&&snap.ok){\n          if(d2.state.roundIndex!==snap.round.index){ d2.state.roundIndex=snap.round.index; d2.state.myVote=null; }\n          if(snap.round.status==='revealed'){ d2.state.myVote=null; }\n          d2.state.snap=snap;\n        } else if(snap&&snap.error==='room_not_found'){ d2.state.notFound=true; }\n      }catch(_){ }\n      finally{ refreshing=false; }\n    };\n    (async()=>{\n      // resolve identity — wait briefly for auth only if no guest identity yet\n      let user=d2.auth('pp-auth-strat').user;\n      if(!(user && user._id)){ try{ const s=localStorage.getItem('pp_user'); if(s) user=JSON.parse(s); }catch(_){} }\n      let self;\n      if(user&&user._id){ self={pid:'u_'+user._id, name:user.name||user.email, role:'player', userId:user._id}; }\n      else{\n        const pid=localStorage.getItem('pp_pid'), name=localStorage.getItem('pp_name');\n        if(!pid||!name){ ppNav(d2, '/join/'+code); return; }\n        self={pid, name, role:localStorage.getItem('pp_role_'+code)||'player', userId:null};\n      }\n      d2.state.self=self;\n      try{ await engine().invoke({action:'join', code, participantId:self.pid, name:self.name, role:self.role}); }catch(_){ }\n      await refresh();\n      // realtime\n      const sock=d2.socket('ppSocket');\n      sock.subscribe(code);\n      sock.onMessage(()=>{ refresh(); });\n      // heartbeat\n      const hb=setInterval(()=>{ engine().invoke({action:'heartbeat', code, participantId:self.pid, name:self.name, role:self.role}).catch(()=>{}); }, 20000);\n      const poll=setInterval(()=>{ refresh(); }, 5000);\n      d2.onUnmount(()=>{ clearInterval(hb); clearInterval(poll); engine().invoke({action:'leave', code, participantId:self.pid}).catch(()=>{}); });\n    })();\n\n    d2.on('.pcard','click', async (e,el)=>{\n      const self=d2.state.self; if(!self) return;\n      const v=el.dataset.val; d2.state.myVote=v;\n      try{ await engine().invoke({action:'cast-vote', code, participantId:self.pid, name:self.name, value:v}); }catch(_){}\n      refresh();\n    });\n    d2.on('.reveal','click', async ()=>{ try{ await engine().invoke({action:'reveal', code}); }catch(_){}; refresh(); });\n    d2.on('.newstory','click', async ()=>{\n      const s=await d2.dialog.prompt('Qual a próxima tarefa / story?','');\n      if(s&&s.trim()){ try{ await engine().invoke({action:'new-story', code, story:s.trim()}); }catch(_){}; d2.state.myVote=null; refresh(); }\n    });\n    d2.on('.invite','click', async ()=>{\n      const url=window.location.origin+'/join/'+code;\n      try{ await navigator.clipboard.writeText(url); }catch(_){}\n      d2.state.copied=true; setTimeout(()=>{ d2.state.copied=false; }, 1800);\n    });\n    d2.on('.leave','click', async ()=>{\n      const self=d2.state.self;\n      try{ if(self) await engine().invoke({action:'leave', code, participantId:self.pid}); }catch(_){}\n      ppNav(d2, self&&self.userId?'/app':'/join/'+code);\n    });\n  },\n}"}}}}}}}},{"_id":"6a669c91b768b226f05aefa9","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"pp-shell","package":"fernando-playground","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 PKG = 'fernando-playground@';\nconst TYPES = {\n  auth:   PKG + 'pp-auth',\n  home:   PKG + 'pp-home',\n  create: PKG + 'pp-create-room',\n  join:   PKG + 'pp-join',\n  room:   PKG + 'pp-room',\n};\n\nfunction readUser(){\n  try{ const s = localStorage.getItem('pp_user'); if(s){ const u = JSON.parse(s); if(u && u._id) return u; } }catch(_){}\n  return null;\n}\n\nfunction viewFor(path, user){\n  const parts = String(path || '/').split('/').filter(Boolean);\n  const head = (parts[0] || '').toLowerCase();\n  if(head === 'join') return 'join';\n  if(head === 'room') return 'room';\n  if(head === 'new')  return user ? 'create' : 'auth';\n  if(head === 'app')  return user ? 'home' : 'auth';\n  return user ? 'home' : 'auth';\n}\n\nexport default {\n  css: `\n    :host{position:absolute;inset:0;background:#0b0e14;overflow:hidden}\n    .v{position:absolute;inset:0;overflow:auto;background:#0b0e14}\n  `,\n  render(d2){\n    const path = d2.state.path || (typeof window !== 'undefined' ? window.location.pathname : '/');\n    const user = readUser();\n    const view = viewFor(path, user);\n\n    const params = {};\n    if(view === 'home')   params.rooms = d2.params.rooms;\n    if(view === 'create') params.decks = d2.params.decks;\n\n    const nav = (p)=>{\n      if(!p) return true;\n      try{ window.history.pushState({}, '', p); }\n      catch(_){ window.location.assign(p); return true; }\n      d2.state.path = p;\n      return true;\n    };\n\n    return `<div class=\"v\">${d2.render(TYPES[view], params, { key: view, onNavigate: nav })}</div>`;\n  },\n  mounted(d2){\n    if(typeof window !== 'undefined'){\n      window.__ppShell = true;\n      d2.onUnmount(()=>{ try{ window.__ppShell = false; }catch(_){} });\n      d2.state.path = window.location.pathname;\n      d2.on(window, 'popstate', ()=>{ d2.state.path = window.location.pathname; });\n    }\n  },\n}"}},"rooms":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rooms"}}}},"decks":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"decks"}}}}}}}}}},{"_id":"6a66b26b4eab3e4ed9012641","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"ppk-auth-screen","package":"fernando-playground","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 THEME_KEY = 'ppk.theme'\n\n// Tema: preferencia salva > preferencia do sistema > claro.\nconst readTheme = () => {\n  try {\n    const t = window.localStorage.getItem(THEME_KEY)\n    if (t === 'dark' || t === 'light') return t\n  } catch (e) { /* segue */ }\n  try {\n    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark'\n  } catch (e) { /* segue */ }\n  return 'light'\n}\nconst writeTheme = (t) => {\n  try { window.localStorage.setItem(THEME_KEY, t) } catch (e) { /* segue */ }\n}\n\nconst esc = (s) => String(s == null ? '' : s)\n  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n\nconst SIGNUP_ERRORS = {\n  email_invalid: 'Informe um e-mail válido.',\n  name_required: 'Informe seu nome.',\n  password_too_short: 'A senha precisa de ao menos 6 caracteres.',\n  email_taken: 'Esse e-mail já tem uma conta. Use \"Entrar\".',\n}\n\nconst show = (v) => { try { return JSON.stringify(v) } catch (x) { return String(v) } }\n\n// O runtime aceita a credencial em duas formas dependendo da versão; tenta as duas.\n// O goTo() do router é no-op silencioso se a referência não resolver.\n// Tenta as formas conhecidas, CONFERE se a rota mudou, e só então desiste.\nconst navTo = async (d2, path) => {\n  const here = () => { try { return window.location.pathname || '' } catch (e) { return '' } }\n  const before = here()\n  const attempts = [\n    () => { const r = d2.router('ppk-router'); if (r && r.goTo) r.goTo(path) },\n    () => { const r = d2.router(); if (r && r.goTo) r.goTo(path) },\n    () => {\n      const p = d2.process('ppk-routerInterface')\n      if (p && p.get) { const h = p.get('history'); if (h && h.push) h.push(path) }\n    },\n  ]\n  for (let i = 0; i < attempts.length; i++) {\n    try { attempts[i]() } catch (e) { d2.log('PPK-NAV t' + i + ' erro ' + (e && e.message ? e.message : e)) }\n    await new Promise((r) => setTimeout(r, 250))\n    const now = here()\n    if (now !== before || now.indexOf(path) >= 0) { d2.log('PPK-NAV ok via t' + i + ' -> ' + now); return true }\n  }\n  d2.log('PPK-NAV nenhum router respondeu; caindo para window.location')\n  try { window.location.assign(path); return true } catch (e) { d2.log('PPK-NAV location falhou') }\n  return false\n}\n\nconst isErrorish = (v) => {\n  if (!v) return true\n  if (v instanceof Error) return true\n  if (typeof v === 'object') {\n    if (v.isError === true) return true\n    if (v.error) return true\n    if (v.component && String(v.component).toLowerCase().indexOf('error') >= 0) return true\n  }\n  return false\n}\n\nconst errText = (e) => {\n  if (!e) return ''\n  const st = e.status || (e.response && e.response.status)\n  const server = e.response && e.response.data\n    ? (e.response.data.message || e.response.data.error || show(e.response.data))\n    : ''\n  if (server) return String(server)\n  if (st === 400) return 'O servidor recusou as credenciais (400).'\n  if (st === 401) return 'E-mail ou senha incorretos.'\n  return e.message ? String(e.message) : ''\n}\n\nconst tryLogin = async (d2, auth, email, password) => {\n  // O runtime exige DOIS params: a referência da strategy E o objeto credentials.\n  // Mensagem literal do 400: \"Both `authenticationStrategyReference` and\n  // `credentials` are required params.\" Mesma assinatura do login do MCP.\n  const creds = { email: email, password: password }\n  const shapes = [\n    { authenticationStrategyReference: 'ppk-auth', credentials: creds },\n    { strategyReference: 'ppk-auth', credentials: creds },\n    { credentials: creds },\n  ]\n  let last = null\n  for (let i = 0; i < shapes.length; i++) {\n    let threw = false\n    try {\n      last = await auth.login(shapes[i])\n    } catch (e) {\n      threw = true\n      last = e\n    }\n    await new Promise((r) => setTimeout(r, 350))\n\n    // Tres sondas independentes — nenhuma e confiavel isolada.\n\n    // (a) handler Rush lendo @__Session.user.\n    //     Pode ser cega: o invoke talvez mande o token do bundle, nao a sessao.\n    let whoAuthed = false\n    let whoWho = ''\n    try {\n      const who = await d2.rush('ppk-whoami').invoke({})\n      const w = (who && who.data) ? who.data : who\n      whoAuthed = !!(w && w.authed)\n      whoWho = (w && (w.email || w.userId)) ? String(w.email || w.userId) : ''\n    } catch (we) { whoAuthed = false }\n\n    // (b) a tabela verifier da strategy e auto-filtrada pela sessao:\n    //     logado devolve 1 linha (a propria), anonimo devolve 0.\n    //     Vai pela API de dados, caminho diferente do Rush.\n    let rowAuthed = false\n    let rowWho = ''\n    try {\n      const ds = d2.data('ppk-usersDataSet')\n      if (ds) {\n        if (ds.refresh) { await ds.refresh() }\n        await new Promise((r) => setTimeout(r, 250))\n        const rows = ds.records ? (ds.records() || []) : []\n        rowAuthed = rows.length > 0\n        if (rowAuthed) { rowWho = String(rows[0].email || rows[0]._id || '') }\n      }\n    } catch (de) { rowAuthed = false }\n\n    // (c) getters locais — diagnostico apenas, nao decidem nada.\n    let localUser = ''\n    try { localUser = auth.user ? show(auth.user) : String(auth.user) } catch (ue) { localUser = 'erro' }\n\n    const authed = whoAuthed || rowAuthed\n    d2.log('PPK-AUTH forma' + i\n      + ' threw=' + threw\n      + ' erro=' + (threw ? errText(last) : '-')\n      + ' whoami=' + whoAuthed + (whoWho ? ('(' + whoWho + ')') : '')\n      + ' verifierRows=' + rowAuthed + (rowWho ? ('(' + rowWho + ')') : '')\n      + ' authUser=' + localUser)\n\n    // Para na PRIMEIRA que autenticar — seguir tentando poderia derrubar\n    // uma sessao que ja estava boa.\n    if (authed) return { ok: true, res: last, who: whoWho || rowWho }\n  }\n  return { ok: false, res: last }\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%}\n    *{box-sizing:border-box}\n    .themed{height:100%;position:relative}\n    .tbtn{position:absolute;top:16px;right:16px;width:32px;height:32px;border-radius:8px;\n      border:1px solid #eaeaea;background:#fff;color:#666;cursor:pointer;font-size:14px;\n      display:flex;align-items:center;justify-content:center;font-family:inherit;z-index:5}\n    .tbtn:hover{border-color:#000;color:#000}\n    .wrap{height:100%;display:flex;align-items:center;justify-content:center;padding:24px;\n      background:#fff;color:#000;\n      font:400 14px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,sans-serif}\n    .card{width:100%;max-width:380px}\n    .brand{display:flex;align-items:center;gap:10px;margin-bottom:32px}\n    .logo{width:32px;height:32px;border-radius:8px;background:#000;color:#fff;display:flex;\n      align-items:center;justify-content:center;font-weight:700;font-size:13px;letter-spacing:-.02em}\n    .bt{margin:0;font-size:14px;font-weight:600;letter-spacing:-.01em;line-height:1.2}\n    .bs{color:#666;font-size:12px}\n    h2{margin:0 0 6px;font-size:26px;font-weight:600;letter-spacing:-.03em}\n    .sub{margin:0 0 26px;color:#666}\n    label{display:block;font-size:13px;font-weight:500;margin:0 0 6px}\n    .row{margin-bottom:16px}\n    .field{width:100%;height:40px;padding:0 12px;border:1px solid #eaeaea;border-radius:6px;\n      background:#fff;color:#000;font-size:14px;outline:none;transition:border-color .15s,box-shadow .15s}\n    .field::placeholder{color:#999}\n    .field:focus{border-color:#000;box-shadow:0 0 0 3px rgba(0,0,0,.06)}\n    .pwrap{position:relative}\n    .pw{padding-right:84px}\n    .eye{position:absolute;right:5px;top:5px;height:30px;padding:0 10px;border:0;border-radius:5px;\n      background:transparent;color:#666;font-size:11.5px;font-weight:600;cursor:pointer;letter-spacing:.01em}\n    .eye:hover{color:#000;background:#f4f4f5}\n    .submit{width:100%;height:40px;border:0;border-radius:6px;background:#000;color:#fff;\n      font-size:14px;font-weight:500;cursor:pointer;transition:opacity .15s}\n    .submit:hover:not(:disabled){opacity:.85}\n    .submit:disabled{opacity:.45;cursor:not-allowed}\n    .alt{margin:18px 0 0;text-align:center;color:#666;font-size:13px}\n    .toggle{color:#000;font-weight:500;cursor:pointer;text-decoration:underline;text-underline-offset:2px}\n    .err{margin:0 0 16px;padding:10px 12px;border:1px solid #f3c9c7;background:#fdf4f4;\n      color:#b3261e;border-radius:6px;font-size:13px}\n    .divider{display:flex;align-items:center;gap:12px;margin:26px 0 18px;color:#999;font-size:12px}\n    .divider:before,.divider:after{content:'';flex:1;height:1px;background:#eaeaea}\n    .guest{width:100%;height:40px;border:1px solid #eaeaea;border-radius:6px;background:#fff;\n      color:#000;font-size:14px;font-weight:500;cursor:pointer;transition:border-color .15s}\n    .guest:hover{border-color:#000}\n    [data-theme=\"dark\"]{\n      .wrap{background:#000;color:#fff}\n      .logo{background:#fff;color:#000}\n      .tbtn{background:#111;border-color:#262626;color:#888}\n      .tbtn:hover{border-color:#fff;color:#fff}\n      .bs,.sub,.alt,.divider{color:#888}\n      .field{background:#000;border-color:#333;color:#fff}\n      .field::placeholder{color:#666}\n      .field:focus{border-color:#fff;box-shadow:0 0 0 3px rgba(255,255,255,.1)}\n      .eye{color:#888}\n      .eye:hover{color:#fff;background:#1c1c1c}\n      .submit{background:#fff;color:#000}\n      .toggle{color:#fff}\n      .divider:before,.divider:after{background:#333}\n      .guest{background:#000;border-color:#333;color:#fff}\n      .guest:hover{border-color:#fff}\n      .err{border-color:#552220;background:#1a0f0f;color:#f87171}\n    }\n  `,\n\n  render(d2) {\n    const reg = d2.state.mode === 'register'\n    const busy = !!d2.state.busy\n    const err = d2.state.error\n    const theme = d2.state.theme || readTheme()\n    return `<div class=\"themed\" data-theme=\"${theme}\">\n      <button class=\"tbtn\" title=\"${theme === 'dark' ? 'Mudar para claro' : 'Mudar para escuro'}\">${theme === 'dark' ? '☀' : '☽'}</button>\n      <div class=\"wrap\"><div class=\"card\">\n      <div class=\"brand\">\n        <div class=\"logo\">PP</div>\n        <div><p class=\"bt\">Poker Planning</p><span class=\"bs\">Estimativa em equipe</span></div>\n      </div>\n      <h2>${reg ? 'Criar conta' : 'Entrar'}</h2>\n      <p class=\"sub\">${reg ? 'Facilitadores criam e conduzem as salas.' : 'Acesse para criar e retomar suas salas.'}</p>\n      ${err ? `<div class=\"err\">${esc(err)}</div>` : ''}\n      ${reg ? `<div class=\"row\"><label for=\"ppk-name\">Nome</label>\n        <input class=\"field\" id=\"ppk-name\" name=\"name\" data-k=\"name\" type=\"text\"\n          value=\"${esc(d2.state.f_name || '')}\"\n          placeholder=\"Seu nome\" autocomplete=\"name\" autocapitalize=\"words\" spellcheck=\"false\"/></div>` : ''}\n      <div class=\"row\"><label for=\"ppk-email\">E-mail</label>\n        <input class=\"field\" id=\"ppk-email\" name=\"email\" data-k=\"email\" type=\"email\" inputmode=\"email\"\n          value=\"${esc(d2.state.f_email || '')}\"\n          placeholder=\"voce@empresa.com\" autocomplete=\"${reg ? 'email' : 'username'}\"\n          autocapitalize=\"off\" autocorrect=\"off\" spellcheck=\"false\"/></div>\n      <div class=\"row\"><label for=\"ppk-pass\">Senha</label>\n        <div class=\"pwrap\">\n          <input class=\"field pw\" id=\"ppk-pass\" name=\"password\" data-k=\"password\"\n            type=\"${d2.state.showPw ? 'text' : 'password'}\"\n            value=\"${esc(d2.state.f_password || '')}\"\n            placeholder=\"Mínimo 6 caracteres\" autocomplete=\"${reg ? 'new-password' : 'current-password'}\"\n            spellcheck=\"false\"/>\n          <button class=\"eye\" type=\"button\" aria-label=\"${d2.state.showPw ? 'Ocultar senha' : 'Mostrar senha'}\">${d2.state.showPw ? 'Ocultar' : 'Mostrar'}</button>\n        </div></div>\n      <button class=\"submit\" ${busy ? 'disabled' : ''}>${busy ? 'Aguarde…' : (reg ? 'Criar conta' : 'Entrar')}</button>\n      <p class=\"alt\">${reg ? 'Já tem conta?' : 'Ainda não tem conta?'} <span class=\"toggle\">${reg ? 'Entrar' : 'Criar conta'}</span></p>\n      <div class=\"divider\">ou</div>\n      <button class=\"guest\">Entrar em uma sala com código</button>\n    </div></div></div>`\n  },\n\n  mounted(d2) {\n    // Fonte de verdade em d2.state para o valor sobreviver a um re-render;\n    // cai pro DOM quando o campo foi preenchido por autofill do browser.\n    d2.state.theme = readTheme()\n    d2.on('.tbtn', 'click', () => {\n      const next = (d2.state.theme || readTheme()) === 'dark' ? 'light' : 'dark'\n      d2.state.theme = next\n      writeTheme(next)\n      d2.refresh()\n    })\n\n    const val = (k) => {\n      const el = d2.$('[data-k=\"' + k + '\"]')\n      const fromDom = el && el.value ? String(el.value).trim() : ''\n      if (fromDom) return fromDom\n      const s = d2.state['f_' + k]\n      return s ? String(s).trim() : ''\n    }\n    const submit = async () => {\n      if (d2.state.busy) return\n      const reg = d2.state.mode === 'register'\n      const email = val('email')\n      const password = val('password')\n      const name = val('name')\n      if (!email || !password) { d2.state.error = 'Preencha e-mail e senha.'; d2.refresh(); return }\n      if (reg && !name) { d2.state.error = 'Informe seu nome.'; d2.refresh(); return }\n      if (reg && password.length < 6) { d2.state.error = 'A senha precisa de ao menos 6 caracteres.'; d2.refresh(); return }\n      d2.state.error = ''\n      d2.state.busy = true\n      d2.refresh()\n      try {\n        const auth = d2.auth('ppk-auth')\n\n        if (reg) {\n          const sres = await d2.rush('ppk-signup').invoke({ name: name, email: email, password: password })\n          const s = (sres && sres.data) ? sres.data : sres\n          d2.log('PPK-AUTH signup retorno=' + show(s))\n          if (!s || !s.ok) {\n            d2.state.error = SIGNUP_ERRORS[s && s.error] || 'Não foi possível criar a conta.'\n            return\n          }\n        }\n\n        const li = await tryLogin(d2, auth, email, password)\n        if (!li.ok) {\n          const detail = errText(li.res)\n          d2.state.error = reg\n            ? ('A conta foi criada, mas a sessão não abriu' + (detail ? ' (' + detail + ')' : '') + '. Tente entrar com o e-mail e a senha.')\n            : (detail || 'E-mail ou senha incorretos.')\n          return\n        }\n\n        await navTo(d2, '/salas')\n      } catch (e) {\n        const msg = (e && e.message) ? String(e.message) : ''\n        d2.log('PPK-AUTH excecao', e)\n        d2.state.error = msg || 'Falha na autenticação.'\n      } finally {\n        d2.state.busy = false\n        d2.refresh()\n      }\n    }\n    d2.on('.field', 'input', (e, el) => {\n      if (el.dataset && el.dataset.k) d2.state['f_' + el.dataset.k] = el.value\n    })\n    d2.on('.eye', 'click', () => {\n      const inp = d2.$('[data-k=\"password\"]')\n      const btn = d2.$('.eye')\n      if (!inp || !btn) return\n      const reveal = inp.type === 'password'\n      inp.type = reveal ? 'text' : 'password'\n      btn.textContent = reveal ? 'Ocultar' : 'Mostrar'\n      btn.setAttribute('aria-label', reveal ? 'Ocultar senha' : 'Mostrar senha')\n      d2.state.showPw = reveal\n      inp.focus()\n    })\n    d2.on('.submit', 'click', submit)\n    d2.on('.guest', 'click', () => { navTo(d2, '/entrar') })\n    d2.on('.toggle', 'click', () => {\n      d2.state.error = ''\n      d2.state.mode = d2.state.mode === 'register' ? 'login' : 'register'\n      d2.refresh()\n    })\n    d2.on(d2.root, 'keydown', (e) => { if (e.key === 'Enter') submit() })\n  },\n}"}}}}}}}},{"_id":"6a66b319c834e87e6d3c98f7","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"ppk-my-rooms","package":"fernando-playground","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 THEME_KEY = 'ppk.theme'\n\nconst readTheme = () => {\n  try {\n    const t = window.localStorage.getItem(THEME_KEY)\n    if (t === 'dark' || t === 'light') return t\n  } catch (e) { /* segue */ }\n  try {\n    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark'\n  } catch (e) { /* segue */ }\n  return 'light'\n}\nconst writeTheme = (t) => {\n  try { window.localStorage.setItem(THEME_KEY, t) } catch (e) { /* segue */ }\n}\n\nconst esc = (s) => String(s == null ? '' : s)\n  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n\nconst DECKS = {\n  'fibonacci': 'Fibonacci',\n  'fibonacci-mod': 'Fibonacci modificada',\n  'sequential': 'Sequencial',\n  'powers-of-two': 'Potências de 2',\n  'tshirt': 'T-shirt',\n  'custom': 'Customizado',\n}\n\n// O goTo() do router é no-op silencioso se a referência não resolver.\nconst navTo = async (d2, path) => {\n  const here = () => { try { return window.location.pathname || '' } catch (e) { return '' } }\n  const before = here()\n  const attempts = [\n    () => { const r = d2.router('ppk-router'); if (r && r.goTo) r.goTo(path) },\n    () => { const r = d2.router(); if (r && r.goTo) r.goTo(path) },\n    () => {\n      const p = d2.process('ppk-routerInterface')\n      if (p && p.get) { const h = p.get('history'); if (h && h.push) h.push(path) }\n    },\n  ]\n  for (let i = 0; i < attempts.length; i++) {\n    try { attempts[i]() } catch (e) { d2.log('PPK-NAV t' + i + ' erro') }\n    await new Promise((r) => setTimeout(r, 250))\n    const now = here()\n    if (now !== before || now.indexOf(path) >= 0) { d2.log('PPK-NAV ok via t' + i); return true }\n  }\n  try { window.location.assign(path); return true } catch (e) { d2.log('PPK-NAV location falhou') }\n  return false\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%}\n    *{box-sizing:border-box}\n    .themed{height:100%}\n    .tbtn{width:30px;height:30px;border:1px solid #eaeaea;border-radius:7px;background:#fff;\n      color:#666;cursor:pointer;font-size:13px;display:flex;align-items:center;\n      justify-content:center;flex:0 0 auto;font-family:inherit}\n    .tbtn:hover{border-color:#000;color:#000}\n    .page{height:100%;overflow:auto;background:#fafafa;color:#000;\n      font:400 14px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,sans-serif}\n    .top{position:sticky;top:0;z-index:2;display:flex;align-items:center;justify-content:space-between;\n      gap:16px;padding:0 24px;height:60px;background:rgba(255,255,255,.85);\n      backdrop-filter:saturate(180%) blur(8px);border-bottom:1px solid #eaeaea}\n    .brand{display:flex;align-items:center;gap:10px;min-width:0}\n    .logo{width:28px;height:28px;border-radius:7px;background:#000;color:#fff;display:flex;\n      align-items:center;justify-content:center;font-weight:700;font-size:12px;flex:0 0 auto}\n    .bt{margin:0;font-size:14px;font-weight:600;letter-spacing:-.01em}\n    .who{display:flex;align-items:center;gap:12px;font-size:13px;color:#666}\n    .link{color:#000;font-weight:500;cursor:pointer;background:none;border:0;font-size:13px;padding:0}\n    .link:hover{text-decoration:underline;text-underline-offset:2px}\n    .body{max-width:880px;margin:0 auto;padding:36px 24px 64px}\n    .head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:26px}\n    h2{margin:0 0 4px;font-size:26px;font-weight:600;letter-spacing:-.03em}\n    .sub{margin:0;color:#666}\n    .new{height:36px;padding:0 14px;border:0;border-radius:6px;background:#000;color:#fff;\n      font-size:14px;font-weight:500;cursor:pointer;white-space:nowrap;flex:0 0 auto}\n    .new:hover{opacity:.85}\n    .sec{margin:0 0 10px;font-size:12px;font-weight:600;text-transform:uppercase;\n      letter-spacing:.06em;color:#999}\n    .list{display:flex;flex-direction:column;gap:10px;margin-bottom:34px}\n    .item{display:flex;align-items:center;gap:16px;padding:16px 18px;background:#fff;\n      border:1px solid #eaeaea;border-radius:10px;transition:border-color .15s,box-shadow .15s}\n    .item:hover{border-color:#d4d4d4;box-shadow:0 1px 3px rgba(0,0,0,.04)}\n    .item.dim{opacity:.62}\n    .code{font:600 15px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.06em;\n      padding:9px 11px;background:#f4f4f5;border-radius:7px;flex:0 0 auto}\n    .meta{min-width:0;flex:1}\n    .name{font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n    .dets{color:#888;font-size:12.5px;margin-top:2px}\n    .acts{display:flex;align-items:center;gap:8px;flex:0 0 auto}\n    .btn{height:32px;padding:0 12px;border:1px solid #eaeaea;border-radius:6px;background:#fff;\n      color:#000;font-size:13px;font-weight:500;cursor:pointer;transition:border-color .15s}\n    .btn:hover{border-color:#000}\n    .btn.primary{background:#000;color:#fff;border-color:#000}\n    .btn.primary:hover{opacity:.85}\n    .btn:disabled{opacity:.45;cursor:not-allowed}\n    .empty{padding:52px 24px;text-align:center;background:#fff;border:1px dashed #dcdcdc;border-radius:10px}\n    .empty p{margin:0 0 16px;color:#666}\n    .skel{height:66px;background:#fff;border:1px solid #eaeaea;border-radius:10px;\n      margin-bottom:10px;animation:pulse 1.4s ease-in-out infinite}\n    @keyframes pulse{0%,100%{opacity:1}50%{opacity:.55}}\n    [data-theme=\"dark\"]{\n      .page{background:#0a0a0a;color:#fff}\n      .tbtn{background:#111;border-color:#262626;color:#888}\n      .tbtn:hover{border-color:#fff;color:#fff}\n      .top{background:rgba(10,10,10,.85);border-bottom-color:#262626}\n      .logo{background:#fff;color:#000}\n      .who,.sub,.dets,.empty p{color:#888}\n      .link{color:#fff}\n      .item,.empty,.skel{background:#111;border-color:#262626}\n      .item:hover{border-color:#3f3f3f}\n      .code{background:#1c1c1c}\n      .new{background:#fff;color:#000}\n      .btn{background:#111;border-color:#262626;color:#fff}\n      .btn:hover{border-color:#fff}\n      .btn.primary{background:#fff;color:#000;border-color:#fff}\n      .empty{border-color:#333}\n      .sec{color:#666}\n    }\n  `,\n\n  render(d2) {\n    const auth = d2.auth('ppk-auth')\n    // auth.user vem {} (objeto vazio) mesmo com sessao valida, entao nao serve\n    // como identidade. A confiavel vem da tabela verifier da strategy, que e\n    // auto-filtrada pela sessao e devolve exatamente a linha do usuario logado.\n    const me = (d2.state.me && (d2.state.me._id || d2.state.me.id)) ? d2.state.me : (auth.user || {})\n    const user = me\n    const ds = d2.data('rooms')\n    const uid = me._id || me.id || null\n    const theme = d2.state.theme || readTheme()\n\n    const top = `<div class=\"top\">\n      <div class=\"brand\"><div class=\"logo\">PP</div><p class=\"bt\">Poker Planning</p></div>\n      <div class=\"who\">${esc(user.name || user.email || '')}\n        <button class=\"tbtn\" title=\"${theme === 'dark' ? 'Mudar para claro' : 'Mudar para escuro'}\">${theme === 'dark' ? '☀' : '☽'}</button>\n        <button class=\"link logout\">Sair</button></div>\n    </div>`\n\n    if (ds.loading || !d2.state.meLoaded) {\n      return `<div class=\"themed\" data-theme=\"${theme}\"><div class=\"page\">${top}<div class=\"body\">\n        <div class=\"head\"><div><h2>Minhas salas</h2><p class=\"sub\">Carregando…</p></div></div>\n        <div class=\"skel\"></div><div class=\"skel\"></div><div class=\"skel\"></div>\n      </div></div></div>`\n    }\n\n    const mine = (ds.records() || []).filter((r) => uid && String(r.ownerUserId) === String(uid))\n    const active = mine.filter((r) => r.status !== 'closed')\n    const closed = mine.filter((r) => r.status === 'closed')\n\n    const row = (r, dim) => `<div class=\"item ${dim ? 'dim' : ''}\" data-key=\"${esc(r._id)}\">\n      <div class=\"code\">${esc(r.code)}</div>\n      <div class=\"meta\">\n        <div class=\"name\">${esc(r.name)}</div>\n        <div class=\"dets\">${esc(DECKS[r.deckId] || r.deckId || '')}${r.createdAt ? ' · criada em ' + esc(d2.fmt.date(r.createdAt)) : ''}</div>\n      </div>\n      <div class=\"acts\">\n        ${dim\n          ? `<button class=\"btn open\" data-code=\"${esc(r.code)}\">Ver resumo</button>`\n          : `<button class=\"btn primary open\" data-code=\"${esc(r.code)}\">Retomar</button>\n             <button class=\"btn close\" data-id=\"${esc(r._id)}\" ${d2.state.busyId === r._id ? 'disabled' : ''}>${d2.state.busyId === r._id ? '…' : 'Encerrar'}</button>`}\n      </div>\n    </div>`\n\n    const body = active.length === 0 && closed.length === 0\n      ? `<div class=\"empty\"><p>Você ainda não criou nenhuma sala.</p>\n         <button class=\"btn primary new\">Criar a primeira sala</button></div>`\n      : `${active.length ? `<p class=\"sec\">Ativas · ${active.length}</p><div class=\"list\">${active.map((r) => row(r, false)).join('')}</div>` : ''}\n         ${closed.length ? `<p class=\"sec\">Encerradas · ${closed.length}</p><div class=\"list\">${closed.map((r) => row(r, true)).join('')}</div>` : ''}`\n\n    return `<div class=\"themed\" data-theme=\"${theme}\"><div class=\"page\">${top}<div class=\"body\">\n      <div class=\"head\">\n        <div><h2>Minhas salas</h2><p class=\"sub\">Crie uma sala nova ou retome uma sessão em andamento.</p></div>\n        <button class=\"new\">Nova sala</button>\n      </div>\n      ${body}\n    </div></div></div>`\n  },\n\n  mounted(d2) {\n    const nav = () => {\n      let base = {}\n      try { base = d2.router('ppk-router') || {} } catch (e) { base = {} }\n      if (!base.params) { try { base = d2.router() || {} } catch (e) { base = {} } }\n      return {\n        params: base.params || {},\n        path: base.path || '',\n        goTo: (path) => navTo(d2, path),\n      }\n    }\n    // Descobre quem esta logado pela tabela verifier (auto-filtrada pela sessao).\n    const loadMe = async () => {\n      try {\n        const uds = d2.data('ppk-usersDataSet')\n        if (uds) {\n          if (uds.refresh) { await uds.refresh() }\n          await new Promise((r) => setTimeout(r, 250))\n          const rows = uds.records ? (uds.records() || []) : []\n          if (rows.length) { d2.state.me = rows[0] }\n          d2.log('PPK-ROOMS me=' + (rows.length ? String(rows[0].email || rows[0]._id) : 'nenhum'))\n        }\n      } catch (e) {\n        d2.log('PPK-ROOMS erro ao identificar usuario')\n      }\n      // O filtro por dono e feito no cliente; sem carregar todas as paginas,\n      // as salas do usuario poderiam ficar fora da primeira pagina.\n      try {\n        const rds = d2.data('rooms')\n        if (rds && rds.loadAll) { await rds.loadAll() }\n      } catch (e2) {\n        d2.log('PPK-ROOMS loadAll indisponivel')\n      }\n      d2.state.meLoaded = true\n      d2.refresh()\n    }\n    loadMe()\n\n    d2.state.theme = readTheme()\n    d2.on('.tbtn', 'click', () => {\n      const next = (d2.state.theme || readTheme()) === 'dark' ? 'light' : 'dark'\n      d2.state.theme = next\n      writeTheme(next)\n      d2.refresh()\n    })\n\n    d2.on('.new', 'click', () => nav().goTo('/nova'))\n    d2.on('.open', 'click', (e, el) => nav().goTo('/sala/' + el.dataset.code))\n    d2.on('.logout', 'click', async () => {\n      try { await d2.auth('ppk-auth').logout() } catch (err) { d2.log(err) }\n      nav().goTo('/')\n    })\n    d2.on('.close', 'click', async (e, el) => {\n      const id = el.dataset.id\n      if (!id || d2.state.busyId) return\n      d2.state.busyId = id\n      d2.refresh()\n      try {\n        await d2.data('rooms').update({ _id: id, status: 'closed', closedAt: new Date().toISOString() })\n      } catch (err) {\n        d2.log(err)\n      } finally {\n        d2.state.busyId = null\n        d2.refresh()\n      }\n    })\n  },\n}"}},"rooms":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rooms"}}}}}}}}}},{"_id":"6a66b37d39c8071d2a805007","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"ppk-new-room","package":"fernando-playground","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 THEME_KEY = 'ppk.theme'\n\nconst readTheme = () => {\n  try {\n    const t = window.localStorage.getItem(THEME_KEY)\n    if (t === 'dark' || t === 'light') return t\n  } catch (e) { /* segue */ }\n  try {\n    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark'\n  } catch (e) { /* segue */ }\n  return 'light'\n}\nconst writeTheme = (t) => {\n  try { window.localStorage.setItem(THEME_KEY, t) } catch (e) { /* segue */ }\n}\n\nconst esc = (s) => String(s == null ? '' : s)\n  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n\nconst TOGGLES = [\n  { k: 'autoReveal', def: true, label: 'Revelar automaticamente', hint: 'Vira as cartas quando todos votarem' },\n  { k: 'showStats', def: true, label: 'Mostrar estatísticas', hint: 'Média, mediana, moda e dispersão' },\n  { k: 'facilitatorVotes', def: true, label: 'Facilitador vota', hint: 'Você também recebe uma mão de cartas' },\n  { k: 'allowChange', def: false, label: 'Permitir trocar voto depois de revelar', hint: '' },\n  { k: 'anonymous', def: false, label: 'Voto anônimo', hint: 'Mostra as cartas sem o nome de quem votou' },\n  { k: 'timer', def: false, label: 'Timer por rodada', hint: '' },\n]\n\n// O goTo() do router é no-op silencioso se a referência não resolver.\nconst navTo = async (d2, path) => {\n  const here = () => { try { return window.location.pathname || '' } catch (e) { return '' } }\n  const before = here()\n  const attempts = [\n    () => { const r = d2.router('ppk-router'); if (r && r.goTo) r.goTo(path) },\n    () => { const r = d2.router(); if (r && r.goTo) r.goTo(path) },\n    () => {\n      const p = d2.process('ppk-routerInterface')\n      if (p && p.get) { const h = p.get('history'); if (h && h.push) h.push(path) }\n    },\n  ]\n  for (let i = 0; i < attempts.length; i++) {\n    try { attempts[i]() } catch (e) { d2.log('PPK-NAV t' + i + ' erro') }\n    await new Promise((r) => setTimeout(r, 250))\n    const now = here()\n    if (now !== before || now.indexOf(path) >= 0) { d2.log('PPK-NAV ok via t' + i); return true }\n  }\n  try { window.location.assign(path); return true } catch (e) { d2.log('PPK-NAV location falhou') }\n  return false\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%}\n    *{box-sizing:border-box}\n    .themed{height:100%}\n    .grow{flex:1}\n    .tbtn{width:30px;height:30px;border:1px solid #eaeaea;border-radius:7px;background:#fff;\n      color:#666;cursor:pointer;font-size:13px;display:flex;align-items:center;\n      justify-content:center;flex:0 0 auto;font-family:inherit}\n    .tbtn:hover{border-color:#000;color:#000}\n    .page{height:100%;overflow:auto;background:#fafafa;color:#000;\n      font:400 14px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,sans-serif}\n    .top{display:flex;align-items:center;gap:14px;padding:0 24px;height:60px;\n      background:#fff;border-bottom:1px solid #eaeaea}\n    .back{background:none;border:0;color:#666;font-size:13px;cursor:pointer;padding:0}\n    .back:hover{color:#000}\n    .bt{margin:0;font-size:14px;font-weight:600;letter-spacing:-.01em}\n    .body{max-width:620px;margin:0 auto;padding:36px 24px 72px}\n    h2{margin:0 0 4px;font-size:26px;font-weight:600;letter-spacing:-.03em}\n    .sub{margin:0 0 30px;color:#666}\n    .card{background:#fff;border:1px solid #eaeaea;border-radius:10px;padding:22px;margin-bottom:18px}\n    .sec{margin:0 0 14px;font-size:12px;font-weight:600;text-transform:uppercase;\n      letter-spacing:.06em;color:#999}\n    label{display:block;font-size:13px;font-weight:500;margin:0 0 6px}\n    .field{width:100%;height:40px;padding:0 12px;border:1px solid #eaeaea;border-radius:6px;\n      background:#fff;color:#000;font-size:14px;outline:none;transition:border-color .15s,box-shadow .15s}\n    .field:focus{border-color:#000;box-shadow:0 0 0 3px rgba(0,0,0,.06)}\n    .field::placeholder{color:#999}\n    .decks{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px}\n    .deck{text-align:left;padding:12px 13px;border:1px solid #eaeaea;border-radius:8px;background:#fff;\n      color:#000;font-family:inherit;\n      cursor:pointer;transition:border-color .15s,box-shadow .15s}\n    .deck:hover{border-color:#c8c8c8}\n    .deck[data-sel]{border-color:#000;box-shadow:0 0 0 1px #000}\n    .dn{font-size:13.5px;font-weight:600;color:inherit;letter-spacing:-.01em}\n    .dv{margin-top:5px;font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;color:#888;\n      overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n    .tg{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;\n      padding:13px 0;border-bottom:1px solid #f2f2f2}\n    .tg:last-child{border-bottom:0;padding-bottom:0}\n    .tl{font-size:13.5px;font-weight:500}\n    .th{font-size:12.5px;color:#888;margin-top:1px}\n    .sw{position:relative;width:38px;height:22px;border-radius:999px;background:#e4e4e7;border:0;\n      cursor:pointer;flex:0 0 auto;transition:background .18s;margin-top:2px}\n    .sw:after{content:'';position:absolute;top:3px;left:3px;width:16px;height:16px;border-radius:50%;\n      background:#fff;transition:transform .18s;box-shadow:0 1px 2px rgba(0,0,0,.2)}\n    .sw[data-on]{background:#000}\n    .sw[data-on]:after{transform:translateX(16px)}\n    .secs{margin-top:12px;display:flex;align-items:center;gap:8px;font-size:13px;color:#666}\n    .secs input{width:78px;height:34px;text-align:center;border:1px solid #eaeaea;border-radius:6px;\n      background:#fff;color:#000;font-size:13px;outline:none}\n    .submit{width:100%;height:42px;border:0;border-radius:6px;background:#000;color:#fff;\n      font-size:14px;font-weight:500;cursor:pointer}\n    .submit:hover:not(:disabled){opacity:.85}\n    .submit:disabled{opacity:.45;cursor:not-allowed}\n    .err{margin:0 0 16px;padding:10px 12px;border:1px solid #f3c9c7;background:#fdf4f4;\n      color:#b3261e;border-radius:6px;font-size:13px}\n    [data-theme=\"dark\"]{\n      .page{background:#0a0a0a;color:#fff}\n      .tbtn{background:#111;border-color:#262626;color:#888}\n      .tbtn:hover{border-color:#fff;color:#fff}\n      .top{background:#111;border-bottom-color:#262626}\n      .back{color:#888}.back:hover{color:#fff}\n      .sub,.dv,.th,.secs{color:#888}\n      .card{background:#111;border-color:#262626}\n      .deck{background:#111;border-color:#262626;color:#fff}\n      .deck:hover{border-color:#3f3f3f}\n      .deck[data-sel]{border-color:#fff;box-shadow:0 0 0 1px #fff}\n      .field,.secs input{background:#000;border-color:#333;color:#fff}\n      .dv{color:#9a9a9a}\n      .field:focus{border-color:#fff;box-shadow:0 0 0 3px rgba(255,255,255,.1)}\n      .tg{border-bottom-color:#1f1f1f}\n      .sw{background:#333}.sw[data-on]{background:#fff}\n      .sw[data-on]:after{background:#000}\n      .submit{background:#fff;color:#000}\n      .err{border-color:#552220;background:#1a0f0f;color:#f87171}\n      .sec{color:#8a8a8a}\n    }\n  `,\n\n  render(d2) {\n    const on = (k, def) => (d2.state[k] === undefined ? def : !!d2.state[k])\n    const ds = d2.data('decks')\n    const decks = (ds.records() || []).slice().sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))\n    const sel = d2.state.deckId || 'fibonacci'\n    const busy = !!d2.state.busy\n\n    const theme = d2.state.theme || readTheme()\n    return `<div class=\"themed\" data-theme=\"${theme}\"><div class=\"page\">\n      <div class=\"top\"><button class=\"back\">← Minhas salas</button><p class=\"bt\">Nova sala</p>\n        <div class=\"grow\"></div>\n        <button class=\"tbtn\" title=\"${theme === 'dark' ? 'Mudar para claro' : 'Mudar para escuro'}\">${theme === 'dark' ? '☀' : '☽'}</button>\n      </div>\n      <div class=\"body\">\n        <h2>Nova sala</h2>\n        <p class=\"sub\">Configure a sessão. Você pode mudar tudo isso depois, durante o jogo.</p>\n        ${d2.state.error ? `<div class=\"err\">${esc(d2.state.error)}</div>` : ''}\n\n        <div class=\"card\">\n          <p class=\"sec\">Identificação</p>\n          <label>Nome da sala</label>\n          <input class=\"field\" id=\"ppk-roomname\" name=\"roomName\" data-k=\"name\" type=\"text\"\n            value=\"${esc(d2.state.f_name || '')}\" autocomplete=\"off\"\n            placeholder=\"Ex: Sprint 42 — Squad Pagamentos\" maxlength=\"80\"/>\n        </div>\n\n        <div class=\"card\">\n          <p class=\"sec\">Baralho</p>\n          <div class=\"decks\">${decks.map((dk) => `\n            <button class=\"deck\" data-deck=\"${esc(dk.deckId)}\" ${sel === dk.deckId ? 'data-sel' : ''} data-key=\"${esc(dk.deckId)}\">\n              <div class=\"dn\">${esc(dk.name)}</div>\n              <div class=\"dv\">${esc((dk.values || []).slice(0, 7).join('  '))}</div>\n            </button>`).join('')}\n          </div>\n        </div>\n\n        <div class=\"card\">\n          <p class=\"sec\">Regras</p>\n          ${TOGGLES.map((t) => `\n            <div class=\"tg\">\n              <div><div class=\"tl\">${esc(t.label)}</div>${t.hint ? `<div class=\"th\">${esc(t.hint)}</div>` : ''}</div>\n              <button class=\"sw\" data-tg=\"${esc(t.k)}\" ${on(t.k, t.def) ? 'data-on' : ''}></button>\n            </div>`).join('')}\n          ${on('timer', false) ? `<div class=\"secs\">Duração <input data-k=\"timerSeconds\" type=\"number\" min=\"10\" max=\"600\" value=\"${esc(d2.state.timerSeconds || 60)}\"/> segundos</div>` : ''}\n        </div>\n\n        <button class=\"submit\" ${busy ? 'disabled' : ''}>${busy ? 'Criando sala…' : 'Criar sala'}</button>\n      </div>\n    </div></div>`\n  },\n\n  mounted(d2) {\n    const nav = () => {\n      let base = {}\n      try { base = d2.router('ppk-router') || {} } catch (e) { base = {} }\n      if (!base.params) { try { base = d2.router() || {} } catch (e) { base = {} } }\n      return {\n        params: base.params || {},\n        path: base.path || '',\n        goTo: (path) => navTo(d2, path),\n      }\n    }\n    d2.state.theme = readTheme()\n    d2.on('.tbtn', 'click', () => {\n      const next = (d2.state.theme || readTheme()) === 'dark' ? 'light' : 'dark'\n      d2.state.theme = next\n      writeTheme(next)\n      d2.refresh()\n    })\n\n    d2.on('.back', 'click', () => nav().goTo('/salas'))\n    // Guarda o que foi digitado para o valor sobreviver ao re-render.\n    d2.on('.field', 'input', (e, el) => {\n      if (el.dataset && el.dataset.k) d2.state['f_' + el.dataset.k] = el.value\n    })\n    d2.on('.deck', 'click', (e, el) => { d2.state.deckId = el.dataset.deck; d2.refresh() })\n    d2.on('.sw', 'click', (e, el) => {\n      const k = el.dataset.tg\n      const t = TOGGLES.filter((x) => x.k === k)[0]\n      const cur = d2.state[k] === undefined ? (t ? t.def : false) : !!d2.state[k]\n      d2.state[k] = !cur\n      d2.refresh()\n    })\n    d2.on('.secs input', 'input', (e, el) => { d2.state.timerSeconds = Number(el.value) || 60 })\n\n    d2.on('.submit', 'click', async () => {\n      if (d2.state.busy) return\n      const nameEl = d2.$('[data-k=\"name\"]')\n      const fromDom = nameEl && nameEl.value ? String(nameEl.value).trim() : ''\n      const name = fromDom || (d2.state.f_name ? String(d2.state.f_name).trim() : '')\n      if (!name) { d2.state.error = 'Dê um nome para a sala.'; d2.refresh(); return }\n      const on = (k, def) => (d2.state[k] === undefined ? def : !!d2.state[k])\n      d2.state.error = ''\n      d2.state.busy = true\n      d2.refresh()\n      try {\n        const res = await d2.rush('ppk-create-room').invoke({\n          name: name,\n          deckId: d2.state.deckId || 'fibonacci',\n          disableAutoReveal: !on('autoReveal', true),\n          hideStats: !on('showStats', true),\n          facilitatorAbstains: !on('facilitatorVotes', true),\n          allowChangeAfterReveal: on('allowChange', false),\n          anonymousVotes: on('anonymous', false),\n          enableTimer: on('timer', false),\n          timerSeconds: Number(d2.state.timerSeconds) || 60,\n        })\n        const r = (res && res.data) ? res.data : res\n        if (!r || !r.ok) {\n          d2.state.error = (r && r.error === 'not_authenticated')\n            ? 'Sua sessão expirou. Entre novamente.'\n            : 'Não foi possível criar a sala.'\n          return\n        }\n        try { window.localStorage.setItem('ppk.token.' + r.code, r.token) } catch (err) { d2.log(err) }\n        nav().goTo('/sala/' + r.code)\n      } catch (e) {\n        d2.state.error = (e && e.message) ? e.message : 'Falha ao criar a sala.'\n      } finally {\n        d2.state.busy = false\n        d2.refresh()\n      }\n    })\n  },\n}"}},"decks":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"decks"}}}}}}}}}},{"_id":"6a66b3bf102e63106d6b371b","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"ppk-join-screen","package":"fernando-playground","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 THEME_KEY = 'ppk.theme'\n\nconst readTheme = () => {\n  try {\n    const t = window.localStorage.getItem(THEME_KEY)\n    if (t === 'dark' || t === 'light') return t\n  } catch (e) { /* segue */ }\n  try {\n    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark'\n  } catch (e) { /* segue */ }\n  return 'light'\n}\nconst writeTheme = (t) => {\n  try { window.localStorage.setItem(THEME_KEY, t) } catch (e) { /* segue */ }\n}\n\nconst esc = (s) => String(s == null ? '' : s)\n  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n\nconst ERRORS = {\n  code_required: 'Informe o código da sala.',\n  room_not_found: 'Não encontramos uma sala ativa com esse código.',\n  name_required: 'Informe seu nome.',\n  invalid_role: 'Escolha jogador ou espectador.',\n}\n\n// O goTo() do router é no-op silencioso se a referência não resolver.\nconst navTo = async (d2, path) => {\n  const here = () => { try { return window.location.pathname || '' } catch (e) { return '' } }\n  const before = here()\n  const attempts = [\n    () => { const r = d2.router('ppk-router'); if (r && r.goTo) r.goTo(path) },\n    () => { const r = d2.router(); if (r && r.goTo) r.goTo(path) },\n    () => {\n      const p = d2.process('ppk-routerInterface')\n      if (p && p.get) { const h = p.get('history'); if (h && h.push) h.push(path) }\n    },\n  ]\n  for (let i = 0; i < attempts.length; i++) {\n    try { attempts[i]() } catch (e) { d2.log('PPK-NAV t' + i + ' erro') }\n    await new Promise((r) => setTimeout(r, 250))\n    const now = here()\n    if (now !== before || now.indexOf(path) >= 0) { d2.log('PPK-NAV ok via t' + i); return true }\n  }\n  try { window.location.assign(path); return true } catch (e) { d2.log('PPK-NAV location falhou') }\n  return false\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%}\n    *{box-sizing:border-box}\n    .themed{height:100%;position:relative}\n    .tbtn{position:absolute;top:16px;right:16px;width:32px;height:32px;border-radius:8px;\n      border:1px solid #eaeaea;background:#fff;color:#666;cursor:pointer;font-size:14px;\n      display:flex;align-items:center;justify-content:center;font-family:inherit;z-index:5}\n    .tbtn:hover{border-color:#000;color:#000}\n    .wrap{height:100%;display:flex;align-items:center;justify-content:center;padding:24px;\n      background:#fff;color:#000;\n      font:400 14px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,sans-serif}\n    .card{width:100%;max-width:400px}\n    .brand{display:flex;align-items:center;gap:10px;margin-bottom:32px}\n    .logo{width:32px;height:32px;border-radius:8px;background:#000;color:#fff;display:flex;\n      align-items:center;justify-content:center;font-weight:700;font-size:13px}\n    .bt{margin:0;font-size:14px;font-weight:600;letter-spacing:-.01em;line-height:1.2}\n    .bs{color:#666;font-size:12px}\n    h2{margin:0 0 6px;font-size:26px;font-weight:600;letter-spacing:-.03em}\n    .sub{margin:0 0 26px;color:#666}\n    label{display:block;font-size:13px;font-weight:500;margin:0 0 6px}\n    .row{margin-bottom:16px}\n    .field{width:100%;height:40px;padding:0 12px;border:1px solid #eaeaea;border-radius:6px;\n      background:#fff;color:#000;font-size:14px;outline:none;transition:border-color .15s,box-shadow .15s}\n    .field::placeholder{color:#999}\n    .field:focus{border-color:#000;box-shadow:0 0 0 3px rgba(0,0,0,.06)}\n    .field.code{font:600 20px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.22em;\n      text-align:center;height:52px}\n    .roles{display:grid;grid-template-columns:1fr 1fr;gap:10px}\n    .role{text-align:left;padding:13px 14px;border:1px solid #eaeaea;border-radius:8px;background:#fff;\n      cursor:pointer;transition:border-color .15s,box-shadow .15s}\n    .role:hover{border-color:#c8c8c8}\n    .role[data-sel]{border-color:#000;box-shadow:0 0 0 1px #000}\n    .rn{font-size:13.5px;font-weight:500}\n    .rh{font-size:12px;color:#888;margin-top:2px}\n    .submit{width:100%;height:42px;border:0;border-radius:6px;background:#000;color:#fff;\n      font-size:14px;font-weight:500;cursor:pointer;margin-top:8px}\n    .submit:hover:not(:disabled){opacity:.85}\n    .submit:disabled{opacity:.45;cursor:not-allowed}\n    .err{margin:0 0 16px;padding:10px 12px;border:1px solid #f3c9c7;background:#fdf4f4;\n      color:#b3261e;border-radius:6px;font-size:13px}\n    .alt{margin:18px 0 0;text-align:center;color:#666;font-size:13px}\n    .link{color:#000;font-weight:500;cursor:pointer;text-decoration:underline;text-underline-offset:2px}\n    [data-theme=\"dark\"]{\n      .wrap{background:#000;color:#fff}\n      .logo{background:#fff;color:#000}\n      .tbtn{background:#111;border-color:#262626;color:#888}\n      .tbtn:hover{border-color:#fff;color:#fff}\n      .bs,.sub,.rh,.alt{color:#888}\n      .field,.role{background:#000;border-color:#333;color:#fff}\n      .field::placeholder{color:#666}\n      .field:focus{border-color:#fff;box-shadow:0 0 0 3px rgba(255,255,255,.1)}\n      .role:hover{border-color:#555}\n      .role[data-sel]{border-color:#fff;box-shadow:0 0 0 1px #fff}\n      .submit{background:#fff;color:#000}\n      .link{color:#fff}\n      .err{border-color:#552220;background:#1a0f0f;color:#f87171}\n    }\n  `,\n\n  render(d2) {\n    const role = d2.state.role || 'player'\n    const busy = !!d2.state.busy\n    let fromUrl = ''\n    try {\n      const q = String(window.location.search || '')\n      const m = q.match(/[?&]code=([^&#]+)/)\n      if (m) fromUrl = decodeURIComponent(m[1])\n    } catch (e) { fromUrl = '' }\n    const preCode = d2.state.f_code !== undefined ? d2.state.f_code\n      : (d2.state.code !== undefined ? d2.state.code : fromUrl)\n    const theme = d2.state.theme || readTheme()\n    return `<div class=\"themed\" data-theme=\"${theme}\">\n      <button class=\"tbtn\" title=\"${theme === 'dark' ? 'Mudar para claro' : 'Mudar para escuro'}\">${theme === 'dark' ? '☀' : '☽'}</button>\n      <div class=\"wrap\"><div class=\"card\">\n      <div class=\"brand\">\n        <div class=\"logo\">PP</div>\n        <div><p class=\"bt\">Poker Planning</p><span class=\"bs\">Estimativa em equipe</span></div>\n      </div>\n      <h2>Entrar na sala</h2>\n      <p class=\"sub\">Use o código que o facilitador compartilhou.</p>\n      ${d2.state.error ? `<div class=\"err\">${esc(d2.state.error)}</div>` : ''}\n      <div class=\"row\">\n        <label for=\"ppk-code\">Código da sala</label>\n        <input class=\"field code\" id=\"ppk-code\" name=\"room-code\" data-k=\"code\" type=\"text\"\n          inputmode=\"numeric\" pattern=\"[0-9]*\" maxlength=\"8\" autocomplete=\"off\"\n          autocapitalize=\"off\" autocorrect=\"off\" spellcheck=\"false\"\n          placeholder=\"000000\" value=\"${esc(preCode)}\"/>\n      </div>\n      <div class=\"row\">\n        <label for=\"ppk-jname\">Seu nome</label>\n        <input class=\"field\" id=\"ppk-jname\" name=\"nickname\" data-k=\"name\" type=\"text\"\n          value=\"${esc(d2.state.f_name || '')}\"\n          autocomplete=\"nickname\" autocapitalize=\"words\" spellcheck=\"false\"\n          placeholder=\"Como o time te chama\" maxlength=\"40\"/>\n      </div>\n      <div class=\"row\">\n        <label>Como você participa</label>\n        <div class=\"roles\">\n          <button class=\"role\" data-role=\"player\" ${role === 'player' ? 'data-sel' : ''}>\n            <div class=\"rn\">Jogador</div><div class=\"rh\">Vota nas histórias</div>\n          </button>\n          <button class=\"role\" data-role=\"spectator\" ${role === 'spectator' ? 'data-sel' : ''}>\n            <div class=\"rn\">Espectador</div><div class=\"rh\">Só acompanha</div>\n          </button>\n        </div>\n      </div>\n      <button class=\"submit\" ${busy ? 'disabled' : ''}>${busy ? 'Entrando…' : 'Entrar na sala'}</button>\n      <p class=\"alt\">É facilitador? <span class=\"link login\">Entrar com sua conta</span></p>\n    </div></div></div>`\n  },\n\n  mounted(d2) {\n    const nav = () => {\n      let base = {}\n      try { base = d2.router('ppk-router') || {} } catch (e) { base = {} }\n      if (!base.params) { try { base = d2.router() || {} } catch (e) { base = {} } }\n      return {\n        params: base.params || {},\n        path: base.path || '',\n        goTo: (path) => navTo(d2, path),\n      }\n    }\n    try {\n      const last = window.localStorage.getItem('ppk.name')\n      if (last) {\n        const el = d2.$('[data-k=\"name\"]')\n        if (el && !el.value) el.value = last\n      }\n    } catch (err) { d2.log(err) }\n\n    d2.state.theme = readTheme()\n    d2.on('.tbtn', 'click', () => {\n      const next = (d2.state.theme || readTheme()) === 'dark' ? 'light' : 'dark'\n      d2.state.theme = next\n      writeTheme(next)\n      d2.refresh()\n    })\n\n    // Guarda o que foi digitado para o valor sobreviver ao re-render.\n    d2.on('.field', 'input', (e, el) => {\n      if (el.dataset && el.dataset.k) d2.state['f_' + el.dataset.k] = el.value\n    })\n    d2.on('.role', 'click', (e, el) => { d2.state.role = el.dataset.role; d2.refresh() })\n    d2.on('.login', 'click', () => nav().goTo('/'))\n\n    const submit = async () => {\n      if (d2.state.busy) return\n      const codeEl = d2.$('[data-k=\"code\"]')\n      const nameEl = d2.$('[data-k=\"name\"]')\n      const code = (codeEl && codeEl.value ? String(codeEl.value).trim() : '')\n        || (d2.state.f_code ? String(d2.state.f_code).trim() : '')\n      const name = (nameEl && nameEl.value ? String(nameEl.value).trim() : '')\n        || (d2.state.f_name ? String(d2.state.f_name).trim() : '')\n      if (!code) { d2.state.error = 'Informe o código da sala.'; d2.refresh(); return }\n      if (!name) { d2.state.error = 'Informe seu nome.'; d2.refresh(); return }\n      d2.state.code = code\n      d2.state.error = ''\n      d2.state.busy = true\n      d2.refresh()\n      let known = ''\n      try { known = window.localStorage.getItem('ppk.token.' + code) || '' } catch (err) { known = '' }\n      try {\n        const res = await d2.rush('ppk-join-room').invoke({\n          code: code,\n          name: name,\n          role: d2.state.role || 'player',\n          token: known,\n        })\n        const r = (res && res.data) ? res.data : res\n        if (!r || !r.ok) {\n          d2.state.error = ERRORS[r && r.error] || 'Não foi possível entrar na sala.'\n          return\n        }\n        try {\n          window.localStorage.setItem('ppk.token.' + code, r.me.token)\n          window.localStorage.setItem('ppk.name', name)\n        } catch (err) { d2.log(err) }\n        nav().goTo('/sala/' + code)\n      } catch (e) {\n        d2.state.error = (e && e.message) ? e.message : 'Falha ao entrar na sala.'\n      } finally {\n        d2.state.busy = false\n        d2.refresh()\n      }\n    }\n    d2.on('.submit', 'click', submit)\n    d2.on(d2.root, 'keydown', (e) => { if (e.key === 'Enter') submit() })\n  },\n}"}}}}}}}},{"_id":"6a66b69753991cb86a773df8","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"ppk-table","package":"fernando-playground","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 THEME_KEY = 'ppk.theme'\n\nconst readTheme = () => {\n  try {\n    const t = window.localStorage.getItem(THEME_KEY)\n    if (t === 'dark' || t === 'light') return t\n  } catch (e) { /* segue */ }\n  try {\n    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark'\n  } catch (e) { /* segue */ }\n  return 'light'\n}\nconst writeTheme = (t) => {\n  try { window.localStorage.setItem(THEME_KEY, t) } catch (e) { /* segue */ }\n}\n\nconst esc = (s) => String(s == null ? '' : s)\n  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n\nconst initials = (n) => String(n || '?').trim().split(/\\s+/).slice(0, 2).map((w) => w.charAt(0).toUpperCase()).join('')\n// Confirmacao inline: o primeiro toque arma, o segundo executa.\n// Nao depende de d2.dialog.confirm, que na primeira chamada pode resolver\n// undefined (servico ainda nao montado) e fazer o clique virar no-op.\nconst armed = (d2, kind, id) => {\n  const c = d2.state.confirm\n  return !!(c && c.kind === kind && String(c.id) === String(id))\n}\n\nconst isOffline = (p, serverTime) => {\n  if (!p.lastSeenAt || !serverTime) return false\n  return (new Date(serverTime).getTime() - new Date(p.lastSeenAt).getTime()) > 45000\n}\n\n// Lê um parâmetro de rota sem depender do d2.router(), que pode não resolver\n// (\"Definition not found: Router / RouterInterface\" no console).\nconst routeParam = (d2, key, prefix) => {\n  // 1) processo cru do RouterInterface — é o que a navegação usa e resolve\n  try {\n    const p = d2.process('ppk-routerInterface')\n    if (p && p.get) {\n      const rp = p.get('routeParams')\n      if (rp && rp[key]) return String(rp[key])\n    }\n  } catch (e) { /* segue */ }\n  // 2) handle tipado, quando resolve\n  try {\n    const r = d2.router('ppk-router')\n    if (r && r.params && r.params[key]) return String(r.params[key])\n  } catch (e) { /* segue */ }\n  // 3) a própria URL\n  try {\n    const m = String(window.location.pathname || '').match(new RegExp('/' + prefix + '/([^/?#]+)'))\n    if (m) return decodeURIComponent(m[1])\n  } catch (e) { /* segue */ }\n  return ''\n}\n\n// O goTo() do router é no-op silencioso se a referência não resolver.\nconst navTo = async (d2, path) => {\n  const here = () => { try { return window.location.pathname || '' } catch (e) { return '' } }\n  const before = here()\n  const attempts = [\n    () => { const r = d2.router('ppk-router'); if (r && r.goTo) r.goTo(path) },\n    () => { const r = d2.router(); if (r && r.goTo) r.goTo(path) },\n    () => {\n      const p = d2.process('ppk-routerInterface')\n      if (p && p.get) { const h = p.get('history'); if (h && h.push) h.push(path) }\n    },\n  ]\n  for (let i = 0; i < attempts.length; i++) {\n    try { attempts[i]() } catch (e) { d2.log('PPK-NAV t' + i + ' erro') }\n    await new Promise((r) => setTimeout(r, 250))\n    const now = here()\n    if (now !== before || now.indexOf(path) >= 0) { d2.log('PPK-NAV ok via t' + i); return true }\n  }\n  try { window.location.assign(path); return true } catch (e) { d2.log('PPK-NAV location falhou') }\n  return false\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%}\n    *{box-sizing:border-box}\n    .themed{height:100%}\n    .tbtn{width:30px;height:30px;border:1px solid #eaeaea;border-radius:7px;background:#fff;\n      color:#666;cursor:pointer;font-size:13px;display:flex;align-items:center;\n      justify-content:center;flex:0 0 auto;font-family:inherit}\n    .tbtn:hover{border-color:#000;color:#000}\n    .app{height:100%;display:flex;flex-direction:column;background:#fafafa;color:#000;\n      font:400 14px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,sans-serif}\n    .bar{display:flex;align-items:center;gap:14px;padding:0 18px;height:56px;flex:0 0 auto;\n      background:#fff;border-bottom:1px solid #eaeaea}\n    .logo{width:26px;height:26px;border-radius:7px;background:#000;color:#fff;display:flex;\n      align-items:center;justify-content:center;font-weight:700;font-size:11px;flex:0 0 auto}\n    .rname{font-weight:600;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;\n      text-overflow:ellipsis;max-width:280px}\n    .chip{display:flex;align-items:center;gap:7px;height:30px;padding:0 10px;border:1px solid #eaeaea;\n      border-radius:7px;background:#fff;cursor:pointer;font:600 13px/1 ui-monospace,SFMono-Regular,Menlo,monospace;\n      letter-spacing:.08em;color:#000}\n    .chip:hover{border-color:#000}\n    .chip small{font-family:inherit;font-size:10px;opacity:.5;letter-spacing:0}\n    .grow{flex:1}\n    .cnt{font-size:13px;color:#666;white-space:nowrap}\n    .ico{width:30px;height:30px;border:1px solid #eaeaea;border-radius:7px;background:#fff;color:#000;\n      cursor:pointer;font-size:13px;display:flex;align-items:center;justify-content:center;flex:0 0 auto}\n    .ico:hover{border-color:#000}\n    .main{flex:1;display:flex;min-height:0}\n    .stage{flex:1;display:flex;flex-direction:column;min-width:0;overflow:auto}\n    .side{width:320px;flex:0 0 auto;border-left:1px solid #eaeaea;background:#fff;\n      display:flex;flex-direction:column;min-height:0}\n    .issue{padding:22px 26px 18px;border-bottom:1px solid #eaeaea;background:#fff}\n    .ikey{font:600 11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:#888;letter-spacing:.06em}\n    .ititle{margin:7px 0 0;font-size:19px;font-weight:600;letter-spacing:-.02em}\n    .idesc{margin:6px 0 0;color:#666;font-size:13.5px}\n    .rmeta{margin-top:10px;display:flex;align-items:center;gap:10px;font-size:12px;color:#888}\n    .pill{padding:3px 8px;border-radius:999px;background:#f4f4f5;font-weight:500}\n    .pill.live{background:#e8f5e9;color:#1b7e33}\n    .pill.rev{background:#eef2ff;color:#3538cd}\n    .none{padding:56px 26px;text-align:center;color:#888}\n    .table{flex:1;display:flex;align-items:center;justify-content:center;padding:26px;min-height:230px}\n    .seats{display:flex;flex-wrap:wrap;gap:18px;justify-content:center;max-width:760px}\n    .seat{width:96px;display:flex;flex-direction:column;align-items:center;gap:8px;position:relative}\n    .kick{position:absolute;top:-5px;right:6px;width:20px;height:20px;border-radius:50%;\n      border:1px solid #eaeaea;background:#fff;color:#999;font-size:10px;line-height:1;\n      cursor:pointer;opacity:0;transition:opacity .12s,border-color .12s,color .12s;padding:0;\n      display:flex;align-items:center;justify-content:center;font-family:inherit}\n    .seat:hover .kick{opacity:1}\n    .kick:hover{border-color:#b3261e;color:#b3261e}\n    .slot{width:66px;height:92px;perspective:600px}\n    .flip{position:relative;width:100%;height:100%;transform-style:preserve-3d;\n      transition:transform .5s cubic-bezier(.2,.8,.2,1)}\n    .slot[data-open] .flip{transform:rotateY(180deg)}\n    .face{position:absolute;inset:0;backface-visibility:hidden;border-radius:9px;\n      display:flex;align-items:center;justify-content:center;font-weight:700}\n    .back{background:#000;border:1px solid #000;color:#fff;font-size:11px;letter-spacing:.1em}\n    .back.wait{background:#fff;border:1px dashed #d4d4d8;color:#c4c4c8}\n    .front{transform:rotateY(180deg);background:#fff;border:1px solid #000;font-size:22px;color:#000}\n    .pname{font-size:12.5px;font-weight:500;max-width:96px;text-align:center;white-space:nowrap;\n      overflow:hidden;text-overflow:ellipsis}\n    .pname.off{color:#b4b4b8}\n    .prole{font-size:11px;color:#999}\n    .res{padding:0 26px 22px}\n    .rescard{background:#fff;border:1px solid #eaeaea;border-radius:12px;padding:18px 20px}\n    .stats{display:flex;flex-wrap:wrap;gap:26px;margin-bottom:16px}\n    .st{min-width:64px}\n    .sl{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#999;font-weight:600}\n    .sv{font-size:24px;font-weight:600;letter-spacing:-.02em;margin-top:2px}\n    .dist{display:flex;align-items:flex-end;gap:8px;height:56px;margin-bottom:16px}\n    .bar1{flex:0 0 34px;display:flex;flex-direction:column;align-items:center;gap:5px}\n    .fillbar{width:100%;background:#000;border-radius:4px 4px 0 0;min-height:3px}\n    .bl{font-size:11px;color:#666;font-weight:600}\n    .consensus{display:flex;align-items:center;gap:8px;padding:9px 12px;background:#e8f5e9;\n      color:#1b7e33;border-radius:8px;font-size:13px;font-weight:500;margin-bottom:14px}\n    .acts{display:flex;flex-wrap:wrap;gap:9px}\n    .btn{height:34px;padding:0 13px;border:1px solid #eaeaea;border-radius:7px;background:#fff;\n      color:#000;font-size:13px;font-weight:500;cursor:pointer}\n    .btn:hover{border-color:#000}\n    .btn.pri{background:#000;color:#fff;border-color:#000}\n    .btn.pri:hover{opacity:.85}\n    .btn:disabled{opacity:.45;cursor:not-allowed}\n    .hand{flex:0 0 auto;border-top:1px solid #eaeaea;background:#fff;padding:16px 26px}\n    .hl{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#999;font-weight:600;\n      margin-bottom:10px}\n    .cards{display:flex;flex-wrap:wrap;gap:9px}\n    .card{width:54px;height:76px;border:1px solid #eaeaea;border-radius:9px;background:#fff;color:#000;\n      font-size:17px;font-weight:700;cursor:pointer;transition:transform .13s,border-color .13s,box-shadow .13s}\n    .card:hover{transform:translateY(-5px);border-color:#000}\n    .card[data-sel]{background:#000;color:#fff;border-color:#000;transform:translateY(-7px);\n      box-shadow:0 5px 14px rgba(0,0,0,.22)}\n    .card:disabled{opacity:.4;cursor:not-allowed;transform:none}\n    .sh{padding:15px 18px;border-bottom:1px solid #eaeaea;display:flex;align-items:center;\n      justify-content:space-between;gap:10px}\n    .sh h3{margin:0;font-size:13px;font-weight:600}\n    .prog{font-size:12px;color:#888}\n    .ilist{flex:1;overflow:auto;padding:10px}\n    .irow{display:flex;align-items:flex-start;gap:10px;padding:10px 11px;border-radius:8px;\n      border:1px solid transparent}\n    .irow:hover{background:#fafafa}\n    .irow[data-cur]{background:#f4f4f5;border-color:#e4e4e7}\n    .ibody{min-width:0;flex:1;cursor:pointer}\n    .it{font-size:13px;font-weight:500;line-height:1.35}\n    .ik{font:11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:#999;margin-bottom:2px}\n    .est{flex:0 0 auto;min-width:26px;height:22px;padding:0 6px;border-radius:6px;background:#000;\n      color:#fff;font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center}\n    .est.pend{background:#f4f4f5;color:#a1a1aa}\n    .del{flex:0 0 auto;border:0;background:none;color:#c4c4c8;cursor:pointer;font-size:14px;padding:0 2px;\n      font-family:inherit}\n    .del:hover{color:#b3261e}\n    .del.armed{color:#b3261e;font-weight:700}\n    .kick.armed{opacity:1;border-color:#b3261e;color:#b3261e;font-weight:700}\n    .toast{position:absolute;left:50%;bottom:24px;transform:translateX(-50%);z-index:11;\n      padding:9px 14px;border-radius:999px;background:#000;color:#fff;font-size:13px;\n      font-weight:500;box-shadow:0 4px 16px rgba(0,0,0,.25)}\n    .addbox{padding:11px;border-top:1px solid #eaeaea;display:flex;gap:8px}\n    .addbox input{flex:1;height:34px;padding:0 10px;border:1px solid #eaeaea;border-radius:7px;\n      background:#fff;color:#000;font-size:13px;outline:none}\n    .addbox input:focus{border-color:#000}\n    .ov{position:absolute;inset:0;background:rgba(255,255,255,.94);display:flex;align-items:center;\n      justify-content:center;padding:24px;z-index:9}\n    .ovc{width:100%;max-width:360px}\n    .ovc h2{margin:0 0 6px;font-size:22px;font-weight:600;letter-spacing:-.02em}\n    .ovc p{margin:0 0 20px;color:#666;font-size:13.5px}\n    .fld{width:100%;height:40px;padding:0 12px;border:1px solid #eaeaea;border-radius:6px;background:#fff;\n      color:#000;font-size:14px;outline:none;margin-bottom:12px}\n    .fld:focus{border-color:#000}\n    .roles{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-bottom:14px}\n    .role{padding:11px 12px;border:1px solid #eaeaea;border-radius:8px;background:#fff;cursor:pointer;\n      text-align:left;font-size:13px;font-weight:500;color:#000}\n    .role[data-sel]{border-color:#000;box-shadow:0 0 0 1px #000}\n    .err{margin:0 0 14px;padding:9px 11px;border:1px solid #f3c9c7;background:#fdf4f4;color:#b3261e;\n      border-radius:6px;font-size:13px}\n    .conf{position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:8}\n    .imp{position:absolute;inset:0;background:rgba(255,255,255,.95);display:flex;align-items:center;\n      justify-content:center;padding:24px;z-index:10}\n    .impc{width:100%;max-width:540px}\n    .impc h3{margin:0 0 6px;font-size:19px;font-weight:600;letter-spacing:-.02em}\n    .impc p{margin:0 0 14px;color:#666;font-size:13px}\n    .ta{width:100%;height:190px;padding:11px 12px;border:1px solid #eaeaea;border-radius:8px;\n      background:#fff;color:#000;font:12.5px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace;\n      outline:none;resize:vertical;margin-bottom:12px}\n    .ta:focus{border-color:#000}\n    .timer{padding:3px 9px;border-radius:999px;background:#f4f4f5;font-weight:600}\n    .timer.warn{background:#fff4e5;color:#b45309}\n    .timer.over{background:#fdecea;color:#b3261e}\n    .link{background:none;border:0;color:#666;font-size:12px;cursor:pointer;padding:0;text-decoration:underline;text-underline-offset:2px}\n    .link:hover{color:#000}\n    .cf{position:absolute;top:-14px;width:8px;height:14px;border-radius:2px;animation:fall 2.4s linear forwards}\n    @keyframes fall{to{transform:translateY(105vh) rotate(620deg);opacity:.1}}\n    @media (max-width:900px){\n      .main{flex-direction:column}\n      .side{width:auto;border-left:0;border-top:1px solid #eaeaea;max-height:300px}\n      .rname{max-width:140px}\n    }\n    [data-theme=\"dark\"]{\n      .app{background:#0a0a0a;color:#fff}\n      .tbtn{background:#111;border-color:#262626;color:#888}\n      .tbtn:hover{border-color:#fff;color:#fff}\n      .bar,.issue,.side,.hand,.rescard{background:#111;border-color:#262626}\n      .bar{border-bottom-color:#262626}\n      .logo{background:#fff;color:#000}\n      .chip,.ico,.btn,.addbox input,.fld,.role{background:#111;border-color:#262626;color:#fff}\n      .chip:hover,.ico:hover,.btn:hover,.addbox input:focus,.fld:focus{border-color:#fff}\n      .btn.pri{background:#fff;color:#000;border-color:#fff}\n      .cnt,.idesc,.rmeta,.prog,.none,.ovc p{color:#888}\n      .pill{background:#1c1c1c;color:#aaa}\n      .pill.live{background:#10291a;color:#4ade80}\n      .pill.rev{background:#161a33;color:#93a4ff}\n      .back{background:#fff;border-color:#fff;color:#000}\n      .back.wait{background:#111;border-color:#333;color:#444}\n      .front{background:#111;border-color:#fff;color:#fff}\n      .card{background:#111;border-color:#262626;color:#fff}\n      .card:hover{border-color:#fff}\n      .card[data-sel]{background:#fff;color:#000;border-color:#fff}\n      .sh,.ilist,.addbox{border-color:#262626}\n      .irow:hover{background:#171717}\n      .irow[data-cur]{background:#1a1a1a;border-color:#2e2e2e}\n      .est{background:#fff;color:#000}\n      .est.pend{background:#1c1c1c;color:#666}\n      .fillbar{background:#fff}\n      .consensus{background:#10291a;color:#4ade80}\n      .ov{background:rgba(10,10,10,.95)}\n      .role[data-sel]{border-color:#fff;box-shadow:0 0 0 1px #fff}\n      .err{border-color:#552220;background:#1a0f0f;color:#f87171}\n      .pname.off{color:#4a4a4a}\n      .kick{background:#111;border-color:#333;color:#888}\n      .kick:hover{border-color:#f87171;color:#f87171}\n      .kick.armed{border-color:#f87171;color:#f87171}\n      .del.armed{color:#f87171}\n      .toast{background:#fff;color:#000}\n      .imp{background:rgba(10,10,10,.96)}\n      .ta{background:#000;border-color:#333;color:#fff}\n      .ta:focus{border-color:#fff}\n      .timer{background:#1c1c1c;color:#aaa}\n      .timer.warn{background:#2a1c07;color:#fbbf24}\n      .timer.over{background:#2a0f0f;color:#f87171}\n      .link{color:#888}.link:hover{color:#fff}\n    }\n  `,\n\n  render(d2) {\n    const snap = d2.state.snap\n\n    const theme = d2.state.theme || readTheme()\n    if (d2.state.fatal) {\n      return `<div class=\"themed\" data-theme=\"${theme}\"><div class=\"app\"><div class=\"none\"><p>${esc(d2.state.fatal)}</p>\n        <button class=\"btn pri gohome\">Voltar ao início</button></div></div></div>`\n    }\n    if (!snap) {\n      return `<div class=\"themed\" data-theme=\"${theme}\"><div class=\"app\"><div class=\"none\">Carregando a sala…</div></div></div>`\n    }\n\n    const room = snap.room || {}\n    const st = room.settings || {}\n    const me = snap.me || {}\n    const round = snap.round || {}\n    const isFac = me.role === 'facilitator'\n    const canVote = me.role === 'player' || (isFac && st.facilitatorVotes === true)\n    const revealed = round.status === 'revealed'\n    const voting = round.status === 'voting'\n    const parts = snap.participants || []\n    const issues = (snap.issues || []).slice().sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))\n    const cur = issues.filter((i) => i.id === room.currentIssueId)[0]\n    const shown = {}\n    ;(snap.revealedCards || []).forEach((c) => { shown[c.participantId] = c.value })\n    const stats = round.stats || null\n    const anon = st.anonymousVotes === true\n    const voters = parts.filter((p) => p.role !== 'spectator').length\n\n    let timerHtml = ''\n    if (st.timerEnabled === true && voting && round.startedAt) {\n      const total = Number(st.timerSeconds) || 60\n      const left = Math.ceil((new Date(round.startedAt).getTime() + total * 1000 - Date.now()) / 1000)\n      const secs = left > 0 ? left : 0\n      const cls = secs === 0 ? 'over' : (secs <= 10 ? 'warn' : '')\n      timerHtml = `<span class=\"timer ${cls}\">${secs === 0 ? 'tempo esgotado' : secs + 's'}</span>`\n    }\n\n    const joinOverlay = me.known === false ? `<div class=\"ov\"><div class=\"ovc\">\n      <h2>Entrar na sala</h2>\n      <p>Sala <strong>${esc(room.name || '')}</strong> · código ${esc(room.code || '')}</p>\n      ${d2.state.joinError ? `<div class=\"err\">${esc(d2.state.joinError)}</div>` : ''}\n      <input class=\"fld\" data-k=\"jname\" type=\"text\" value=\"${esc(d2.state.f_jname || '')}\"\n        autocomplete=\"nickname\" placeholder=\"Seu nome\" maxlength=\"40\"/>\n      <div class=\"roles\">\n        <button class=\"role\" data-jrole=\"player\" ${(d2.state.jrole || 'player') === 'player' ? 'data-sel' : ''}>Jogador</button>\n        <button class=\"role\" data-jrole=\"spectator\" ${d2.state.jrole === 'spectator' ? 'data-sel' : ''}>Espectador</button>\n      </div>\n      <button class=\"btn pri dojoin\" style=\"width:100%;height:40px\">${d2.state.busy ? 'Entrando…' : 'Entrar'}</button>\n    </div></div>` : ''\n\n    const seats = parts.map((p) => {\n      const off = isOffline(p, snap.serverTime)\n      const val = shown[p.id]\n      const spec = p.role === 'spectator'\n      const slot = spec\n        ? `<div class=\"slot\"><div class=\"flip\"><div class=\"face back wait\">👁</div></div></div>`\n        : `<div class=\"slot\" ${revealed && val !== undefined ? 'data-open' : ''}>\n             <div class=\"flip\">\n               <div class=\"face ${p.voted ? 'back' : 'back wait'}\">${p.voted ? 'PP' : ''}</div>\n               <div class=\"face front\">${esc(val === undefined ? '' : val)}</div>\n             </div>\n           </div>`\n      const isMe = String(p.id) === String(me.id || '')\n      return `<div class=\"seat\" data-key=\"${esc(p.id)}\">\n        ${slot}\n        <div class=\"pname ${off ? 'off' : ''}\">${anon && !revealed ? esc(initials(p.name)) : esc(p.name)}</div>\n        ${spec ? `<div class=\"prole\">espectador</div>` : ''}\n        ${isMe ? '' : `<button class=\"kick ${armed(d2, 'kick', p.id) ? 'armed' : ''}\" data-kick=\"${esc(p.id)}\" data-name=\"${esc(p.name)}\" title=\"${armed(d2, 'kick', p.id) ? 'Clique de novo para remover' : 'Remover da sala'}\">${armed(d2, 'kick', p.id) ? '✓' : '✕'}</button>`}\n      </div>`\n    }).join('')\n\n    const hasDist = stats && stats.distribution && stats.distribution.length > 0\n    const distMax = hasDist ? Math.max.apply(null, stats.distribution.map((d) => d.n)) : 1\n    const dist = hasDist\n      ? stats.distribution.map((d) => `<div class=\"bar1\" data-key=\"${esc(d.value)}\">\n          <div class=\"fillbar\" style=\"height:${Math.round((d.n / distMax) * 40)}px\"></div>\n          <div class=\"bl\">${esc(d.value)}</div></div>`).join('')\n      : ''\n\n    const showStats = st.showStats !== false && stats\n    const avg = stats && stats.average !== null && stats.average !== undefined\n      ? Math.round(stats.average * 100) / 100 : null\n\n    const resPanel = revealed ? `<div class=\"res\"><div class=\"rescard\">\n      ${stats && stats.consensus ? `<div class=\"consensus\">🎉 Consenso — o time votou igual</div>` : ''}\n      ${showStats ? `<div class=\"stats\">\n        <div class=\"st\"><div class=\"sl\">Média</div><div class=\"sv\">${avg === null ? '—' : esc(avg)}</div></div>\n        <div class=\"st\"><div class=\"sl\">Mediana</div><div class=\"sv\">${stats.median === null || stats.median === undefined ? '—' : esc(stats.median)}</div></div>\n        <div class=\"st\"><div class=\"sl\">Moda</div><div class=\"sv\">${esc(stats.mode || '—')}</div></div>\n        <div class=\"st\"><div class=\"sl\">Faixa</div><div class=\"sv\">${stats.min === null || stats.min === undefined ? '—' : esc(stats.min + '–' + stats.max)}</div></div>\n        <div class=\"st\"><div class=\"sl\">Votos</div><div class=\"sv\">${esc(stats.votes || 0)}</div></div>\n      </div>` : ''}\n      ${dist ? `<div class=\"dist\">${dist}</div>` : ''}\n      ${isFac ? `<div class=\"acts\">\n        ${stats && stats.mode ? `<button class=\"btn pri accept\" data-v=\"${esc(stats.mode)}\">Aceitar ${esc(stats.mode)}</button>` : ''}\n        <button class=\"btn revote\">Revotar</button>\n        <button class=\"btn next\">Próxima história</button>\n      </div>` : ''}\n    </div></div>` : ''\n\n    const facBar = isFac && cur && voting ? `<div class=\"acts\" style=\"margin-top:14px\">\n      <button class=\"btn pri reveal\" ${d2.state.busy ? 'disabled' : ''}>Revelar votos</button>\n      <button class=\"btn nudge\">Cutucar quem falta</button>\n    </div>` : ''\n\n    const stage = cur\n      ? `<div class=\"issue\">\n          ${cur.key ? `<div class=\"ikey\">${esc(cur.key)}</div>` : ''}\n          <p class=\"ititle\">${esc(cur.title)}</p>\n          ${cur.description ? `<p class=\"idesc\">${esc(cur.description)}</p>` : ''}\n          <div class=\"rmeta\">\n            <span class=\"pill ${revealed ? 'rev' : 'live'}\">${revealed ? 'Revelado' : 'Votando'}</span>\n            <span>Rodada ${esc(round.index || 1)}</span>\n            <span>${esc(snap.votedCount || 0)} de ${esc(voters)} votaram</span>\n            ${timerHtml}\n          </div>\n          ${facBar}\n        </div>\n        <div class=\"table\"><div class=\"seats\">${seats}</div></div>\n        ${resPanel}`\n      : `<div class=\"none\">\n          <p>Nenhuma história em pauta.</p>\n          ${isFac ? `<p style=\"font-size:13px\">Escolha uma história no backlog ao lado para abrir a votação.</p>` : ''}\n        </div>`\n\n    const canPlay = voting || (revealed && st.allowChangeAfterReveal === true)\n    const hand = cur && canVote ? `<div class=\"hand\">\n      <div class=\"hl\">Sua mão${me.vote ? ' · você votou ' + esc(me.vote) : ''}</div>\n      <div class=\"cards\">${(snap.cards || []).map((c) => `\n        <button class=\"card\" data-v=\"${esc(c)}\" ${me.vote === c ? 'data-sel' : ''}\n          ${(!canPlay || d2.state.busy) ? 'disabled' : ''}\n          data-key=\"${esc(c)}\">${esc(c)}</button>`).join('')}\n      </div>\n    </div>` : ''\n\n    const prog = snap.progress || {}\n    const rows = issues.map((i) => `\n      <div class=\"irow\" ${i.id === room.currentIssueId ? 'data-cur' : ''} data-key=\"${esc(i.id)}\">\n        <div class=\"ibody\" data-pick=\"${esc(i.id)}\">\n          ${i.key ? `<div class=\"ik\">${esc(i.key)}</div>` : ''}\n          <div class=\"it\">${esc(i.title)}</div>\n        </div>\n        <div class=\"est ${i.status === 'estimated' ? '' : 'pend'}\">${esc(i.finalEstimate || '–')}</div>\n        ${isFac ? `<button class=\"del ${armed(d2, 'del', i.id) ? 'armed' : ''}\" data-del=\"${esc(i.id)}\" title=\"${armed(d2, 'del', i.id) ? 'Clique de novo para remover' : 'Remover'}\">${armed(d2, 'del', i.id) ? '✓' : '✕'}</button>` : ''}\n      </div>`).join('')\n\n    const side = `<div class=\"side\">\n      <div class=\"sh\">\n        <h3>Backlog</h3>\n        <span class=\"prog\">${esc(prog.estimated || 0)}/${esc(prog.total || 0)} · ${esc(prog.points || 0)} pts\n          ${isFac ? `<br/><button class=\"link impopen\">importar CSV</button>` : ''}</span>\n      </div>\n      <div class=\"ilist\">${rows || `<div class=\"none\" style=\"padding:26px 12px;font-size:13px\">Backlog vazio.</div>`}</div>\n      ${isFac ? `<div class=\"addbox\">\n        <input data-k=\"newissue\" type=\"text\" placeholder=\"Nova história…\" maxlength=\"120\"/>\n        <button class=\"btn additem\">Adicionar</button>\n      </div>` : ''}\n    </div>`\n\n    const impOverlay = d2.state.imp ? `<div class=\"imp\"><div class=\"impc\">\n      <h3>Importar backlog de CSV</h3>\n      <p>Uma história por linha, na ordem <strong>chave, título, descrição, link</strong>.\n      A primeira linha é tratada como cabeçalho.</p>\n      ${d2.state.impError ? `<div class=\"err\">${esc(d2.state.impError)}</div>` : ''}\n      <textarea class=\"ta\" data-k=\"csv\" spellcheck=\"false\" placeholder=\"key,title,description,url&#10;PAY-201,Split de pagamento,Dividir entre vendedores,\">${esc(d2.state.f_csv || '')}</textarea>\n      <div class=\"acts\">\n        <button class=\"btn pri impgo\" ${d2.state.impBusy ? 'disabled' : ''}>${d2.state.impBusy ? 'Importando…' : 'Importar'}</button>\n        <button class=\"btn impcancel\">Cancelar</button>\n      </div>\n    </div></div>` : ''\n\n    const COLORS = ['#000', '#666', '#1b7e33', '#3538cd', '#b3261e']\n    let confetti = ''\n    if (d2.state.party) {\n      let pieces = ''\n      for (let i = 0; i < 40; i++) {\n        pieces += `<div class=\"cf\" style=\"left:${(i * 2.5) % 100}%;background:${COLORS[i % 5]};animation-delay:${(i % 10) * 0.09}s\"></div>`\n      }\n      confetti = `<div class=\"conf\">${pieces}</div>`\n    }\n\n    return `<div class=\"themed\" data-theme=\"${theme}\"><div class=\"app\" style=\"position:relative\">\n      <div class=\"bar\">\n        <div class=\"logo\">PP</div>\n        <div class=\"rname\">${esc(room.name || '')}</div>\n        <button class=\"chip copy\"><small>código</small>${esc(room.code || '')}</button>\n        <div class=\"grow\"></div>\n        <div class=\"cnt\">${esc(parts.length)} na sala</div>\n        <button class=\"tbtn\" title=\"${theme === 'dark' ? 'Mudar para claro' : 'Mudar para escuro'}\">${theme === 'dark' ? '☀' : '☽'}</button>\n        <button class=\"btn sum\">Resumo</button>\n        <button class=\"ico leave\" title=\"Sair da sala\">⏏</button>\n      </div>\n      <div class=\"main\">\n        <div class=\"stage\">${stage}${hand}</div>\n        ${side}\n      </div>\n      ${joinOverlay}${confetti}${impOverlay}\n      ${d2.state.toast ? `<div class=\"toast\">${esc(d2.state.toast)}</div>` : ''}\n    </div></div>`\n  },\n\n  mounted(d2) {\n    const nav = () => {\n      let base = {}\n      try { base = d2.router('ppk-router') || {} } catch (e) { base = {} }\n      if (!base.params) { try { base = d2.router() || {} } catch (e) { base = {} } }\n      return {\n        params: base.params || {},\n        path: base.path || '',\n        goTo: (path) => navTo(d2, path),\n      }\n    }\n    const code = routeParam(d2, 'code', 'sala')\n    d2.log('PPK-TABLE code=' + code)\n    const tokKey = 'ppk.token.' + code\n    const readTok = () => { try { return window.localStorage.getItem(tokKey) || '' } catch (e) { return '' } }\n\n    const reload = async () => {\n      try {\n        const res = await d2.rush('ppk-snapshot').invoke({ code: code, token: readTok() })\n        const r = (res && res.data) ? res.data : res\n        if (!r || !r.ok) { d2.state.fatal = 'Sala não encontrada ou encerrada.'; return }\n        const prev = d2.state.snap\n        const wasRevealed = prev && prev.round && prev.round.status === 'revealed'\n        d2.state.snap = r\n        if (!wasRevealed && r.round && r.round.status === 'revealed' && r.round.stats && r.round.stats.consensus) {\n          d2.state.party = true\n          setTimeout(() => { d2.state.party = false; d2.refresh() }, 2600)\n        }\n        d2.refresh()\n      } catch (e) {\n        d2.log(e)\n      }\n    }\n\n    const act = async (ref, payload) => {\n      if (d2.state.busy) return\n      d2.state.busy = true\n      try {\n        await d2.rush(ref).invoke(payload)\n      } catch (e) {\n        d2.log(e)\n      } finally {\n        d2.state.busy = false\n        d2.refresh()\n        await reload()\n      }\n    }\n\n    reload()\n\n    try {\n      const sock = d2.socket('ppk-socket')\n      sock.subscribe('room:' + code)\n      sock.onMessage(() => { reload() })\n    } catch (e) { d2.log(e) }\n\n    const hb = setInterval(async () => {\n      const snap = d2.state.snap\n      if (!snap || !snap.room) return\n      try {\n        await d2.rush('ppk-heartbeat').invoke({ roomId: snap.room.id, token: readTok() })\n      } catch (e) { d2.log(e) }\n      // Rede de seguranca: converge presenca (offline) e qualquer evento\n      // de socket que tenha se perdido.\n      await reload()\n    }, 20000)\n    d2.onUnmount(() => clearInterval(hb))\n\n    const tick = setInterval(() => {\n      const s = d2.state.snap\n      if (!s || !s.room || !s.room.settings) return\n      if (s.room.settings.timerEnabled !== true) return\n      if (!s.round || s.round.status !== 'voting') return\n      d2.refresh()\n    }, 1000)\n    d2.onUnmount(() => clearInterval(tick))\n\n    d2.on('.sum', 'click', () => nav().goTo('/resumo/' + code))\n    d2.on('.impopen', 'click', () => { d2.state.imp = true; d2.state.impError = ''; d2.refresh() })\n    d2.on('.impcancel', 'click', () => { d2.state.imp = false; d2.refresh() })\n    d2.on('.impgo', 'click', async () => {\n      const snap = d2.state.snap\n      const ta = d2.$('[data-k=\"csv\"]')\n      const text = (ta && ta.value ? String(ta.value).trim() : '')\n        || (d2.state.f_csv ? String(d2.state.f_csv).trim() : '')\n      if (!snap || !text) { d2.state.impError = 'Cole o conteúdo do CSV.'; d2.refresh(); return }\n      d2.state.impBusy = true\n      d2.state.impError = ''\n      d2.refresh()\n      try {\n        const res = await d2.rush('ppk-import-csv').invoke({ roomId: snap.room.id, csv: text })\n        const r = (res && res.data) ? res.data : res\n        if (!r || !r.ok) { d2.state.impError = 'Nenhuma linha válida encontrada no CSV.' } else { d2.state.imp = false }\n      } catch (e) {\n        d2.log(e)\n        d2.state.impError = 'Falha ao importar o CSV.'\n      } finally {\n        d2.state.impBusy = false\n        await reload()\n      }\n    })\n\n    d2.state.theme = readTheme()\n    d2.on('.tbtn', 'click', () => {\n      const next = (d2.state.theme || readTheme()) === 'dark' ? 'light' : 'dark'\n      d2.state.theme = next\n      writeTheme(next)\n      d2.refresh()\n    })\n\n    d2.on('.gohome', 'click', () => nav().goTo('/'))\n    // Remover alguem da sala: qualquer participante pode, o servidor confere\n    // que quem pede tem token valido naquela sala.\n    d2.on('.kick', 'click', (e, el) => {\n      const snap = d2.state.snap\n      const id = el.dataset.kick\n      if (!snap || !snap.room || !id) return\n      if (armed(d2, 'kick', id)) {\n        d2.state.confirm = null\n        act('ppk-kick', { roomId: snap.room.id, token: readTok(), participantId: id })\n        return\n      }\n      arm('kick', id)\n    })\n\n    // Sair de verdade: remove a si mesmo e esquece o token local,\n    // senao reabrir o link reconectaria como o mesmo participante.\n    d2.on('.leave', 'click', async () => {\n      const snap = d2.state.snap\n      if (snap && snap.room && snap.me && snap.me.id) {\n        if (!armed(d2, 'leave', 'self')) { arm('leave', 'self'); toast('Clique de novo para sair da sala'); return }\n        d2.state.confirm = null\n        try {\n          await d2.rush('ppk-kick').invoke({\n            roomId: snap.room.id, token: readTok(), participantId: snap.me.id,\n          })\n        } catch (e) { d2.log(e) }\n        try { window.localStorage.removeItem(tokKey) } catch (e) { d2.log(e) }\n      }\n      nav().goTo('/')\n    })\n\n    const toast = (msg) => {\n      d2.state.toast = msg\n      d2.refresh()\n      setTimeout(() => { d2.state.toast = null; d2.refresh() }, 2200)\n    }\n\n    const arm = (kind, id) => {\n      d2.state.confirm = { kind: kind, id: id }\n      d2.refresh()\n      setTimeout(() => {\n        if (armed(d2, kind, id)) { d2.state.confirm = null; d2.refresh() }\n      }, 4000)\n    }\n\n    d2.on('.copy', 'click', async () => {\n      let ok = false\n      try { await navigator.clipboard.writeText(window.location.href); ok = true } catch (e) { d2.log(e) }\n      toast(ok ? 'Link da sala copiado' : 'Não foi possível copiar o link')\n    })\n\n    // Guarda o que foi digitado para o valor sobreviver ao re-render.\n    d2.on('.fld', 'input', (e, el) => {\n      if (el.dataset && el.dataset.k) d2.state['f_' + el.dataset.k] = el.value\n    })\n    d2.on('.ta', 'input', (e, el) => {\n      if (el.dataset && el.dataset.k) d2.state['f_' + el.dataset.k] = el.value\n    })\n    d2.on('.role', 'click', (e, el) => { d2.state.jrole = el.dataset.jrole; d2.refresh() })\n    d2.on('.dojoin', 'click', async () => {\n      const el = d2.$('[data-k=\"jname\"]')\n      const name = (el && el.value ? String(el.value).trim() : '')\n        || (d2.state.f_jname ? String(d2.state.f_jname).trim() : '')\n      if (!name) { d2.state.joinError = 'Informe seu nome.'; return }\n      d2.state.joinError = ''\n      d2.state.busy = true\n      try {\n        const res = await d2.rush('ppk-join-room').invoke({\n          code: code, name: name, role: d2.state.jrole || 'player', token: readTok(),\n        })\n        const r = (res && res.data) ? res.data : res\n        if (!r || !r.ok) { d2.state.joinError = 'Não foi possível entrar.'; return }\n        try {\n          window.localStorage.setItem(tokKey, r.me.token)\n          window.localStorage.setItem('ppk.name', name)\n        } catch (err) { d2.log(err) }\n        await reload()\n      } finally {\n        d2.state.busy = false\n      }\n    })\n\n    d2.on('.card', 'click', (e, el) => {\n      const snap = d2.state.snap\n      if (!snap || !snap.round) return\n      act('ppk-vote', { roundId: snap.round.id, token: readTok(), value: el.dataset.v })\n    })\n\n    d2.on('.reveal', 'click', () => {\n      const snap = d2.state.snap\n      if (snap && snap.round) act('ppk-reveal', { roundId: snap.round.id })\n    })\n    d2.on('.nudge', 'click', () => {\n      const snap = d2.state.snap\n      if (snap && snap.room && snap.round) act('ppk-nudge', { roomId: snap.room.id, roundId: snap.round.id })\n    })\n    d2.on('.revote', 'click', () => {\n      const snap = d2.state.snap\n      if (snap && snap.room) act('ppk-new-round', { roomId: snap.room.id })\n    })\n    d2.on('.accept', 'click', (e, el) => {\n      const snap = d2.state.snap\n      if (snap && snap.room) act('ppk-save-estimate', {\n        roomId: snap.room.id, issueId: snap.room.currentIssueId, value: el.dataset.v,\n      })\n    })\n    d2.on('.next', 'click', () => {\n      const snap = d2.state.snap\n      if (!snap) return\n      const sorted = (snap.issues || []).slice().sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))\n      const pend = sorted.filter((i) => i.status !== 'estimated' && i.id !== snap.room.currentIssueId)[0]\n      if (!pend) { toast('Todas as histórias já foram estimadas'); return }\n      act('ppk-set-issue', { roomId: snap.room.id, issueId: pend.id })\n    })\n    d2.on('.ibody', 'click', (e, el) => {\n      const snap = d2.state.snap\n      const id = el.dataset.pick\n      if (!snap || !id || snap.me.role !== 'facilitator') return\n      act('ppk-set-issue', { roomId: snap.room.id, issueId: id })\n    })\n    d2.on('.del', 'click', (e, el) => {\n      const id = el.dataset.del\n      if (!id) return\n      if (armed(d2, 'del', id)) {\n        d2.state.confirm = null\n        act('ppk-issues-crud', { action: 'remove', issueId: id })\n        return\n      }\n      arm('del', id)\n    })\n    d2.on('.additem', 'click', () => {\n      const snap = d2.state.snap\n      const el = d2.$('[data-k=\"newissue\"]')\n      const title = el && el.value ? el.value.trim() : ''\n      if (!snap || !title) return\n      el.value = ''\n      act('ppk-issues-crud', { action: 'add', roomId: snap.room.id, title: title })\n    })\n  },\n}"}}}}}}}},{"_id":"6a66bcd8dc1fded10bfdb41b","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"ppk-summary","package":"fernando-playground","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 THEME_KEY = 'ppk.theme'\n\nconst readTheme = () => {\n  try {\n    const t = window.localStorage.getItem(THEME_KEY)\n    if (t === 'dark' || t === 'light') return t\n  } catch (e) { /* segue */ }\n  try {\n    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark'\n  } catch (e) { /* segue */ }\n  return 'light'\n}\nconst writeTheme = (t) => {\n  try { window.localStorage.setItem(THEME_KEY, t) } catch (e) { /* segue */ }\n}\n\nconst esc = (s) => String(s == null ? '' : s)\n  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n\nconst num = (v) => {\n  if (v === null || v === undefined || v === '') return null\n  const n = Number(String(v).replace('½', '0.5'))\n  return isFinite(n) ? n : null\n}\n\n// Lê um parâmetro de rota sem depender do d2.router(), que pode não resolver.\nconst routeParam = (d2, key, prefix) => {\n  try {\n    const p = d2.process('ppk-routerInterface')\n    if (p && p.get) {\n      const rp = p.get('routeParams')\n      if (rp && rp[key]) return String(rp[key])\n    }\n  } catch (e) { /* segue */ }\n  try {\n    const r = d2.router('ppk-router')\n    if (r && r.params && r.params[key]) return String(r.params[key])\n  } catch (e) { /* segue */ }\n  try {\n    const m = String(window.location.pathname || '').match(new RegExp('/' + prefix + '/([^/?#]+)'))\n    if (m) return decodeURIComponent(m[1])\n  } catch (e) { /* segue */ }\n  return ''\n}\n\n// O goTo() do router é no-op silencioso se a referência não resolver.\nconst navTo = async (d2, path) => {\n  const here = () => { try { return window.location.pathname || '' } catch (e) { return '' } }\n  const before = here()\n  const attempts = [\n    () => { const r = d2.router('ppk-router'); if (r && r.goTo) r.goTo(path) },\n    () => { const r = d2.router(); if (r && r.goTo) r.goTo(path) },\n    () => {\n      const p = d2.process('ppk-routerInterface')\n      if (p && p.get) { const h = p.get('history'); if (h && h.push) h.push(path) }\n    },\n  ]\n  for (let i = 0; i < attempts.length; i++) {\n    try { attempts[i]() } catch (e) { d2.log('PPK-NAV t' + i + ' erro') }\n    await new Promise((r) => setTimeout(r, 250))\n    const now = here()\n    if (now !== before || now.indexOf(path) >= 0) { d2.log('PPK-NAV ok via t' + i); return true }\n  }\n  try { window.location.assign(path); return true } catch (e) { d2.log('PPK-NAV location falhou') }\n  return false\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%}\n    *{box-sizing:border-box}\n    .themed{height:100%}\n    .tbtn{width:30px;height:30px;border:1px solid #eaeaea;border-radius:7px;background:#fff;\n      color:#666;cursor:pointer;font-size:13px;display:flex;align-items:center;\n      justify-content:center;flex:0 0 auto;font-family:inherit}\n    .tbtn:hover{border-color:#000;color:#000}\n    .page{height:100%;overflow:auto;background:#fafafa;color:#000;\n      font:400 14px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,sans-serif}\n    .top{display:flex;align-items:center;gap:14px;padding:0 24px;height:60px;background:#fff;\n      border-bottom:1px solid #eaeaea;position:sticky;top:0;z-index:2}\n    .back{background:none;border:0;color:#666;font-size:13px;cursor:pointer;padding:0}\n    .back:hover{color:#000}\n    .logo{width:26px;height:26px;border-radius:7px;background:#000;color:#fff;display:flex;\n      align-items:center;justify-content:center;font-weight:700;font-size:11px}\n    .bt{margin:0;font-size:14px;font-weight:600;letter-spacing:-.01em}\n    .grow{flex:1}\n    .btn{height:34px;padding:0 13px;border:1px solid #eaeaea;border-radius:7px;background:#fff;\n      color:#000;font-size:13px;font-weight:500;cursor:pointer}\n    .btn:hover{border-color:#000}\n    .btn.pri{background:#000;color:#fff;border-color:#000}\n    .btn.pri:hover{opacity:.85}\n    .body{max-width:900px;margin:0 auto;padding:34px 24px 70px}\n    h2{margin:0 0 4px;font-size:26px;font-weight:600;letter-spacing:-.03em}\n    .sub{margin:0 0 28px;color:#666}\n    .kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-bottom:30px}\n    .kpi{background:#fff;border:1px solid #eaeaea;border-radius:10px;padding:16px 18px}\n    .kl{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#999;font-weight:600}\n    .kv{font-size:26px;font-weight:600;letter-spacing:-.02em;margin-top:3px}\n    .sec{margin:0 0 12px;font-size:12px;font-weight:600;text-transform:uppercase;\n      letter-spacing:.06em;color:#999}\n    .rows{display:flex;flex-direction:column;gap:9px}\n    .row{background:#fff;border:1px solid #eaeaea;border-radius:10px;overflow:hidden}\n    .rh{display:flex;align-items:center;gap:14px;padding:14px 17px;cursor:pointer}\n    .rh:hover{background:#fafafa}\n    .ik{font:11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:#999}\n    .it{font-size:14px;font-weight:500;line-height:1.35}\n    .meta{min-width:0;flex:1}\n    .badge{flex:0 0 auto;min-width:30px;height:26px;padding:0 8px;border-radius:7px;background:#000;\n      color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center}\n    .badge.pend{background:#f4f4f5;color:#a1a1aa}\n    .rc{flex:0 0 auto;font-size:12px;color:#888}\n    .caret{flex:0 0 auto;color:#c4c4c8;font-size:11px}\n    .det{border-top:1px solid #f2f2f2;padding:14px 17px;background:#fcfcfc}\n    .rnd{padding:10px 0;border-bottom:1px solid #f2f2f2}\n    .rnd:last-child{border-bottom:0}\n    .rtitle{font-size:12px;font-weight:600;color:#666;margin-bottom:7px}\n    .votes{display:flex;flex-wrap:wrap;gap:7px}\n    .v{display:flex;align-items:center;gap:6px;padding:4px 9px;border:1px solid #eaeaea;\n      border-radius:999px;background:#fff;font-size:12px}\n    .vv{font-weight:700}\n    .stat{margin-top:8px;font-size:12px;color:#888}\n    .none{padding:50px 20px;text-align:center;color:#888}\n    [data-theme=\"dark\"]{\n      .page{background:#0a0a0a;color:#fff}\n      .tbtn{background:#111;border-color:#262626;color:#888}\n      .tbtn:hover{border-color:#fff;color:#fff}\n      .top,.kpi,.row,.btn,.v{background:#111;border-color:#262626}\n      .top{border-bottom-color:#262626}\n      .logo{background:#fff;color:#000}\n      .back{color:#888}.back:hover{color:#fff}\n      .sub,.rc,.stat,.none,.rtitle{color:#888}\n      .btn:hover{border-color:#fff}\n      .btn.pri{background:#fff;color:#000;border-color:#fff}\n      .rh:hover{background:#171717}\n      .det{background:#0d0d0d;border-top-color:#1f1f1f}\n      .rnd{border-bottom-color:#1f1f1f}\n      .badge{background:#fff;color:#000}\n      .badge.pend{background:#1c1c1c;color:#666}\n      .sec,.ik{color:#666}\n    }\n  `,\n\n  render(d2) {\n    const snap = d2.state.snap\n    const hist = d2.state.hist\n\n    const theme = d2.state.theme || readTheme()\n    if (d2.state.fatal) {\n      return `<div class=\"themed\" data-theme=\"${theme}\"><div class=\"page\"><div class=\"none\"><p>${esc(d2.state.fatal)}</p></div></div></div>`\n    }\n    if (!snap || !hist) {\n      return `<div class=\"themed\" data-theme=\"${theme}\"><div class=\"page\"><div class=\"none\">Carregando o resumo…</div></div></div>`\n    }\n\n    const room = snap.room || {}\n    const names = {}\n    ;(hist.participants || []).forEach((p) => { names[p.id] = p.name })\n    const rounds = hist.rounds || []\n    const issues = (hist.issues || []).slice().sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))\n    const estimated = issues.filter((i) => i.status === 'estimated')\n    const points = estimated.reduce((acc, i) => {\n      const n = num(i.finalEstimate)\n      return acc + (n === null ? 0 : n)\n    }, 0)\n    const consensusRounds = rounds.filter((r) => r.stats && r.stats.consensus === true).length\n    const consensusRate = rounds.length ? Math.round((consensusRounds / rounds.length) * 100) : 0\n    const open = d2.state.open || {}\n\n    const roundBlock = (r) => {\n      const vs = (r.votes || []).map((v) => `<div class=\"v\"><span>${esc(names[v.participantId] || '—')}</span><span class=\"vv\">${esc(v.value)}</span></div>`).join('')\n      const s = r.stats || {}\n      const bits = []\n      if (s.average !== null && s.average !== undefined) bits.push('média ' + (Math.round(s.average * 100) / 100))\n      if (s.median !== null && s.median !== undefined) bits.push('mediana ' + s.median)\n      if (s.mode) bits.push('moda ' + s.mode)\n      if (s.consensus === true) bits.push('consenso')\n      return `<div class=\"rnd\">\n        <div class=\"rtitle\">Rodada ${esc(r.index || 1)}${r.revealedAt ? ' · ' + esc(d2.fmt.time(r.revealedAt)) : ''}</div>\n        <div class=\"votes\">${vs || '<span class=\"stat\">Sem votos registrados.</span>'}</div>\n        ${bits.length ? `<div class=\"stat\">${esc(bits.join(' · '))}</div>` : ''}\n      </div>`\n    }\n\n    const rows = issues.map((i) => {\n      const mine = rounds.filter((r) => r.issueId === i.id).slice().sort((a, b) => (a.index || 0) - (b.index || 0))\n      const isOpen = !!open[i.id]\n      return `<div class=\"row\" data-key=\"${esc(i.id)}\">\n        <div class=\"rh\" data-toggle=\"${esc(i.id)}\">\n          <div class=\"meta\">\n            ${i.key ? `<div class=\"ik\">${esc(i.key)}</div>` : ''}\n            <div class=\"it\">${esc(i.title)}</div>\n          </div>\n          <div class=\"rc\">${esc(mine.length)} ${mine.length === 1 ? 'rodada' : 'rodadas'}</div>\n          <div class=\"badge ${i.status === 'estimated' ? '' : 'pend'}\">${esc(i.finalEstimate || '–')}</div>\n          <div class=\"caret\">${isOpen ? '▲' : '▼'}</div>\n        </div>\n        ${isOpen ? `<div class=\"det\">${mine.length ? mine.map(roundBlock).join('') : '<span class=\"stat\">Nenhuma rodada concluída.</span>'}</div>` : ''}\n      </div>`\n    }).join('')\n\n    return `<div class=\"themed\" data-theme=\"${theme}\"><div class=\"page\">\n      <div class=\"top\">\n        <button class=\"back\">← Voltar à mesa</button>\n        <div class=\"logo\">PP</div>\n        <p class=\"bt\">Resumo da sessão</p>\n        <div class=\"grow\"></div>\n        <button class=\"tbtn\" title=\"${theme === 'dark' ? 'Mudar para claro' : 'Mudar para escuro'}\">${theme === 'dark' ? '☀' : '☽'}</button>\n        <button class=\"btn pri export\">${d2.state.exporting ? 'Gerando…' : 'Exportar CSV'}</button>\n      </div>\n      <div class=\"body\">\n        <h2>${esc(room.name || '')}</h2>\n        <p class=\"sub\">Sala ${esc(room.code || '')} · ${esc(rounds.length)} ${rounds.length === 1 ? 'rodada concluída' : 'rodadas concluídas'}</p>\n        <div class=\"kpis\">\n          <div class=\"kpi\"><div class=\"kl\">Estimadas</div><div class=\"kv\">${esc(estimated.length)}/${esc(issues.length)}</div></div>\n          <div class=\"kpi\"><div class=\"kl\">Pontos</div><div class=\"kv\">${esc(Math.round(points * 100) / 100)}</div></div>\n          <div class=\"kpi\"><div class=\"kl\">Rodadas</div><div class=\"kv\">${esc(rounds.length)}</div></div>\n          <div class=\"kpi\"><div class=\"kl\">Consenso</div><div class=\"kv\">${esc(consensusRate)}%</div></div>\n        </div>\n        <p class=\"sec\">Histórias</p>\n        <div class=\"rows\">${rows || '<div class=\"none\">Nenhuma história neste backlog.</div>'}</div>\n      </div>\n    </div></div>`\n  },\n\n  mounted(d2) {\n    const nav = () => {\n      let base = {}\n      try { base = d2.router('ppk-router') || {} } catch (e) { base = {} }\n      if (!base.params) { try { base = d2.router() || {} } catch (e) { base = {} } }\n      return {\n        params: base.params || {},\n        path: base.path || '',\n        goTo: (path) => navTo(d2, path),\n      }\n    }\n    const code = routeParam(d2, 'code', 'resumo')\n\n    const load = async () => {\n      try {\n        const s = await d2.rush('ppk-snapshot').invoke({ code: code, token: '' })\n        const snap = (s && s.data) ? s.data : s\n        if (!snap || !snap.ok) { d2.state.fatal = 'Sala não encontrada.'; d2.refresh(); return }\n        d2.state.snap = snap\n        const h = await d2.rush('ppk-history').invoke({ roomId: snap.room.id })\n        d2.state.hist = (h && h.data) ? h.data : h\n        d2.refresh()\n      } catch (e) {\n        d2.log(e)\n        d2.state.fatal = 'Falha ao carregar o resumo.'\n        d2.refresh()\n      }\n    }\n    load()\n\n    d2.state.theme = readTheme()\n    d2.on('.tbtn', 'click', () => {\n      const next = (d2.state.theme || readTheme()) === 'dark' ? 'light' : 'dark'\n      d2.state.theme = next\n      writeTheme(next)\n      d2.refresh()\n    })\n\n    d2.on('.back', 'click', () => nav().goTo('/sala/' + code))\n\n    d2.on('.rh', 'click', (e, el) => {\n      const id = el.dataset.toggle\n      if (!id) return\n      const open = Object.assign({}, d2.state.open || {})\n      open[id] = !open[id]\n      d2.state.open = open\n      d2.refresh()\n    })\n\n    d2.on('.export', 'click', async () => {\n      if (d2.state.exporting) return\n      d2.state.exporting = true\n      d2.refresh()\n      let csv = ''\n      try {\n        const res = await d2.rush('ppk-export-csv').invoke({ roomId: d2.state.snap.room.id })\n        if (typeof res === 'string') csv = res\n        else if (res && typeof res.content === 'string') csv = res.content\n        else if (res && res.data && typeof res.data === 'string') csv = res.data\n      } catch (err) {\n        d2.log(err)\n      }\n      if (!csv) {\n        const issues = (d2.state.hist.issues || []).slice().sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))\n        const lines = ['key,title,final_estimate,status']\n        issues.forEach((i) => {\n          const clean = (v) => String(v == null ? '' : v).split(',').join(' ')\n          lines.push([clean(i.key), clean(i.title), clean(i.finalEstimate), clean(i.status)].join(','))\n        })\n        csv = lines.join('\\n')\n      }\n      try {\n        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' })\n        const url = URL.createObjectURL(blob)\n        const a = document.createElement('a')\n        a.href = url\n        a.download = 'poker-planning-' + (d2.state.snap.room.code || 'sessao') + '.csv'\n        a.click()\n        setTimeout(() => URL.revokeObjectURL(url), 1500)\n      } catch (err) {\n        d2.log(err)\n      }\n      d2.state.exporting = false\n      d2.refresh()\n    })\n  },\n}"}}}}}}}},{"_id":"6a67b514a112dbd02d679009","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"naturaConfig","package":"fernando-playground","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 esc = (s) => ('' + (s == null ? '' : s)).replace(/</g,'&lt;').replace(/>/g,'&gt;')\nconst attr = (s) => ('' + (s == null ? '' : s)).replace(/&/g,'&amp;').replace(/\"/g,'&quot;').replace(/</g,'&lt;')\n\nconst pick = (res, key) => {\n  if (!res) return null\n  if (Array.isArray(res[key])) return res[key]\n  if (res.data && Array.isArray(res.data[key])) return res.data[key]\n  return null\n}\n\nconst byOrdem = (a, b) => {\n  const oa = Number(a.ordem) || 0, ob = Number(b.ordem) || 0\n  if (oa !== ob) return oa - ob\n  return ('' + (a.manifestacao || a.titulo || '')).localeCompare('' + (b.manifestacao || b.titulo || ''), 'pt')\n}\n\nconst normalizeList = (arr) => {\n  const seen = {}, out = []\n  ;(arr || []).forEach(s => {\n    const v = ('' + (s == null ? '' : s)).replace(/\\s+/g,' ').trim()\n    if (v && !seen[v]){ seen[v] = 1; out.push(v) }\n  })\n  return out\n}\n\nconst splitCsvLine = (line) => {\n  const cells = []\n  let cur = '', q = false\n  for (let i = 0; i < line.length; i++){\n    const ch = line.charAt(i)\n    if (q){\n      if (ch === '\"'){ if (line.charAt(i+1) === '\"'){ cur += '\"'; i++ } else { q = false } }\n      else cur += ch\n    } else if (ch === '\"'){ q = true }\n    else if (ch === ',' || ch === ';' || ch === '\\t'){ cells.push(cur); cur = '' }\n    else cur += ch\n  }\n  cells.push(cur)\n  return cells.map(c => c.replace(/^\\s+|\\s+$/g,''))\n}\n\nconst parseImport = (text) => {\n  const out = []\n  const t = ('' + (text || '')).replace(/^﻿/, '').trim()\n  if (!t) return out\n  if (t.charAt(0) === '[' || t.charAt(0) === '{'){\n    try {\n      const j = JSON.parse(t)\n      const arr = Array.isArray(j) ? j : [j]\n      arr.forEach(item => {\n        if (typeof item === 'string') out.push(item)\n        else if (item && Array.isArray(item.manifestacoes)) item.manifestacoes.forEach(m => { if (typeof m === 'string') out.push(m) })\n        else if (item && typeof item.manifestacao === 'string') out.push(item.manifestacao)\n      })\n      if (out.length) return out\n    } catch(e){}\n  }\n  const lines = t.split(/\\r?\\n/).filter(l => l.replace(/\\s/g,'').length)\n  if (!lines.length) return out\n  const rows = lines.map(splitCsvLine)\n  const head = rows[0].map(h => h.toLowerCase())\n  let col = -1\n  for (let i = 0; i < head.length; i++){ if (head[i].indexOf('manifesta') >= 0){ col = i; break } }\n  let start = 0\n  if (col >= 0) start = 1\n  else col = 0\n  for (let i = start; i < rows.length; i++){ const v = rows[i][col]; if (v) out.push(v) }\n  return out\n}\n\nconst renderOrientacoes = (d2) => {\n  const list = (d2.state.orientacoes || []).slice().sort(byOrdem)\n  const ed = d2.state.editing\n  const editing = ed && ed.tipo === 'ori' ? ed : null\n  const form = editing ? `\n    <div class=\"card form-card\">\n      <div class=\"clabel\">${editing._id ? 'Editar orientação' : 'Nova orientação'}</div>\n      <div class=\"field\"><label>Título</label><input id=\"o-titulo\" class=\"in\" value=\"${attr(editing.titulo)}\" placeholder=\"Ex.: Embalagem amassada no transporte\"/></div>\n      <div class=\"field\"><label>Instrução para o robô</label><textarea id=\"o-instrucao\" class=\"ta\" placeholder=\"Escreva a orientação como se estivesse instruindo o analista...\">${esc(editing.instrucao)}</textarea></div>\n      <label class=\"chk\"><input type=\"checkbox\" id=\"o-ativa\" ${editing.ativa ? 'checked' : ''}/> <span>Ativa (o robô considera esta orientação)</span></label>\n      <div class=\"frow\"><button class=\"ori-save btn\">Salvar</button><button class=\"cancel btn2\">Cancelar</button></div>\n    </div>` : `<button class=\"ori-new btn\">＋ Nova orientação</button>`\n  const cards = list.length ? list.map(o => `\n    <div class=\"card item\" data-key=\"${o._id}\">\n      <div class=\"irow\">\n        <div class=\"ititle\">${esc(o.titulo) || '(sem título)'}</div>\n        <span class=\"badge ${o.ativa ? 'on' : 'off'}\">${o.ativa ? 'Ativa' : 'Inativa'}</span>\n      </div>\n      <p class=\"itext\">${esc(o.instrucao)}</p>\n      <div class=\"iacts\">\n        <button class=\"ori-toggle lnk\" data-id=\"${o._id}\">${o.ativa ? 'Desativar' : 'Ativar'}</button>\n        <button class=\"ori-edit lnk\" data-id=\"${o._id}\">Editar</button>\n        <button class=\"ori-del lnk danger\" data-id=\"${o._id}\">Excluir</button>\n      </div>\n    </div>`).join('') : `<div class=\"empty-s\">Nenhuma orientação complementar. O robô usa apenas o prompt base.</div>`\n  return `<div class=\"note-box\">O robô já tem um prompt base cobrindo a análise de dano e defeito. Use as orientações abaixo apenas para <b>complementar ou desempatar</b> — por exemplo, dizer qual manifestação usar quando duas parecem cabíveis.</div>${form}<div class=\"list\">${cards}</div>`\n}\n\nconst renderManifestacoes = (d2) => {\n  const list = (d2.state.manifestacoes || []).slice().sort(byOrdem)\n  const ed = d2.state.editing\n  const editing = ed && ed.tipo === 'man' ? ed : null\n  const csv = d2.state.csv\n  const preview = csv ? `\n    <div class=\"card prev\">\n      <div class=\"clabel\">Importar de arquivo</div>\n      <div class=\"pinfo\">\n        <div><b>${esc(csv.arquivo)}</b></div>\n        <div class=\"pstats\">\n          <span class=\"pstat\">${csv.itens.length} ${csv.itens.length === 1 ? 'linha válida' : 'linhas válidas'}</span>\n          <span class=\"pstat ok\">${csv.novas} ${csv.novas === 1 ? 'nova' : 'novas'}</span>\n          <span class=\"pstat dim\">${csv.repetidas} já ${csv.repetidas === 1 ? 'existe' : 'existem'}</span>\n        </div>\n      </div>\n      ${csv.itens.length ? `<div class=\"psample\">${csv.itens.slice(0,6).map(t => `<div class=\"pline\">${esc(t)}</div>`).join('')}${csv.itens.length > 6 ? `<div class=\"pmore\">+ ${csv.itens.length - 6} ${csv.itens.length - 6 === 1 ? 'linha' : 'linhas'}</div>` : ''}</div>` : `<div class=\"empty-s\">Nenhuma linha reconhecida no arquivo.</div>`}\n      <div class=\"frow\">\n        <button class=\"imp-merge btn\" ${csv.novas ? '' : 'disabled'}>Adicionar ${csv.novas} ${csv.novas === 1 ? 'nova' : 'novas'}</button>\n        <button class=\"imp-replace btn2 danger\" ${csv.itens.length ? '' : 'disabled'}>Substituir toda a base</button>\n        <button class=\"cancel btn2\">Cancelar</button>\n      </div>\n    </div>` : ''\n  const form = editing ? `\n    <div class=\"card form-card\">\n      <div class=\"clabel\">${editing._id ? 'Editar manifestação' : 'Nova manifestação'}</div>\n      <div class=\"field\"><label>Texto exato da manifestacao_n3</label><input id=\"m-texto\" class=\"in\" value=\"${attr(editing.manifestacao)}\" placeholder=\"Ex.: Produto vazando ou mal fechado\"/><div class=\"hint\">Escreva exatamente como o ServiceNow espera receber — o robô copia este texto literalmente.</div></div>\n      <label class=\"chk\"><input type=\"checkbox\" id=\"m-ativa\" ${editing.ativa ? 'checked' : ''}/> <span>Ativa (o robô pode escolher esta opção)</span></label>\n      <div class=\"frow\"><button class=\"man-save btn\">Salvar</button><button class=\"cancel btn2\">Cancelar</button></div>\n    </div>` : ''\n  const rows = list.length ? list.map(m => `\n    <div class=\"mrow ${m.ativa ? '' : 'dimmed'}\" data-key=\"${m._id}\">\n      <span class=\"mord\">${Number(m.ordem) || '—'}</span>\n      <span class=\"mtext\">${esc(m.manifestacao)}</span>\n      <span class=\"badge ${m.ativa ? 'on' : 'off'}\">${m.ativa ? 'Ativa' : 'Inativa'}</span>\n      <span class=\"macts\">\n        <button class=\"man-toggle lnk\" data-id=\"${m._id}\">${m.ativa ? 'Desativar' : 'Ativar'}</button>\n        <button class=\"man-edit lnk\" data-id=\"${m._id}\">Editar</button>\n        <button class=\"man-del lnk danger\" data-id=\"${m._id}\">Excluir</button>\n      </span>\n    </div>`).join('') : `<div class=\"empty-s\">Base vazia. O robô não conseguirá classificar nenhum caso.</div>`\n  const ativas = list.filter(m => m.ativa).length\n  return `<div class=\"note-box\">Esta é a base oficial que alimenta o campo <b>manifestacao_n3</b> do ServiceNow. Quando o veredicto é <b>procede</b>, o robô escolhe <b>exatamente uma</b> opção ativa desta lista e copia o texto literalmente. Se nenhuma se aplicar, ele marca o caso para análise humana em vez de inventar uma opção.</div>\n    ${preview}${form}\n    <div class=\"mtoolbar\">\n      <button class=\"man-new btn\">＋ Nova manifestação</button>\n      <button class=\"imp-open btn2\">⇪ Importar CSV / JSON</button>\n      <input type=\"file\" class=\"imp-file\" accept=\".csv,.json,.txt\" />\n      <span class=\"spacer\"></span>\n      <span class=\"mcount\">${ativas} de ${list.length} ativas</span>\n    </div>\n    <div class=\"mlist\">${rows}</div>`\n}\n\nexport default {\n  css: `\n:host{ all:initial; }\n*{ box-sizing:border-box; margin:0; padding:0; }\n.n-root{ --font:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;\n  font-family:var(--font); width:100%; height:100%; -webkit-font-smoothing:antialiased; display:flex; flex-direction:column; }\n.n-root.light{ --bg:#ffffff;--bg-subtle:#fafafa;--panel:#ffffff;--text:#0a0a0a;--muted:#666666;--faint:#8f8f8f;\n  --border:#ebebeb;--border-strong:#d4d4d4;--accent:#EB6619;--accent-hover:#d25610;--accent-soft:#fff2ea;\n  --shadow:0 1px 2px rgba(0,0,0,.04),0 8px 24px rgba(0,0,0,.06);\n  --ok:#0a7d38;--ok-soft:#e9f7ee;--bad:#c02626;--bad-soft:#fdeaea; }\n.n-root.dark{ --bg:#0a0a0a;--bg-subtle:#111111;--panel:#161616;--text:#ededed;--muted:#a0a0a0;--faint:#707070;\n  --border:#262626;--border-strong:#383838;--accent:#EB6619;--accent-hover:#ff7a2e;--accent-soft:#2a1509;\n  --shadow:0 1px 2px rgba(0,0,0,.4),0 8px 24px rgba(0,0,0,.5);\n  --ok:#3fcf6b;--ok-soft:#0f2417;--bad:#f26a6a;--bad-soft:#2a1010; }\n.hdr-slot{ height:60px; flex:none; width:100%; }\n.body{ flex:1; width:100%; background:var(--bg-subtle); overflow:auto; }\n.page{ max-width:900px; margin:0 auto; padding:32px 24px 64px; }\n.ptitle{ font-size:26px; font-weight:700; color:var(--text); letter-spacing:-.025em; margin-bottom:6px; }\n.psub{ font-size:14px; color:var(--muted); margin-bottom:24px; line-height:1.5; }\n.tabs{ display:flex; gap:4px; border-bottom:1px solid var(--border); margin-bottom:24px; }\n.tab{ font-family:var(--font); font-size:14px; font-weight:600; color:var(--muted); background:transparent; border:0;\n  border-bottom:2px solid transparent; padding:10px 14px; cursor:pointer; margin-bottom:-1px; display:flex; align-items:center; gap:8px; }\n.tab:hover{ color:var(--text); }\n.tab.on{ color:var(--accent); border-bottom-color:var(--accent); }\n.tab .n{ font-size:11px; background:var(--bg-subtle); border:1px solid var(--border); border-radius:999px; padding:1px 7px; color:var(--faint); }\n.tab.on .n{ background:var(--accent-soft); border-color:var(--accent); color:var(--accent); }\n.note-box{ background:var(--accent-soft); border:1px solid var(--accent); border-radius:12px; padding:14px 16px; font-size:13px; color:var(--text); line-height:1.6; margin-bottom:20px; }\n.card{ background:var(--panel); border:1px solid var(--border); border-radius:14px; padding:20px; box-shadow:var(--shadow); }\n.clabel{ font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; color:var(--faint); margin-bottom:14px; }\n.form-card{ margin-bottom:20px; border-color:var(--accent); }\n.field{ margin-bottom:14px; }\n.field label{ display:block; font-size:13px; font-weight:600; color:var(--text); margin-bottom:6px; }\n.field .hint{ font-size:12px; color:var(--faint); margin-top:5px; line-height:1.5; }\n.in,.ta{ width:100%; font-family:var(--font); font-size:14px; color:var(--text); background:var(--bg); border:1px solid var(--border-strong); border-radius:10px; padding:10px 12px; outline:none; transition:border-color .15s,box-shadow .15s; }\n.in:focus,.ta:focus{ border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft); }\n.ta{ resize:vertical; min-height:88px; line-height:1.5; }\n.chk{ display:flex; align-items:center; gap:8px; font-size:13px; color:var(--text); cursor:pointer; margin-bottom:16px; }\n.chk input{ width:16px; height:16px; accent-color:var(--accent); cursor:pointer; }\n.frow{ display:flex; gap:10px; flex-wrap:wrap; }\n.btn{ padding:10px 16px; border:0; border-radius:10px; background:var(--accent); color:#fff; font-family:var(--font); font-size:14px; font-weight:600; cursor:pointer; transition:background .15s; }\n.btn:hover{ background:var(--accent-hover); }\n.btn:disabled,.btn2:disabled{ opacity:.5; cursor:default; }\n.btn2{ padding:10px 16px; border-radius:10px; border:1px solid var(--border-strong); background:var(--panel); color:var(--text); font-family:var(--font); font-size:14px; font-weight:600; cursor:pointer; transition:border-color .15s,color .15s; }\n.btn2:hover{ border-color:var(--accent); color:var(--accent); }\n.btn2.danger:hover{ border-color:var(--bad); color:var(--bad); }\n.list{ display:flex; flex-direction:column; gap:12px; margin-top:20px; }\n.item .irow{ display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:8px; }\n.ititle{ font-size:15px; font-weight:600; color:var(--text); }\n.itext{ font-size:13px; color:var(--muted); line-height:1.6; }\n.iacts,.macts{ display:flex; gap:14px; margin-top:12px; }\n.macts{ margin-top:0; }\n.lnk{ background:transparent; border:0; padding:0; font-family:var(--font); font-size:13px; font-weight:600; color:var(--muted); cursor:pointer; }\n.lnk:hover{ color:var(--accent); }\n.lnk.danger:hover{ color:var(--bad); }\n.badge{ flex:none; font-size:11px; font-weight:700; padding:3px 9px; border-radius:999px; }\n.badge.on{ background:var(--ok-soft); color:var(--ok); }\n.badge.off{ background:var(--bg-subtle); color:var(--faint); border:1px solid var(--border); }\n.empty-s{ font-size:14px; color:var(--faint); text-align:center; padding:32px 0; }\n.mtoolbar{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:14px; }\n.spacer{ flex:1; }\n.mcount{ font-size:13px; color:var(--faint); }\n.imp-file{ display:none; }\n.mlist{ background:var(--panel); border:1px solid var(--border); border-radius:14px; overflow:hidden; box-shadow:var(--shadow); }\n.mrow{ display:flex; align-items:center; gap:12px; padding:12px 16px; border-bottom:1px solid var(--border); }\n.mrow:last-child{ border-bottom:0; }\n.mrow.dimmed .mtext{ color:var(--faint); text-decoration:line-through; }\n.mord{ flex:none; width:24px; font-size:12px; color:var(--faint); text-align:right; }\n.mtext{ flex:1; font-size:14px; color:var(--text); line-height:1.45; }\n.prev{ margin-bottom:20px; border-color:var(--accent); }\n.pinfo{ font-size:14px; color:var(--text); margin-bottom:12px; }\n.pstats{ display:flex; gap:8px; flex-wrap:wrap; margin-top:8px; }\n.pstat{ font-size:12px; font-weight:600; padding:4px 10px; border-radius:999px; background:var(--bg-subtle); border:1px solid var(--border); color:var(--muted); }\n.pstat.ok{ background:var(--ok-soft); border-color:var(--ok); color:var(--ok); }\n.pstat.dim{ color:var(--faint); }\n.psample{ background:var(--bg-subtle); border:1px solid var(--border); border-radius:10px; padding:10px 12px; margin-bottom:14px; }\n.pline{ font-size:12px; color:var(--muted); padding:3px 0; line-height:1.4; }\n.pmore{ font-size:12px; color:var(--faint); padding-top:6px; font-weight:600; }\n.loading{ font-size:14px; color:var(--faint); padding:40px 0; text-align:center; }\n.errbar{ background:var(--bad-soft); border:1px solid var(--bad); color:var(--bad); border-radius:10px; padding:10px 14px; font-size:13px; margin-bottom:16px; }\n.busy{ opacity:.6; pointer-events:none; }\n`,\n  render(d2){\n    const theme = d2.shared.get('theme','light',{global:true})\n    const navSlot = `<div class=\"hdr-slot\">${d2.render('fernando-playground@naturaNav', { active:'config' }, { key:'nav' })}</div>`\n    const wrap = (inner) => `<div class=\"n-root ${theme}\">${navSlot}<main class=\"body\"><div class=\"page\">${inner}</div></main></div>`\n\n    const ori = d2.state.orientacoes, man = d2.state.manifestacoes\n    if (!Array.isArray(ori) || !Array.isArray(man)){\n      return wrap(`<div class=\"ptitle\">Configuração do robô</div><div class=\"loading\">Carregando configurações…</div>`)\n    }\n    const tab = d2.state.tab || 'orientacoes'\n    const oriAtivas = ori.filter(o => o.ativa).length\n    const manAtivas = man.filter(m => m.ativa).length\n    const erro = d2.state.erro ? `<div class=\"errbar\">${esc(d2.state.erro)}</div>` : ''\n    const body = tab === 'manifestacoes' ? renderManifestacoes(d2) : renderOrientacoes(d2)\n    return wrap(`\n      <div class=\"ptitle\">Configuração do robô</div>\n      <div class=\"psub\">Ajuste como o robô analisa as fotos e qual base de manifestações ele usa para classificar o resultado.</div>\n      <div class=\"tabs\">\n        <button class=\"tab ${tab === 'orientacoes' ? 'on' : ''}\" data-tab=\"orientacoes\">Orientações do robô<span class=\"n\">${oriAtivas}/${ori.length}</span></button>\n        <button class=\"tab ${tab === 'manifestacoes' ? 'on' : ''}\" data-tab=\"manifestacoes\">Base manifestacao_n3<span class=\"n\">${manAtivas}/${man.length}</span></button>\n      </div>\n      ${erro}\n      <div class=\"${d2.state.busy ? 'busy' : ''}\">${body}</div>\n    `)\n  },\n  mounted(d2){\n    const load = async () => {\n      try {\n        const res = await d2.rush('config-listar').invoke({})\n        d2.setState({ orientacoes: pick(res,'orientacoes') || [], manifestacoes: pick(res,'manifestacoes') || [] })\n      } catch(e){\n        d2.setState({ orientacoes: [], manifestacoes: [], erro: 'Não foi possível carregar as configurações.' })\n      }\n    }\n    load()\n\n    const val = (sel) => { const el = d2.$(sel); return el ? (el.value || '').trim() : '' }\n    const checked = (sel) => { const el = d2.$(sel); return !!(el && el.checked) }\n    const nextOrdem = (list) => (list || []).reduce((mx, r) => Math.max(mx, Number(r.ordem) || 0), 0) + 1\n    const find = (list, id) => (list || []).filter(r => r._id === id)[0]\n\n    const salvarOri = async (registro) => {\n      d2.setState({ busy: true, erro: '' })\n      try { await d2.rush('config-salvar-orientacao').invoke({ registro }); d2.setState({ editing: null }); await load() }\n      catch(e){ d2.setState({ erro: 'Falha ao salvar a orientação.' }) }\n      d2.setState({ busy: false })\n    }\n    const salvarMan = async (registro) => {\n      d2.setState({ busy: true, erro: '' })\n      try { await d2.rush('config-salvar-manifestacao').invoke({ registro }); d2.setState({ editing: null }); await load() }\n      catch(e){ d2.setState({ erro: 'Falha ao salvar a manifestação.' }) }\n      d2.setState({ busy: false })\n    }\n\n    d2.on('.tab', 'click', (e, el) => { d2.setState({ tab: el.dataset.tab, editing: null, csv: null, erro: '' }) })\n    d2.on('.cancel', 'click', () => { d2.setState({ editing: null, csv: null }) })\n\n    d2.on('.ori-new', 'click', () => { d2.state.editing = { tipo:'ori', _id:null, titulo:'', instrucao:'', ativa:true } })\n    d2.on('.ori-edit', 'click', (e, el) => {\n      const o = find(d2.state.orientacoes, el.dataset.id); if (!o) return\n      d2.state.editing = { tipo:'ori', _id:o._id, titulo:o.titulo || '', instrucao:o.instrucao || '', ativa:!!o.ativa, ordem:o.ordem }\n    })\n    d2.on('.ori-save', 'click', () => {\n      const ed = d2.state.editing || {}\n      const titulo = val('#o-titulo'), instrucao = val('#o-instrucao')\n      if (!instrucao){ d2.setState({ erro: 'Escreva a instrução antes de salvar.' }); return }\n      const reg = { titulo, instrucao, ativa: checked('#o-ativa'), ordem: ed.ordem != null ? ed.ordem : nextOrdem(d2.state.orientacoes) }\n      if (ed._id) reg._id = ed._id\n      else reg.createdAt = new Date().toISOString()\n      salvarOri(reg)\n    })\n    d2.on('.ori-toggle', 'click', (e, el) => {\n      const o = find(d2.state.orientacoes, el.dataset.id); if (!o) return\n      salvarOri({ _id:o._id, titulo:o.titulo, instrucao:o.instrucao, ativa:!o.ativa, ordem:o.ordem })\n    })\n    d2.on('.ori-del', 'click', async (e, el) => {\n      const o = find(d2.state.orientacoes, el.dataset.id); if (!o) return\n      const ok = await d2.dialog.confirm(`Excluir a orientação \"${o.titulo || 'sem título'}\"?`)\n      if (!ok) return\n      d2.setState({ busy: true, erro: '' })\n      try { await d2.rush('config-excluir-orientacao').invoke({ id:o._id }); await load() }\n      catch(err){ d2.setState({ erro: 'Falha ao excluir a orientação.' }) }\n      d2.setState({ busy: false })\n    })\n\n    d2.on('.man-new', 'click', () => { d2.state.editing = { tipo:'man', _id:null, manifestacao:'', ativa:true } })\n    d2.on('.man-edit', 'click', (e, el) => {\n      const m = find(d2.state.manifestacoes, el.dataset.id); if (!m) return\n      d2.state.editing = { tipo:'man', _id:m._id, manifestacao:m.manifestacao || '', ativa:!!m.ativa, ordem:m.ordem }\n    })\n    d2.on('.man-save', 'click', () => {\n      const ed = d2.state.editing || {}\n      const texto = val('#m-texto')\n      if (!texto){ d2.setState({ erro: 'Escreva o texto da manifestação antes de salvar.' }); return }\n      const reg = { manifestacao: texto, ativa: checked('#m-ativa'), ordem: ed.ordem != null ? ed.ordem : nextOrdem(d2.state.manifestacoes) }\n      if (ed._id) reg._id = ed._id\n      else reg.createdAt = new Date().toISOString()\n      salvarMan(reg)\n    })\n    d2.on('.man-toggle', 'click', (e, el) => {\n      const m = find(d2.state.manifestacoes, el.dataset.id); if (!m) return\n      salvarMan({ _id:m._id, manifestacao:m.manifestacao, ativa:!m.ativa, ordem:m.ordem })\n    })\n    d2.on('.man-del', 'click', async (e, el) => {\n      const m = find(d2.state.manifestacoes, el.dataset.id); if (!m) return\n      const ok = await d2.dialog.confirm(`Excluir \"${m.manifestacao}\" da base? O robô deixa de poder classificar casos nessa opção.`)\n      if (!ok) return\n      d2.setState({ busy: true, erro: '' })\n      try { await d2.rush('config-excluir-manifestacao').invoke({ id:m._id }); await load() }\n      catch(err){ d2.setState({ erro: 'Falha ao excluir a manifestação.' }) }\n      d2.setState({ busy: false })\n    })\n\n    d2.on('.imp-open', 'click', () => { const f = d2.$('.imp-file'); if (f) f.click() })\n    d2.on('.imp-file', 'change', async (e, el) => {\n      const f = el.files && el.files[0]\n      el.value = ''\n      if (!f) return\n      let text = ''\n      try { text = await f.text() } catch(err){ d2.setState({ erro: 'Não foi possível ler o arquivo.' }); return }\n      const itens = normalizeList(parseImport(text))\n      const atuais = {}\n      ;(d2.state.manifestacoes || []).forEach(m => { atuais[('' + (m.manifestacao || '')).trim()] = 1 })\n      let novas = 0\n      itens.forEach(t => { if (!atuais[t]) novas++ })\n      d2.setState({ csv: { arquivo: f.name, itens, novas, repetidas: itens.length - novas }, editing: null, erro: '' })\n    })\n    d2.on('.imp-merge', 'click', async () => {\n      const csv = d2.state.csv; if (!csv || !csv.itens.length) return\n      const atuais = {}\n      ;(d2.state.manifestacoes || []).forEach(m => { atuais[('' + (m.manifestacao || '')).trim()] = 1 })\n      const novas = csv.itens.filter(t => !atuais[t])\n      if (!novas.length) return\n      d2.setState({ busy: true, erro: '' })\n      let ordem = nextOrdem(d2.state.manifestacoes)\n      try {\n        for (let i = 0; i < novas.length; i++){\n          await d2.rush('config-salvar-manifestacao').invoke({ registro: { manifestacao: novas[i], ativa: true, ordem: ordem, createdAt: new Date().toISOString() } })\n          ordem++\n        }\n        d2.setState({ csv: null }); await load()\n      } catch(err){ d2.setState({ erro: 'Falha durante a importação — parte dos itens pode não ter sido gravada.' }) }\n      d2.setState({ busy: false })\n    })\n    d2.on('.imp-replace', 'click', async () => {\n      const csv = d2.state.csv; if (!csv || !csv.itens.length) return\n      const atuais = (d2.state.manifestacoes || []).slice()\n      const ok = await d2.dialog.confirm(`Isso apaga as ${atuais.length} manifestações atuais e insere as ${csv.itens.length} do arquivo. Continuar?`)\n      if (!ok) return\n      d2.setState({ busy: true, erro: '' })\n      try {\n        for (let i = 0; i < atuais.length; i++){ await d2.rush('config-excluir-manifestacao').invoke({ id: atuais[i]._id }) }\n        for (let i = 0; i < csv.itens.length; i++){\n          await d2.rush('config-salvar-manifestacao').invoke({ registro: { manifestacao: csv.itens[i], ativa: true, ordem: i + 1, createdAt: new Date().toISOString() } })\n        }\n        d2.setState({ csv: null }); await load()\n      } catch(err){ d2.setState({ erro: 'Falha durante a substituição — confira a base antes de usar o robô.' }) }\n      d2.setState({ busy: false })\n    })\n  },\n}"}}}}}}}},{"_id":"6a67ca0ce3f6ea773df412d6","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"jwtDecoder","package":"fernando-playground","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 SAMPLE = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE5MDAwMDAwMDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'\n\nconst esc = (s) => ('' + (s == null ? '' : s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')\n\nconst b64urlDecode = (str) => {\n  let s = ('' + str).replace(/-/g,'+').replace(/_/g,'/')\n  while (s.length % 4) s += '='\n  const bin = atob(s)\n  try {\n    const bytes = new Uint8Array(bin.length)\n    for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)\n    return new TextDecoder('utf-8').decode(bytes)\n  } catch(e){ return bin }\n}\n\nconst decodeJwt = (tokenRaw) => {\n  let t = ('' + (tokenRaw || '')).trim()\n  if (t.indexOf('%') >= 0){ try { t = decodeURIComponent(t) } catch(e){} }\n  t = t.replace(/^Bearer\\s+/i, '')\n  let cookieWrapped = false\n  if (t.indexOf('s:') === 0){ t = t.slice(2); cookieWrapped = true }\n  if (!t) throw new Error('Cole um token para decodificar.')\n  const parts = t.split('.')\n  if (parts.length < 2) throw new Error('Token inválido — esperado no mínimo header.payload separados por ponto.')\n  let header, payload\n  try { header = JSON.parse(b64urlDecode(parts[0])) }\n  catch(e){ throw new Error('Não foi possível decodificar o HEADER (base64url inválido ou não é JSON).') }\n  try { payload = JSON.parse(b64urlDecode(parts[1])) }\n  catch(e){ throw new Error('Não foi possível decodificar o PAYLOAD (base64url inválido ou não é JSON).') }\n  if (parts.length > 3) cookieWrapped = true\n  return { header, payload, signature: parts[2] || '', partes: parts.length, cookieWrapped }\n}\n\nconst getTokenFromBrowser = () => {\n  const looksJwt = (v) => /eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+/.test('' + (v || ''))\n  try {\n    const cookies = ('' + (document.cookie || '')).split(';').map(s => s.trim()).filter(Boolean)\n    for (let i = 0; i < cookies.length; i++){\n      const eq = cookies[i].indexOf('=')\n      if (eq < 0) continue\n      const name = cookies[i].slice(0, eq)\n      let value = cookies[i].slice(eq + 1)\n      try { value = decodeURIComponent(value) } catch(e){}\n      if (name.indexOf('data2') >= 0 && looksJwt(value)) return { token: value, origem: 'cookie ' + name }\n    }\n    for (let i = 0; i < cookies.length; i++){\n      const eq = cookies[i].indexOf('=')\n      if (eq < 0) continue\n      let value = cookies[i].slice(eq + 1)\n      try { value = decodeURIComponent(value) } catch(e){}\n      if (looksJwt(value)) return { token: value, origem: 'cookie ' + cookies[i].slice(0, eq) }\n    }\n  } catch(e){}\n  try {\n    for (let i = 0; i < localStorage.length; i++){\n      const k = localStorage.key(i)\n      const v = localStorage.getItem(k)\n      if (looksJwt(v)) return { token: v, origem: 'localStorage.' + k }\n    }\n  } catch(e){}\n  return null\n}\n\nconst fmtTs = (v) => {\n  const n = Number(v)\n  if (!n) return null\n  const ms = n > 100000000000 ? n : n * 1000\n  try { return new Date(ms).toLocaleString('pt-BR') } catch(e){ return '' + v }\n}\n\nconst CLAIMS = [\n  { k:'iss', label:'iss (emissor)' },\n  { k:'sub', label:'sub (sujeito)' },\n  { k:'aud', label:'aud (audiência)' },\n  { k:'jti', label:'jti (id do token)' },\n]\n\nexport default {\n  css: `\n:host{ all:initial; }\n*{ box-sizing:border-box; margin:0; padding:0; }\n.root{ --font:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;\n  --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace;\n  --bg:#fafafa;--panel:#fff;--text:#0a0a0a;--muted:#666;--faint:#8f8f8f;--border:#ebebeb;--border-strong:#d4d4d4;\n  --accent:#EB6619;--accent-hover:#d25610;--accent-soft:#fff2ea;\n  --ok:#0a7d38;--ok-soft:#e9f7ee;--bad:#c02626;--bad-soft:#fdeaea;--warn:#9a6700;--warn-soft:#fdf3e0;\n  font-family:var(--font); width:100%; height:100%; background:var(--bg); overflow:auto; -webkit-font-smoothing:antialiased; }\n.page{ max-width:960px; margin:0 auto; padding:28px 24px 56px; }\nh1{ font-size:22px; font-weight:700; color:var(--text); letter-spacing:-.02em; margin-bottom:4px; }\n.sub{ font-size:13px; color:var(--muted); margin-bottom:20px; line-height:1.5; }\n.card{ background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:18px; margin-bottom:16px; }\nlabel.lbl{ display:block; font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; color:var(--faint); margin-bottom:8px; }\ntextarea{ width:100%; min-height:110px; font-family:var(--mono); font-size:12px; line-height:1.5; color:var(--text);\n  background:var(--bg); border:1px solid var(--border-strong); border-radius:8px; padding:10px 12px; outline:none; resize:vertical; word-break:break-all; }\ntextarea:focus{ border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft); }\n.row{ display:flex; gap:8px; flex-wrap:wrap; margin-top:12px; align-items:center; }\nbutton{ font-family:var(--font); font-size:13px; font-weight:600; border-radius:8px; cursor:pointer; padding:9px 14px; }\n.primary{ background:var(--accent); color:#fff; border:0; }\n.primary:hover{ background:var(--accent-hover); }\n.ghost{ background:var(--panel); color:var(--text); border:1px solid var(--border-strong); }\n.ghost:hover{ border-color:var(--accent); color:var(--accent); }\n.hintline{ font-size:12px; color:var(--faint); margin-top:10px; line-height:1.5; }\n.err{ background:var(--bad-soft); border:1px solid var(--bad); color:var(--bad); border-radius:8px; padding:10px 12px; font-size:13px; margin-bottom:16px; }\n.okbar{ background:var(--ok-soft); border:1px solid var(--ok); color:var(--ok); border-radius:8px; padding:9px 12px; font-size:12px; font-weight:600; margin-bottom:16px; }\n.grid2{ display:grid; grid-template-columns:1fr 1fr; gap:16px; }\n@media(max-width:760px){ .grid2{ grid-template-columns:1fr; } }\n.ctitle{ font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:var(--faint); margin-bottom:10px; }\npre{ font-family:var(--mono); font-size:12px; line-height:1.55; color:var(--text); background:var(--bg);\n  border:1px solid var(--border); border-radius:8px; padding:12px; overflow:auto; max-height:320px; white-space:pre-wrap; word-break:break-word; }\ntable{ width:100%; border-collapse:collapse; font-size:13px; }\ntd{ padding:7px 0; border-bottom:1px solid var(--border); color:var(--text); vertical-align:top; }\ntr:last-child td{ border-bottom:0; }\ntd.k{ width:190px; color:var(--muted); font-weight:600; white-space:nowrap; padding-right:12px; }\ntd.v{ font-family:var(--mono); font-size:12px; word-break:break-all; }\n.pill{ display:inline-block; font-size:11px; font-weight:700; padding:2px 8px; border-radius:999px; margin-left:6px; }\n.pill.ok{ background:var(--ok-soft); color:var(--ok); }\n.pill.bad{ background:var(--bad-soft); color:var(--bad); }\n.pill.warn{ background:var(--warn-soft); color:var(--warn); }\n.sig{ font-family:var(--mono); font-size:11px; color:var(--faint); word-break:break-all; margin-top:6px; }\n.note{ background:var(--warn-soft); border:1px solid var(--warn); color:var(--warn); border-radius:8px; padding:9px 12px; font-size:12px; margin-bottom:14px; line-height:1.5; }\n`,\n  render(d2){\n    const r = d2.state.result\n    const erro = d2.state.erro\n    const flash = d2.state.flash\n    const body = []\n    if (erro) body.push(`<div class=\"err\">${esc(erro)}</div>`)\n    if (flash && !erro) body.push(`<div class=\"okbar\">${esc(flash)}</div>`)\n    if (r){\n      const p = r.payload || {}\n      const now = Math.floor(Date.now() / 1000)\n      const expTxt = p.exp ? `${esc(fmtTs(p.exp))}<span class=\"pill ${Number(p.exp) < now ? 'bad' : 'ok'}\">${Number(p.exp) < now ? 'EXPIRADO' : 'válido'}</span>` : '—'\n      const rows = []\n      rows.push(`<tr><td class=\"k\">alg / typ</td><td class=\"v\">${esc((r.header || {}).alg || '?')} / ${esc((r.header || {}).typ || '?')}</td></tr>`)\n      CLAIMS.forEach(c => { if (p[c.k] != null) rows.push(`<tr><td class=\"k\">${esc(c.label)}</td><td class=\"v\">${esc(typeof p[c.k] === 'object' ? JSON.stringify(p[c.k]) : p[c.k])}</td></tr>`) })\n      if (p.iat != null) rows.push(`<tr><td class=\"k\">iat (emitido em)</td><td class=\"v\">${esc(fmtTs(p.iat))}</td></tr>`)\n      if (p.nbf != null) rows.push(`<tr><td class=\"k\">nbf (válido a partir)</td><td class=\"v\">${esc(fmtTs(p.nbf))}</td></tr>`)\n      rows.push(`<tr><td class=\"k\">exp (expira em)</td><td class=\"v\">${p.exp ? expTxt : '— <span class=\"pill warn\">sem expiração</span>'}</td></tr>`)\n      body.push(`\n        ${r.cookieWrapped ? `<div class=\"note\">Detectei um wrapper de cookie assinado (prefixo <b>s:</b> e/ou 4 segmentos). Removi o wrapper e decodifiquei o JWT interno.</div>` : ''}\n        <div class=\"card\">\n          <div class=\"ctitle\">Resumo</div>\n          <table>${rows.join('')}</table>\n          <div class=\"sig\">assinatura: ${esc(r.signature) || '(ausente)'}</div>\n        </div>\n        <div class=\"grid2\">\n          <div class=\"card\"><div class=\"ctitle\">Header</div><pre>${esc(JSON.stringify(r.header, null, 2))}</pre></div>\n          <div class=\"card\"><div class=\"ctitle\">Payload</div><pre>${esc(JSON.stringify(r.payload, null, 2))}</pre></div>\n        </div>`)\n    }\n    return `<div class=\"root\"><div class=\"page\">\n      <h1>JWT Decoder</h1>\n      <div class=\"sub\">Decodifica header e payload no navegador e joga tudo no <b>console do browser</b> (F12 → Console). Decodificar não valida assinatura — é leitura, não verificação.</div>\n      <div class=\"card\">\n        <label class=\"lbl\" for=\"tok\">Token</label>\n        <textarea id=\"tok\" spellcheck=\"false\" placeholder=\"Cole aqui o JWT (header.payload.assinatura) — aceita também 'Bearer ...' e cookie assinado 's:...'\"></textarea>\n        <div class=\"row\">\n          <button class=\"primary go\">Decodificar e logar no console</button>\n          <button class=\"ghost sess\">Token da sessão</button>\n          <button class=\"ghost demo\">Exemplo</button>\n          <button class=\"ghost clr\">Limpar</button>\n        </div>\n        <div class=\"hintline\">O resultado sai no console como objeto navegável (<span style=\"font-family:var(--mono)\">header</span>, <span style=\"font-family:var(--mono)\">payload</span>, <span style=\"font-family:var(--mono)\">signature</span>) e também aparece abaixo.</div>\n      </div>\n      ${body.join('')}\n    </div></div>`\n  },\n  mounted(d2){\n    const ta = () => d2.$('#tok')\n    const setTok = (v) => { const el = ta(); if (el){ el.value = v; el.focus() } }\n\n    d2.on('.go', 'click', () => {\n      const el = ta()\n      const raw = el ? el.value : ''\n      try {\n        const r = decodeJwt(raw)\n        const p = r.payload || {}\n        const now = Math.floor(Date.now() / 1000)\n        console.group('%cJWT decodificado', 'background:#EB6619;color:#fff;padding:2px 8px;border-radius:3px;font-weight:bold')\n        console.log('header:', r.header)\n        console.log('payload:', r.payload)\n        console.log('signature:', r.signature)\n        if (p.iat != null) console.log('iat (emitido em):', fmtTs(p.iat))\n        if (p.exp != null) console.log('exp (expira em):', fmtTs(p.exp), Number(p.exp) < now ? '→ EXPIRADO' : '→ válido')\n        else console.log('exp: (o token não tem expiração)')\n        if (r.cookieWrapped) console.log('obs: o valor tinha wrapper de cookie assinado (s:...) — removido antes de decodificar')\n        console.log('objeto completo:', r)\n        console.groupEnd()\n        d2.setState({ result: r, erro: '', flash: 'Decodificado e logado no console (F12 → Console).' })\n      } catch(e){\n        console.error('[JWT Decoder] falha ao decodificar:', e && e.message ? e.message : e)\n        d2.setState({ result: null, erro: (e && e.message) || 'Falha ao decodificar o token.', flash: '' })\n      }\n    })\n\n    d2.on('.sess', 'click', () => {\n      const found = getTokenFromBrowser()\n      if (found){\n        setTok(found.token)\n        console.log('%cToken encontrado', 'background:#0a7d38;color:#fff;padding:2px 8px;border-radius:3px', found.origem)\n        d2.setState({ erro: '', flash: 'Token carregado de ' + found.origem + '. Clique em Decodificar.' })\n      } else {\n        d2.setState({ erro: 'Nenhum token visível para o JavaScript. O cookie de sessão do Data2 é httpOnly, então copie o valor no DevTools (Application → Cookies) e cole aqui.', flash: '' })\n      }\n    })\n\n    d2.on('.demo', 'click', () => { setTok(SAMPLE); d2.setState({ erro: '', flash: 'Exemplo carregado. Clique em Decodificar.' }) })\n    d2.on('.clr', 'click', () => { setTok(''); d2.setState({ result: null, erro: '', flash: '' }) })\n  },\n}"}}}}}}}},{"_id":"6a69160758398d1f9e29b53f","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"wlchatui","package":"fernando-playground","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 esc = (s) => String(s == null ? '' : s).replace(/[&<>\"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' }[c]))\nconst SVG = (p) => `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${p}</svg>`\nconst ICON = {\n  clip: SVG(`<path d=\"M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48\"/>`),\n  mic: `<span class=\"i-mic\">${SVG(`<path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"22\"/><line x1=\"8\" y1=\"22\" x2=\"16\" y2=\"22\"/>`)}</span>`,\n  send: `<span class=\"i-send\">${SVG(`<line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"/><polygon points=\"22 2 15 22 11 13 2 9 22 2\"/>`)}</span>`,\n  stop: `<span class=\"i-stop\"><svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"7\" y=\"7\" width=\"10\" height=\"10\" rx=\"2\"/></svg></span>`\n}\n\nexport default {\n  css: `\n  .app{--brand:#4F46E5;display:flex;flex-direction:column;height:100%;max-width:560px;margin:0 auto;box-shadow:0 0 40px rgba(16,16,40,.07);font:14px/1.45 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;background:#f7f7fb;color:#16161a}\n  .hd{display:flex;align-items:center;gap:10px;padding:12px 16px;background:#fff;border-bottom:1px solid #e9e9f0;flex:0 0 auto}\n  .av{width:36px;height:36px;border-radius:50%;background:var(--brand);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;flex:0 0 auto}\n  .hdt{flex:1;min-width:0}\n  .hdt b{display:block;font-size:14px}\n  .pill{font-size:11px;color:#6b6b7b}\n  .dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:#22c55e;margin-right:5px;vertical-align:middle}\n  .dot.wait{background:#f59e0b}\n  .dot.off{background:#9ca3af}\n  .hbtn{border:1px solid #e0e0ea;background:#fff;color:#555;border-radius:999px;padding:6px 12px;font-size:12px;cursor:pointer;flex:0 0 auto}\n  .hbtn:hover{border-color:var(--brand);color:var(--brand)}\n  .log{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px}\n  .row{display:flex}\n  .row.me{justify-content:flex-end}\n  .b{max-width:78%;padding:9px 13px;border-radius:16px;white-space:pre-wrap;word-break:break-word;box-shadow:0 1px 2px rgba(16,16,40,.06)}\n  .b.them{background:#fff;border-bottom-left-radius:5px}\n  .b.me{background:var(--brand);color:#fff;border-bottom-right-radius:5px}\n  .b.agent{background:#fff;border-bottom-left-radius:5px;border-left:3px solid var(--brand)}\n  .who{font-size:11px;color:#6b6b7b;margin-bottom:3px;font-weight:600}\n  .sys{align-self:center;background:#ececf3;color:#5b5b6b;font-size:12px;padding:5px 12px;border-radius:999px}\n  .err{align-self:center;background:#fee2e2;color:#991b1b;font-size:12px;padding:5px 12px;border-radius:999px}\n  .b img{display:block;width:100%;height:auto;max-width:240px;border-radius:10px;margin-bottom:6px}\n  .b audio{display:block;width:230px;height:36px;margin-bottom:6px}\n  .faq{margin-top:6px;font-size:11px;color:#8a8a9a}\n  .qr{display:flex;flex-wrap:wrap;gap:6px;padding:0 16px 10px;flex:0 0 auto}\n  .qb{border:1px solid var(--brand);color:var(--brand);background:#fff;border-radius:999px;padding:7px 13px;font-size:13px;cursor:pointer}\n  .qb:hover{background:var(--brand);color:#fff}\n  .bar{display:flex;align-items:center;gap:8px;padding:10px 12px;background:#fff;border-top:1px solid #e9e9f0;flex:0 0 auto}\n  .field{flex:1;display:flex;align-items:center;background:#f2f2f7;border:1px solid transparent;border-radius:22px;padding:0 14px;transition:border-color .15s,background .15s}\n  .field:focus-within{border-color:var(--brand);background:#fff}\n  .in{flex:1;border:0;outline:0;background:transparent;font:inherit;font-size:14px;padding:11px 0;color:inherit}\n  .in::placeholder{color:#9a9aad}\n  .iconbtn{width:36px;height:36px;border-radius:50%;border:0;background:transparent;color:#7a7a8c;cursor:pointer;\n      display:flex;align-items:center;justify-content:center;flex:0 0 auto;transition:background .15s,color .15s}\n  .iconbtn:hover{background:#eeeef5;color:var(--brand)}\n  .iconbtn svg{width:20px;height:20px;display:block}\n  .act-btn{width:44px;height:44px;border-radius:50%;border:0;background:var(--brand);color:#fff;cursor:pointer;\n      display:flex;align-items:center;justify-content:center;flex:0 0 auto;box-shadow:0 3px 10px rgba(79,70,229,.3);\n      transition:transform .12s,background .18s,box-shadow .18s}\n  .act-btn:hover{transform:scale(1.06)}\n  .act-btn:active{transform:scale(.93)}\n  .act-btn svg{width:21px;height:21px;display:block}\n  .act-btn .i-send,.act-btn .i-stop{display:none}\n  .bar[data-typing] .act-btn .i-mic{display:none}\n  .bar[data-typing] .act-btn .i-send{display:block}\n  .bar[data-rec] .act-btn{background:#ef4444;box-shadow:0 3px 10px rgba(239,68,68,.38)}\n  .bar[data-rec] .act-btn .i-mic,.bar[data-rec] .act-btn .i-send{display:none}\n  .bar[data-rec] .act-btn .i-stop{display:block}\n  .reclive{flex:1;display:flex;align-items:center;gap:10px;padding:11px 16px;background:#fef2f2;border-radius:22px;color:#b91c1c;font-size:13px}\n  .recdot{width:9px;height:9px;border-radius:50%;background:#ef4444;animation:pulse 1s infinite;flex:0 0 auto}\n  @keyframes pulse{0%,100%{opacity:1}50%{opacity:.2}}\n  .iconbtn[disabled],.act-btn[disabled]{opacity:.45;cursor:default;transform:none}\n  .gate{margin:auto;background:#fff;padding:26px;border-radius:16px;width:min(320px,86%);text-align:center;box-shadow:0 8px 30px rgba(16,16,40,.08)}\n  .gate h3{margin:0 0 6px;font-size:17px}\n  .gate p{margin:0 0 16px;color:#6b6b7b;font-size:13px}\n  .gate input{width:100%;box-sizing:border-box;padding:11px;border:1px solid #dcdce6;border-radius:10px;margin-bottom:10px;font-size:14px}\n  .gate button{width:100%;padding:11px;border:0;border-radius:10px;background:var(--brand);color:#fff;font-size:14px;font-weight:600;cursor:pointer}\n  .typing{display:flex;gap:4px;align-items:center}\n  .typing i{width:6px;height:6px;border-radius:50%;background:#b9b9c9;animation:bl 1.2s infinite}\n  .typing i:nth-child(2){animation-delay:.2s}\n  .typing i:nth-child(3){animation-delay:.4s}\n  @keyframes bl{0%,60%,100%{opacity:.3}30%{opacity:1}}\n  `,\n\n  render(d2) {\n    const S = d2.state\n    const D = S.data\n    const brand = (D && D.color) || '#4F46E5'\n\n    if (!S.name) {\n      return `<div class=\"app\" style=\"--brand:${esc(brand)}\"><div class=\"gate\">\n        <h3>${esc((D && D.brand) || 'Atendimento')}</h3>\n        <p>Como podemos te chamar?</p>\n        <input class=\"gname\" placeholder=\"Seu nome\"/>\n        <button class=\"gok\">Começar</button>\n      </div></div>`\n    }\n    if (!D) {\n      return `<div class=\"app\" style=\"--brand:${esc(brand)}\"><div class=\"gate\"><p>Carregando atendimento…</p></div></div>`\n    }\n\n    const st = D.status || 'novo'\n    const msgs = D.messages || []\n    const pill = st === 'humano'\n      ? `<span class=\"dot\"></span>Você está falando com ${esc(D.agentName || 'um atendente')}`\n      : st === 'fila'\n        ? `<span class=\"dot wait\"></span>Encaminhando para um atendente…`\n        : st === 'encerrada'\n          ? `<span class=\"dot off\"></span>Atendimento encerrado`\n          : `<span class=\"dot\"></span>Assistente virtual · ${esc(D.segmentName || '')}`\n\n    const canBot = st === 'bot' || st === 'novo'\n    const humanBtn = (D.handoffEnabled && canBot)\n      ? `<button class=\"hbtn human\">Falar com atendente</button>`\n      : ''\n\n    const hello = `<div class=\"row\" data-key=\"greeting\"><div class=\"b them\">${esc(D.greeting || 'Olá! Como posso ajudar?')}</div></div>`\n\n    const body = msgs.map((m) => {\n      if (m.role === 'system') return `<div class=\"sys\" data-key=\"${esc(m._id)}\">${esc(m.content)}</div>`\n      const me = m.role === 'user'\n      const media = (m.type === 'image' && m.mediaUrl)\n        ? `<img src=\"${esc(m.mediaUrl)}\" alt=\"imagem enviada\"/>`\n        : (m.type === 'audio' && m.mediaUrl)\n          ? `<audio controls src=\"${esc(m.mediaUrl)}\"></audio>`\n          : ''\n      const who = m.role === 'agent' ? `<div class=\"who\">${esc(m.agentName || 'Atendente')}</div>` : ''\n      const faq = m.faqTitle ? `<div class=\"faq\">Base: ${esc(m.faqTitle)}</div>` : ''\n      const cls = me ? 'me' : (m.role === 'agent' ? 'agent' : 'them')\n      return `<div class=\"row ${me ? 'me' : ''}\" data-key=\"${esc(m._id)}\"><div class=\"b ${cls}\">${who}${media}${esc(m.content || '')}${faq}</div></div>`\n    }).join('')\n\n    const pend = S.pending ? `<div class=\"row me\" data-key=\"pending\"><div class=\"b me\">${esc(S.pending)}</div></div>` : ''\n    const typing = S.sending ? `<div class=\"row\"><div class=\"b them typing\"><i></i><i></i><i></i></div></div>` : ''\n    const err = S.err ? `<div class=\"err\">${esc(S.err)}</div>` : ''\n\n    let opts = []\n    if (canBot) {\n      if (!msgs.length) opts = D.quickReplies || []\n      else {\n        const rev = msgs.slice().reverse()\n        const lastA = rev.find((m) => m.role === 'assistant')\n        opts = (lastA && lastA.options) || []\n      }\n    }\n    const qr = (opts.length && !S.sending)\n      ? `<div class=\"qr\">${opts.map((o) => `<button class=\"qb\" data-q=\"${esc(o)}\">${esc(o)}</button>`).join('')}</div>`\n      : ''\n\n    const dis = S.sending ? 'disabled' : ''\n    const closed = st === 'encerrada'\n    const bar = closed\n      ? `<div class=\"bar\"><span class=\"pill\" style=\"padding:8px\">Esta conversa foi encerrada.</span></div>`\n      : `<div class=\"bar\" ${S.recording ? 'data-rec' : ''}>\n          <input type=\"file\" class=\"file\" accept=\"image/*\" style=\"display:none\"/>\n          ${S.recording\n            ? `<div class=\"reclive\"><span class=\"recdot\"></span>Gravando… toque para enviar</div>`\n            : `<button class=\"iconbtn att\" title=\"Enviar imagem\" ${dis}>${ICON.clip}</button>\n               <div class=\"field\"><input class=\"in\" placeholder=\"Escreva sua mensagem…\" ${dis}/></div>`}\n          <button class=\"act-btn\" title=\"${S.recording ? 'Parar e enviar' : 'Gravar áudio ou enviar'}\" ${dis}>${ICON.mic}${ICON.send}${ICON.stop}</button>\n        </div>`\n\n    return `<div class=\"app\" style=\"--brand:${esc(brand)}\">\n      <div class=\"hd\">\n        <div class=\"av\">${esc((D.brand || 'A').slice(0, 1).toUpperCase())}</div>\n        <div class=\"hdt\"><b>${esc(D.brand || 'Atendimento')}</b><div class=\"pill\">${pill}</div></div>\n        ${humanBtn}\n      </div>\n      <div class=\"log\">${hello}${body}${pend}${typing}${err}</div>\n      ${qr}\n      ${bar}\n    </div>`\n  },\n\n  mounted(d2) {\n    const tslug = d2.param('tenantSlug', 'demo')\n    const KEYT = 'wl_thread_' + tslug\n    const KEYN = 'wl_name_' + tslug\n    let tid = null\n    try { tid = localStorage.getItem(KEYT); d2.state.name = localStorage.getItem(KEYN) || '' } catch (e) { d2.state.name = '' }\n    if (!tid) {\n      tid = 't-' + Math.random().toString(36).slice(2, 9) + '-' + Date.now()\n      try { localStorage.setItem(KEYT, tid) } catch (e) {}\n    }\n\n    const un = (r) => (r && typeof r === 'object' && r.data && typeof r.data === 'object' && !Array.isArray(r.data)) ? r.data : r\n\n    const fetchThread = async () => {\n      try { return un(await d2.rush('wlthread').invoke({ threadId: tid, tenantSlug: tslug })) }\n      catch (e) { d2.state.err = 'Falha ao carregar: ' + (e && e.message ? e.message : e); return null }\n    }\n\n    const reload = async () => {\n      const fresh = await fetchThread()\n      if (fresh) d2.setState({ data: fresh, err: null })\n    }\n\n    const send = async (text, extra) => {\n      const t = (text || '').trim()\n      if (!t && !(extra && extra.mediaUrl)) return\n      d2.setState({ pending: t || 'mídia enviada', sending: true, err: null })\n      try {\n        await d2.rush('wlchat').invoke(Object.assign({\n          threadId: tid, tenantSlug: tslug, userName: d2.state.name, message: t\n        }, extra || {}))\n      } catch (e) { d2.state.err = 'Falha ao enviar: ' + (e && e.message ? e.message : e) }\n      const fresh = await fetchThread()\n      d2.setState({ data: fresh || d2.state.data, pending: null, sending: false })\n    }\n\n    const sendFromInput = () => {\n      const el = d2.$('.in')\n      if (!el) return\n      const v = el.value\n      el.value = ''\n      send(v)\n    }\n\n    d2.on('.gok', 'click', () => {\n      const el = d2.$('.gname')\n      const v = ((el && el.value) || '').trim()\n      if (!v) return\n      try { localStorage.setItem(KEYN, v) } catch (e) {}\n      d2.state.name = v\n    })\n    d2.on('.gname', 'keydown', (e) => { if (e.key === 'Enter') { const b = d2.$('.gok'); if (b) b.click() } })\n    const syncTyping = () => {\n      const bar = d2.$('.bar')\n      const inp = d2.$('.in')\n      if (!bar) return\n      if (inp && (inp.value || '').trim()) bar.setAttribute('data-typing', '')\n      else bar.removeAttribute('data-typing')\n    }\n    d2.on('.in', 'keydown', (e) => { if (e.key === 'Enter') sendFromInput() })\n    d2.on('.in', 'input', syncTyping)\n    d2.on('.qb', 'click', (e, el) => send(el.dataset.q, { selectedOption: el.dataset.q }))\n    d2.on('.att', 'click', () => { const f = d2.$('.file'); if (f) f.click() })\n\n    d2.on('.file', 'change', async (e, el) => {\n      const f = el.files && el.files[0]\n      if (!f) return\n      el.value = ''\n      d2.state.sending = true\n      try {\n        const up = await d2.drive('wldrive').upload([f])\n        const url = up && up[0] && up[0].fileUrl\n        const inp = d2.$('.in')\n        const txt = ((inp && inp.value) || '').trim() || 'Segue a foto, pode me ajudar?'\n        if (inp) inp.value = ''\n        await send(txt, { mediaType: 'image', mediaUrl: url })\n      } catch (err) { d2.state.err = 'Falha no upload da imagem' }\n      d2.state.sending = false\n    })\n\n    let rec = null\n    let chunks = []\n    d2.on('.act-btn', 'click', async () => {\n      if (d2.state.recording) { if (rec) rec.stop(); return }\n      const inp = d2.$('.in')\n      if (inp && (inp.value || '').trim()) { sendFromInput(); syncTyping(); return }\n      try {\n        const stream = await navigator.mediaDevices.getUserMedia({ audio: true })\n        chunks = []\n        rec = new MediaRecorder(stream)\n        rec.ondataavailable = (ev) => { if (ev.data && ev.data.size) chunks.push(ev.data) }\n        rec.onstop = async () => {\n          stream.getTracks().forEach((t) => t.stop())\n          d2.state.recording = false\n          d2.state.sending = true\n          try {\n            const blob = new Blob(chunks, { type: (rec && rec.mimeType) || 'audio/webm' })\n            const ext = String(blob.type).indexOf('ogg') >= 0 ? 'ogg' : 'webm'\n            const file = new File([blob], 'audio-' + Date.now() + '.' + ext, { type: blob.type })\n            const up = await d2.drive('wldrive').upload([file])\n            const url = up && up[0] && up[0].fileUrl\n            const tr = un(await d2.rush('wltranscribe').invoke({ mediaUrl: url }))\n            const txt = (tr && tr.transcript) || ''\n            await send(txt, { mediaType: 'audio', mediaUrl: url, transcript: txt })\n          } catch (err) { d2.state.err = 'Falha ao enviar o áudio' }\n          d2.state.sending = false\n        }\n        rec.start()\n        d2.state.recording = true\n      } catch (err) { d2.state.err = 'Não consegui acessar o microfone' }\n    })\n\n    d2.on('.human', 'click', async () => {\n      d2.state.sending = true\n      try { await d2.rush('wlhandoff').invoke({ threadId: tid, action: 'pedir' }) } catch (e) { d2.state.err = 'Falha ao chamar atendente' }\n      await reload()\n      d2.state.sending = false\n    })\n\n    try {\n      const sock = d2.socket('wlsocket')\n      sock.subscribe('thread-' + tid)\n      sock.onMessage(() => { reload() })\n    } catch (e) {}\n\n    reload()\n  },\n\n  updated(d2) {\n    const log = d2.$('.log')\n    if (log) log.scrollTop = log.scrollHeight\n    const bar = d2.$('.bar')\n    const inp = d2.$('.in')\n    if (bar) {\n      if (inp && (inp.value || '').trim()) bar.setAttribute('data-typing', '')\n      else bar.removeAttribute('data-typing')\n    }\n  }\n}"}},"tenantSlug":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"tenantSlug"}}}}}}}}}},{"_id":"6a69174958398d1f9e29bc11","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"wlmesaui","package":"fernando-playground","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 esc = (s) => String(s == null ? '' : s).replace(/[&<>\"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' }[c]))\nconst cut = (s, n) => { const t = String(s == null ? '' : s); return t.length > n ? t.slice(0, n) + '…' : t }\nconst LABEL = { fila: 'Na fila', humano: 'Em atendimento', bot: 'Com o bot', encerrada: 'Encerrada', novo: 'Novo' }\n\nexport default {\n  css: `\n  .mesa{--brand:#4F46E5;display:grid;grid-template-columns:330px 1fr;grid-template-rows:auto 1fr;height:100%;\n        font:14px/1.45 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;background:#f4f4f8;color:#16161a}\n  .top{grid-column:1/-1;display:flex;align-items:center;gap:12px;padding:12px 18px;background:#fff;border-bottom:1px solid #e6e6ef}\n  .top b{font-size:15px}\n  .top .sub{font-size:12px;color:#7a7a8c;flex:1}\n  .who{display:flex;align-items:center;gap:8px;font-size:12px;color:#7a7a8c}\n  .who select{padding:7px 10px;border:1px solid #dcdce6;border-radius:8px;font-size:13px;background:#fff}\n  .side{border-right:1px solid #e6e6ef;background:#fff;display:flex;flex-direction:column;min-height:0}\n  .tabs{display:flex;gap:4px;padding:10px;border-bottom:1px solid #f0f0f5;flex:0 0 auto}\n  .tab{flex:1;padding:6px 4px;border:0;background:#f2f2f7;border-radius:8px;font-size:12px;cursor:pointer;color:#5b5b6b}\n  .tab[data-on]{background:var(--brand);color:#fff;font-weight:600}\n  .list{flex:1;overflow-y:auto;min-height:0}\n  .conv{padding:12px 14px;border-bottom:1px solid #f3f3f7;cursor:pointer}\n  .conv:hover{background:#fafaff}\n  .conv[data-on]{background:#f1efff;border-left:3px solid var(--brand);padding-left:11px}\n  .conv .l1{display:flex;align-items:center;gap:6px}\n  .conv .nm{font-weight:600;font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n  .conv .tm{font-size:11px;color:#a0a0b0;flex:0 0 auto}\n  .conv .l2{font-size:12px;color:#7a7a8c;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n  .bdg{font-size:10px;padding:2px 7px;border-radius:999px;font-weight:700;letter-spacing:.02em}\n  .bdg.fila{background:#fef3c7;color:#92400e}\n  .bdg.humano{background:#dcfce7;color:#166534}\n  .bdg.bot{background:#e8e8f0;color:#55556a}\n  .bdg.encerrada{background:#f1f1f4;color:#9a9aab}\n  .main{display:flex;flex-direction:column;min-height:0;min-width:0}\n  .mhd{display:flex;align-items:center;gap:10px;padding:12px 18px;background:#fff;border-bottom:1px solid #e6e6ef;flex:0 0 auto}\n  .mhd .t{flex:1;min-width:0}\n  .mhd .t b{display:block;font-size:14px}\n  .mhd .t span{font-size:12px;color:#7a7a8c}\n  .act{border:1px solid #dcdce6;background:#fff;color:#444;border-radius:8px;padding:7px 12px;font-size:12px;cursor:pointer}\n  .act:hover{border-color:var(--brand);color:var(--brand)}\n  .act.pri{background:var(--brand);color:#fff;border-color:var(--brand);font-weight:600}\n  .act[disabled]{opacity:.45;cursor:default}\n  .log{flex:1;overflow-y:auto;padding:18px;display:flex;flex-direction:column;gap:10px;min-height:0}\n  .row{display:flex}\n  .row.rt{justify-content:flex-end}\n  .b{max-width:70%;padding:9px 13px;border-radius:14px;white-space:pre-wrap;word-break:break-word;box-shadow:0 1px 2px rgba(16,16,40,.06)}\n  .b.cli{background:#fff;border-bottom-left-radius:4px}\n  .b.bot{background:#ececf4;color:#3a3a4a;border-bottom-right-radius:4px}\n  .b.ag{background:var(--brand);color:#fff;border-bottom-right-radius:4px}\n  .b img{display:block;width:100%;height:auto;max-width:230px;border-radius:9px;margin-bottom:6px}\n  .b audio{display:block;width:220px;height:36px;margin-bottom:6px}\n  .meta{font-size:11px;opacity:.75;margin-bottom:3px}\n  .sys{align-self:center;background:#e4e4ec;color:#5b5b6b;font-size:12px;padding:4px 12px;border-radius:999px}\n  .err{align-self:center;background:#fee2e2;color:#991b1b;font-size:12px;padding:5px 12px;border-radius:999px}\n  .bar{display:flex;gap:8px;padding:12px 16px;background:#fff;border-top:1px solid #e6e6ef;flex:0 0 auto}\n  .in{flex:1;padding:10px 14px;border:1px solid #dcdce6;border-radius:10px;outline:0;font-size:14px}\n  .in:focus{border-color:var(--brand)}\n  .empty{margin:auto;text-align:center;color:#9a9aab;font-size:13px;padding:40px}\n  .hint{padding:10px 16px;background:#fffbeb;color:#92400e;font-size:12px;border-top:1px solid #fde68a}\n  `,\n\n  render(d2) {\n    const S = d2.state\n    const Q = S.queue\n    const brand = (Q && Q.color) || '#4F46E5'\n    if (!Q) return `<div class=\"mesa\" style=\"--brand:${esc(brand)}\"><div class=\"empty\">Carregando mesa…</div></div>`\n\n    const agents = Q.agents || []\n    const me = S.agent || ''\n    const convs = Q.conversations || []\n    const f = S.filter || 'fila'\n    const shown = convs.filter((c) => {\n      if (f === 'todas') return true\n      if (f === 'fila') return c.status === 'fila'\n      if (f === 'meus') return c.status === 'humano' && c.agentName === me\n      return c.status === 'encerrada'\n    })\n    const nFila = convs.filter((c) => c.status === 'fila').length\n\n    const tabs = [['fila', 'Fila' + (nFila ? ' (' + nFila + ')' : '')], ['meus', 'Meus'], ['todas', 'Todas'], ['encerrada', 'Encerradas']]\n      .map(([k, l]) => `<button class=\"tab\" data-f=\"${k}\" ${f === k ? 'data-on' : ''}>${esc(l)}</button>`).join('')\n\n    const list = shown.length ? shown.map((c) => `\n      <div class=\"conv\" data-key=\"${esc(c._id)}\" data-t=\"${esc(c.threadId)}\" ${S.open === c.threadId ? 'data-on' : ''}>\n        <div class=\"l1\">\n          <span class=\"nm\">${esc(c.userName || 'Visitante')}</span>\n          <span class=\"bdg ${esc(c.status)}\">${esc(LABEL[c.status] || c.status)}</span>\n          <span class=\"tm\">${esc(d2.fmt.time(c.updatedAt || c.createdAt))}</span>\n        </div>\n        <div class=\"l2\">${esc(cut(c.lastMessage, 64))}</div>\n      </div>`).join('') : `<div class=\"empty\">Nada aqui.</div>`\n\n    const T = S.thread\n    let main = `<div class=\"empty\">Selecione uma conversa à esquerda.</div>`\n    if (S.open && T) {\n      const st = T.status || 'bot'\n      const mine = st === 'humano' && T.agentName === me\n      const msgs = T.messages || []\n      const body = msgs.map((m) => {\n        if (m.role === 'system') return `<div class=\"sys\" data-key=\"${esc(m._id)}\">${esc(m.content)}</div>`\n        const cli = m.role === 'user'\n        const media = (m.type === 'image' && m.mediaUrl)\n          ? `<img src=\"${esc(m.mediaUrl)}\" alt=\"imagem do cliente\"/>`\n          : (m.type === 'audio' && m.mediaUrl)\n            ? `<audio controls src=\"${esc(m.mediaUrl)}\"></audio>`\n            : ''\n        const cls = cli ? 'cli' : (m.role === 'agent' ? 'ag' : 'bot')\n        const tag = cli ? '' : `<div class=\"meta\">${m.role === 'agent' ? esc(m.agentName || 'Atendente') : 'Assistente'}</div>`\n        return `<div class=\"row ${cli ? '' : 'rt'}\" data-key=\"${esc(m._id)}\"><div class=\"b ${cls}\">${tag}${media}${esc(m.content || '')}</div></div>`\n      }).join('')\n\n      const dis = S.sending ? 'disabled' : ''\n      let actions = ''\n      if (st === 'fila' || st === 'bot') actions = `<button class=\"act pri assumir\" ${dis}>Assumir conversa</button>`\n      else if (st === 'humano') actions = `<button class=\"act devolver\" ${dis}>Devolver ao bot</button><button class=\"act encerrar\" ${dis}>Encerrar</button>`\n\n      const bar = mine\n        ? `<div class=\"bar\"><input class=\"in\" placeholder=\"Responder como ${esc(me)}…\" ${dis}/><button class=\"act pri send\" ${dis}>Enviar</button></div>`\n        : st === 'humano'\n          ? `<div class=\"hint\">Esta conversa está com ${esc(T.agentName || 'outro atendente')}.</div>`\n          : `<div class=\"hint\">O assistente virtual está conduzindo. Assuma a conversa para responder.</div>`\n\n      const err = S.err ? `<div class=\"err\">${esc(S.err)}</div>` : ''\n      main = `<div class=\"mhd\">\n          <div class=\"t\"><b>${esc(S.openName || 'Visitante')}</b>\n            <span>${esc(LABEL[st] || st)}${T.agentName ? ' · ' + esc(T.agentName) : ''}</span></div>\n          ${actions}\n        </div>\n        <div class=\"log\">${body}${err}</div>\n        ${bar}`\n    }\n\n    return `<div class=\"mesa\" style=\"--brand:${esc(brand)}\">\n      <div class=\"top\">\n        <b>${esc(Q.brand || 'Mesa')}</b>\n        <span class=\"sub\">Mesa de atendimento</span>\n        <div class=\"who\">Atendente:\n          <select class=\"agentsel\">\n            <option value=\"\">Selecione…</option>\n            ${agents.map((a) => `<option value=\"${esc(a.name)}\" ${a.name === me ? 'selected' : ''}>${esc(a.name)}</option>`).join('')}\n          </select>\n        </div>\n      </div>\n      <div class=\"side\"><div class=\"tabs\">${tabs}</div><div class=\"list\">${list}</div></div>\n      <div class=\"main\">${main}</div>\n    </div>`\n  },\n\n  mounted(d2) {\n    const tslug = d2.param('tenantSlug', 'demo')\n    const KEYA = 'wl_agent_' + tslug\n    try { d2.state.agent = localStorage.getItem(KEYA) || '' } catch (e) { d2.state.agent = '' }\n    d2.state.filter = 'fila'\n\n    const un = (r) => (r && typeof r === 'object' && r.data && typeof r.data === 'object' && !Array.isArray(r.data)) ? r.data : r\n    let sock = null\n    let room = null\n\n    const loadQueue = async () => {\n      try { d2.state.queue = un(await d2.rush('wlqueue').invoke({ tenantSlug: tslug })) }\n      catch (e) { d2.state.err = 'Falha ao carregar a fila' }\n    }\n    const loadThread = async () => {\n      if (!d2.state.open) return\n      try { d2.state.thread = un(await d2.rush('wlthread').invoke({ threadId: d2.state.open, tenantSlug: tslug })) }\n      catch (e) { d2.state.err = 'Falha ao carregar a conversa' }\n    }\n    const refresh = async () => { await loadQueue(); await loadThread() }\n\n    const open = async (tid, name) => {\n      d2.state.open = tid\n      d2.state.openName = name || 'Visitante'\n      d2.state.thread = null\n      if (sock) {\n        if (room) sock.unsubscribe(room)\n        room = 'thread-' + tid\n        sock.subscribe(room)\n      }\n      await loadThread()\n    }\n\n    d2.on('.agentsel', 'change', (e, el) => {\n      d2.state.agent = el.value\n      try { localStorage.setItem(KEYA, el.value) } catch (er) {}\n    })\n    d2.on('.tab', 'click', (e, el) => { d2.state.filter = el.dataset.f })\n    d2.on('.conv', 'click', (e, el) => {\n      const nm = el.querySelector('.nm')\n      open(el.dataset.t, nm ? nm.textContent : '')\n    })\n\n    const act = async (action) => {\n      if (!d2.state.open) return\n      if (action === 'assumir' && !d2.state.agent) { d2.state.err = 'Escolha seu nome no topo antes de assumir.'; return }\n      d2.state.sending = true\n      try {\n        await d2.rush('wlhandoff').invoke({ threadId: d2.state.open, action: action, agentName: d2.state.agent })\n        d2.state.err = null\n      } catch (e) { d2.state.err = 'Falha na ação: ' + action }\n      await refresh()\n      d2.state.sending = false\n    }\n    d2.on('.assumir', 'click', () => act('assumir'))\n    d2.on('.devolver', 'click', () => act('devolver'))\n    d2.on('.encerrar', 'click', () => act('encerrar'))\n\n    const send = async () => {\n      const el = d2.$('.in')\n      if (!el) return\n      const v = (el.value || '').trim()\n      if (!v || !d2.state.open) return\n      el.value = ''\n      d2.state.sending = true\n      try {\n        await d2.rush('wlagent').invoke({ threadId: d2.state.open, agentName: d2.state.agent, message: v })\n        d2.state.err = null\n      } catch (e) { d2.state.err = 'Falha ao enviar' }\n      await refresh()\n      d2.state.sending = false\n    }\n    d2.on('.send', 'click', send)\n    d2.on('.in', 'keydown', (e) => { if (e.key === 'Enter') send() })\n\n    try {\n      sock = d2.socket('wlsocket')\n      sock.subscribe('fila-' + tslug)\n      sock.onMessage(() => { refresh() })\n    } catch (e) {}\n\n    loadQueue()\n  },\n\n  updated(d2) {\n    const log = d2.$('.log')\n    if (log) log.scrollTop = log.scrollHeight\n  }\n}"}},"tenantSlug":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"tenantSlug"}}}}}}}}}},{"_id":"6a691913e2a83c84c3685e5d","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"wladminui","package":"fernando-playground","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 esc = (s) => String(s == null ? '' : s).replace(/[&<>\"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' }[c]))\nconst cut = (s, n) => { const t = String(s == null ? '' : s); return t.length > n ? t.slice(0, n) + '…' : t }\n\nexport default {\n  css: `\n  .adm{--brand:#4F46E5;display:grid;grid-template-columns:380px 1fr;grid-template-rows:auto 1fr;height:100%;\n       font:14px/1.5 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;background:#f4f4f8;color:#16161a}\n  .top{grid-column:1/-1;display:flex;align-items:center;gap:12px;padding:12px 20px;background:#fff;border-bottom:1px solid #e6e6ef}\n  .top b{font-size:15px}\n  .top .sub{font-size:12px;color:#7a7a8c;flex:1}\n  .pane{overflow-y:auto;min-height:0;padding:18px}\n  .left{border-right:1px solid #e6e6ef;background:#fff}\n  .card{background:#fff;border:1px solid #e9e9f0;border-radius:12px;padding:16px;margin-bottom:16px}\n  .left .card{border:0;padding:0}\n  h4{margin:0 0 4px;font-size:13px;text-transform:uppercase;letter-spacing:.05em;color:#8a8a9a}\n  .hlp{font-size:12px;color:#8a8a9a;margin:0 0 12px}\n  label{display:block;font-size:12px;color:#6b6b7b;margin:10px 0 4px;font-weight:600}\n  input,select,textarea{width:100%;box-sizing:border-box;padding:9px 11px;border:1px solid #dcdce6;border-radius:8px;\n       font:inherit;font-size:13px;background:#fff;outline:0}\n  input:focus,select:focus,textarea:focus{border-color:var(--brand)}\n  textarea{min-height:120px;resize:vertical;line-height:1.5}\n  .btn{border:1px solid #dcdce6;background:#fff;color:#444;border-radius:8px;padding:9px 14px;font-size:13px;cursor:pointer}\n  .btn:hover{border-color:var(--brand);color:var(--brand)}\n  .btn.pri{background:var(--brand);color:#fff;border-color:var(--brand);font-weight:600}\n  .btn.dg{color:#b91c1c;border-color:#f0c8c8}\n  .btn.dg:hover{background:#fee2e2;border-color:#ef4444;color:#991b1b}\n  .btn[disabled]{opacity:.45;cursor:default}\n  .btns{display:flex;gap:8px;margin-top:16px;flex-wrap:wrap}\n  .persona{background:#f6f5ff;border:1px solid #e4e0ff;border-radius:10px;padding:12px;font-size:12px;color:#4a4a63;margin-top:12px}\n  .persona b{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#7c74c8;margin-bottom:4px}\n  .persona p{margin:0 0 8px}\n  .hd2{display:flex;align-items:center;gap:10px;margin-bottom:12px}\n  .hd2 h4{margin:0;flex:1}\n  .art{display:flex;align-items:center;gap:10px;padding:11px 13px;background:#fff;border:1px solid #ececf3;border-radius:10px;margin-bottom:7px;cursor:pointer}\n  .art:hover{border-color:var(--brand)}\n  .art[data-on]{border-color:var(--brand);background:#f6f5ff}\n  .art .t{flex:1;min-width:0}\n  .art .t b{display:block;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n  .art .t span{font-size:12px;color:#8a8a9a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}\n  .st{font-size:10px;padding:2px 8px;border-radius:999px;font-weight:700;flex:0 0 auto}\n  .st.published{background:#dcfce7;color:#166534}\n  .st.draft{background:#fef3c7;color:#92400e}\n  .ok{background:#dcfce7;color:#166534;padding:8px 12px;border-radius:8px;font-size:12px;margin-bottom:12px}\n  .err{background:#fee2e2;color:#991b1b;padding:8px 12px;border-radius:8px;font-size:12px;margin-bottom:12px}\n  .empty{color:#9a9aab;font-size:13px;padding:20px 0}\n  `,\n\n  render(d2) {\n    const S = d2.state\n    const D = S.data\n    const brand = (D && D.tenant && D.tenant.primaryColor) || '#4F46E5'\n    if (!D) return `<div class=\"adm\" style=\"--brand:${esc(brand)}\"><div class=\"pane\">Carregando painel…</div></div>`\n\n    const t = D.tenant || {}\n    const segs = D.segments || []\n    const seg = segs.find((s) => s.slug === t.segmentSlug) || {}\n    const faqs = (D.faqs || []).filter((f) => f.segmentSlug === t.segmentSlug)\n    const ed = S.editing\n\n    const msg = S.ok ? `<div class=\"ok\">${esc(S.ok)}</div>` : (S.err ? `<div class=\"err\">${esc(S.err)}</div>` : '')\n    const dis = S.saving ? 'disabled' : ''\n\n    const left = `<div class=\"card\">\n      <h4>Ramo de atuação</h4>\n      <p class=\"hlp\">Trocar o ramo muda a persona, os assuntos permitidos, os botões e a base que o assistente enxerga — sem republicar nada.</p>\n      <select class=\"segsel\" ${dis}>\n        ${segs.map((s) => `<option value=\"${esc(s.slug)}\" ${s.slug === t.segmentSlug ? 'selected' : ''}>${esc(s.name)}</option>`).join('')}\n      </select>\n      <div class=\"persona\">\n        <b>Persona ativa</b><p>${esc(seg.personaPrompt || '—')}</p>\n        <b>Assuntos bloqueados</b><p>${esc(seg.blockedTopics || '—')}</p>\n      </div>\n    </div>\n    <div class=\"card\">\n      <h4>Identidade da marca</h4>\n      <label>Nome</label><input class=\"t-name\" value=\"${esc(t.name || '')}\" ${dis}/>\n      <label>Saudação</label><input class=\"t-greet\" value=\"${esc(t.greeting || '')}\" ${dis}/>\n      <label>Cor primária</label><input class=\"t-color\" value=\"${esc(t.primaryColor || '')}\" ${dis}/>\n      <label>Bloqueios extras desta marca</label><input class=\"t-block\" value=\"${esc(t.extraBlockedTopics || '')}\" placeholder=\"ex: nomes de fornecedores\" ${dis}/>\n      <div class=\"btns\"><button class=\"btn pri t-save\" ${dis}>Salvar marca</button></div>\n    </div>`\n\n    const list = faqs.length ? faqs.map((f) => `\n      <div class=\"art\" data-key=\"${esc(f._id)}\" data-id=\"${esc(f._id)}\" ${ed && ed._id === f._id ? 'data-on' : ''}>\n        <div class=\"t\"><b>${esc(f.title)}</b><span>${esc(cut(f.content, 78))}</span></div>\n        <span class=\"st ${esc(f.status)}\">${f.status === 'published' ? 'publicado' : 'rascunho'}</span>\n      </div>`).join('') : `<div class=\"empty\">Nenhum artigo neste ramo ainda.</div>`\n\n    let form = ''\n    if (ed) {\n      const isNew = !ed._id\n      form = `<div class=\"card\" data-key=\"form-${esc(ed._id || 'new')}\">\n        <div class=\"hd2\"><h4>${isNew ? 'Novo artigo' : 'Editando artigo'}</h4></div>\n        <label>Título</label><input class=\"f-title\" value=\"${esc(ed.title || '')}\" ${dis}/>\n        <label>Categoria</label><input class=\"f-cat\" value=\"${esc(ed.category || '')}\" ${dis}/>\n        <label>Situação</label>\n        <select class=\"f-status\" ${dis}>\n          <option value=\"published\" ${ed.status === 'published' ? 'selected' : ''}>Publicado — o assistente usa</option>\n          <option value=\"draft\" ${ed.status !== 'published' ? 'selected' : ''}>Rascunho — o assistente ignora</option>\n        </select>\n        <label>Conteúdo</label><textarea class=\"f-content\" ${dis}>${esc(ed.content || '')}</textarea>\n        <div class=\"btns\">\n          <button class=\"btn pri f-save\" ${dis}>${S.saving ? 'Salvando…' : 'Salvar artigo'}</button>\n          <button class=\"btn f-cancel\" ${dis}>Cancelar</button>\n          ${isNew ? '' : `<button class=\"btn dg f-del\" ${dis}>Excluir</button>`}\n        </div>\n      </div>`\n    }\n\n    return `<div class=\"adm\" style=\"--brand:${esc(brand)}\">\n      <div class=\"top\"><b>${esc(t.name || 'Painel')}</b><span class=\"sub\">Curadoria e configuração</span></div>\n      <div class=\"pane left\">${left}</div>\n      <div class=\"pane\">\n        ${msg}\n        <div class=\"card\">\n          <div class=\"hd2\"><h4>Base de conhecimento — ${esc(seg.name || '')}</h4>\n            <button class=\"btn pri f-new\" ${dis}>Novo artigo</button></div>\n          <p class=\"hlp\">O assistente só responde com o que estiver publicado aqui. Rascunho some da resposta na hora.</p>\n          ${list}\n        </div>\n        ${form}\n      </div>\n    </div>`\n  },\n\n  mounted(d2) {\n    const tslug = d2.param('tenantSlug', 'demo')\n    const un = (r) => (r && typeof r === 'object' && r.data && typeof r.data === 'object' && !Array.isArray(r.data)) ? r.data : r\n\n    const call = async (payload, okMsg) => {\n      d2.state.saving = true\n      try {\n        d2.state.data = un(await d2.rush('wladmin').invoke(Object.assign({ tenantSlug: tslug }, payload)))\n        d2.state.err = null\n        if (okMsg) d2.state.ok = okMsg\n      } catch (e) { d2.state.err = 'Falha: ' + (e && e.message ? e.message : e); d2.state.ok = null }\n      d2.state.saving = false\n    }\n\n    const readForm = () => ({\n      title: (d2.$('.f-title') || {}).value || '',\n      category: (d2.$('.f-cat') || {}).value || '',\n      status: (d2.$('.f-status') || {}).value || 'draft',\n      content: (d2.$('.f-content') || {}).value || ''\n    })\n\n    d2.on('.segsel', 'change', (e, el) => {\n      call({ action: 'setSegment', segmentSlug: el.value }, 'Ramo alterado. O assistente já está respondendo com a nova persona.')\n      d2.state.editing = null\n    })\n\n    d2.on('.t-save', 'click', () => call({\n      action: 'saveTenant',\n      name: (d2.$('.t-name') || {}).value || '',\n      greeting: (d2.$('.t-greet') || {}).value || '',\n      primaryColor: (d2.$('.t-color') || {}).value || '',\n      extraBlockedTopics: (d2.$('.t-block') || {}).value || ''\n    }, 'Marca atualizada.'))\n\n    d2.on('.f-new', 'click', () => { d2.state.ok = null; d2.state.editing = { title: '', category: '', content: '', status: 'published' } })\n    d2.on('.f-cancel', 'click', () => { d2.state.editing = null })\n    d2.on('.art', 'click', (e, el) => {\n      const f = ((d2.state.data || {}).faqs || []).find((x) => x._id === el.dataset.id)\n      if (f) { d2.state.ok = null; d2.state.editing = Object.assign({}, f) }\n    })\n\n    d2.on('.f-save', 'click', async () => {\n      const ed = d2.state.editing\n      if (!ed) return\n      const f = readForm()\n      if (!f.title.trim() || !f.content.trim()) { d2.state.err = 'Título e conteúdo são obrigatórios.'; return }\n      const seg = ((d2.state.data || {}).tenant || {}).segmentSlug\n      if (ed._id) await call(Object.assign({ action: 'updateFaq', faqId: ed._id }, f), 'Artigo salvo.')\n      else await call(Object.assign({ action: 'createFaq', segmentSlug: seg }, f), 'Artigo criado.')\n      d2.state.editing = null\n    })\n\n    d2.on('.f-del', 'click', async () => {\n      const ed = d2.state.editing\n      if (!ed || !ed._id) return\n      const yes = await d2.dialog.confirm('Excluir \"' + ed.title + '\" da base?')\n      if (!yes) return\n      await call({ action: 'deleteFaq', faqId: ed._id }, 'Artigo excluído.')\n      d2.state.editing = null\n    })\n\n    call({ action: 'load' })\n  }\n}"}},"tenantSlug":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"tenantSlug"}}}}}}}}}},{"_id":"6a6a944c848ce022d78ecf02","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"geo-mapa-territorio","package":"fernando-playground","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 PALETA = ['#3b82f6', '#10b981', '#f59e0b', '#06b6d4', '#a855f7', '#ef4444', '#ec4899', '#84cc16']\n\nconst esc = (s) => String(s == null ? '' : s).replace(/[&<>\"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' }[c]))\n\nconst hm = (m) => {\n  const n = Math.round(Number(m) || 0)\n  if (n < 60) return n + ' min'\n  const h = Math.floor(n / 60), r = n % 60\n  return r ? h + 'h' + String(r).padStart(2, '0') : h + 'h'\n}\n\nconst projetar = (pts, W, H, PAD) => {\n  const lats = pts.map((p) => p.lat), lons = pts.map((p) => p.lon)\n  const latMin = Math.min.apply(null, lats), latMax = Math.max.apply(null, lats)\n  const lonMin = Math.min.apply(null, lons), lonMax = Math.max.apply(null, lons)\n  const kx = Math.cos(((latMin + latMax) / 2) * Math.PI / 180)\n  const spanX = Math.max(0.0001, (lonMax - lonMin) * kx)\n  const spanY = Math.max(0.0001, latMax - latMin)\n  const s = Math.min((W - 2 * PAD) / spanX, (H - 2 * PAD) / spanY)\n  const offX = PAD + ((W - 2 * PAD) - spanX * s) / 2\n  const offY = PAD + ((H - 2 * PAD) - spanY * s) / 2\n  return { x: (lon) => offX + (lon - lonMin) * kx * s, y: (lat) => offY + (latMax - lat) * s }\n}\n\n// Contorno PR/SC/RS — malha oficial IBGE (v3/malhas, qualidade intermediaria),\n// simplificada por Douglas-Peucker com tolerancia 0.055 graus (~6 km, ~4 px na escala da tela).\nconst CONTORNOS = [[[-49.13,-24.682],[-49.312,-24.663],[-49.201,-24.345],[-49.608,-23.839],[-49.549,-23.704],[-49.654,-23.212],[-49.912,-23.051],[-49.986,-22.897],[-50.796,-22.951],[-50.895,-22.794],[-51.265,-22.668],[-51.718,-22.669],[-52.109,-22.516],[-52.223,-22.675],[-52.589,-22.566],[-52.697,-22.627],[-52.972,-22.57],[-53.547,-22.89],[-53.762,-23.359],[-53.987,-23.467],[-54.102,-23.952],[-54.334,-24.116],[-54.258,-24.355],[-54.437,-24.941],[-54.425,-25.153],[-54.619,-25.451],[-54.593,-25.592],[-54.425,-25.659],[-54.116,-25.498],[-53.862,-25.659],[-53.842,-25.932],[-53.557,-26.287],[-53.284,-26.246],[-53.093,-26.392],[-52.738,-26.342],[-52.186,-26.445],[-51.878,-26.598],[-51.495,-26.587],[-51.411,-26.717],[-51.239,-26.628],[-51.293,-26.434],[-51.203,-26.298],[-50.716,-26.245],[-50.628,-26.063],[-50.513,-26.026],[-50.322,-26.134],[-50.247,-26.03],[-49.934,-26.019],[-49.55,-26.233],[-49.167,-26.001],[-48.595,-25.977],[-48.436,-25.649],[-48.023,-25.231],[-48.182,-25.205],[-48.24,-24.991],[-48.412,-24.98],[-48.556,-25.084],[-48.502,-24.746],[-48.572,-24.691],[-49.13,-24.682]],[[-52.443,-26.438],[-52.738,-26.342],[-53.093,-26.392],[-53.284,-26.246],[-53.639,-26.251],[-53.74,-26.742],[-53.672,-26.943],[-53.837,-27.168],[-53.518,-27.202],[-53.381,-27.094],[-53.296,-27.132],[-53.322,-27.21],[-53.071,-27.157],[-53.048,-27.084],[-53,-27.207],[-52.851,-27.17],[-52.691,-27.284],[-52.432,-27.218],[-52.3,-27.319],[-52.261,-27.257],[-51.837,-27.522],[-51.633,-27.489],[-51.315,-27.675],[-50.924,-27.972],[-50.859,-28.14],[-50.544,-28.427],[-49.748,-28.494],[-49.703,-28.629],[-49.961,-28.772],[-49.958,-29.068],[-50.146,-29.198],[-50.041,-29.355],[-50.115,-29.262],[-49.958,-29.199],[-49.713,-29.326],[-49.376,-28.956],[-48.746,-28.505],[-48.596,-27.851],[-48.478,-27.762],[-48.374,-27.472],[-48.615,-27.272],[-48.482,-27.153],[-48.614,-27.1],[-48.571,-27.009],[-48.688,-26.671],[-48.504,-26.237],[-48.595,-25.977],[-49.167,-26.001],[-49.55,-26.233],[-49.934,-26.019],[-50.247,-26.03],[-50.322,-26.134],[-50.513,-26.026],[-50.628,-26.063],[-50.716,-26.245],[-51.203,-26.298],[-51.293,-26.434],[-51.239,-26.628],[-51.411,-26.717],[-51.512,-26.582],[-51.878,-26.598],[-52.186,-26.445],[-52.443,-26.438]],[[-53.947,-31.955],[-53.726,-32.098],[-53.584,-32.451],[-53.167,-32.649],[-52.766,-32.426],[-52.777,-32.231],[-52.623,-32.146],[-52.688,-32.319],[-52.588,-32.538],[-52.73,-32.847],[-52.832,-32.915],[-53.002,-32.765],[-53.147,-32.819],[-53.174,-33.003],[-53.432,-33.162],[-53.438,-33.516],[-53.534,-33.651],[-53.37,-33.744],[-52.777,-33.283],[-52.495,-32.867],[-52.293,-32.338],[-52.097,-32.162],[-52.194,-32.058],[-52.113,-31.978],[-52.228,-32.036],[-52.139,-31.918],[-52.226,-31.752],[-52.031,-31.692],[-51.919,-31.311],[-51.639,-31.271],[-51.629,-31.153],[-51.446,-31.089],[-51.498,-30.918],[-51.39,-30.88],[-51.381,-30.643],[-51.293,-30.708],[-51.265,-30.48],[-51.122,-30.397],[-51.295,-30.303],[-51.301,-30.054],[-50.93,-30.436],[-50.915,-30.326],[-50.673,-30.297],[-50.598,-30.196],[-50.574,-30.481],[-50.649,-30.454],[-50.619,-30.361],[-50.729,-30.368],[-50.721,-30.78],[-50.968,-30.898],[-50.995,-31.048],[-51.177,-31.08],[-51.237,-31.457],[-51.428,-31.492],[-51.613,-31.729],[-51.86,-31.87],[-52.098,-31.836],[-52.005,-31.951],[-52.077,-32.143],[-50.879,-31.227],[-50.334,-30.501],[-50.018,-29.772],[-49.713,-29.326],[-49.958,-29.199],[-50.115,-29.262],[-50.041,-29.355],[-50.145,-29.27],[-49.958,-29.068],[-49.961,-28.772],[-49.703,-28.629],[-49.748,-28.494],[-50.544,-28.427],[-50.859,-28.14],[-50.924,-27.972],[-51.483,-27.563],[-51.701,-27.48],[-51.837,-27.522],[-52.261,-27.257],[-52.3,-27.319],[-52.432,-27.218],[-52.691,-27.284],[-52.851,-27.17],[-52.971,-27.218],[-53.048,-27.084],[-53.071,-27.157],[-53.322,-27.21],[-53.296,-27.132],[-53.381,-27.094],[-53.518,-27.202],[-53.947,-27.151],[-54.284,-27.448],[-54.817,-27.536],[-54.949,-27.779],[-55.083,-27.797],[-55.025,-27.835],[-55.203,-27.858],[-55.438,-28.086],[-55.771,-28.241],[-55.692,-28.416],[-55.871,-28.357],[-55.884,-28.479],[-56.3,-28.808],[-56.43,-29.079],[-56.589,-29.12],[-56.822,-29.49],[-57.298,-29.835],[-57.352,-30.008],[-57.594,-30.179],[-57.568,-30.252],[-57.204,-30.285],[-57.078,-30.091],[-56.85,-30.089],[-56.024,-30.786],[-56.005,-31.082],[-55.871,-31.072],[-55.579,-30.84],[-55.247,-31.252],[-55.074,-31.332],[-55.002,-31.269],[-54.836,-31.442],[-54.587,-31.456],[-54.455,-31.653],[-53.947,-31.955]]]\n\n// A projecao ancora nos limites da malha, nao nos vendedores — senao o contorno vaza do viewBox.\nconst PONTOS_MAPA = CONTORNOS.reduce((acc, anel) => acc.concat(anel), []).map((c) => ({ lon: c[0], lat: c[1] }))\n\nexport default {\n  css: `\n  .app{display:grid;grid-template-columns:1fr 420px;height:100%;box-sizing:border-box;background:#f5f6f8;color:#161a1f;\n       font:14px/1.45 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}\n  .vazio{display:flex;align-items:center;justify-content:center;height:100%;color:#8a929c;font:15px sans-serif}\n  .mapa{position:relative;overflow:hidden;background:#dbe4ee}\n  .mapa svg{width:100%;height:100%;display:block}\n  .uf{fill:#fafbfc;stroke:#a7b4c3;stroke-width:1.1;stroke-linejoin:round}\n  .pin text{font:11px sans-serif;fill:#5b646f;pointer-events:none}\n  .pin circle{stroke:#fff;stroke-width:1.5}\n  .pin.on circle{stroke:#111;stroke-width:2.5}\n  .pin.on text{fill:#111;font-weight:700}\n  .pin.off{opacity:.28}\n  .spoke{stroke-width:1.6;stroke-dasharray:4 4;opacity:.75}\n  .rota{fill:none;stroke-width:2.6;opacity:.92;stroke-linejoin:round;stroke-linecap:round}\n  .alvo text{font:11px sans-serif;font-weight:700;fill:#111}\n  .legenda{position:absolute;left:14px;bottom:14px;background:rgba(255,255,255,.94);border:1px solid #dfe4ea;\n           border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:5px;font-size:11px}\n  .legenda i{display:inline-block;width:9px;height:9px;border-radius:3px;margin-right:7px}\n  .painel{background:#fff;border-left:1px solid #e1e5ea;display:flex;flex-direction:column;overflow-y:auto}\n  .cab{padding:18px 18px 12px;border-bottom:1px solid #eef0f3}\n  .cab h1{margin:0;font-size:16px;font-weight:700}\n  .cab p{margin:4px 0 0;font-size:12px;color:#8a929c}\n  .bloco{padding:14px 18px;border-bottom:1px solid #eef0f3}\n  .bloco h2{margin:0 0 10px;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:#98a1ab}\n  .lista{display:flex;flex-direction:column;gap:5px}\n  .dot{display:inline-block;width:9px;height:9px;border-radius:3px;flex:none}\n  .grp{display:flex;align-items:center;gap:9px;width:100%;text-align:left;padding:8px 10px;border:1px solid #e6eaee;\n       border-radius:9px;background:#fff;cursor:pointer;font:inherit;color:inherit}\n  .grp:hover{background:#f7f9fb}\n  .grp.on{border-color:#111;background:#f2f4f7}\n  .gn{flex:1;font-size:13px}\n  .gc{font-size:11px;color:#98a1ab;font-variant-numeric:tabular-nums}\n  .vd{display:flex;align-items:center;gap:9px;padding:6px 10px;border-radius:8px;cursor:pointer;font-size:13px}\n  .vd:hover{background:#f7f9fb}\n  .cid{margin-left:auto;font-size:11px;color:#98a1ab}\n  .acao{margin-top:12px;width:100%;padding:11px;border:0;border-radius:9px;background:#111;color:#fff;\n        font:600 13px inherit;cursor:pointer}\n  .acao[disabled]{background:#d5dae0;color:#8a929c;cursor:default}\n  .dica{padding:22px 18px;color:#98a1ab;font-size:13px}\n  .origem{background:#f5f7f9;border:1px solid #e6eaee;border-radius:9px;padding:10px;font-size:12px;\n          line-height:1.6;margin-bottom:10px}\n  .rk{border:1px solid #e6eaee;border-radius:10px;padding:10px;cursor:pointer;background:#fff}\n  .rk:hover{background:#f7f9fb}\n  .rk.on{border-color:#111;box-shadow:0 0 0 1px #111}\n  .rk.ruim{opacity:.62}\n  .rk-top{display:flex;align-items:center;gap:8px}\n  .pos{font-size:11px;font-weight:700;color:#98a1ab;width:14px}\n  .rn{font-size:13px;font-weight:600;flex:1}\n  .tag{font-size:10px;font-weight:700;padding:2px 6px;border-radius:5px;letter-spacing:.02em}\n  .tag.ok{background:#dcfce7;color:#15803d}\n  .tag.no{background:#fee2e2;color:#b91c1c}\n  .tag.full{background:#fef3c7;color:#a16207}\n  .rk-m{display:flex;flex-wrap:wrap;gap:4px 12px;margin-top:7px;font-size:11px;color:#5b646f}\n  .rk-v{margin-top:6px;font-size:11px;color:#8a929c}\n  .mv{margin-top:9px;padding:7px 12px;border:1px solid #111;border-radius:7px;background:#fff;color:#111;\n      font:600 11px inherit;cursor:pointer}\n  .mv:hover{background:#111;color:#fff}\n  `,\n\n  render(d2) {\n    const st = d2.state\n    if (st.erro) return `<div class=\"vazio\">Falha: ${esc(st.erro)}</div>`\n    const dados = st.dados || d2.param('dadosDemo', null)\n    if (!dados) return `<div class=\"vazio\">Carregando território…</div>`\n\n    const grupos = dados.grupos || []\n    const vendedores = dados.vendedores || []\n    if (!vendedores.length) return `<div class=\"vazio\">Sem vendedores cadastrados.</div>`\n\n    const corDe = {}\n    grupos.forEach((g, i) => { corDe[g.codigo] = PALETA[i % PALETA.length] })\n\n    const sel = st.sel || []\n    const grupoSel = st.grupoSel || null\n    const rank = st.rank || d2.param('rankDemo', null)\n    const destaque = st.destaque || d2.param('destaqueDemo', null)\n\n    const W = 760, H = 1000\n    const pr = projetar(PONTOS_MAPA, W, H, 24)\n    const terra = CONTORNOS.map((anel) =>\n      `<path class=\"uf\" d=\"M${anel.map((c) => pr.x(c[0]).toFixed(1) + ',' + pr.y(c[1]).toFixed(1)).join('L')}Z\"/>`\n    ).join('')\n\n    let alvo = null, membrosAlvo = []\n    if (rank && destaque) {\n      const linha = (rank.ranking || []).filter((r) => r.codigo === destaque)[0]\n      if (linha) {\n        const ref = vendedores.filter((v) => v.key === linha.reuniaoDepois)[0]\n        if (ref) alvo = { key: linha.reuniaoDepois, lat: ref.lat, lon: ref.lon, cidade: ref.cidade, cor: corDe[destaque] || '#111' }\n        membrosAlvo = vendedores.filter((v) => v.grupo === destaque || sel.indexOf(v.codigo) >= 0)\n      }\n    }\n\n    // Traçado real da estrada quando o OSRM já respondeu; reta tracejada enquanto não.\n    const rotas = st.rotas || d2.param('rotasDemo', null) || {}\n    const linhas = alvo ? membrosAlvo.map((v) => {\n      const geo = rotas[alvo.key + '|' + v.key]\n      if (geo && geo.length > 1) {\n        return `<path class=\"rota\" stroke=\"${alvo.cor}\" d=\"M${geo.map((c) => pr.x(c[0]).toFixed(1) + ',' + pr.y(c[1]).toFixed(1)).join('L')}\"/>`\n      }\n      return `<line class=\"spoke\" stroke=\"${alvo.cor}\" x1=\"${pr.x(v.lon).toFixed(1)}\" y1=\"${pr.y(v.lat).toFixed(1)}\" x2=\"${pr.x(alvo.lon).toFixed(1)}\" y2=\"${pr.y(alvo.lat).toFixed(1)}\"/>`\n    }).join('') : ''\n\n    const focado = grupoSel || destaque\n    const bolas = vendedores.map((v) => {\n      const escolhido = sel.indexOf(v.codigo) >= 0\n      const relevante = escolhido || (grupoSel && v.grupo === grupoSel) || (destaque && v.grupo === destaque)\n      const apagado = focado && !relevante\n      const cls = 'pin' + (escolhido ? ' on' : '') + (apagado ? ' off' : '')\n      const X = pr.x(v.lon), Y = pr.y(v.lat)\n      const naDireita = X > W * 0.72\n      const rotulo = (!focado || relevante)\n        ? `<text x=\"${(naDireita ? X - 11 : X + 11).toFixed(1)}\" y=\"${(Y + 4).toFixed(1)}\" text-anchor=\"${naDireita ? 'end' : 'start'}\">${esc(v.cidade)}</text>`\n        : ''\n      return `<g class=\"${cls}\" data-key=\"${v.codigo}\"><circle cx=\"${X.toFixed(1)}\" cy=\"${Y.toFixed(1)}\" r=\"${escolhido ? 9 : 6}\" fill=\"${corDe[v.grupo] || '#94a3b8'}\"/>${rotulo}</g>`\n    }).join('')\n\n    const marca = alvo ? `<g class=\"alvo\"><circle cx=\"${pr.x(alvo.lon).toFixed(1)}\" cy=\"${pr.y(alvo.lat).toFixed(1)}\" r=\"16\" fill=\"none\" stroke=\"${alvo.cor}\" stroke-width=\"3\"/><text x=\"${pr.x(alvo.lon).toFixed(1)}\" y=\"${(pr.y(alvo.lat) - 23).toFixed(1)}\" text-anchor=\"middle\">reunião: ${esc(alvo.cidade)}</text></g>` : ''\n\n    const legenda = grupos.map((g) => `<span><i style=\"background:${corDe[g.codigo]}\"></i>${esc(g.nome)}</span>`).join('')\n\n    const listaGrupos = grupos.map((g) => {\n      const n = vendedores.filter((v) => v.grupo === g.codigo).length\n      return `<button class=\"grp${grupoSel === g.codigo ? ' on' : ''}\" data-key=\"${g.codigo}\" data-g=\"${g.codigo}\"><span class=\"dot\" style=\"background:${corDe[g.codigo]}\"></span><span class=\"gn\">${esc(g.nome)}</span><span class=\"gc\">${n}/${g.cap}</span></button>`\n    }).join('')\n\n    const blocoVend = grupoSel ? `<div class=\"bloco\"><h2>2 · quem vai sair</h2><div class=\"lista\">${\n      vendedores.filter((v) => v.grupo === grupoSel).map((v) =>\n        `<label class=\"vd\" data-key=\"${v.codigo}\"><input type=\"checkbox\" class=\"vsel\" data-v=\"${v.codigo}\" ${sel.indexOf(v.codigo) >= 0 ? 'checked' : ''}/><span>${esc(v.nome)}</span><span class=\"cid\">${esc(v.cidade)}/${esc(v.uf)}</span></label>`\n      ).join('')\n    }</div><button class=\"acao btn-sugerir\" ${sel.length && !st.ocupado ? '' : 'disabled'}>${st.ocupado ? 'Calculando…' : 'Sugerir destinos (' + sel.length + ')'}</button></div>`\n      : `<div class=\"dica\">Escolha um grupo de origem para começar.</div>`\n\n    let blocoRank = ''\n    if (rank && rank.grupoOrigem) {\n      const o = rank.grupoOrigem\n      const linhasRank = (rank.ranking || []).map((r, i) =>\n        `<div class=\"rk${destaque === r.codigo ? ' on' : ''}${r.viavel ? '' : ' ruim'}\" data-key=\"${r.codigo}\" data-g=\"${r.codigo}\">\n          <div class=\"rk-top\"><span class=\"pos\">${i + 1}</span><span class=\"dot\" style=\"background:${corDe[r.codigo]}\"></span><span class=\"rn\">${esc(r.nome)}</span>${r.viavel ? '<span class=\"tag ok\">viável</span>' : '<span class=\"tag no\">acima do teto</span>'}${r.cheio ? '<span class=\"tag full\">lotado</span>' : ''}</div>\n          <div class=\"rk-m\"><span>reunião: <b>${esc(r.reuniaoDepois)}</b></span><span>média ${hm(r.medioAntesMin)} → <b>${hm(r.medioDepoisMin)}</b></span><span>pior: <b>${hm(r.maxDepoisMin)}</b></span><span>${esc(r.ocupacao)}</span></div>\n          <div class=\"rk-v\">${(r.temposMovidos || []).map((t) => esc(t.nome) + ': ' + hm(t.min)).join(' · ')}</div>\n          <button class=\"mv btn-mover\" data-g=\"${r.codigo}\">Mover para cá</button>\n        </div>`\n      ).join('')\n      blocoRank = `<div class=\"bloco\"><h2>3 · melhores destinos (teto ${hm(rank.limiteMin)})</h2>\n        <div class=\"origem\">Origem fica com <b>${o.qtdDepois}</b> vendedores — média ${hm(o.medioAntesMin)} → <b>${hm(o.medioDepoisMin)}</b> · pior caso ${hm(o.maxAntesMin)} → <b>${hm(o.maxDepoisMin)}</b></div>\n        <div class=\"lista\">${linhasRank}</div></div>`\n    }\n\n    return `<div class=\"app\">\n      <div class=\"mapa\"><svg viewBox=\"0 0 ${W} ${H}\" preserveAspectRatio=\"xMidYMid meet\">${terra}${linhas}${bolas}${marca}</svg><div class=\"legenda\">${legenda}</div></div>\n      <div class=\"painel\">\n        <div class=\"cab\"><h1>Território comercial · Região Sul</h1><p>${vendedores.length} vendedores · ${grupos.length} grupos · ${(dados.setores || []).length} setores · tempos reais de estrada (OSRM)</p></div>\n        <div class=\"bloco\"><h2>1 · grupo de origem</h2><div class=\"lista\">${listaGrupos}</div></div>\n        ${blocoVend}\n        ${blocoRank}\n      </div>\n    </div>`\n  },\n\n  mounted(d2) {\n    const carregar = async () => {\n      try {\n        const r = await d2.rush('geo-dados').invoke({})\n        d2.state.dados = (r && r.data) ? r.data : r\n        d2.state.erro = null\n      } catch (e) { d2.state.erro = (e && e.message) || String(e) }\n    }\n    carregar()\n\n    // Busca a geometria real das estradas do grupo destacado. Cache por sessão em d2.state.rotas.\n    const sincronizarRotas = async (rank, dest) => {\n      const dados = d2.state.dados\n      if (!dados || !rank || !dest) return\n      const linha = (rank.ranking || []).filter((r) => r.codigo === dest)[0]\n      if (!linha) return\n      const destino = linha.reuniaoDepois\n      const sel = d2.state.sel || []\n      const origens = (dados.vendedores || [])\n        .filter((v) => v.grupo === dest || sel.indexOf(v.codigo) >= 0)\n        .map((v) => v.key)\n      const cache = d2.state.rotas || {}\n      const faltam = origens.filter((o, i) => o !== destino && !cache[destino + '|' + o] && origens.indexOf(o) === i)\n      if (!faltam.length) return\n      try {\n        const r = await d2.rush('geo-rotas').invoke({ destino: destino, origens: faltam })\n        const dat = (r && r.data) ? r.data : r\n        const lista = (dat && dat.rotas) ? dat.rotas : []\n        const novo = Object.assign({}, d2.state.rotas || {})\n        lista.forEach((x) => { novo[destino + '|' + x.origem] = x.coords })\n        d2.state.rotas = novo\n      } catch (e) {\n        // sem geometria a tela continua funcionando com a reta tracejada\n      }\n    }\n\n    d2.on('.grp', 'click', (e, el) => {\n      d2.setState({ grupoSel: el.dataset.g, sel: [], rank: null, destaque: null })\n    })\n\n    d2.on('.vsel', 'change', (e, el) => {\n      const c = el.dataset.v\n      const atual = d2.state.sel || []\n      d2.setState({\n        sel: atual.indexOf(c) >= 0 ? atual.filter((x) => x !== c) : atual.concat([c]),\n        rank: null,\n        destaque: null\n      })\n    })\n\n    d2.on('.btn-sugerir', 'click', async () => {\n      const sel = d2.state.sel || []\n      if (!sel.length) return\n      d2.state.ocupado = true\n      try {\n        const r = await d2.rush('geo-sugerir-grupos').invoke({ vendedores: sel, limiteMin: Number(d2.param('limiteMin', 180)) })\n        const dat = (r && r.data) ? r.data : r\n        const top = dat && dat.ranking && dat.ranking[0] ? dat.ranking[0].codigo : null\n        d2.setState({ rank: dat, destaque: top })\n        if (top) sincronizarRotas(dat, top)\n      } catch (e) { d2.state.erro = (e && e.message) || String(e) }\n      d2.state.ocupado = false\n    })\n\n    d2.on('.rk', 'click', (e, el) => {\n      if (e.target && e.target.closest && e.target.closest('.btn-mover')) return\n      d2.state.destaque = el.dataset.g\n      sincronizarRotas(d2.state.rank, el.dataset.g)\n    })\n\n    d2.on('.btn-mover', 'click', async (e, el) => {\n      const sel = d2.state.sel || []\n      if (!sel.length) return\n      const ok = await d2.dialog.confirm('Mover ' + sel.length + ' vendedor(es) para ' + el.dataset.g + '?')\n      if (!ok) return\n      d2.state.ocupado = true\n      try {\n        await d2.rush('geo-mover').invoke({ vendedores: sel, destino: el.dataset.g })\n        await carregar()\n        d2.setState({ sel: [], rank: null, destaque: null })\n      } catch (er) { d2.state.erro = (er && er.message) || String(er) }\n      d2.state.ocupado = false\n    })\n  }\n}"}},"limiteMin":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"limiteMin"}}}},"dadosDemo":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dadosDemo"}}}},"rankDemo":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rankDemo"}}}},"destaqueDemo":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"destaqueDemo"}}}}}}}}}},{"_id":"6a738e1a1eb1bd398f5f4057","component":"data2-core-types@Component","system":"66f6c624d8a4a0c67041a910","params":{"extends":"data2-core-primitives@Block","component":"scr-app","package":"fernando-playground","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 kf(n){ n = Number(n) || 0; if (n >= 1e6) return (n / 1e6).toFixed(n >= 1e7 ? 0 : 1).replace('.', ',') + 'M'; if (n >= 1e3) return (n / 1e3).toFixed(n >= 1e4 ? 0 : 1).replace('.', ',') + 'k'; return String(n); }\nfunction cap(t){ return (t || '').charAt(0).toUpperCase() + (t || '').slice(1); }\nfunction platLabel(p){ return p === 'tiktok' ? 'TikTok' : 'Instagram'; }\nfunction platCls(p){ return p === 'tiktok' ? 'tt' : 'ig'; }\n\nfunction view(d2){\n  const st = d2.store('scr-ui');\n  const D = st.get('data');\n  if (!D) return `<div class=\"loading\">Carregando dados do CRM…</div>`;\n  const posts = (D.posts || []).slice();\n  const comentarios = D.comentarios || [];\n  const alertas = (D.alertas || []).slice();\n  const insight = st.get('insight') || (D.insights && D.insights[0]) || null;\n  const tab = st.get('tab') || 'dashboard';\n  const platF = st.get('plat') || 'todas';\n  const sel = st.get('sel');\n  const replies = st.get('replies') || {};\n  const replyLoading = st.get('replyLoading') || {};\n  const insightLoading = st.get('insightLoading');\n  const alertAtivos = alertas.filter(a => a.status !== 'resolvido');\n  const sum = (k) => posts.reduce((a, p) => a + (Number(p[k]) || 0), 0);\n  const avg = (k) => posts.length ? (posts.reduce((a, p) => a + (Number(p[k]) || 0), 0) / posts.length) : 0;\n  const kpis = [\n    { l: 'Posts (30d)', v: posts.length, s: 'Instagram + TikTok' },\n    { l: 'Curtidas', v: kf(sum('curtidas')), s: 'total no período' },\n    { l: 'Comentários', v: kf(sum('comentarios')), s: 'total no período' },\n    { l: 'Engaj. médio', v: avg('taxaEngajamento').toFixed(1).replace('.', ',') + '%', s: 'por post' },\n    { l: 'Sentimento +', v: Math.round(avg('sentimentoPositivoPct')) + '%', s: 'positivo médio' },\n    { l: 'Alertas', v: alertAtivos.length, s: 'ativos agora' },\n  ];\n  const sideNav = `\n    <div class=\"side\">\n      <div class=\"brand\">\n        <div class=\"logo\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\"><path fill=\"#fff\" d=\"M12 2l2.9 6.3 6.9.7-5.1 4.6 1.4 6.8L12 17.8 5 20.4l1.4-6.8L1.3 9l6.9-.7z\"/></svg></div>\n        <div><b>Social CRM</b><small>Botafogo · IA</small></div>\n      </div>\n      <div class=\"nav\">\n        <button class=\"nv ${tab==='dashboard'?'on':''}\" data-tab=\"dashboard\">📊 Dashboard</button>\n        <button class=\"nv ${tab==='alertas'?'on':''}\" data-tab=\"alertas\">🚨 Alertas ${alertAtivos.length?`<span class=\"badge\">${alertAtivos.length}</span>`:''}</button>\n        <button class=\"nv ${tab==='conectar'?'on':''}\" data-tab=\"conectar\">🔗 Conectar redes</button>\n        <button class=\"nv ${tab==='agenda'?'on':''}\" data-tab=\"agenda\">🗓️ Agenda</button>\n        <button class=\"nv ${tab==='comunidade'?'on':''}\" data-tab=\"comunidade\">🧑‍🤝‍🧑 Comunidade</button>\n        <button class=\"nv ${tab==='horario'?'on':''}\" data-tab=\"horario\">⏰ Melhor Horário</button>\n      </div>\n      <div class=\"foot\">POC · dados simulados<br/>Análise por IA da Data²</div>\n    </div>`;\n  let content = '';\n  if (tab === 'dashboard') {\n    const list = posts.filter(p => platF === 'todas' || p.plataforma === platF).sort((a, b) => (b.scoreDesempenho || 0) - (a.scoreDesempenho || 0));\n    const postCards = list.map(p => `\n      <div class=\"post op\" data-post=\"${p.postId}\" data-key=\"${p.postId}\">\n        <div class=\"thumb\" style=\"background-image:url('${p.thumbnailUrl}')\"><span class=\"rk\">#${p.rankPosicao}</span></div>\n        <div class=\"p-body\">\n          <div class=\"p-top\"><span class=\"tag ${platCls(p.plataforma)}\">${platLabel(p.plataforma)}</span><span class=\"tag\">${cap(p.categoriaConteudo)}</span><span class=\"score\">${p.scoreDesempenho}<small>/100</small></span></div>\n          <div class=\"cap\">${p.legenda}</div>\n          <div class=\"mets\">❤ ${kf(p.curtidas)} · 💬 ${kf(p.comentarios)} · ↗ ${kf(p.compartilhamentos)}${p.visualizacoes?` · ▶ ${kf(p.visualizacoes)}`:''} · ${String(p.taxaEngajamento).replace('.',',')}%</div>\n          <div class=\"sbar\"><i class=\"pos\" style=\"width:${p.sentimentoPositivoPct}%\"></i><i class=\"neu\" style=\"width:${p.sentimentoNeutroPct}%\"></i><i class=\"neg\" style=\"width:${p.sentimentoNegativoPct}%\"></i></div>\n        </div>\n      </div>`).join('');\n    const catMax = insight && insight.topCategorias ? Math.max(...insight.topCategorias.map(c => c.scoreMedio || 0)) : 100;\n    const insightHtml = insight ? `\n      <div class=\"panel insight\">\n        <h2>💡 O que performa melhor <span class=\"muted\">${insight.periodo || 'últimos 30 dias'}</span></h2>\n        <div class=\"resumo\">${insight.resumo}</div>\n        <ul class=\"recs\">${(insight.recomendacoes||[]).map((r,i)=>`<li><span class=\"n\">${i+1}</span><span>${r}</span></li>`).join('')}</ul>\n        <div class=\"cats\">${(insight.topCategorias||[]).map(c=>`<div class=\"cat\"><div class=\"cl2\"><span>${cap(c.categoria)}</span><span>${c.scoreMedio}</span></div><div class=\"track\"><div class=\"fill\" style=\"width:${Math.round((c.scoreMedio/catMax)*100)}%\"></div></div></div>`).join('')}</div>\n        <div style=\"margin-top:16px\"><button class=\"btn ghost rgi\" ${insightLoading?'disabled':''}>${insightLoading?'<span class=\"spin\"></span> Gerando…':'✨ Regenerar com IA'}</button></div>\n      </div>` : '';\n    const alertHtml = `\n      <div class=\"panel\" style=\"margin-top:20px\">\n        <h2>🚨 Alertas de sentimento <span class=\"muted\">${alertAtivos.length} ativos</span></h2>\n        ${alertas.map(a=>`<div class=\"alert op ${a.severidade==='alta'?'':'media'}\" data-post=\"${a.postId}\"><div class=\"sev\"></div><div><div class=\"msg\">${a.mensagem}</div><div class=\"mt\">${a.severidade==='alta'?'Severidade alta':'Severidade média'} · ${a.qtdNegativos} comentários negativos</div></div></div>`).join('')}\n      </div>`;\n    content = `\n      <div class=\"head\"><div><h1>Dashboard</h1><p>Desempenho e sentimento dos últimos 30 dias, analisados por IA</p></div>\n        <div class=\"accts\"><div class=\"acct\"><span class=\"dot\"></span>📸 @botafogo</div><div class=\"acct\"><span class=\"dot\"></span>🎵 @botafogo</div></div></div>\n      <div class=\"kpis\">${kpis.map(k=>`<div class=\"kpi\"><div class=\"l\">${k.l}</div><div class=\"v\">${k.v}</div><div class=\"s\">${k.s}</div></div>`).join('')}</div>\n      <div class=\"cols\">\n        <div><div class=\"panel\"><h2>🏆 Ranking de posts</h2>\n          <div class=\"filters\"><button class=\"flt ${platF==='todas'?'on':''}\" data-plat=\"todas\">Todas</button><button class=\"flt ${platF==='instagram'?'on':''}\" data-plat=\"instagram\">Instagram</button><button class=\"flt ${platF==='tiktok'?'on':''}\" data-plat=\"tiktok\">TikTok</button></div>\n          ${postCards}</div></div>\n        <div>${insightHtml}${alertHtml}</div>\n      </div>`;\n  } else if (tab === 'alertas') {\n    content = `\n      <div class=\"head\"><div><h1>Alertas de sentimento</h1><p>Picos de comentários negativos detectados automaticamente</p></div></div>\n      <div class=\"panel\">${alertas.length?alertas.map(a=>{const p=posts.find(x=>x.postId===a.postId)||{};return `<div class=\"alert op ${a.severidade==='alta'?'':'media'}\" data-post=\"${a.postId}\"><div class=\"sev\"></div><div style=\"flex:1\"><div class=\"msg\"><b>${p.legenda||a.postId}</b></div><div class=\"msg\" style=\"margin-top:4px\">${a.mensagem}</div><div class=\"mt\">${a.severidade==='alta'?'Severidade alta':'Severidade média'} · ${a.qtdNegativos} negativos · ${a.status}</div></div></div>`;}).join(''):'<div style=\"color:#6b7280;font-size:14px\">Nenhum alerta ativo 🎉</div>'}</div>`;\n  } else if (tab === 'conectar') {\n    const conn = (D.contas||[]);\n    const others = [{n:'Facebook',i:'👍',c:'#eef3ff'},{n:'X / Twitter',i:'𝕏',c:'#f0f1f3'},{n:'YouTube',i:'▶',c:'#fdeeee'}];\n    content = `\n      <div class=\"head\"><div><h1>Conectar redes</h1><p>Conecte os perfis do clube para a IA analisar posts e comentários</p></div></div>\n      <div class=\"panel conectar\"><div class=\"grid\">\n        ${conn.map(c=>`<div class=\"cc\"><div class=\"ico\" style=\"background:${c.plataforma==='tiktok'?'#e9f7f8':'#fdecf3'}\">${c.plataforma==='tiktok'?'🎵':'📸'}</div><div class=\"st on\">● Conectado</div><div><b style=\"font-size:15px\">${c.plataforma==='tiktok'?'TikTok':'Instagram'}</b><div style=\"color:#6b7280;font-size:13px\">${c.handle}</div></div><div style=\"font-size:13px;color:#374151\">${kf(c.seguidores)} seguidores</div></div>`).join('')}\n        ${others.map(o=>`<div class=\"cc\" style=\"opacity:.85\"><div class=\"ico\" style=\"background:${o.c}\">${o.i}</div><div class=\"st off\">Não conectado</div><div><b style=\"font-size:15px\">${o.n}</b><div style=\"color:#6b7280;font-size:13px\">Disponível na POC</div></div><button class=\"btn ghost mk\" data-net=\"${o.n}\">Conectar</button></div>`).join('')}\n      </div></div>`;\n  } else if (tab === 'agenda') {\n    const planejados = (D.planejados || []).slice().sort((a, b) => {\n      const pa = Number(a.prioridade), pb = Number(b.prioridade);\n      if (!isNaN(pa) && !isNaN(pb) && pa !== pb) return pa - pb;\n      return String(a.dataSugerida||'').localeCompare(String(b.dataSugerida||''));\n    });\n    const planLoading = st.get('planLoading');\n    const planCards = planejados.map(p => `\n      <div class=\"panel\">\n        <div class=\"p-top\"><span class=\"tag\">${cap(p.categoriaConteudo)}</span><span class=\"tag\">${cap(p.formato)}</span>${p.prioridade!=null?`<span class=\"score\" style=\"margin-left:auto;font-size:13px\">P${p.prioridade}</span>`:''}</div>\n        <div class=\"cap\" style=\"white-space:normal;font-size:14.5px;font-weight:600;margin:8px 0 6px\">${p.ideia||''}</div>\n        <div style=\"font-size:12.5px;color:#6b7280;line-height:1.5;margin-bottom:10px\">${p.justificativa||''}</div>\n        <div class=\"mets\">📅 ${p.dataSugerida?String(p.dataSugerida).slice(0,10).split('-').reverse().join('/'):'—'}${p.horarioSugerido?` · 🕒 ${p.horarioSugerido}`:''}</div>\n      </div>`).join('');\n    content = `\n      <div class=\"head\"><div><h1>Agenda de conteúdo</h1><p>Sugestões de posts geradas por IA a partir do desempenho recente</p></div>\n        <button class=\"btn gsp\" ${planLoading?'disabled':''}>${planLoading?'<span class=\"spin\"></span> Gerando…':'✨ Gerar sugestões com IA'}</button></div>\n      ${planejados.length ? `<div class=\"plans\">${planCards}</div>` : `<div class=\"panel\" style=\"text-align:center;padding:44px 20px;color:#6b7280\"><div style=\"font-size:34px;margin-bottom:10px\">🗓️</div><div style=\"font-weight:700;color:#0b0b0c;margin-bottom:4px\">Nenhuma sugestão gerada ainda</div><div style=\"font-size:13px\">Clique em \"Gerar sugestões com IA\" para criar um plano de conteúdo.</div></div>`}`;\n  } else if (tab === 'comunidade') {\n    const byAutor = {};\n    comentarios.forEach(c => {\n      const a = c.autor || '—';\n      if (!byAutor[a]) byAutor[a] = { autor: a, curtidas: 0, count: 0, posts: new Set(), sent: { positivo: 0, negativo: 0, neutro: 0 } };\n      const e = byAutor[a];\n      e.curtidas += Number(c.curtidas) || 0;\n      e.count += 1;\n      if (c.postId) e.posts.add(c.postId);\n      const s = c.sentimento || 'neutro';\n      e.sent[s] = (e.sent[s] || 0) + 1;\n    });\n    const authors = Object.values(byAutor).map(e => {\n      const predominante = Object.entries(e.sent).sort((x, y) => y[1] - x[1])[0][0];\n      return { autor: e.autor, curtidas: e.curtidas, count: e.count, postsCount: e.posts.size, predominante };\n    });\n    const formadores = authors.slice().sort((a, b) => b.curtidas - a.curtidas).slice(0, 5);\n    const promotores = authors.filter(a => a.predominante === 'positivo' && a.postsCount >= 2).sort((a, b) => b.curtidas - a.curtidas).slice(0, 5);\n    const detratores = authors.filter(a => a.predominante === 'negativo').sort((a, b) => b.curtidas - a.curtidas).slice(0, 5);\n    const renderCommList = (list, emptyMsg) => list.length ? list.map(a => `\n      <div class=\"cu-item\">\n        <div class=\"cu-top\"><span class=\"au\">@${String(a.autor||'').replace(/^@/,'')}</span><span class=\"stag ${a.predominante}\">${a.predominante}</span></div>\n        <div class=\"mets\">❤ ${kf(a.curtidas)} · 💬 ${a.count} coment. · 📌 ${a.postsCount} post${a.postsCount===1?'':'s'}</div>\n      </div>`).join('') : `<div style=\"color:#6b7280;font-size:13px\">${emptyMsg}</div>`;\n    content = `\n      <div class=\"head\"><div><h1>Comunidade</h1><p>Vozes que mais engajam nos comentários (${comentarios.length} comentários analisados)</p></div></div>\n      <div class=\"cols3\">\n        <div class=\"panel\"><h2>💡 Formadores de opinião</h2>${renderCommList(formadores, 'Sem dados suficientes ainda.')}</div>\n        <div class=\"panel\"><h2>✅ Promotores</h2>${renderCommList(promotores, 'Nenhum promotor identificado ainda.')}</div>\n        <div class=\"panel\"><h2>🚨 Detratores</h2>${renderCommList(detratores, 'Nenhum detrator identificado ainda.')}</div>\n      </div>`;\n  } else if (tab === 'horario') {\n    const byHour = {};\n    posts.forEach(p => {\n      if (!p.publicadoEm) return;\n      const d = new Date(p.publicadoEm);\n      if (isNaN(d.getTime())) return;\n      const h = d.getUTCHours();\n      if (!byHour[h]) byHour[h] = { hour: h, sumEng: 0, count: 0 };\n      byHour[h].sumEng += Number(p.taxaEngajamento) || 0;\n      byHour[h].count += 1;\n    });\n    const hours = Object.values(byHour).map(x => ({ hour: x.hour, avg: x.sumEng / x.count, count: x.count })).sort((a, b) => b.avg - a.avg);\n    const maxAvg = hours.length ? hours[0].avg : 1;\n    const top = hours.slice(0, 3).map(h => `${String(h.hour).padStart(2,'0')}h`).join(', ');\n    const barsHtml = hours.length ? hours.map(h => `\n      <div class=\"cat\"><div class=\"cl2\"><span>${String(h.hour).padStart(2,'0')}h–${String((h.hour+1)%24).padStart(2,'0')}h <small style=\"color:#9aa0a6;font-weight:500\">(${h.count} post${h.count===1?'':'s'})</small></span><span>${h.avg.toFixed(1).replace('.',',')}%</span></div><div class=\"track\"><div class=\"fill\" style=\"width:${Math.round((h.avg/maxAvg)*100)}%\"></div></div></div>`).join('') : '<div style=\"color:#6b7280;font-size:13px\">Sem dados de horário suficientes ainda.</div>';\n    const dayNames = ['Domingo','Segunda','Terça','Quarta','Quinta','Sexta','Sábado'];\n    const byDay = {};\n    posts.forEach(p => {\n      if (!p.publicadoEm) return;\n      const d = new Date(p.publicadoEm);\n      if (isNaN(d.getTime())) return;\n      const w = d.getUTCDay();\n      if (!byDay[w]) byDay[w] = { day: w, sumEng: 0, count: 0 };\n      byDay[w].sumEng += Number(p.taxaEngajamento) || 0;\n      byDay[w].count += 1;\n    });\n    const days = Object.values(byDay).map(x => ({ day: x.day, avg: x.sumEng / x.count, count: x.count })).sort((a, b) => b.avg - a.avg);\n    const maxDayAvg = days.length ? days[0].avg : 1;\n    const daysHtml = days.map(d => `\n      <div class=\"cat\"><div class=\"cl2\"><span>${dayNames[d.day]} <small style=\"color:#9aa0a6;font-weight:500\">(${d.count})</small></span><span>${d.avg.toFixed(1).replace('.',',')}%</span></div><div class=\"track\"><div class=\"fill\" style=\"width:${Math.round((d.avg/maxDayAvg)*100)}%\"></div></div></div>`).join('');\n    content = `\n      <div class=\"head\"><div><h1>Melhor horário</h1><p>Engajamento médio por hora de publicação (UTC)</p></div></div>\n      ${top ? `<div class=\"callout\">🔥 Melhor horário para postar: <b>${top}</b></div>` : ''}\n      <div class=\"panel\"><h2>⏰ Engajamento médio por hora</h2><div class=\"cats\">${barsHtml}</div></div>\n      ${days.length ? `<div class=\"panel\" style=\"margin-top:20px\"><h2>📅 Melhor dia da semana</h2><div class=\"cats\">${daysHtml}</div></div>` : ''}`;\n  }\n  let overlay = '';\n  if (sel) {\n    const p = posts.find(x => x.postId === sel);\n    if (p) {\n      const cms = comentarios.filter(c => c.postId === p.postId);\n      const cmtHtml = cms.length ? cms.map(c => {\n        const reply = (replies && replies[c.comentarioId]) || c.respostaSugerida || '';\n        const loading = replyLoading && replyLoading[c.comentarioId];\n        return `<div class=\"cmt\" data-key=\"${c.comentarioId}\">\n          <div class=\"ct\"><span class=\"au\">${c.autor}</span><span class=\"stag ${c.sentimento}\">${c.sentimento}</span></div>\n          <div class=\"tx\">${c.texto}</div>\n          <div class=\"cf\"><span class=\"lk\">❤ ${kf(c.curtidas)}</span><button class=\"btn ghost gr\" style=\"padding:6px 11px;font-size:11.5px\" data-cid=\"${c.comentarioId}\" ${loading?'disabled':''}>${loading?'<span class=\"spin\"></span> Gerando…':(reply?'↻ Nova sugestão':'✨ Gerar resposta')}</button></div>\n          ${reply?`<div class=\"reply\"><div class=\"rl\">✨ Resposta sugerida pela IA</div>${reply}</div>`:''}\n        </div>`;\n      }).join('') : '<div style=\"color:#6b7280;font-size:13px\">Sem comentários carregados para este post.</div>';\n      overlay = `\n        <div class=\"ov\"><div class=\"draw\">\n          <div class=\"dh\"><span class=\"tag ${platCls(p.plataforma)}\">${platLabel(p.plataforma)}</span><b>Detalhe do post · #${p.rankPosicao}</b><button class=\"x cl\">✕</button></div>\n          <div class=\"db\">\n            <div class=\"hero\"><div class=\"thumb\" style=\"background-image:url('${p.thumbnailUrl}')\"></div><div><span class=\"tag\">${cap(p.categoriaConteudo)}</span><div class=\"cap2\">${p.legenda}</div><div style=\"font-size:13px;color:#6b7280\">Score de desempenho <b style=\"color:#0b0b0c;font-size:16px\">${p.scoreDesempenho}/100</b></div></div></div>\n            <div class=\"mgrid\"><div class=\"m\"><div class=\"l\">Curtidas</div><div class=\"v\">${kf(p.curtidas)}</div></div><div class=\"m\"><div class=\"l\">Comentários</div><div class=\"v\">${kf(p.comentarios)}</div></div><div class=\"m\"><div class=\"l\">Compart.</div><div class=\"v\">${kf(p.compartilhamentos)}</div></div><div class=\"m\"><div class=\"l\">${p.visualizacoes?'Visualizações':'Alcance'}</div><div class=\"v\">${kf(p.visualizacoes||p.alcance)}</div></div></div>\n            <div class=\"panel\" style=\"margin-bottom:18px\"><h2>😊 Sentimento dos comentários</h2><div class=\"sent-big\"><div class=\"b pos\">${p.sentimentoPositivoPct}%<span>Positivo</span></div><div class=\"b neu\">${p.sentimentoNeutroPct}%<span>Neutro</span></div><div class=\"b neg\">${p.sentimentoNegativoPct}%<span>Negativo</span></div></div></div>\n            <h2 style=\"font-size:15px;margin:0 0 12px\">💬 Comentários <span style=\"color:#9aa0a6;font-weight:500;font-size:12px\">— gere respostas com 1 clique</span></h2>\n            ${cmtHtml}\n          </div>\n        </div></div>`;\n    }\n  }\n  return sideNav + `<div class=\"main\">${content}</div>` + overlay;\n}\n\nexport default {\n  css: `\n  :host, .wrap { font-family: 'Inter', system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }\n  * { box-sizing: border-box; }\n  .wrap { display:flex; height:100%; min-height:100%; background:#f4f5f7; color:#0b0b0c; position:relative; }\n  .side { width:236px; flex:0 0 236px; background:#0b0b0c; color:#fff; display:flex; flex-direction:column; padding:20px 14px; gap:6px; }\n  .brand { display:flex; align-items:center; gap:10px; padding:6px 8px 16px; }\n  .brand .logo { width:38px; height:38px; border-radius:50%; background:#111; border:2px solid #fff; display:flex; align-items:center; justify-content:center; }\n  .brand b { font-size:15px; line-height:1.1; } .brand small{ display:block; color:#9aa0a6; font-weight:500; font-size:11px; }\n  .nav { display:flex; flex-direction:column; gap:2px; margin-top:8px; }\n  .nav button { all:unset; cursor:pointer; display:flex; align-items:center; gap:10px; padding:11px 12px; border-radius:10px; color:#c9ccd1; font-size:14px; font-weight:500; }\n  .nav button:hover { background:#17181a; color:#fff; }\n  .nav button.on { background:#fff; color:#0b0b0c; font-weight:600; }\n  .nav .badge { margin-left:auto; background:#dc2626; color:#fff; font-size:11px; font-weight:700; border-radius:999px; padding:1px 7px; }\n  .side .foot { margin-top:auto; font-size:11px; color:#6b7280; padding:8px; }\n  .main { flex:1; overflow:auto; padding:26px 30px; }\n  .head { display:flex; align-items:center; justify-content:space-between; margin-bottom:20px; flex-wrap:wrap; gap:12px; }\n  .head h1 { font-size:22px; margin:0; letter-spacing:-0.02em; } .head p{ margin:3px 0 0; color:#6b7280; font-size:13px; }\n  .accts { display:flex; gap:8px; }\n  .acct { display:flex; align-items:center; gap:8px; background:#fff; border:1px solid #e5e7eb; border-radius:12px; padding:7px 12px; font-size:12px; font-weight:600; }\n  .acct .dot{ width:8px;height:8px;border-radius:50%;background:#16a34a; }\n  .kpis { display:grid; grid-template-columns:repeat(6,1fr); gap:12px; margin-bottom:22px; }\n  @media (max-width:1100px){ .kpis{ grid-template-columns:repeat(3,1fr);} .cols{ grid-template-columns:1fr !important; } }\n  .kpi { background:#fff; border:1px solid #e9eaec; border-radius:14px; padding:14px 16px; }\n  .kpi .l { color:#6b7280; font-size:12px; font-weight:600; } .kpi .v{ font-size:24px; font-weight:750; letter-spacing:-0.02em; margin-top:4px; } .kpi .s{ font-size:11px; color:#9aa0a6; margin-top:2px; }\n  .cols { display:grid; grid-template-columns:1.6fr 1fr; gap:20px; align-items:start; }\n  .panel { background:#fff; border:1px solid #e9eaec; border-radius:16px; padding:18px; }\n  .panel h2 { font-size:15px; margin:0 0 14px; display:flex; align-items:center; gap:8px; }\n  .panel h2 .muted { color:#9aa0a6; font-weight:500; font-size:12px; margin-left:auto; }\n  .filters { display:flex; gap:6px; margin:0 0 14px; }\n  .filters button{ all:unset; cursor:pointer; font-size:12px; font-weight:600; color:#6b7280; padding:6px 12px; border-radius:999px; border:1px solid #e5e7eb; }\n  .filters button.on{ background:#0b0b0c; color:#fff; border-color:#0b0b0c; }\n  .post { display:flex; gap:14px; padding:12px; border-radius:14px; cursor:pointer; border:1px solid transparent; }\n  .post:hover { background:#fafbfc; border-color:#eceef0; }\n  .thumb { position:relative; width:74px; height:74px; flex:0 0 74px; border-radius:12px; background:#111 center/cover no-repeat; overflow:hidden; }\n  .thumb .rk{ position:absolute; top:0; left:0; background:#0b0b0c; color:#fff; font-size:11px; font-weight:700; padding:2px 7px; border-bottom-right-radius:8px; }\n  .p-body { flex:1; min-width:0; }\n  .p-top { display:flex; align-items:center; gap:7px; margin-bottom:3px; }\n  .tag { font-size:10.5px; font-weight:700; text-transform:uppercase; letter-spacing:.03em; padding:2px 7px; border-radius:6px; background:#f0f1f3; color:#4b5563; }\n  .tag.ig{ background:#fdecf3; color:#c1338a; } .tag.tt{ background:#e9f7f8; color:#0b8b93; }\n  .score { margin-left:auto; font-weight:750; font-size:15px; }\n  .score small{ font-weight:500; color:#9aa0a6; font-size:10px; }\n  .cap { font-size:13px; font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-bottom:6px; }\n  .mets { font-size:12px; color:#6b7280; display:flex; gap:10px; flex-wrap:wrap; margin-bottom:7px; }\n  .sbar { display:flex; height:6px; border-radius:999px; overflow:hidden; background:#eee; }\n  .sbar i{ display:block; height:100%; } .sbar .pos{ background:#16a34a; } .sbar .neu{ background:#cbd0d6; } .sbar .neg{ background:#dc2626; }\n  .insight .resumo{ font-size:13.5px; line-height:1.55; color:#22262b; }\n  .recs{ list-style:none; padding:0; margin:14px 0 0; display:flex; flex-direction:column; gap:9px; }\n  .recs li{ display:flex; gap:9px; font-size:13px; line-height:1.45; }\n  .recs li .n{ flex:0 0 20px; height:20px; border-radius:6px; background:#0b0b0c; color:#fff; font-size:11px; font-weight:700; display:flex; align-items:center; justify-content:center; }\n  .cats{ margin-top:16px; display:flex; flex-direction:column; gap:9px; }\n  .cat{ font-size:12px; } .cat .cl2{ display:flex; justify-content:space-between; margin-bottom:3px; font-weight:600; }\n  .cat .track{ height:8px; background:#eef0f2; border-radius:999px; overflow:hidden; } .cat .fill{ height:100%; background:linear-gradient(90deg,#0b0b0c,#3a3a3a); border-radius:999px; }\n  .btn{ all:unset; cursor:pointer; background:#0b0b0c; color:#fff; font-size:12.5px; font-weight:600; padding:9px 14px; border-radius:10px; display:inline-flex; align-items:center; gap:7px; }\n  .btn.ghost{ background:#fff; color:#0b0b0c; border:1px solid #d8dade; }\n  .btn:disabled{ opacity:.55; cursor:default; }\n  .alert{ display:flex; gap:11px; padding:12px; border-radius:12px; border:1px solid #f0d9d9; background:#fdf5f5; cursor:pointer; margin-bottom:10px; }\n  .alert.media{ border-color:#f3e6c8; background:#fdfaf1; }\n  .alert .sev{ width:9px; height:9px; border-radius:50%; background:#dc2626; margin-top:5px; flex:0 0 9px; } .alert.media .sev{ background:#f59e0b; }\n  .alert .msg{ font-size:12.5px; line-height:1.45; color:#3a2020; } .alert.media .msg{ color:#3a3020; }\n  .alert .mt{ font-size:11px; color:#9aa0a6; margin-top:4px; }\n  .ov{ position:absolute; inset:0; background:rgba(11,11,12,.45); display:flex; justify-content:flex-end; z-index:20; }\n  .draw{ width:min(680px,94%); height:100%; background:#f4f5f7; overflow:auto; box-shadow:-20px 0 60px rgba(0,0,0,.2); }\n  .draw .dh{ position:sticky; top:0; background:#0b0b0c; color:#fff; padding:16px 22px; display:flex; align-items:center; gap:12px; z-index:2; }\n  .draw .dh .x{ all:unset; cursor:pointer; margin-left:auto; font-size:20px; line-height:1; padding:4px 10px; border-radius:8px; } .draw .dh .x:hover{ background:#1e1f22; }\n  .draw .db{ padding:22px; }\n  .hero{ display:flex; gap:16px; margin-bottom:18px; }\n  .hero .thumb{ width:120px; height:120px; flex:0 0 120px; border-radius:16px; }\n  .hero .cap2{ font-size:15px; font-weight:600; line-height:1.4; margin:2px 0 8px; }\n  .mgrid{ display:grid; grid-template-columns:repeat(4,1fr); gap:10px; margin-bottom:18px; }\n  .m{ background:#fff; border:1px solid #e9eaec; border-radius:12px; padding:11px 12px; } .m .l{ font-size:11px; color:#6b7280; font-weight:600; } .m .v{ font-size:18px; font-weight:750; margin-top:2px; }\n  .sent-big{ display:flex; gap:10px; margin-bottom:8px; }\n  .sent-big .b{ flex:1; text-align:center; border-radius:12px; padding:12px; color:#fff; font-weight:700; }\n  .sent-big .b span{ display:block; font-size:11px; font-weight:600; opacity:.9; margin-top:2px; }\n  .sent-big .pos{ background:#16a34a; } .sent-big .neu{ background:#94a3b8; } .sent-big .neg{ background:#dc2626; }\n  .cmt{ background:#fff; border:1px solid #e9eaec; border-radius:12px; padding:13px 14px; margin-bottom:10px; }\n  .cmt .ct{ display:flex; align-items:center; gap:8px; margin-bottom:5px; } .cmt .au{ font-weight:700; font-size:13px; }\n  .stag{ font-size:10px; font-weight:700; text-transform:uppercase; padding:2px 7px; border-radius:6px; }\n  .stag.positivo{ background:#e6f6ec; color:#16a34a; } .stag.negativo{ background:#fbe7e7; color:#dc2626; } .stag.neutro{ background:#eef0f2; color:#64748b; }\n  .cmt .tx{ font-size:13.5px; line-height:1.5; } .cmt .cf{ display:flex; align-items:center; gap:10px; margin-top:9px; }\n  .cmt .lk{ font-size:12px; color:#9aa0a6; }\n  .reply{ margin-top:10px; background:#f6faf7; border:1px solid #d6ebdd; border-left:3px solid #16a34a; border-radius:10px; padding:11px 12px; font-size:13px; line-height:1.5; }\n  .reply .rl{ font-size:10.5px; font-weight:700; text-transform:uppercase; color:#16a34a; margin-bottom:4px; letter-spacing:.03em; display:flex; align-items:center; gap:6px; }\n  .conectar .grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:14px; }\n  .cc{ background:#fff; border:1px solid #e9eaec; border-radius:16px; padding:18px; display:flex; flex-direction:column; gap:10px; }\n  .cc .ico{ width:44px; height:44px; border-radius:12px; display:flex; align-items:center; justify-content:center; font-size:22px; }\n  .cc .st{ font-size:12px; font-weight:700; padding:3px 9px; border-radius:999px; align-self:flex-start; }\n  .cc .st.on{ background:#e6f6ec; color:#16a34a; } .cc .st.off{ background:#f0f1f3; color:#9aa0a6; }\n  .plans{ display:grid; grid-template-columns:repeat(auto-fill,minmax(280px,1fr)); gap:14px; }\n  .cols3{ display:grid; grid-template-columns:repeat(3,1fr); gap:16px; align-items:start; }\n  @media (max-width:1100px){ .cols3{ grid-template-columns:1fr !important; } }\n  .cu-item{ padding:10px 0; border-bottom:1px solid #f0f1f3; } .cu-item:last-child{ border-bottom:none; }\n  .cu-top{ display:flex; justify-content:space-between; align-items:center; margin-bottom:4px; } .cu-top .au{ font-weight:700; font-size:13.5px; }\n  .callout{ background:#0b0b0c; color:#fff; border-radius:14px; padding:14px 18px; font-size:14px; font-weight:600; margin-bottom:18px; }\n  .loading{ display:flex; height:100%; width:100%; align-items:center; justify-content:center; color:#6b7280; font-size:14px; }\n  .spin{ width:15px;height:15px;border:2px solid #ffffff55;border-top-color:#fff;border-radius:50%;display:inline-block;animation:sp .7s linear infinite; }\n  .btn.ghost .spin{ border:2px solid #0b0b0c33; border-top-color:#0b0b0c; }\n  @keyframes sp{ to{ transform:rotate(360deg);} }\n  `,\n  render(d2){ return `<div class=\"wrap\">${view(d2)}</div>`; },\n  mounted(d2, root){\n    const S = () => d2.store('scr-ui');\n    const rerender = () => { const w = d2.$('.wrap'); if (w) w.innerHTML = view(d2); };\n    const load = async () => {\n      try { const res = await d2.rush('scr-dados').invoke({}); S().set('data', (res && res.data) ? res.data : res); }\n      catch (e) { d2.log('load err', e); }\n      rerender();\n    };\n    load();\n    const pick = (e, sel) => (e && e.target && e.target.closest) ? e.target.closest(sel) : null;\n    d2.on('.nv', 'click', (e) => { const el = pick(e, '.nv'); if (!el) return; S().set('tab', el.dataset.tab); S().set('sel', null); rerender(); });\n    d2.on('.flt', 'click', (e) => { const el = pick(e, '.flt'); if (!el) return; S().set('plat', el.dataset.plat); rerender(); });\n    d2.on('.op', 'click', (e) => { const el = pick(e, '.op'); if (!el) return; S().set('sel', el.dataset.post); rerender(); });\n    d2.on('.cl', 'click', () => { S().set('sel', null); rerender(); });\n    d2.on('.ov', 'click', (e) => { if (e.target && e.target.classList && e.target.classList.contains('ov')) { S().set('sel', null); rerender(); } });\n    d2.on('.mk', 'click', (e) => { const el = pick(e, '.mk'); if (!el) return; d2.dialog.alert('Na POC, ' + el.dataset.net + ' entra pelo mesmo fluxo do Instagram/TikTok — basta plugar as credenciais do cliente.'); });\n    d2.on('.rgi', 'click', async () => {\n      S().set('insightLoading', true); rerender();\n      try { const res = await d2.rush('scr-gerar-insight').invoke({}); S().set('insight', (res && res.data) ? res.data.insight : null); }\n      catch (err) { d2.dialog.alert('Erro ao gerar insight: ' + ((err && err.message) || err)); }\n      S().set('insightLoading', false); rerender();\n    });\n    d2.on('.gsp', 'click', async () => {\n      S().set('planLoading', true); rerender();\n      try {\n        await d2.rush('scr-planejar-posts').invoke({});\n        const res = await d2.rush('scr-dados').invoke({});\n        S().set('data', (res && res.data) ? res.data : res);\n      } catch (err) { d2.dialog.alert('Erro ao gerar sugestões: ' + ((err && err.message) || err)); }\n      S().set('planLoading', false); rerender();\n    });\n    d2.on('.gr', 'click', async (e) => {\n      const el = pick(e, '.gr'); if (!el) return;\n      const cid = el.dataset.cid;\n      const D = S().get('data');\n      const c = ((D && D.comentarios) || []).find(x => x.comentarioId === cid);\n      if (!c) return;\n      S().set('replyLoading', Object.assign({}, S().get('replyLoading'), { [cid]: true })); rerender();\n      try {\n        const res = await d2.rush('scr-sugerir-resposta').invoke({ texto: c.texto, autor: c.autor, sentimento: c.sentimento });\n        const r = (res && res.data) ? res.data.resposta : '';\n        S().set('replies', Object.assign({}, S().get('replies'), { [cid]: r }));\n      } catch (err) { S().set('replies', Object.assign({}, S().get('replies'), { [cid]: 'Erro ao gerar resposta.' })); }\n      S().set('replyLoading', Object.assign({}, S().get('replyLoading'), { [cid]: false })); rerender();\n    });\n  }\n}"}}}}}}}},{"_id":"5ed9bed7b9e0891ce913e884","component":"data2-core-types@Component","params":{"package":"data2-core","component":"DataSource","renderable":{"dimensions":{"disabled":false,"component":"data2-core-types@Dimensions","params":{"width":1366,"exactWidth":false,"exactHeight":false,"height":768}}}}},{"_id":"5f472ae463c680de4ff296e9","component":"data2-core-types@Component","params":{"package":"data2-core","component":"Data2TableDataSource","extends":"data2-core@DataSource","renderable":{"dimensions":{"disabled":false,"component":"data2-core-types@Dimensions","params":{"width":1366,"exactWidth":false,"exactHeight":false,"height":768}}}}}]}},{"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":"6a41abc7f6cc6f867cf126fc","component":"data2-rush@HTTPListener","reference":"chat-turn","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a43b41a097719b83c28ac1f","component":"data2-rush@HTTPListener","reference":"preco-pedido-a","system":"66f6c624d8a4a0c67041a910","params":{},"versioning":"enabled","bundleId":"6a43ff1ed998f9a77692e956"},{"_id":"6a43b42f0bb65a76fafe71b5","component":"data2-rush@HTTPListener","reference":"preco-pedido-b","system":"66f6c624d8a4a0c67041a910","params":{},"versioning":"enabled","bundleId":"6a43ff1ed998f9a77692e956"},{"_id":"6a43e1d2fbbc6ccb0615328b","component":"data2-rush@HTTPListener","reference":"consultoria-turn","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a455eed73c150abf6e19092","component":"data2-core-ai@Assistant","reference":"consultora-ouvido","system":"66f6c624d8a4a0c67041a910","params":{"model":"cerebras","_context":"Assistant de transcrição de áudio. Recebe um áudio do cliente (mensagem de voz numa loja de beleza) e transcreve fielmente para texto em português do Brasil, sem adicionar comentários."}},{"_id":"6a4514582e19654bbcdf806c","component":"data2-rush@HTTPListener","reference":"sugere-complemento","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a46d97bd17c9b3315ae4194","component":"data2-rush@HTTPListener","reference":"analisa-cabelo","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a46c245fb4b5485eb73f74d","component":"data2-rush@HTTPListener","reference":"registra-consulta","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a464a24b0a857d0aa5cb3ca","component":"data2-rush@HTTPListener","reference":"verifica-login","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a465252bf38d382814eb704","component":"data2-rush@HTTPListener","reference":"cria-conta","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a46ce8f8c5c69fa78efc860","component":"data2-rush@HTTPListener","reference":"exclui-consulta","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a7ca707a214a2d6a5eec970","component":"data2-rush@HTTPListener","reference":"mv-pre-listar","system":"66f6c624d8a4a0c67041a910","params":{},"deleteMetadata":{}},{"_id":"6a7ca53b8157bd3190ab15c7","component":"data2-rush@HTTPListener","reference":"mv-expo-listar","system":"66f6c624d8a4a0c67041a910","params":{},"deleteMetadata":{}},{"_id":"6a7ca70c7c3ad3f0fdbc86d2","component":"data2-rush@HTTPListener","reference":"mv-pre-excluir","system":"66f6c624d8a4a0c67041a910","params":{},"deleteMetadata":{}},{"_id":"6a68af7e93c05a54c77ea336","component":"data2-rush@HTTPListener","reference":"mv-midia-upload","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a7ca544cb3cd9441afa3b76","component":"data2-rush@HTTPListener","reference":"mv-expo-salvar","system":"66f6c624d8a4a0c67041a910","params":{},"deleteMetadata":{}},{"_id":"6a7ca5478157bd3190ab15ea","component":"data2-rush@HTTPListener","reference":"mv-expo-excluir","system":"66f6c624d8a4a0c67041a910","params":{},"deleteMetadata":{}},{"_id":"6a59494c55b95245d58e76c3","component":"data2-rush@HTTPListener","reference":"mv-cria-conta","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a5904f1ce0eeb9aa5b97c93","component":"data2-core-ai@Assistant","reference":"mv-autofill-visao","system":"66f6c624d8a4a0c67041a910","params":{"model":"cerebras","_context":"Você é um assistente de catalogação de acervo da Natura. Recebe a imagem de um item (pode ser uma revista de ciclo, um produto/embalagem, um documento, uma fotografia, um cartaz ou um frame de vídeo) e deve sugerir o preenchimento da ficha catalográfica a partir do que vê na imagem e do seu conhecimento sobre a Natura. Seja preciso e conservador: só preencha um campo quando houver evidência razoável; deixe vazio o que não conseguir inferir.","behaviour":"Responda SOMENTE com um objeto JSON válido, sem texto antes ou depois, exatamente com estas chaves: {\"titulo\": string, \"categoria_slug\": um de [\"museologico\",\"iconografia\",\"biblioteca\",\"arquivo\",\"depoimento\",\"cosmetoteca\",\"audiovisual\"], \"marca_slug\": string (ex.: natura, ekos, chronos, kaiak, tododia, essencial, mamae-bebe, sr-n) ou \"\", \"ciclo_codigo\": string (ex.: \"13/2026\") ou \"\", \"origem\": string ou \"\", \"descricao\": string, \"tags\": array de strings, \"confianca\": número de 0 a 1}. Use strings vazias ou arrays vazios quando não souber.","creativity":30,"json":true}},{"_id":"6a5950c655b95245d58e8bd2","component":"data2-rush@HTTPListener","reference":"mv-pre-enviar","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a5a626355b95245d5911712","component":"data2-rush@HTTPListener","reference":"mv-expo-publica","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a5f5e8addf162bd287ba0df","component":"data2-rush@HTTPListener","reference":"analisar-reclamacao","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a9b05e1b8a4e75627735788","component":"data2-rush@HTTPListener","reference":"salvar-caso","system":"66f6c624d8a4a0c67041a910","params":{},"deleteMetadata":{}},{"_id":"6a5f777c136fbfcfe2354e43","component":"data2-rush@HTTPListener","reference":"listar-casos","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66c84953991cb86a7756c3","component":"data2-rush@HTTPListener","reference":"ppk-whoami","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66c0a853991cb86a774bd8","component":"data2-rush@HTTPListener","reference":"ppk-signup","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66ac96c834e87e6d3c8e32","component":"data2-rush@HTTPListener","reference":"ppk-create-room","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66acf6c2a2aeac4a09f209","component":"data2-rush@HTTPListener","reference":"ppk-join-room","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66b52fdc1fded10bfda973","component":"data2-rush@HTTPListener","reference":"ppk-snapshot","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66af0fc2a2aeac4a09f5d9","component":"data2-rush@HTTPListener","reference":"ppk-heartbeat","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66af7953991cb86a7731f7","component":"data2-rush@HTTPListener","reference":"ppk-import-csv","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66af26c2a2aeac4a09f5e9","component":"data2-rush@HTTPListener","reference":"ppk-kick","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66ae2dc834e87e6d3c90fb","component":"data2-rush@HTTPListener","reference":"ppk-vote","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66adf0c2bc45743953141b","component":"data2-rush@HTTPListener","reference":"ppk-reveal","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66af32c834e87e6d3c92fe","component":"data2-rush@HTTPListener","reference":"ppk-nudge","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66aef7c2a2aeac4a09f5b4","component":"data2-rush@HTTPListener","reference":"ppk-new-round","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66af0553991cb86a77315c","component":"data2-rush@HTTPListener","reference":"ppk-save-estimate","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66ad9c0613f70fa450bc95","component":"data2-rush@HTTPListener","reference":"ppk-set-issue","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66ad4439c8071d2a804632","component":"data2-rush@HTTPListener","reference":"ppk-issues-crud","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66bc65c834e87e6d3ca79e","component":"data2-rush@HTTPListener","reference":"ppk-history","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a66b0699f8782fe4af2b5cc","component":"data2-rush@HTTPListener","reference":"ppk-export-csv","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a67b40e8a1fce328e7698b8","component":"data2-rush@HTTPListener","reference":"config-listar","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a67b41a00e67f40da11ceda","component":"data2-rush@HTTPListener","reference":"config-salvar-orientacao","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a67b42c7c6882a866a3546f","component":"data2-rush@HTTPListener","reference":"config-salvar-manifestacao","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a67b4298c7c6eabf260f1d6","component":"data2-rush@HTTPListener","reference":"config-excluir-orientacao","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a67b4324c303226dc7e1c19","component":"data2-rush@HTTPListener","reference":"config-excluir-manifestacao","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a6915447ac798c44b15ef7b","component":"data2-rush@HTTPListener","reference":"wlthread","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a6910c3dc1fded10b03c0bb","component":"data2-rush@HTTPListener","reference":"wlchat","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a6910ad049cd0da321dda79","component":"data2-rush@HTTPListener","reference":"wltranscribe","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a69144ce2a83c84c3684910","component":"data2-rush@HTTPListener","reference":"wlhandoff","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a6916cedc1fded10b03db48","component":"data2-rush@HTTPListener","reference":"wlqueue","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a69144fed63093569e1641b","component":"data2-rush@HTTPListener","reference":"wlagent","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a69181a7ac798c44b15fd77","component":"data2-rush@HTTPListener","reference":"wladmin","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a6a9315b7b663cea441759c","component":"data2-rush@HTTPListener","reference":"geo-dados","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a6a99d05dd7c77c5ead9b38","component":"data2-rush@HTTPListener","reference":"geo-rotas","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a6a902fcc3580273c2a1563","component":"data2-rush@HTTPListener","reference":"geo-sugerir-grupos","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a6a9321681c20bc50de4f71","component":"data2-rush@HTTPListener","reference":"geo-mover","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a738d389c4ebe28efc6859f","component":"data2-rush@HTTPListener","reference":"scr-dados","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a738cc2d62fb41d14d94cff","component":"data2-rush@HTTPListener","reference":"scr-gerar-insight","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a74a9fc5c63d915f8fac64e","component":"data2-rush@HTTPListener","reference":"scr-planejar-posts","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a738c3812b4135152ce61e1","component":"data2-rush@HTTPListener","reference":"scr-sugerir-resposta","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a4c28536c020b3085bfe735","component":"data2-rush@HTTPListener","reference":"translate","system":"66f6c624d8a4a0c67041a910","params":{}},{"_id":"6a5904dece0eeb9aa5b97c3f","component":"data2-core-ai@Assistant","reference":"mv-acervo-chat","system":"66f6c624d8a4a0c67041a910","params":{"model":"cerebras","_context":"Você é o curador virtual do acervo \"Memória Viva\" da Natura — um acervo histórico com itens museológicos, iconografia, documentos de biblioteca e arquivo, depoimentos, cosmetoteca e audiovisual. Sua função é responder perguntas sobre a história, marcas, produtos, ciclos e campanhas da Natura usando EXCLUSIVAMENTE os itens do acervo fornecidos no contexto de cada pergunta. Sempre que citar informação, referencie o item pelo título e pelo código interno (ex.: \"Revista Espaço Natura Ciclo 13/2026 — mv-10001\"). Se a resposta não estiver amparada pelos itens fornecidos, diga com franqueza que não encontrou registro no acervo, em vez de inventar.","behaviour":"Responda em português do Brasil, com tom curatorial, claro e conciso (no máximo alguns parágrafos curtos). Não invente itens, datas ou fatos que não estejam no contexto. Ao final, quando fizer sentido, liste os itens do acervo que embasaram a resposta.","creativity":25}}]}},{"reference":"ComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a554e26a9d5bff0a7a556ac","component":"data2-core-primitives@Router","system":"66f6c624d8a4a0c67041a910","displayName":"Efeitos Malucos Router","params":{"routerType":{"component":"data2-core-primitives@String","params":{"content":"browser"}},"routes":[{"component":"data2-core-primitives@Route","params":{"path":"","name":"Neon Magnet","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"BG6ut1t0Zrr0aS6bnlbLs"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/cordas","name":"Neon Strings","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"oRBr598XfkQN2PQ0G83vq"}}}}]},"reference":"efeitos-malucos-router"},{"_id":"6a59459ee92c91046121dd4d","component":"data2-core-primitives@Router","system":"66f6c624d8a4a0c67041a910","displayName":"mv_router","params":{"routerType":{"component":"data2-core-primitives@String","params":{"content":"browser"}},"routes":[{"component":"data2-core-primitives@Route","params":{"path":"","name":"Entrar — Memória Viva","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"g9KGbPOZ41a2GY5PA5hcR"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/enviar","name":"Catalogar item — Memória Viva","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"ltrYl5PQZu6cVWjQCMXeF"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/exposicao/:slug","name":"Exposição — Memória Viva","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"-GSMFqttfNMlc7dXNIo42"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/app","name":"Memória Viva — Acervo Natura","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"HS5K7uZB689OxLhAeGGxB"}},"policies":[{"component":"data2-core-types@PolicyReference","params":{"authenticationStrategy":{"reference":"mv-login"}}}]}}],"login":{"component":"data2-core-types@ArtboardReference","params":{"reference":"g9KGbPOZ41a2GY5PA5hcR"}}},"reference":"mv-router"},{"_id":"6a5f61d43e93dc847956ada1","component":"data2-core-primitives@Router","system":"66f6c624d8a4a0c67041a910","displayName":"MainRouter","params":{"routerType":{"component":"data2-core-primitives@String","params":{"content":"browser"}},"routes":[{"component":"data2-core-primitives@Route","params":{"path":"","name":"Reclamação · Natura Garantia","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"gGSJExsiBYDDsm18wyaEz"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/casos","name":"Casos analisados · Natura Garantia","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"4Ty9rSlLbXNnKUpUJHa3U"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/config","name":"Configuração do robô · Natura Garantia","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"_pG_aoBUQ11B7SJQN49lE"}}}}]},"reference":"MainRouter"},{"_id":"6a66b40dcff9da88983b6c95","component":"data2-core-primitives@Router","system":"66f6c624d8a4a0c67041a910","displayName":"PPK Router","params":{"routerType":{"component":"data2-core-primitives@String","params":{"content":"browser"}},"routes":[{"component":"data2-core-primitives@Route","params":{"path":"/","name":"Poker Planning","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"OiXm4F-MardpHIZZcmd7f"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/entrar","name":"Entrar na sala","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"aTn2lDz4A8cT2m1BRlbDf"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/salas","name":"Minhas salas","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"dHgx-_kB1VWHwz93gyDYF"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"ppk-auth"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/nova","name":"Nova sala","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"MywUIC1LBAuk6iNWvOy4X"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"ppk-auth"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/sala/:code","name":"Mesa","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"3ncctPLOy9PoFGcdrx-lA"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/resumo/:code","name":"Resumo da sessão","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"32i2Dh1bRKrui3mAeBgFU"}}}}],"login":{"component":"data2-core-types@ArtboardReference","params":{"reference":"OiXm4F-MardpHIZZcmd7f"}}},"reference":"ppk-router"},{"_id":"6a4c282d2daf06e6ec4c77ab","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"Tradutor","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Tradutor"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"vrgeqr-u_f14TMxxotSCa"},{"_id":"6a5551a3b4bd55357412d2e7","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"Neon Strings — efeitos malucos","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Neon Strings — efeitos malucos"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"oRBr598XfkQN2PQ0G83vq"},{"_id":"6a554e17d2b3f7e98e86bb2c","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"Neon Magnet — efeitos malucos","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Neon Magnet — efeitos malucos"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"BG6ut1t0Zrr0aS6bnlbLs"},{"_id":"6a594585f6fdf852ef3ff1eb","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"Memória Viva — Login","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Memória Viva — Login"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"g9KGbPOZ41a2GY5PA5hcR"},{"_id":"6a5906da6fb67bea960c70b0","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"Memória Viva — App","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Memória Viva — App"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"HS5K7uZB689OxLhAeGGxB"},{"_id":"6a5a12cc8a6d78ebeaa1eb2f","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"Memória Viva — Exposição","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Memória Viva — Exposição"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"-GSMFqttfNMlc7dXNIo42"},{"_id":"6a5951236fb67bea960dbf22","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"Memória Viva — Enviar","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Memória Viva — Enviar"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"ltrYl5PQZu6cVWjQCMXeF"},{"_id":"6a5f61af22084cd562567ed9","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"Configuração do Robô","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Configuração do Robô"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"_pG_aoBUQ11B7SJQN49lE"},{"_id":"6a5f61acc7d243e2156bb90a","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"Casos Analisados","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Casos Analisados"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"4Ty9rSlLbXNnKUpUJHa3U"},{"_id":"6a5f61a93e93dc847956ad04","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"Reclamação","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Reclamação"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"gGSJExsiBYDDsm18wyaEz"},{"_id":"6a66b2727ecf068e46cddcfb","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"PPK · Entrar","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"PPK · Entrar"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"OiXm4F-MardpHIZZcmd7f"},{"_id":"6a66bce139c8071d2a805e5e","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"PPK · Resumo da Sessão","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"PPK · Resumo da Sessão"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"32i2Dh1bRKrui3mAeBgFU"},{"_id":"6a66b404c2a2aeac4a09fd5f","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"PPK · Mesa (Fase 4)","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"PPK · Mesa (Fase 4)"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"3ncctPLOy9PoFGcdrx-lA"},{"_id":"6a66b3d89f8782fe4af2bb88","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"PPK · Nova Sala","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"PPK · Nova Sala"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"MywUIC1LBAuk6iNWvOy4X"},{"_id":"6a66b3d0102e63106d6b374c","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"PPK · Minhas Salas","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"PPK · Minhas Salas"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"dHgx-_kB1VWHwz93gyDYF"},{"_id":"6a66b3de9f8782fe4af2bb97","component":"data2-core-primitives@Artboard","system":"66f6c624d8a4a0c67041a910","displayName":"PPK · Entrar com Código","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"PPK · Entrar com Código"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"aTn2lDz4A8cT2m1BRlbDf"},{"_id":"be839d1756f9eb425590f2f5","parent":"6a4c282d2daf06e6ec4c77ab","component":"fernando-playground@translator-app","system":"66f6c624d8a4a0c67041a910","displayName":"Translator App Instance","params":{"translateApi":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"translate"}},"super":{"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":900}}}},"reference":"xiT_pf-0qLjqmQX7YOPYz"},{"_id":"67231156c5dd9f8421abb2a5","parent":"6a5551a3b4bd55357412d2e7","component":"fernando-playground@neon-strings","system":"66f6c624d8a4a0c67041a910","displayName":"neon-strings","params":{"bg":{"component":"data2-core-types@Color","params":{"rgba":"#08080f"}},"ink":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"title":"Strings","hint":"arraste o mouse pelas cordas","lines":8,"tension":1,"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"top":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}}}},"reference":"hSri_8SwBXQ89HCs_v-Yi"},{"_id":"5b73c2ebc0ecc5f401e0a839","parent":"6a554e17d2b3f7e98e86bb2c","component":"fernando-playground@neon-magnet","system":"66f6c624d8a4a0c67041a910","displayName":"neon-magnet","params":{"bg":{"component":"data2-core-types@Color","params":{"rgba":"#0a0a12"}},"ink":{"component":"data2-core-types@Color","params":{"rgba":"#ffffff"}},"title":"Data²","tagline":"Orchestrating intelligence at production scale.","stick":0.55,"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"top":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}}}},"reference":"lJyKJyBqGqNsOxEQK2Lwn"},{"_id":"57b810d04b6fcd3386539b80","parent":"6a594585f6fdf852ef3ff1eb","component":"fernando-playground@mv-auth-shell","system":"66f6c624d8a4a0c67041a910","displayName":"mv-auth-shell","params":{"super":{"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":900}}}},"reference":"IWwRmkCZgVUa44jvNxpl8"},{"_id":"da31cebc0e7c9bdc9220a31a","parent":"6a5906da6fb67bea960c70b0","component":"fernando-playground@mv-shell","system":"66f6c624d8a4a0c67041a910","displayName":"mv-shell","params":{"itens":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mv-acervoDataSet"}},"categorias":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mv-categoriasDataSet"}},"marcas":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mv-marcasDataSet"}},"produtos":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mv-produtosDataSet"}},"ciclos":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mv-ciclosDataSet"}},"campanhas":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mv-campanhasDataSet"}},"midias":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mv-midiasDataSet"}},"chat":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mv-acervo-chat"}},"visao":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mv-autofill-visao"}},"super":{"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":900}}},"exposicoes":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mv-exposicoesDataSet"}}},"reference":"RED9ZsSzc3IJP3i9RwphX"},{"_id":"1969014d794261f44c8d2c56","parent":"6a5a12cc8a6d78ebeaa1eb2f","component":"fernando-playground@mv-expo-view","system":"66f6c624d8a4a0c67041a910","displayName":"mv-expo-view","params":{"super":{"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":900}}}},"reference":"kt-BrwvectyLmRKH4PM3v"},{"_id":"15a0532c8d936528156964b7","parent":"6a5951236fb67bea960dbf22","component":"fernando-playground@mv-pre-form","system":"66f6c624d8a4a0c67041a910","displayName":"mv-pre-form","params":{"super":{"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":900}}}},"reference":"SLpKFVlv5IenXeC7mTo8N"},{"_id":"309a337c94ef7ba0702ad491","parent":"6a5f61af22084cd562567ed9","component":"fernando-playground@naturaConfig","system":"66f6c624d8a4a0c67041a910","displayName":"naturaConfig","params":{"super":{"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":900}}}},"reference":"fFWhuN6FNkSCvSuJPeItj"},{"_id":"9f8a0c655175b7e98593bd86","parent":"6a5f61acc7d243e2156bb90a","component":"fernando-playground@naturaCasos","system":"66f6c624d8a4a0c67041a910","displayName":"naturaCasos","params":{"cases":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"complaintsDataSet"}},"super":{"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":900}}}},"reference":"fjNdZueRytF9W5Iwb6HuX"},{"_id":"5703053abc90b84a77afeb53","parent":"6a5f61a93e93dc847956ad04","component":"fernando-playground@naturaReclamacao","system":"66f6c624d8a4a0c67041a910","displayName":"naturaReclamacao","params":{"cases":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"complaintsDataSet"}},"super":{"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":900}}}},"reference":"S_ZOcofd_cQVkzppBfVin"},{"_id":"edf2c0028872fedb7983b0ca","parent":"6a66b2727ecf068e46cddcfb","component":"fernando-playground@ppk-auth-screen","system":"66f6c624d8a4a0c67041a910","displayName":"Auth Screen","params":{"super":{"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":"100%","height":"100%"}}}},"reference":"EaiVscCl_-1m7tzzdXcrY"},{"_id":"145bf660a774250b90f1d569","parent":"6a66bce139c8071d2a805e5e","component":"fernando-playground@ppk-summary","system":"66f6c624d8a4a0c67041a910","displayName":"Summary","params":{"super":{"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":"100%","height":"100%"}}}},"reference":"sG6WdhXU5aswRAd0ZE5uR"},{"_id":"ba58ac3c0d55409b9ef43ac8","parent":"6a66b404c2a2aeac4a09fd5f","component":"fernando-playground@ppk-table","system":"66f6c624d8a4a0c67041a910","displayName":"Mesa","params":{"super":{"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":"100%","height":"100%"}}}},"reference":"NptISkzrBUU1TfndDPNZh"},{"_id":"36148ef35ac231b5cfb2a9ba","parent":"6a66b3d89f8782fe4af2bb88","component":"fernando-playground@ppk-new-room","system":"66f6c624d8a4a0c67041a910","displayName":"New Room","params":{"decks":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"ppk-decksDataSet"}},"super":{"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":"100%","height":"100%"}}}},"reference":"o5XgKyZuLUQuElTpn_Wnn"},{"_id":"ad718200f4140a89d9dfe886","parent":"6a66b3d0102e63106d6b374c","component":"fernando-playground@ppk-my-rooms","system":"66f6c624d8a4a0c67041a910","displayName":"My Rooms","params":{"rooms":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"ppk-roomsDataSet"}},"super":{"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":"100%","height":"100%"}}}},"reference":"J71tkvQyT2NU12qe_wBQ5"},{"_id":"b13228cb80ad328e49089f7f","parent":"6a66b3de9f8782fe4af2bb97","component":"fernando-playground@ppk-join-screen","system":"66f6c624d8a4a0c67041a910","displayName":"Join Screen","params":{"super":{"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":"100%","height":"100%"}}}},"reference":"L7pwRtd9vNtknf7Gt4-T9"}]}},{"_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":"6a4c28b753bf708a566f6cf0","displayName":"Tradutor","reference":"k-OgsKfT8MuAHaPmgJyii","params":{"domain":{"component":"data2-core-primitives@String","params":{"content":"tradutor"}},"entryPoint":{"component":"data2-core-types@ComponentInstanceReference","params":{"component":"data2-core-primitives@Artboard","reference":"vrgeqr-u_f14TMxxotSCa"}}},"system":"66f6c624d8a4a0c67041a910","component":"data2-core-primitives@Application"}]}},{"reference":"DataComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a41acb9e75b1a017079b201","component":"data2-core@Store","system":"66f6c624d8a4a0c67041a910","params":{"initialData":{"theme":"dark","bg":"#0b0f1a","surfaceColor":"#141a29","surfaceAltColor":"#1d2435","text":"#e8edf5","textMuted":"#8a94a8","borderColor":"#2a3247","accent":"#6366f1","userBubbleColor":"#6366f1","userText":"#ffffff","botBubbleColor":"#1d2435","toggleIcon":"☀️","toggleLabel":"Modo claro"}},"reference":"themeStore"},{"_id":"6a41acbfe75b1a017079b22e","component":"data2-core@Store","system":"66f6c624d8a4a0c67041a910","params":{"initialData":{"messages":[],"summary":"","lastBotMessage":"","input":"","loading":false,"currentState":"Descoberta","currentEmoji":"🧭","currentColor":"#6366f1","currentReasoning":"Aguardando a primeira mensagem para detectar o estado da conversa..."}},"reference":"chatStore"},{"_id":"6a4325e6a92372895f217f61","component":"data2-core@Store","system":"66f6c624d8a4a0c67041a910","params":{"initialData":{"states":[{"id":"descoberta","name":"Descoberta","trigger":"Estado PADRÃO. O usuário está fazendo perguntas gerais, explorando o produto, ou a conversa ainda não tem um sinal claro de reclamação nem de intenção de compra.","behaviorPrompt":"Você é um assistente amigável e informativo de um produto SaaS. Responda de forma clara, prestativa e neutra. Explique recursos sem pressionar por venda. Faça perguntas para entender melhor a necessidade do usuário.","emoji":"🧭","color":"#6366f1","isDefault":true},{"id":"vendas","name":"Vendas","trigger":"O usuário demonstra interesse em comprar, fazer upgrade de plano, conhecer preços, ou está avaliando contratar o produto.","behaviorPrompt":"Você está em modo VENDAS. Seja proativo e entusiasta sobre o valor do produto. Destaque benefícios alinhados ao que o usuário busca, crie senso de oportunidade e conduza naturalmente para o próximo passo (teste, contratação, upgrade). Seja persuasivo sem ser agressivo.","emoji":"💰","color":"#22c55e","isDefault":false},{"id":"suporte","name":"Suporte","trigger":"O usuário está relatando um problema, bug, erro, frustração ou reclamando de algo que não funcionou como esperado.","behaviorPrompt":"Você está em modo SUPORTE. Adote um tom calmo, empático e tranquilizador. Reconheça a frustração do usuário, peça desculpas pelo transtorno, e foque em diagnosticar e resolver o problema passo a passo. NÃO tente vender nada neste momento.","emoji":"🛟","color":"#0ea5e9","isDefault":false},{"id":"entusiasmado","name":"Entusiasmado","trigger":"O usuário está empolgado, entusiasmado, elogiando, ou falando de um tema/hobby que claramente gosta (filme, time, jogo, série).","behaviorPrompt":"Você está em modo ENTUSIASMADO. Combine com a energia do usuário: seja caloroso, animado e celebre junto. Use linguagem vibrante e positiva, demonstre genuíno interesse pelo que o usuário curte. Mantenha o tom leve e contagiante.","emoji":"🎉","color":"#f59e0b","isDefault":false}]}},"reference":"statesStore"},{"_id":"6a43e4497a7f4d41f2aff117","component":"data2-core@Store","system":"66f6c624d8a4a0c67041a910","params":{"initialData":{"messages":[],"loading":false,"hasResult":false,"diagnosis":"","routines":[],"ownedText":"","input":"","view":"chat","selectedKey":"","selectedItems":[],"selectedTotal":0,"budget":300,"checkoutNote":"","assinatura":false,"widgetOpen":false}},"reference":"bot-state"},{"_id":"6a46d1bed135f62a64eb4296","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"bot-products-b"}}},"reference":"bot-products-bDataSet"},{"_id":"6a464a2afb4b5485eb719d2d","component":"data2-core-auth@CustomAuthenticationStrategy","system":"66f6c624d8a4a0c67041a910","params":{},"reference":"painel-auth"},{"_id":"6a464a13fb4b5485eb719d00","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"bot-users"}}},"reference":"bot-usersDataSet"},{"_id":"6a5a8138c43989d721ad4cb2","component":"data2-core-auth@CustomAuthenticationStrategy","system":"66f6c624d8a4a0c67041a910","params":{},"reference":"mv-auth"},{"_id":"6a5a817972d72648f3195658","component":"data2-core@AuthenticationStrategy","system":"66f6c624d8a4a0c67041a910","params":{"credentials":[{"key":"email"},{"key":"password"}],"verifier":{"component":"data2-core-types@DataSetReference","params":{"reference":"mv-usuariosDataSet"}},"email":"email","password":"password","role":"role","roleDefault":"membro"},"reference":"mv-login"},{"_id":"6a66a91f4eab3e4ed90117d3","component":"data2-core@AuthenticationStrategy","system":"66f6c624d8a4a0c67041a910","params":{"credentials":[{"key":"email"},{"key":"password"}],"verifier":{"component":"data2-core-types@DataSetReference","params":{"reference":"ppk-usersDataSet"}},"email":"email","password":"password","role":"role","roleDefault":"facilitator"},"reference":"ppk-auth"},{"_id":"6a66a564102e63106d6b1c55","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ppk-users"}}},"reference":"ppk-usersDataSet"},{"_id":"6a66a9de9f8782fe4af2aa7d","component":"data2-core-connect@PowerCordSocket","system":"66f6c624d8a4a0c67041a910","params":{"autoConnect":true,"onMessage":{"component":"data2-core-types@Getter","params":{"prop":{"component":"data2-core-primitives@String","params":{"content":"fn"}},"module":{"fn":{"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"event"}}],"body":{"component":"data2-core-flow@Series","params":{"actions":[{"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"ppk-roomsDataSet"}},"prop":"refresh","args":[]}},{"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"ppk-issuesDataSet"}},"prop":"refresh","args":[]}},{"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"ppk-roundsDataSet"}},"prop":"refresh","args":[]}}]}}}}}}}},"reference":"ppk-socket"},{"_id":"6a67b36c4c303226dc7e1971","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"manifestacoes","schema":[{"component":"data2-core-types@Field","params":{"key":"manifestacao","title":"Manifestação N3","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ativa","title":"Ativa","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"ordem","title":"Ordem","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"createdAt","title":"Criado em","component":"data2-core-primitives@Date"}}]},"reference":"manifestacoes"},{"_id":"6a69142e7ac798c44b15eaad","component":"data2-core-connect@PowerCordSocket","system":"66f6c624d8a4a0c67041a910","params":{"autoConnect":true,"onMessage":{"component":"data2-core-types@Getter","params":{"prop":{"component":"data2-core-primitives@String","params":{"content":"fn"}},"module":{"fn":{"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"event"}}],"body":{"component":"data2-core-flow@Series","params":{"actions":[{"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"wlmessagesDataSet"}},"prop":"refresh","args":[]}},{"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"wlconversationsDataSet"}},"prop":"refresh","args":[]}}]}}}}}}}},"reference":"wlsocket"},{"_id":"6a7394c9586e37170de1a789","component":"data2-core@Store","system":"66f6c624d8a4a0c67041a910","params":{"initialData":{"tab":"dashboard","plat":"todas","sel":null,"replies":{},"replyLoading":{},"insight":null,"insightLoading":false,"data":null}},"reference":"scr-ui"},{"_id":"6a46d1be7068a227f9739807","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"bot-products-b","schema":[{"component":"data2-core-types@Field","params":{"key":"catalog_id","title":"Catálogo","component":"data2-core-primitives@ObjectId"}},{"component":"data2-core-types@Field","params":{"key":"name","title":"Nome","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"brand","title":"Marca","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"routine_step","title":"Etapa da rotina","component":"data2-core-primitives@ObjectId"}},{"component":"data2-core-types@Field","params":{"key":"category","title":"Categoria","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"subcategory","title":"Subcategoria","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"price","title":"Preço","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"image_url","title":"Imagem","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"stock","title":"Estoque","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"duration_days","title":"Duração (dias)","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"usage_period","title":"Período de uso","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"description","title":"Descrição","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"how_to_use","title":"Modo de uso","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"contraindications","title":"Contraindicações","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"sku","title":"SKU","component":"data2-core-primitives@String"}}]},"reference":"bot-products-b"},{"_id":"6a464a12e03c48ad27d2f5b7","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"bot-users","schema":[{"component":"data2-core-types@Field","params":{"key":"name","title":"Nome","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"email","title":"E-mail","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"password","title":"Senha","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"role","title":"Papel","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"active","title":"Ativo","component":"data2-core-primitives@Boolean"}}]},"reference":"bot-users"},{"_id":"6a5945099f691f135af07c6f","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"mv-usuarios"}}},"reference":"mv-usuariosDataSet"},{"_id":"6a66a56439c8071d2a803743","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"ppk-users","schema":[{"component":"data2-core-types@Field","params":{"key":"email","title":"E-mail","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"name","title":"Nome","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"password","title":"Senha","component":"data2-core-types@Password","required":true}},{"component":"data2-core-types@Field","params":{"key":"role","title":"Papel","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"createdAt","title":"Criado em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"lastLoginAt","title":"Último login","component":"data2-core-primitives@Date"}}]},"reference":"ppk-users"},{"_id":"6a66a58639c8071d2a803794","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ppk-rounds"}}},"reference":"ppk-roundsDataSet"},{"_id":"6a66a57edc1fded10bfd8e3f","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ppk-issues"}}},"reference":"ppk-issuesDataSet"},{"_id":"6a66a56c9f8782fe4af2a220","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ppk-rooms"}}},"reference":"ppk-roomsDataSet"},{"_id":"6a690f6a58398d1f9e299751","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"wlconversations"}}},"reference":"wlconversationsDataSet"},{"_id":"6a690f6fdc1fded10b03b9d3","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"wlmessages"}}},"reference":"wlmessagesDataSet"},{"_id":"6a5945083eb46d792cd0db99","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"mv-usuarios","schema":[{"component":"data2-core-types@Field","params":{"key":"email","title":"E-mail","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"password","title":"Senha","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"Nome","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"role","title":"Perfil","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"criado_em","title":"Criado em","component":"data2-core-primitives@Date"}}]},"reference":"mv-usuarios"},{"_id":"6a66a586c834e87e6d3c8105","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"ppk-rounds","schema":[{"component":"data2-core-types@Field","params":{"key":"roomId","title":"Sala","component":"data2-core-primitives@ObjectId","required":true}},{"component":"data2-core-types@Field","params":{"key":"issueId","title":"História","component":"data2-core-primitives@ObjectId","required":true}},{"component":"data2-core-types@Field","params":{"key":"roundIndex","title":"Rodada nº","component":"data2-core-primitives@Number","required":true}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"stats","title":"Estatísticas","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"startedAt","title":"Iniciada em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"revealedAt","title":"Revelada em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"closedAt","title":"Fechada em","component":"data2-core-primitives@Date"}}]},"reference":"ppk-rounds"},{"_id":"6a66a57ec2a2aeac4a09e4ea","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"ppk-issues","schema":[{"component":"data2-core-types@Field","params":{"key":"roomId","title":"Sala","component":"data2-core-primitives@ObjectId","required":true}},{"component":"data2-core-types@Field","params":{"key":"sortOrder","title":"Ordem","component":"data2-core-primitives@Number","required":true}},{"component":"data2-core-types@Field","params":{"key":"issueKey","title":"Chave","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"title","title":"Título","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"description","title":"Descrição","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"url","title":"Link","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"finalEstimate","title":"Estimativa final","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"createdAt","title":"Criada em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"estimatedAt","title":"Estimada em","component":"data2-core-primitives@Date"}}]},"reference":"ppk-issues"},{"_id":"6a66a56cc834e87e6d3c80c3","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"ppk-rooms","schema":[{"component":"data2-core-types@Field","params":{"key":"code","title":"Código","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"name","title":"Nome da sala","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"ownerUserId","title":"Facilitador (user)","component":"data2-core-primitives@ObjectId"}},{"component":"data2-core-types@Field","params":{"key":"deckId","title":"Deck","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"customDeck","title":"Deck customizado","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"settings","title":"Configurações","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"currentIssueId","title":"História em pauta","component":"data2-core-primitives@ObjectId"}},{"component":"data2-core-types@Field","params":{"key":"currentRoundId","title":"Rodada aberta","component":"data2-core-primitives@ObjectId"}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"createdAt","title":"Criada em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"closedAt","title":"Encerrada em","component":"data2-core-primitives@Date"}}]},"reference":"ppk-rooms"},{"_id":"6a690f6adc1fded10b03b9b2","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"wlconversations","schema":[{"component":"data2-core-types@Field","params":{"key":"threadId","title":"Thread","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"tenantSlug","title":"Marca","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"segmentSlug","title":"Ramo no momento","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"userName","title":"Nome do usuário","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"agentName","title":"Atendente","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"handoffReason","title":"Motivo do handoff","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"lastMessage","title":"Última mensagem","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"unreadForAgent","title":"Não lidas (mesa)","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"createdAt","title":"Criada em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"updatedAt","title":"Atualizada em","component":"data2-core-primitives@Date"}}]},"reference":"wlconversations"},{"_id":"6a690f6ff97054f90e438cb2","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"wlmessages","schema":[{"component":"data2-core-types@Field","params":{"key":"threadId","title":"Thread","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"tenantSlug","title":"Marca","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"role","title":"Autor","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"type","title":"Tipo","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"content","title":"Conteúdo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"mediaUrl","title":"URL da mídia","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"mediaRef","title":"Ref no Drive","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"transcript","title":"Transcrição do áudio","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"options","title":"Botões oferecidos","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"selectedOption","title":"Botão escolhido","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"faqTitle","title":"FAQ usada","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"offTopic","title":"Fora de escopo","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"agentName","title":"Atendente","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"createdAt","title":"Criada em","component":"data2-core-primitives@Date"}}]},"reference":"wlmessages"},{"_id":"6a5a127572d72648f3179be8","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"mv-exposicoes"}}},"reference":"mv-exposicoesDataSet"},{"_id":"6a58f57e6fb67bea960c2519","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"mv-midias"}}},"reference":"mv-midiasDataSet"},{"_id":"6a58f57a78be641096f9e0fb","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"mv-campanhas"}}},"reference":"mv-campanhasDataSet"},{"_id":"6a58f568e92c91046120658e","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"mv-ciclos"}}},"reference":"mv-ciclosDataSet"},{"_id":"6a58f572f6fdf852ef3e777e","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"mv-produtos"}}},"reference":"mv-produtosDataSet"},{"_id":"6a58f5649f691f135aef118e","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"mv-marcas"}}},"reference":"mv-marcasDataSet"},{"_id":"6a58f5598a6d78ebea9ef4d2","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"mv-categorias"}}},"reference":"mv-categoriasDataSet"},{"_id":"6a5908646fb67bea960c76ee","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"mv-acervo"}}},"reference":"mv-acervoDataSet"},{"_id":"6a5f5dc4136fbfcfe234e7e3","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"complaints"}}},"reference":"complaintsDataSet"},{"_id":"6a66a55353991cb86a771fd3","component":"data2-core@DataSet","system":"66f6c624d8a4a0c67041a910","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ppk-decks"}}},"reference":"ppk-decksDataSet"},{"_id":"6a5a12757a65d3cf47a91e68","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"mv-exposicoes","schema":[{"component":"data2-core-types@Field","params":{"key":"slug","title":"Slug","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"titulo","title":"Título","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"subtitulo","title":"Subtítulo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"tema","title":"Tema","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"capa_ref","title":"Capa (ref)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"curadoria","title":"Texto de curadoria","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"itens","title":"Itens","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"criado_por","title":"Criado por","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"criado_em","title":"Criado em","component":"data2-core-primitives@Date"}}]},"reference":"mv-exposicoes"},{"_id":"6a58f57e3eb46d792ccf6b24","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"mv-midias","schema":[{"component":"data2-core-types@Field","params":{"key":"item_codigo","title":"Item (código interno)","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"tipo","title":"Tipo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ref","title":"Arquivo (ref)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"legenda","title":"Legenda","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"preferencial","title":"Preferencial","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"is_placeholder","title":"É placeholder","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"ordem","title":"Ordem","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"credito","title":"Crédito / licença","component":"data2-core-primitives@String"}}]},"reference":"mv-midias"},{"_id":"6a58f57a55b95245d58cf6ff","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"mv-campanhas","schema":[{"component":"data2-core-types@Field","params":{"key":"slug","title":"Slug","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"Nome","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"tipo","title":"Tipo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ciclo_codigo","title":"Ciclo (código)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"produto_slug","title":"Produto (slug)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"marca_slug","title":"Marca (slug)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"data","title":"Data","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"descricao","title":"Descrição","component":"data2-core-primitives@String"}}]},"reference":"mv-campanhas"},{"_id":"6a58f568ce0eeb9aa5b93d5a","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"mv-ciclos","schema":[{"component":"data2-core-types@Field","params":{"key":"codigo","title":"Código","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"ano","title":"Ano","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"numero","title":"Número","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"periodo_inicio","title":"Início","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"periodo_fim","title":"Fim","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"destaque","title":"Destaque","component":"data2-core-primitives@String"}}]},"reference":"mv-ciclos"},{"_id":"6a58f572e92c9104612065b3","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"mv-produtos","schema":[{"component":"data2-core-types@Field","params":{"key":"slug","title":"Slug","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"Nome","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"marca_slug","title":"Marca (slug)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"categoria_produto","title":"Categoria do produto","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"descricao","title":"Descrição","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"imagem_ref","title":"Imagem (ref)","component":"data2-core-primitives@String"}}]},"reference":"mv-produtos"},{"_id":"6a58f56355b95245d58cf6cc","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"mv-marcas","schema":[{"component":"data2-core-types@Field","params":{"key":"slug","title":"Slug","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"Nome","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"tipo","title":"Tipo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ano_lancamento","title":"Ano de lançamento","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"descricao","title":"Descrição","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"cor","title":"Cor","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"logo_ref","title":"Logo (ref)","component":"data2-core-primitives@String"}}]},"reference":"mv-marcas"},{"_id":"6a58f5596fb67bea960c2469","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"mv-categorias","schema":[{"component":"data2-core-types@Field","params":{"key":"slug","title":"Slug","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"Nome","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"descricao","title":"Descrição","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"icone","title":"Ícone","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ordem","title":"Ordem","component":"data2-core-primitives@Number"}}]},"reference":"mv-categorias"},{"_id":"6a590864f6fdf852ef3ecb66","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"mv-acervo","schema":[{"component":"data2-core-types@Field","params":{"key":"codigo_interno","title":"Código Interno","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"tombo","title":"Nº de Tombo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"notacao","title":"Notação","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"titulo","title":"Título","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"categoria_slug","title":"Categoria (slug)","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"nivel_descricao","title":"Nível de Descrição","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"fundo","title":"Fundo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"funcao","title":"Função","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"subfuncao","title":"Subfunção","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"origem","title":"Origem","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ciclo_codigo","title":"Ciclo (código)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"marca_slug","title":"Marca (slug)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"produto_slug","title":"Produto (slug)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"campanha_slug","title":"Campanha (slug)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"localizacao","title":"Localização","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"copia","title":"Cópia","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"sigiloso","title":"Sigiloso","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"resp_cadastro","title":"Resp. pelo cadastro","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"data_cadastro","title":"Data de cadastro","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"data_alteracao","title":"Data de alteração","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"descricao","title":"Descrição","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"tags","title":"Tags","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"tags_texto","title":"Tags (texto)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"caracteristicas","title":"Características","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"imagem_ref","title":"Imagem preferencial (ref)","component":"data2-core-primitives@String"}}]},"reference":"mv-acervo"},{"_id":"6a5f5dc4ec2cb6e905302979","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"complaints","schema":[{"component":"data2-core-types@Field","params":{"key":"protocolo","title":"Protocolo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"produto","title":"Produto","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"lote","title":"Lote","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"dataVencimento","title":"Data de Vencimento","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"problema","title":"Problema relatado","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"imagens","title":"Imagens","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"veredicto","title":"Veredicto","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"confianca","title":"Confiança","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"justificativa","title":"Justificativa","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"itensObservados","title":"Itens observados","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"danoVisivel","title":"Dano visível","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"recomendacao","title":"Recomendação","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"analisadoEm","title":"Analisado em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"createdAt","title":"Criado em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"manifestacaoN3","title":"Manifestação N3","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"manifestacaoNenhuma","title":"Nenhuma manifestação se aplica","component":"data2-core-primitives@Boolean"}}]},"reference":"complaints"},{"_id":"6a66a5534eab3e4ed901115f","component":"data2-core@Data2TableDataSource","system":"66f6c624d8a4a0c67041a910","params":{"table":"ppk-decks","schema":[{"component":"data2-core-types@Field","params":{"key":"deckId","title":"Deck ID","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"name","title":"Nome","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"values","title":"Valores","component":"data2-core-primitives@Array","required":true}},{"component":"data2-core-types@Field","params":{"key":"isNumeric","title":"Numérico","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"sortOrder","title":"Ordem","component":"data2-core-primitives@Number"}}]},"reference":"ppk-decks"}]}},{"reference":"BundlesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}}],"processesParams":{}}}}